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

Pavel Grunt pgrunt at redhat.com
Tue Feb 16 10:22:21 UTC 2016


Hi,

with gtk2:
spice-widget-egl.c: In function ‘spice_egl_init’:
spice-widget-egl.c:203:9: error: implicit declaration of function
‘GDK_IS_X11_DISPLAY’ [-Werror=implicit-function-declaration]
     if (GDK_IS_X11_DISPLAY(gdk_dpy)) {
         ^
spice-widget-egl.c:203:5: error: nested extern declaration of
‘GDK_IS_X11_DISPLAY’ [-Werror=nested-externs]
     if (GDK_IS_X11_DISPLAY(gdk_dpy)) {
     ^
spice-widget-egl.c: In function ‘spice_widget_init_egl_win’:
spice-widget-egl.c:272:9: error: implicit declaration of function
‘GDK_IS_X11_WINDOW’ [-Werror=implicit-function-declaration]
     if (GDK_IS_X11_WINDOW(win)) {
         ^
spice-widget-egl.c:272:5: error: nested extern declaration of
‘GDK_IS_X11_WINDOW’ [-Werror=nested-externs]
     if (GDK_IS_X11_WINDOW(win)) {
     ^
spice-widget-egl.c:273:39: error: implicit declaration of function
‘gdk_x11_window_get_xid’ [-Werror=implicit-function-declaration]
         native = (EGLNativeWindowType)gdk_x11_window_get_xid(win);
                                       ^
spice-widget-egl.c:273:9: error: nested extern declaration of
‘gdk_x11_window_get_xid’ [-Werror=nested-externs]
         native = (EGLNativeWindowType)gdk_x11_window_get_xid(win);

besides that I have done some tests and haven't noticed any issues.

For the future (when all bits are in) would be nice to have some instruction how to make it run in the SPICE manual.

Pavel

Also copyright should be till 2016?

On Mon, 2016-02-15 at 14:40 +0100, Marc-André Lureau wrote:
> 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(wid
> get),
>                                             &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_fre
> e, 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;
>  }






More information about the Spice-devel mailing list