[Spice-devel] [PATCH spice-gtk] gtk: add spice-widget GL scanout support

Marc-André Lureau marcandre.lureau at gmail.com
Mon Feb 15 13:40:31 UTC 2016


From: Marc-André Lureau <mlureau at redhat.com>

Hook to spice-glib events to show the GL scanout.

The opengl context is created with egl, and is currently
x11-only (supporting wayland with bare-egl doesn't seem trivial).

Using GtkGLArea is left for a future series, since SpiceDisplay widget
is a GtkDrawingArea and can't be replaced without breaking
ABI. Furthermore, GtkGLArea won't work on non-egl contexts, so this
approach is necessary on gtk+ < 3.16 or X11 (because gdk/x11 uses glx).

Signed-off-by: Marc-André Lureau <marcandre.lureau at redhat.com>
---
v3->v4:
 - update after Victor reivew
 - uppercase true/false
 - use macros for buffer size
 - get rid of useless variable
 - reset attributes on unrealize
 - comment some texture coordinates math
 - explicit != NULL check
 - check earlier if EGL_NO_SURFACE in draw

src/Makefile.am         |   9 +
 src/spice-widget-egl.c  | 605 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/spice-widget-priv.h |  30 +++
 src/spice-widget.c      | 151 ++++++++++--
 4 files changed, 775 insertions(+), 20 deletions(-)
 create mode 100644 src/spice-widget-egl.c

diff --git a/src/Makefile.am b/src/Makefile.am
index 37b89fe..68884e6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -122,6 +122,7 @@ SPICE_GTK_LIBADD_COMMON =		\
 	libspice-client-glib-2.0.la	\
 	$(GTK_LIBS)			\
 	$(CAIRO_LIBS)			\
+	$(EPOXY_LIBS)			\
 	$(LIBM)				\
 	$(NULL)
 
@@ -160,17 +161,25 @@ SPICE_GTK_SOURCES_COMMON +=		\
 endif
 
 if WITH_GTK
+if WITH_EPOXY
+SPICE_GTK_SOURCES_COMMON +=		\
+	spice-widget-egl.c		\
+	$(NULL)
+endif
+
 if HAVE_GTK_2
 libspice_client_gtk_2_0_la_DEPEDENCIES = $(GTK_SYMBOLS_FILE)
 libspice_client_gtk_2_0_la_LDFLAGS = $(SPICE_GTK_LDFLAGS_COMMON)
 libspice_client_gtk_2_0_la_LIBADD = $(SPICE_GTK_LIBADD_COMMON)
 libspice_client_gtk_2_0_la_SOURCES = $(SPICE_GTK_SOURCES_COMMON)
+libspice_client_gtk_2_0_la_CFLAGS = $(EPOXY_CFLAGS)
 nodist_libspice_client_gtk_2_0_la_SOURCES = $(nodist_SPICE_GTK_SOURCES_COMMON)
 else
 libspice_client_gtk_3_0_la_DEPEDENCIES = $(GTK_SYMBOLS_FILE)
 libspice_client_gtk_3_0_la_LDFLAGS = $(SPICE_GTK_LDFLAGS_COMMON)
 libspice_client_gtk_3_0_la_LIBADD = $(SPICE_GTK_LIBADD_COMMON)
 libspice_client_gtk_3_0_la_SOURCES = $(SPICE_GTK_SOURCES_COMMON)
+libspice_client_gtk_3_0_la_CFLAGS = $(EPOXY_CFLAGS)
 nodist_libspice_client_gtk_3_0_la_SOURCES = $(nodist_SPICE_GTK_SOURCES_COMMON)
 endif
 
diff --git a/src/spice-widget-egl.c b/src/spice-widget-egl.c
new file mode 100644
index 0000000..d0f9381
--- /dev/null
+++ b/src/spice-widget-egl.c
@@ -0,0 +1,605 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2014-2015 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#include "config.h"
+
+#include <math.h>
+
+#define EGL_EGLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+
+#include "spice-widget.h"
+#include "spice-widget-priv.h"
+#include "spice-gtk-session-priv.h"
+#include <libdrm/drm_fourcc.h>
+
+#include <gdk/gdkx.h>
+
+#define VERTS_ARRAY_SIZE (sizeof(GLfloat) * 4 * 4)
+#define TEX_ARRAY_SIZE (sizeof(GLfloat) * 4 * 2)
+
+static const char *spice_egl_vertex_src =       \
+"                                               \
+  #version 130\n                                \
+                                                \
+  in vec4 position;                             \
+  in vec2 texcoords;                            \
+  out vec2 tcoords;                             \
+  uniform mat4 mproj;                           \
+                                                \
+  void main()                                   \
+  {                                             \
+    tcoords = texcoords;                        \
+    gl_Position = mproj * position;             \
+  }                                             \
+";
+
+static const char *spice_egl_fragment_src =     \
+"                                               \
+  #version 130\n                                \
+                                                \
+  in vec2 tcoords;                              \
+  out vec4 fragmentColor;                       \
+  uniform sampler2D samp;                       \
+                                                \
+  void  main()                                  \
+  {                                             \
+    fragmentColor = texture2D(samp, tcoords);   \
+  }                                             \
+";
+
+static void apply_ortho(guint mproj, float left, float right,
+                        float bottom, float top, float near, float far)
+
+{
+    float a = 2.0f / (right - left);
+    float b = 2.0f / (top - bottom);
+    float c = -2.0f / (far - near);
+
+    float tx = - (right + left) / (right - left);
+    float ty = - (top + bottom) / (top - bottom);
+    float tz = - (far + near) / (far - near);
+
+    float ortho[16] = {
+        a, 0, 0, 0,
+        0, b, 0, 0,
+        0, 0, c, 0,
+        tx, ty, tz, 1
+    };
+
+    glUniformMatrix4fv(mproj, 1, GL_FALSE, &ortho[0]);
+}
+
+static gboolean spice_egl_init_shaders(SpiceDisplay *display, GError **err)
+{
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
+    GLuint fs = 0, vs = 0, buf;
+    GLint status, tex_loc, prog;
+    gboolean success = FALSE;
+    gchar log[1000] = { 0, };
+    GLsizei len;
+
+    glGetIntegerv(GL_CURRENT_PROGRAM, &prog);
+
+    fs = glCreateShader(GL_FRAGMENT_SHADER);
+    glShaderSource(fs, 1, (const char **)&spice_egl_fragment_src, NULL);
+    glCompileShader(fs);
+    glGetShaderiv(fs, GL_COMPILE_STATUS, &status);
+    if (!status) {
+        glGetShaderInfoLog(fs, sizeof(log), &len, log);
+        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                    "failed to compile fragment shader: %s", log);
+        goto end;
+    }
+
+    vs = glCreateShader(GL_VERTEX_SHADER);
+    glShaderSource(vs, 1, (const char **)&spice_egl_vertex_src, NULL);
+    glCompileShader(vs);
+    glGetShaderiv(vs, GL_COMPILE_STATUS, &status);
+    if (!status) {
+        glGetShaderInfoLog(vs, sizeof(log), &len, log);
+        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                    "failed to compile vertex shader: %s", log);
+        goto end;
+    }
+
+    d->egl.prog = glCreateProgram();
+    glAttachShader(d->egl.prog, fs);
+    glAttachShader(d->egl.prog, vs);
+    glLinkProgram(d->egl.prog);
+    glGetProgramiv(d->egl.prog, GL_LINK_STATUS, &status);
+    if (!status) {
+        glGetProgramInfoLog(d->egl.prog, 1000, &len, log);
+        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                    "error linking shaders: %s", log);
+        goto end;
+    }
+
+    glUseProgram(d->egl.prog);
+    glDetachShader(d->egl.prog, fs);
+    glDetachShader(d->egl.prog, vs);
+
+    d->egl.attr_pos = glGetAttribLocation(d->egl.prog, "position");
+    g_assert(d->egl.attr_pos != -1);
+    d->egl.attr_tex = glGetAttribLocation(d->egl.prog, "texcoords");
+    g_assert(d->egl.attr_tex != -1);
+    tex_loc = glGetUniformLocation(d->egl.prog, "samp");
+    g_assert(tex_loc != -1);
+    d->egl.mproj = glGetUniformLocation(d->egl.prog, "mproj");
+    g_assert(d->egl.mproj != -1);
+
+    glUniform1i(tex_loc, 0);
+
+    /* we only use one VAO, so we always keep it bound */
+    glGenVertexArrays(1, &buf);
+    glBindVertexArray(buf);
+
+    glGenBuffers(1, &buf);
+    glBindBuffer(GL_ARRAY_BUFFER, buf);
+    glBufferData(GL_ARRAY_BUFFER,
+                 VERTS_ARRAY_SIZE + TEX_ARRAY_SIZE,
+                 NULL,
+                 GL_STATIC_DRAW);
+    d->egl.vbuf_id = buf;
+
+    glGenTextures(1, &d->egl.tex_id);
+    glGenTextures(1, &d->egl.tex_pointer_id);
+
+    success = TRUE;
+
+end:
+    if (fs) {
+        glDeleteShader(fs);
+    }
+    if (vs) {
+        glDeleteShader(vs);
+    }
+
+    glUseProgram(prog);
+    return success;
+}
+
+G_GNUC_INTERNAL
+gboolean spice_egl_init(SpiceDisplay *display, GError **err)
+{
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
+    static const EGLint conf_att[] = {
+        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+        EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
+        EGL_RED_SIZE, 8,
+        EGL_GREEN_SIZE, 8,
+        EGL_BLUE_SIZE, 8,
+        EGL_ALPHA_SIZE, 0,
+        EGL_NONE,
+    };
+    static const EGLint ctx_att[] = {
+#ifdef EGL_CONTEXT_MAJOR_VERSION
+        EGL_CONTEXT_MAJOR_VERSION, 3,
+#else
+        EGL_CONTEXT_CLIENT_VERSION, 3,
+#endif
+        EGL_NONE
+    };
+    EGLBoolean b;
+    EGLint major, minor, n;
+    EGLNativeDisplayType dpy = 0;
+    GdkDisplay *gdk_dpy = gdk_display_get_default();
+
+#ifdef GDK_WINDOWING_X11
+    if (GDK_IS_X11_DISPLAY(gdk_dpy)) {
+        dpy = (EGLNativeDisplayType)gdk_x11_display_get_xdisplay(gdk_dpy);
+    }
+#endif
+
+    d->egl.display = eglGetDisplay(dpy);
+    if (d->egl.display == EGL_NO_DISPLAY) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "failed to get EGL display");
+        return FALSE;
+    }
+
+    if (!eglInitialize(d->egl.display, &major, &minor)) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "failed to init EGL display");
+        return FALSE;
+    }
+
+    SPICE_DEBUG("EGL major/minor: %d.%d\n", major, minor);
+    SPICE_DEBUG("EGL version: %s\n",
+                eglQueryString(d->egl.display, EGL_VERSION));
+    SPICE_DEBUG("EGL vendor: %s\n",
+                eglQueryString(d->egl.display, EGL_VENDOR));
+    SPICE_DEBUG("EGL extensions: %s\n",
+                eglQueryString(d->egl.display, EGL_EXTENSIONS));
+
+    b = eglBindAPI(EGL_OPENGL_API);
+    if (!b) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "cannot bind OpenGL API");
+        return FALSE;
+    }
+
+    b = eglChooseConfig(d->egl.display, conf_att, &d->egl.conf,
+                        1, &n);
+
+    if (!b || n != 1) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "cannot find suitable EGL config");
+        return FALSE;
+    }
+
+    d->egl.ctx = eglCreateContext(d->egl.display,
+                                  d->egl.conf,
+                                  EGL_NO_CONTEXT,
+                                  ctx_att);
+    if (!d->egl.ctx) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "cannot create EGL context");
+        return FALSE;
+    }
+
+    eglMakeCurrent(d->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+                   d->egl.ctx);
+
+    return spice_egl_init_shaders(display, err);
+}
+
+static gboolean spice_widget_init_egl_win(SpiceDisplay *display, GdkWindow *win,
+                                          GError **err)
+{
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
+    EGLNativeWindowType native = 0;
+    EGLBoolean b;
+
+    if (d->egl.surface)
+        return TRUE;
+
+#ifdef GDK_WINDOWING_X11
+    if (GDK_IS_X11_WINDOW(win)) {
+        native = (EGLNativeWindowType)gdk_x11_window_get_xid(win);
+    }
+#endif
+
+    if (!native) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "this platform isn't supported");
+        return FALSE;
+    }
+
+    d->egl.surface = eglCreateWindowSurface(d->egl.display,
+                                            d->egl.conf,
+                                            native, NULL);
+
+    if (!d->egl.surface) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "failed to init egl surface");
+        return FALSE;
+    }
+
+    b = eglMakeCurrent(d->egl.display,
+                       d->egl.surface,
+                       d->egl.surface,
+                       d->egl.ctx);
+    if (!b) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "failed to activate context");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+G_GNUC_INTERNAL
+gboolean spice_egl_realize_display(SpiceDisplay *display, GdkWindow *win, GError **err)
+{
+    SPICE_DEBUG("egl realize");
+    if (!spice_widget_init_egl_win(display, win, err))
+        return FALSE;
+
+    spice_egl_resize_display(display, gdk_window_get_width(win),
+                             gdk_window_get_height(win));
+
+    return TRUE;
+}
+
+G_GNUC_INTERNAL
+void spice_egl_unrealize_display(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+    SPICE_DEBUG("egl unrealize %p", d->egl.surface);
+
+    if (d->egl.image != NULL) {
+        eglDestroyImageKHR(d->egl.display, d->egl.image);
+        d->egl.image = NULL;
+    }
+
+    if (d->egl.tex_id) {
+        glDeleteTextures(1, &d->egl.tex_id);
+        d->egl.tex_id = 0;
+    }
+
+    if (d->egl.tex_pointer_id) {
+        glDeleteTextures(1, &d->egl.tex_pointer_id);
+        d->egl.tex_pointer_id = 0;
+    }
+
+    if (d->egl.surface != EGL_NO_SURFACE) {
+        eglDestroySurface(d->egl.display, d->egl.surface);
+        d->egl.surface = EGL_NO_SURFACE;
+    }
+    if (d->egl.vbuf_id) {
+        glDeleteBuffers(1, &d->egl.vbuf_id);
+        d->egl.vbuf_id = 0;
+    }
+
+    if (d->egl.prog) {
+        glDeleteProgram(d->egl.prog);
+        d->egl.prog = 0;
+    }
+
+    if (d->egl.ctx) {
+        eglDestroyContext(d->egl.display, d->egl.ctx);
+        d->egl.ctx = 0;
+    }
+
+    eglMakeCurrent(d->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+                   EGL_NO_CONTEXT);
+}
+
+G_GNUC_INTERNAL
+void spice_egl_resize_display(SpiceDisplay *display, int w, int h)
+{
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
+    int prog;
+
+    glGetIntegerv(GL_CURRENT_PROGRAM, &prog);
+
+    glUseProgram(d->egl.prog);
+    apply_ortho(d->egl.mproj, 0, w, 0, h, -1, 1);
+    glViewport(0, 0, w, h);
+
+    if (d->egl.image)
+        spice_egl_update_display(display);
+
+    glUseProgram(prog);
+}
+
+static void
+draw_rect_from_arrays(SpiceDisplay *display, const void *verts, const void *tex)
+{
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+    glBindBuffer(GL_ARRAY_BUFFER, d->egl.vbuf_id);
+
+    if (verts) {
+        glEnableVertexAttribArray(d->egl.attr_pos);
+        glBufferSubData(GL_ARRAY_BUFFER,
+                        0,
+                        VERTS_ARRAY_SIZE,
+                        verts);
+        glVertexAttribPointer(d->egl.attr_pos, 4, GL_FLOAT,
+                              GL_FALSE, 0, 0);
+    }
+    if (tex) {
+        glEnableVertexAttribArray(d->egl.attr_tex);
+        glBufferSubData(GL_ARRAY_BUFFER,
+                        VERTS_ARRAY_SIZE,
+                        TEX_ARRAY_SIZE,
+                        tex);
+        glVertexAttribPointer(d->egl.attr_tex, 2, GL_FLOAT,
+                              GL_FALSE, 0,
+                              (void *)VERTS_ARRAY_SIZE);
+    }
+
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+    if (verts)
+        glDisableVertexAttribArray(d->egl.attr_pos);
+    if (tex)
+        glDisableVertexAttribArray(d->egl.attr_tex);
+
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+static GLvoid
+client_draw_rect_tex(SpiceDisplay *display,
+                     float x, float y, float w, float h,
+                     float tx, float ty, float tw, float th)
+{
+    float verts[4][4];
+    float tex[4][2];
+
+    verts[0][0] = x;
+    verts[0][1] = y;
+    verts[0][2] = 0.0;
+    verts[0][3] = 1.0;
+    tex[0][0] = tx;
+    tex[0][1] = ty;
+    verts[1][0] = x + w;
+    verts[1][1] = y;
+    verts[1][2] = 0.0;
+    verts[1][3] = 1.0;
+    tex[1][0] = tx + tw;
+    tex[1][1] = ty;
+    verts[2][0] = x;
+    verts[2][1] = y + h;
+    verts[2][2] = 0.0;
+    verts[2][3] = 1.0;
+    tex[2][0] = tx;
+    tex[2][1] = ty + th;
+    verts[3][0] = x + w;
+    verts[3][1] = y + h;
+    verts[3][2] = 0.0;
+    verts[3][3] = 1.0;
+    tex[3][0] = tx + tw;
+    tex[3][1] = ty + th;
+
+    draw_rect_from_arrays(display, verts, tex);
+}
+
+G_GNUC_INTERNAL
+void spice_egl_cursor_set(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
+    GdkPixbuf *image = d->mouse_pixbuf;
+
+    if (image == NULL)
+        return;
+
+    int width = gdk_pixbuf_get_width(image);
+    int height = gdk_pixbuf_get_height(image);
+
+    glBindTexture(GL_TEXTURE_2D, d->egl.tex_pointer_id);
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+                 width, height, 0,
+                 GL_RGBA, GL_UNSIGNED_BYTE,
+                 gdk_pixbuf_read_pixels(image));
+    glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+G_GNUC_INTERNAL
+void spice_egl_update_display(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
+    double s;
+    int x, y, w, h;
+    gdouble tx, ty, tw, th;
+    int prog;
+
+    g_return_if_fail(d->egl.image != NULL);
+    if (d->egl.surface == EGL_NO_SURFACE)
+        return;
+
+    spice_display_get_scaling(display, &s, &x, &y, &w, &h);
+
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    glClear(GL_COLOR_BUFFER_BIT);
+
+    tx = ((float)d->area.x / (float)d->egl.scanout.width);
+    ty = ((float)d->area.y / (float)d->egl.scanout.height);
+    tw = ((float)d->area.width / (float)d->egl.scanout.width);
+    th = ((float)d->area.height / (float)d->egl.scanout.height);
+
+    /* convert to opengl coordinates, 0 is bottom, 1 is top. ty should
+     * be the bottom of the area, since th is upward */
+    /* 1+---------------+ */
+    /*  |               | */
+    /*  |  ty  to  |th  | */
+    /*  |  |   ->  |    | */
+    /*  |  |th     ty   | */
+    /*  |               | */
+    /*  |               | */
+    /*  +---------------+ */
+    /* 0 */
+    ty = 1 - (ty + th);
+
+
+    /* if the scanout is inverted, then invert coordinates and direction too */
+    if (!d->egl.scanout.y0top) {
+        ty = 1 - ty;
+        th = -1 * th;
+    }
+    SPICE_DEBUG("update %f +%d+%d %dx%d +%f+%f %fx%f", s, x, y, w, h,
+                tx, ty, tw, th);
+
+    glBindTexture(GL_TEXTURE_2D, d->egl.tex_id);
+    glDisable(GL_BLEND);
+    glGetIntegerv(GL_CURRENT_PROGRAM, &prog);
+    glUseProgram(d->egl.prog);
+    client_draw_rect_tex(display, x, y, w, h,
+                         tx, ty, tw, th);
+
+    if (d->mouse_mode == SPICE_MOUSE_MODE_SERVER &&
+        d->mouse_guest_x != -1 && d->mouse_guest_y != -1 &&
+        !d->show_cursor &&
+        spice_gtk_session_get_pointer_grabbed(d->gtk_session) &&
+        d->mouse_pixbuf != NULL) {
+        GdkPixbuf *image = d->mouse_pixbuf;
+        int width = gdk_pixbuf_get_width(image);
+        int height = gdk_pixbuf_get_height(image);
+
+        glBindTexture(GL_TEXTURE_2D, d->egl.tex_pointer_id);
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        client_draw_rect_tex(display,
+                             x + (d->mouse_guest_x - d->mouse_hotspot.x) * s,
+                             y + h - (d->mouse_guest_y - d->mouse_hotspot.y) * s,
+                             width, -height,
+                             0, 0, 1, 1);
+    }
+
+    eglSwapBuffers(d->egl.display, d->egl.surface);
+
+    glUseProgram(prog);
+}
+
+
+G_GNUC_INTERNAL
+gboolean spice_egl_update_scanout(SpiceDisplay *display,
+                                  const SpiceGlScanout *scanout,
+                                  GError **err)
+{
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
+    EGLint attrs[13];
+    guint32 format;
+
+    g_return_val_if_fail(scanout != NULL, FALSE);
+    format = scanout->format;
+
+    if (d->egl.image != NULL) {
+        eglDestroyImageKHR(d->egl.display, d->egl.image);
+        d->egl.image = NULL;
+    }
+
+    if (scanout->fd == -1)
+        return TRUE;
+
+    attrs[0] = EGL_DMA_BUF_PLANE0_FD_EXT;
+    attrs[1] = scanout->fd;
+    attrs[2] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
+    attrs[3] = scanout->stride;
+    attrs[4] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
+    attrs[5] = 0;
+    attrs[6] = EGL_WIDTH;
+    attrs[7] = scanout->width;
+    attrs[8] = EGL_HEIGHT;
+    attrs[9] = scanout->height;
+    attrs[10] = EGL_LINUX_DRM_FOURCC_EXT;
+    attrs[11] = format;
+    attrs[12] = EGL_NONE;
+    SPICE_DEBUG("fd:%d stride:%d y0:%d %dx%d format:0x%x (%c%c%c%c)",
+                scanout->fd, scanout->stride, scanout->y0top,
+                scanout->width, scanout->height, format,
+                format & 0xff, (format >> 8) & 0xff, (format >> 16) & 0xff, format >> 24);
+
+    d->egl.image = eglCreateImageKHR(d->egl.display,
+                                       EGL_NO_CONTEXT,
+                                       EGL_LINUX_DMA_BUF_EXT,
+                                       (EGLClientBuffer)NULL,
+                                       attrs);
+
+    glBindTexture(GL_TEXTURE_2D, d->egl.tex_id);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)d->egl.image);
+    d->egl.scanout = *scanout;
+
+    return TRUE;
+}
diff --git a/src/spice-widget-priv.h b/src/spice-widget-priv.h
index 682e889..c708e25 100644
--- a/src/spice-widget-priv.h
+++ b/src/spice-widget-priv.h
@@ -30,6 +30,10 @@
 #include <windows.h>
 #endif
 
+#ifdef USE_EPOXY
+#include <epoxy/egl.h>
+#endif
+
 #include "spice-widget.h"
 #include "spice-common.h"
 #include "spice-gtk-session.h"
@@ -124,6 +128,22 @@ struct _SpiceDisplayPrivate {
     int                     x11_accel_denominator;
     int                     x11_threshold;
 #endif
+#ifdef USE_EPOXY
+    struct {
+        gboolean            enabled;
+        EGLSurface          surface;
+        EGLDisplay          display;
+        EGLConfig           conf;
+        EGLContext          ctx;
+        gint                mproj, attr_pos, attr_tex;
+        guint               vbuf_id;
+        guint               tex_id;
+        guint               tex_pointer_id;
+        guint               prog;
+        EGLImageKHR         image;
+        SpiceGlScanout      scanout;
+    } egl;
+#endif
 };
 
 int      spicex_image_create                 (SpiceDisplay *display);
@@ -135,6 +155,16 @@ void     spicex_expose_event                 (SpiceDisplay *display, GdkEventExp
 #endif
 gboolean spicex_is_scaled                    (SpiceDisplay *display);
 void     spice_display_get_scaling           (SpiceDisplay *display, double *s, int *x, int *y, int *w, int *h);
+gboolean spice_egl_init                      (SpiceDisplay *display, GError **err);
+gboolean spice_egl_realize_display           (SpiceDisplay *display, GdkWindow *win,
+                                              GError **err);
+void     spice_egl_unrealize_display         (SpiceDisplay *display);
+void     spice_egl_update_display            (SpiceDisplay *display);
+void     spice_egl_resize_display            (SpiceDisplay *display, int w, int h);
+gboolean spice_egl_update_scanout            (SpiceDisplay *display,
+                                              const SpiceGlScanout *scanout,
+                                              GError **err);
+void     spice_egl_cursor_set                (SpiceDisplay *display);
 
 G_END_DECLS
 
diff --git a/src/spice-widget.c b/src/spice-widget.c
index 19753e7..c3577a1 100644
--- a/src/spice-widget.c
+++ b/src/spice-widget.c
@@ -552,6 +552,7 @@ static void spice_display_init(SpiceDisplay *display)
     GtkWidget *widget = GTK_WIDGET(display);
     SpiceDisplayPrivate *d;
     GtkTargetEntry targets = { "text/uri-list", 0, 0 };
+    G_GNUC_UNUSED GError *err = NULL;
 
     d = display->priv = SPICE_DISPLAY_GET_PRIVATE(display);
 
@@ -583,6 +584,13 @@ static void spice_display_init(SpiceDisplay *display)
 
     d->mouse_cursor = get_blank_cursor();
     d->have_mitshm = true;
+
+#ifdef USE_EPOXY
+    if (!spice_egl_init(display, &err)) {
+        g_critical("egl init failed: %s", err->message);
+        g_clear_error(&err);
+    }
+#endif
 }
 
 static GObject *
@@ -1132,6 +1140,20 @@ static gboolean do_color_convert(SpiceDisplay *display, GdkRectangle *r)
     return true;
 }
 
+static void set_egl_enabled(SpiceDisplay *display, bool enabled)
+{
+#ifdef USE_EPOXY
+    SpiceDisplayPrivate *d = display->priv;
+
+    if (d->egl.enabled != enabled) {
+        d->egl.enabled = enabled;
+        /* even though the function is marked as deprecated, it's the
+         * only way I found to prevent glitches when the window is
+         * resized. */
+        gtk_widget_set_double_buffered(GTK_WIDGET(display), !enabled);
+    }
+#endif
+}
 
 #if GTK_CHECK_VERSION (2, 91, 0)
 static gboolean draw_event(GtkWidget *widget, cairo_t *cr)
@@ -1140,6 +1162,13 @@ static gboolean draw_event(GtkWidget *widget, cairo_t *cr)
     SpiceDisplayPrivate *d = display->priv;
     g_return_val_if_fail(d != NULL, false);
 
+#ifdef USE_EPOXY
+    if (d->egl.enabled) {
+        spice_egl_update_display(display);
+        return false;
+    }
+#endif
+
     if (d->mark == 0 || d->data == NULL ||
         d->area.width == 0 || d->area.height == 0)
         return false;
@@ -1760,6 +1789,10 @@ static void size_allocate(GtkWidget *widget, GtkAllocation *conf, gpointer data)
         d->ww = conf->width;
         d->wh = conf->height;
         recalc_geometry(widget);
+#ifdef USE_EPOXY
+        if (d->egl.enabled)
+            spice_egl_resize_display(display, conf->width, conf->height);
+#endif
     }
 
     d->mx = conf->x;
@@ -1786,18 +1819,29 @@ static void realize(GtkWidget *widget)
 {
     SpiceDisplay *display = SPICE_DISPLAY(widget);
     SpiceDisplayPrivate *d = display->priv;
+    G_GNUC_UNUSED GError *err = NULL;
 
     GTK_WIDGET_CLASS(spice_display_parent_class)->realize(widget);
 
     d->keycode_map =
         vnc_display_keymap_gdk2xtkbd_table(gtk_widget_get_window(widget),
                                            &d->keycode_maplen);
+
+#ifdef USE_EPOXY
+    if (!spice_egl_realize_display(display, gtk_widget_get_window(GTK_WIDGET(display)), &err)) {
+        g_critical("egl realize failed: %s", err->message);
+        g_clear_error(&err);
+    }
+#endif
     update_image(display);
 }
 
 static void unrealize(GtkWidget *widget)
 {
     spicex_image_destroy(SPICE_DISPLAY(widget));
+#ifdef USE_EPOXY
+    spice_egl_unrealize_display(SPICE_DISPLAY(widget));
+#endif
 
     GTK_WIDGET_CLASS(spice_display_parent_class)->unrealize(widget);
 }
@@ -2206,6 +2250,8 @@ static void invalidate(SpiceChannel *channel,
         .height = h
     };
 
+    set_egl_enabled(display, false);
+
     if (!gtk_widget_get_window(GTK_WIDGET(display)))
         return;
 
@@ -2269,6 +2315,9 @@ static void cursor_set(SpiceCursorChannel *channel,
     } else
         g_warn_if_reached();
 
+#ifdef USE_EPOXY
+    spice_egl_cursor_set(display);
+#endif
     if (d->show_cursor) {
         /* unhide */
         gdk_cursor_unref(d->show_cursor);
@@ -2416,6 +2465,38 @@ static void cursor_reset(SpiceCursorChannel *channel, gpointer data)
     gdk_window_set_cursor(window, NULL);
 }
 
+#ifdef USE_EPOXY
+static void gl_scanout(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    const SpiceGlScanout *scanout;
+    GError *err = NULL;
+
+    scanout = spice_display_get_gl_scanout(SPICE_DISPLAY_CHANNEL(d->display));
+    g_return_if_fail(scanout != NULL);
+
+    SPICE_DEBUG("%s: got scanout",  __FUNCTION__);
+    set_egl_enabled(display, true);
+
+    if (!spice_egl_update_scanout(display, scanout, &err)) {
+        g_critical("update scanout failed: %s", err->message);
+        g_clear_error(&err);
+    }
+}
+
+static void gl_draw(SpiceDisplay *display,
+                    guint32 x, guint32 y, guint32 w, guint32 h)
+{
+    SpiceDisplayPrivate *d = display->priv;
+
+    SPICE_DEBUG("%s",  __FUNCTION__);
+    set_egl_enabled(display, true);
+
+    spice_egl_update_display(display);
+    spice_display_gl_draw_done(SPICE_DISPLAY_CHANNEL(d->display));
+}
+#endif
+
 static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
 {
     SpiceDisplay *display = data;
@@ -2451,6 +2532,13 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
                            primary.stride, primary.shmid, primary.data, display);
             mark(display, primary.marked);
         }
+#ifdef USE_EPOXY
+        spice_g_signal_connect_object(channel, "notify::gl-scanout",
+                                      G_CALLBACK(gl_scanout), display, G_CONNECT_SWAPPED);
+        spice_g_signal_connect_object(channel, "gl-draw",
+                                      G_CALLBACK(gl_draw), display, G_CONNECT_SWAPPED);
+#endif
+
         spice_channel_connect(channel);
         return;
     }
@@ -2634,34 +2722,57 @@ GdkPixbuf *spice_display_get_pixbuf(SpiceDisplay *display)
 {
     SpiceDisplayPrivate *d;
     GdkPixbuf *pixbuf;
-    int x, y;
-    guchar *src, *data, *dest;
+    guchar *data;
 
     g_return_val_if_fail(SPICE_IS_DISPLAY(display), NULL);
 
     d = display->priv;
 
     g_return_val_if_fail(d != NULL, NULL);
-    /* TODO: ensure d->data has been exposed? */
-    g_return_val_if_fail(d->data != NULL, NULL);
-
-    data = g_malloc0(d->area.width * d->area.height * 3);
-    src = d->data;
-    dest = data;
-
-    src += d->area.y * d->stride + d->area.x * 4;
-    for (y = 0; y < d->area.height; ++y) {
-        for (x = 0; x < d->area.width; ++x) {
-          dest[0] = src[x * 4 + 2];
-          dest[1] = src[x * 4 + 1];
-          dest[2] = src[x * 4 + 0];
-          dest += 3;
+    g_return_val_if_fail(d->display != NULL, NULL);
+
+#ifdef USE_EPOXY
+    if (d->egl.enabled) {
+        GdkPixbuf *tmp;
+
+        data = g_malloc0(d->area.width * d->area.height * 4);
+        glReadBuffer(GL_FRONT);
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+        glReadPixels(0, 0, d->area.width, d->area.height,
+                     GL_RGBA, GL_UNSIGNED_BYTE, data);
+        tmp = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, true,
+                                       8, d->area.width, d->area.height,
+                                       d->area.width * 4,
+                                       (GdkPixbufDestroyNotify)g_free, NULL);
+        pixbuf = gdk_pixbuf_flip(tmp, false);
+        g_object_unref(tmp);
+    } else
+#endif
+    {
+        guchar *src, *dest;
+        int x, y;
+
+        /* TODO: ensure d->data has been exposed? */
+        g_return_val_if_fail(d->data != NULL, NULL);
+        data = g_malloc0(d->area.width * d->area.height * 3);
+        src = d->data;
+        dest = data;
+
+        src += d->area.y * d->stride + d->area.x * 4;
+        for (y = 0; y < d->area.height; ++y) {
+            for (x = 0; x < d->area.width; ++x) {
+                dest[0] = src[x * 4 + 2];
+                dest[1] = src[x * 4 + 1];
+                dest[2] = src[x * 4 + 0];
+                dest += 3;
+            }
+            src += d->stride;
         }
-        src += d->stride;
+        pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, false,
+                                          8, d->area.width, d->area.height,
+                                          d->area.width * 3,
+                                          (GdkPixbufDestroyNotify)g_free, NULL);
     }
 
-    pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, false,
-                                      8, d->area.width, d->area.height, d->area.width * 3,
-                                      (GdkPixbufDestroyNotify)g_free, NULL);
     return pixbuf;
 }
-- 
2.5.0



More information about the Spice-devel mailing list