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

Victor Toso lists at victortoso.com
Sat Feb 13 11:00:52 UTC 2016


Hi,

On Fri, Feb 05, 2016 at 12:36:52AM +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>
> ---
>  src/Makefile.am         |   9 +
>  src/spice-widget-egl.c  | 576 ++++++++++++++++++++++++++++++++++++++++++++++++
>  src/spice-widget-priv.h |  30 +++
>  src/spice-widget.c      | 151 +++++++++++--
>  4 files changed, 746 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..b7fd3d9
> --- /dev/null
> +++ b/src/spice-widget-egl.c
> @@ -0,0 +1,576 @@
> +/* -*- 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>
> +
> +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;
> +    }

In case of error in glGetProgramiv don't you need to detach fs and vs
before deleting it?

> +
> +    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,
> +                 (sizeof(GLfloat) * 4 * 4) +
> +                 (sizeof(GLfloat) * 4 * 2),

Maybe a macro for the size of this two values could help code
readability (here and in the draw_rect_from_arrays below)

> +                 NULL,
> +                 GL_STATIC_DRAW);
> +    d->egl.vbuf_id = buf;
> +
> +    glGenTextures(1, &d->egl.tex_id);
> +    glGenTextures(1, &d->egl.tex_pointer_id);
> +
> +    success = true;

gbooleans are upper case

> +
> +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;
> +    EGLenum api;
> +    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;

ditto (gboolean)

> +    }
> +
> +    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));
> +
> +    api = EGL_OPENGL_API;
> +    b = eglBindAPI(api);

why do you need the variable api?

> +    if (!b) {
> +        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
> +                            "cannot bind OpenGLES 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;

ditto (gboolean)

> +}
> +
> +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;

ditto (gboolean)

> +}
> +
> +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);

I think to set it to NULL after destroying it

> +
> +    if (d->egl.tex_id)
> +        glDeleteTextures(1, &d->egl.tex_id);
> +
> +    if (d->egl.tex_pointer_id)
> +        glDeleteTextures(1, &d->egl.tex_pointer_id);
> +
> +    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);
> +
> +    if (d->egl.prog)
> +        glDeleteProgram(d->egl.prog);
> +
> +    if (d->egl.ctx)
> +        eglDestroyContext(d->egl.display, d->egl.ctx);
> +
> +    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,
> +                        sizeof(GLfloat) * 4 * 4,
> +                        verts);
> +        glVertexAttribPointer(d->egl.attr_pos, 4, GL_FLOAT,
> +                              GL_FALSE, 0, 0);
> +    }
> +    if (tex) {
> +        glEnableVertexAttribArray(d->egl.attr_tex);
> +        glBufferSubData(GL_ARRAY_BUFFER,
> +                        sizeof(GLfloat) * 4 * 4,
> +                        sizeof(GLfloat) * 4 * 2,
> +                        tex);
> +        glVertexAttribPointer(d->egl.attr_tex, 2, GL_FLOAT,
> +                              GL_FALSE, 0,
> +                              (void *)(sizeof(GLfloat) * 4 * 4));
> +    }
> +
> +    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);
> +    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);
> +    ty += 1 - th;

err, I did not get the 1 here. Why not just `ty -= th;` ?
Could you clarify with a comment?

y-axis is inverted but seems that you are moving it by 1 there and
in the chunk bellow

> +    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);
> +

Seems that series to eanble mouse as server-side will be handy here...

> +    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) {

Could you make explicit the check against NULL the d->mouse_pixbuf ?

> +        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);
> +    }
> +
> +    if (d->egl.surface != EGL_NO_SURFACE)
> +        eglSwapBuffers(d->egl.display, d->egl.surface);

Why can you have EGL_NO_SURFACE here?

> +
> +    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;

ditto (gboolean)

> +
> +    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;

Could you point me out the documentation related to this attributes if
available?

> +    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 a1a68a6..53d66df 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;

ditto (gboolean)

> +    }
> +#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;

Looks good here

So, this one had only minor comments on it as well. I'm not
knowledgeable with opengl / egl so I can't help much there.
Feel free to address the ones you agree on and push the series.

Virgl integration is very much welcome, thank you for your work on
this.

Acked-by: Victor Toso <victortoso at redhat.com>

>  }
> --
> 2.5.0
> 
> _______________________________________________
> Spice-devel mailing list
> Spice-devel at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/spice-devel


More information about the Spice-devel mailing list