From: Marc-André Lureau <mlureau@xxxxxxxxxx> 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 egl doesn't seem trivial). Using GtkGlArea is also left for the future, since SpiceDisplay widget is a GtkDrawingArea and can't be replaced without breaking ABI. Signed-off-by: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> --- src/Makefile.am | 9 + src/spice-widget-egl.c | 530 ++++++++++++++++++++++++++++++++++++++++++++++++ src/spice-widget-priv.h | 30 +++ src/spice-widget.c | 150 ++++++++++++-- 4 files changed, 699 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..cafa2b3 --- /dev/null +++ b/src/spice-widget-egl.c @@ -0,0 +1,530 @@ +/* -*- 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 = \ +" \ + attribute vec4 position; \ + attribute vec2 texcoords; \ + varying vec2 tcoords; \ + uniform mat4 mproj; \ + \ + void main() \ + { \ + tcoords = texcoords; \ + gl_Position = mproj * position; \ + } \ +"; + +static const char *spice_egl_fragment_src = \ +" \ + varying highp vec2 tcoords; \ + uniform sampler2D samp; \ + \ + void main() \ + { \ + gl_FragColor = 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, vs, prog; + GLint stat; + + fs = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fs, 1, (const char **)&spice_egl_fragment_src, NULL); + glCompileShader(fs); + glGetShaderiv(fs, GL_COMPILE_STATUS, &stat); + if (!stat) { + g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED, + "Failed to compile FS"); + return false; + } + + vs = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vs, 1, (const char **)&spice_egl_vertex_src, NULL); + glCompileShader(vs); + glGetShaderiv(vs, GL_COMPILE_STATUS, &stat); + if (!stat) { + g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED, + "failed to compile VS"); + return false; + } + + prog = glCreateProgram(); + glAttachShader(prog, fs); + glAttachShader(prog, vs); + glLinkProgram(prog); + + glGetProgramiv(prog, GL_LINK_STATUS, &stat); + if (!stat) { + char log[1000]; + GLsizei len; + glGetProgramInfoLog(prog, 1000, &len, log); + g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED, + "Error linking shaders: %s", log); + return false; + } + + glUseProgram(prog); + + d->egl.attr_pos = glGetAttribLocation(prog, "position"); + d->egl.attr_tex = glGetAttribLocation(prog, "texcoords"); + d->egl.tex_loc = glGetUniformLocation(prog, "samp"); + d->egl.mproj = glGetUniformLocation(prog, "mproj"); + + glUniform1i(d->egl.tex_loc, 0); + + glGenBuffers(1, &d->egl.vbuf_id); + glBindBuffer(GL_ARRAY_BUFFER, d->egl.vbuf_id); + glBufferData(GL_ARRAY_BUFFER, + (sizeof(GLfloat) * 4 * 4) + + (sizeof(GLfloat) * 4 * 2), + NULL, + GL_STATIC_DRAW); + + return true; +} + +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, 2, +#else + EGL_CONTEXT_CLIENT_VERSION, 2, +#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_critical("Failed to get EGL display"); + return false; + } + + if (!eglInitialize(d->egl.display, &major, &minor)) { + g_critical("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_ES_API; + b = eglBindAPI(api); + if (!b) { + g_critical("cannot bind OpenGLES API"); + return false; + } + + b = eglChooseConfig(d->egl.display, conf_att, &d->egl.conf, + 1, &n); + + if (!b || n != 1) { + g_critical("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_critical("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, GError **err) +{ + GtkWidget *widget = GTK_WIDGET(display); + SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display); + GdkWindow *gdk_win; + EGLNativeWindowType native = 0; + EGLBoolean b; + + if (d->egl.surface) + return true; + + gdk_win = gtk_widget_get_window(widget); + if (!gdk_win) + return false; + +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_WINDOW(gdk_win)) { + native = (EGLNativeWindowType)gdk_x11_window_get_xid(gdk_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; +} + +#if GTK_CHECK_VERSION (2, 91, 0) +static inline void gdk_drawable_get_size(GdkWindow *w, gint *ww, gint *wh) +{ + *ww = gdk_window_get_width(w); + *wh = gdk_window_get_height(w); +} +#endif + +gboolean spice_egl_realize_display(SpiceDisplay *display, GError **err) +{ + SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display); + int ww, wh; + + SPICE_DEBUG("egl realize"); + if (!spice_widget_init_egl_win(display, err)) + return false; + + glGenTextures(1, &d->egl.tex_id); + glGenTextures(1, &d->egl.tex_pointer_id); + + gdk_drawable_get_size(gtk_widget_get_window(GTK_WIDGET(display)), &ww, &wh); + spice_egl_resize_display(display, ww, wh); + + return true; +} + +void spice_egl_unrealize_display(SpiceDisplay *display) +{ + SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display); + + SPICE_DEBUG("egl unrealize %p", d->egl.surface); + + glDeleteTextures(1, &d->egl.tex_id); + glDeleteTextures(1, &d->egl.tex_pointer_id); + + eglMakeCurrent(d->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + + if (d->egl.surface != EGL_NO_SURFACE) { + eglDestroySurface(d->egl.display, d->egl.surface); + d->egl.surface = EGL_NO_SURFACE; + } +} + +void spice_egl_resize_display(SpiceDisplay *display, int w, int h) +{ + SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display); + + if (d->egl.surface != EGL_NO_SURFACE) { + 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); + } + } +} + +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) { + glBufferSubData(GL_ARRAY_BUFFER, + 0, + sizeof(GLfloat) * 4 * 4, + verts); + glVertexAttribPointer(d->egl.attr_pos, 4, GL_FLOAT, + GL_FALSE, 0, 0); + glEnableVertexAttribArray(d->egl.attr_pos); + } + if (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)); + glEnableVertexAttribArray(d->egl.attr_tex); + } + + 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)); +} + +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; + + g_return_if_fail(d->egl.image != NULL); + g_return_if_fail(d->egl.surface != 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; + 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); + 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) { + 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); +} + + +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 (scanout->fd == -1) + return false; + + if (d->egl.image != NULL) { + eglDestroyImageKHR(d->egl.display, d->egl.image); + d->egl.image = NULL; + } + + 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..39936c3 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,23 @@ struct _SpiceDisplayPrivate { int x11_accel_denominator; int x11_threshold; #endif +#ifdef USE_EPOXY + struct { + gboolean enabled; + EGLSurface surface; + EGLDisplay display; + EGLConfig conf; + EGLContext ctx; + guint32 tex_loc; + guint32 mproj; + guint32 attr_pos, attr_tex; + guint32 vbuf_id; + guint tex_id; + guint tex_pointer_id; + EGLImageKHR image; + SpiceGlScanout scanout; + } egl; +#endif }; int spicex_image_create (SpiceDisplay *display); @@ -135,6 +156,15 @@ 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, 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 77f663f..dc014b7 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,9 @@ static void size_allocate(GtkWidget *widget, GtkAllocation *conf, gpointer data) d->ww = conf->width; d->wh = conf->height; recalc_geometry(widget); +#ifdef USE_EPOXY + spice_egl_resize_display(display, conf->width, conf->height); +#endif } d->mx = conf->x; @@ -1786,18 +1818,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, &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 +2249,8 @@ static void invalidate(SpiceChannel *channel, .height = h }; + set_egl_enabled(display, false); + if (!gtk_widget_get_window(GTK_WIDGET(display))) return; @@ -2269,6 +2314,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 +2464,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 +2531,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 +2721,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 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel