spice: initial opengl/virgl support, postcopy migration fix.

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2.0.22 (GNU/Linux)
 
 iQIcBAABAgAGBQJWzFDwAAoJEEy22O7T6HE4vesP/1IULiyxkiu+rnLquspRKlKr
 Z9GE3svmQmD8BdI88uJqz28+Wwz37TyQGA9ycNmOWNeWwY88SmLo95VhHcGtk2kj
 3FdrwhaPLW+4/0yVS0BerhH4W0jVq2x7V1kRTVrejwE5TL3NaJzBf45bfxoicn68
 /oibclWwNhyaiNWklTfbTgNMaKaSsXqZqJ6wv+YEtIGsH1jhbUh0KHvJRc29rTPT
 nHLGM3IcMDTKfZMTWUyJR7d8R7wdQTxnZiYole4f2e8hJ+Vx6SgbGpkLPCsLlv2U
 jWJCzgsnJEbBD76OjGem9u/NmQoBp8BQFYLmA9Kx512HYw54EKnGQt8Lnft3pjTC
 ptUxA5C9DAniWKABo28Cea/CYV6mEjLR+Yk76omh/emJQib2db0qy2ljBdkb7MJK
 Y+tDCamH6eZ/1CnbNWsY/zp3ThMbCvWy8K9w/8mmRuQ5TH5RZNHSgexAx4voVGlW
 +d9d6+fh9F3sKR9JiAiiRpPATnA7NLqHstuylLAW0t80EMSvqTcuW8PzIS1A1K+p
 JceQxjzfsVIBY77/INYK91yLJUkMDIihi9hFoNy/zN81VyAr4NlRhktKvsQ5pRWV
 rp9CwxuzcKbYVXyLRVNAOQTCFINBhGmD/2K9UYf8vkku9knFywKeCQn0j0LVW+TZ
 X2b5o573noPRDDDMv9e5
 =PeHJ
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/spice/tags/pull-spice-20160223-1' into staging

spice: initial opengl/virgl support, postcopy migration fix.

# gpg: Signature made Tue 23 Feb 2016 12:30:40 GMT using RSA key ID D3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>"
# gpg:                 aka "Gerd Hoffmann <gerd@kraxel.org>"
# gpg:                 aka "Gerd Hoffmann (private) <kraxel@gmail.com>"

* remotes/spice/tags/pull-spice-20160223-1:
  Postcopy+spice: Pass spice migration data earlier
  spice/gl: tweak debug messages.
  spice/gl: add unblock timer
  spice: add opengl/virgl/dmabuf support
  spice: reset cursor on resize
  egl-helpers: add functions for render nodes and dma-buf passing
  configure: add dma-buf support detection.
  spice: init dcl before registering qxl interface

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2016-02-23 16:14:17 +00:00
commit 1b1624092d
9 changed files with 357 additions and 13 deletions

20
configure vendored
View File

@ -279,6 +279,7 @@ smartcard=""
libusb=""
usb_redir=""
opengl=""
opengl_dmabuf="no"
zlib="yes"
lzo=""
snappy=""
@ -3274,7 +3275,7 @@ libs_softmmu="$libs_softmmu $fdt_libs"
# opengl probe (for sdl2, gtk, milkymist-tmu2)
if test "$opengl" != "no" ; then
opengl_pkgs="epoxy"
opengl_pkgs="epoxy libdrm gbm"
if $pkg_config $opengl_pkgs x11; then
opengl_cflags="$($pkg_config --cflags $opengl_pkgs) $x11_cflags"
opengl_libs="$($pkg_config --libs $opengl_pkgs) $x11_libs"
@ -3292,6 +3293,18 @@ if test "$opengl" != "no" ; then
fi
fi
if test "$opengl" = "yes"; then
cat > $TMPC << EOF
#include <epoxy/egl.h>
#ifndef EGL_MESA_image_dma_buf_export
# error mesa/epoxy lacks support for dmabufs (mesa 10.6+)
#endif
int main(void) { return 0; }
EOF
if compile_prog "" "" ; then
opengl_dmabuf=yes
fi
fi
##########################################
# archipelago probe
@ -4752,6 +4765,7 @@ echo "smartcard support $smartcard"
echo "libusb $libusb"
echo "usb net redir $usb_redir"
echo "OpenGL support $opengl"
echo "OpenGL dmabufs $opengl_dmabuf"
echo "libiscsi support $libiscsi"
echo "libnfs support $libnfs"
echo "build guest agent $guest_agent"
@ -5050,6 +5064,7 @@ if test "$gtk" = "yes" ; then
echo "CONFIG_GTK=y" >> $config_host_mak
echo "CONFIG_GTKABI=$gtkabi" >> $config_host_mak
echo "GTK_CFLAGS=$gtk_cflags" >> $config_host_mak
echo "GTK_LIBS=$gtk_libs" >> $config_host_mak
if test "$gtk_gl" = "yes" ; then
echo "CONFIG_GTK_GL=y" >> $config_host_mak
fi
@ -5158,6 +5173,9 @@ if test "$opengl" = "yes" ; then
echo "CONFIG_OPENGL=y" >> $config_host_mak
echo "OPENGL_CFLAGS=$opengl_cflags" >> $config_host_mak
echo "OPENGL_LIBS=$opengl_libs" >> $config_host_mak
if test "$opengl_dmabuf" = "yes" ; then
echo "CONFIG_OPENGL_DMABUF=y" >> $config_host_mak
fi
fi
if test "$lzo" = "yes" ; then

View File

@ -158,6 +158,8 @@ struct MigrationState
/* Flag set once the migration has been asked to enter postcopy */
bool start_postcopy;
/* Flag set after postcopy has sent the device state */
bool postcopy_after_devices;
/* Flag set once the migration thread is running (and needs joining) */
bool migration_thread_running;
@ -211,6 +213,8 @@ bool migration_has_finished(MigrationState *);
bool migration_has_failed(MigrationState *);
/* True if outgoing migration has entered postcopy phase */
bool migration_in_postcopy(MigrationState *);
/* ...and after the device transmission */
bool migration_in_postcopy_after_devices(MigrationState *);
MigrationState *migrate_get_current(void);
void migrate_compress_threads_create(void);

View File

@ -3,10 +3,23 @@
#include <epoxy/gl.h>
#include <epoxy/egl.h>
#include <gbm.h>
extern EGLDisplay *qemu_egl_display;
extern EGLConfig qemu_egl_config;
#ifdef CONFIG_OPENGL_DMABUF
extern int qemu_egl_rn_fd;
extern struct gbm_device *qemu_egl_rn_gbm_dev;
extern EGLContext qemu_egl_rn_ctx;
int qemu_egl_rendernode_open(void);
int egl_rendernode_init(void);
int egl_get_fd_for_texture(uint32_t tex_id, EGLint *stride, EGLint *fourcc);
#endif
EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, Window win);
int qemu_egl_init_dpy(EGLNativeDisplayType dpy, bool gles, bool debug);

View File

@ -24,6 +24,14 @@
#include "ui/console.h"
#include "sysemu/sysemu.h"
#if defined(CONFIG_OPENGL_DMABUF)
# if SPICE_SERVER_VERSION >= 0x000d00 /* release 0.13.0 */
# define HAVE_SPICE_GL 1
# include "ui/egl-helpers.h"
# include "ui/egl-context.h"
# endif
#endif
#define NUM_MEMSLOTS 8
#define MEMSLOT_GENERATION_BITS 8
#define MEMSLOT_SLOT_BITS 8
@ -50,6 +58,7 @@ enum {
QXL_COOKIE_TYPE_IO,
QXL_COOKIE_TYPE_RENDER_UPDATE_AREA,
QXL_COOKIE_TYPE_POST_LOAD_MONITORS_CONFIG,
QXL_COOKIE_TYPE_GL_DRAW_DONE,
};
typedef struct QXLCookie {
@ -104,6 +113,13 @@ struct SimpleSpiceDisplay {
QEMUCursor *cursor;
int mouse_x, mouse_y;
QEMUBH *cursor_bh;
#ifdef HAVE_SPICE_GL
/* opengl rendering */
QEMUBH *gl_unblock_bh;
QEMUTimer *gl_unblock_timer;
int dmabuf_fd;
#endif
};
struct SimpleSpiceUpdate {

View File

@ -905,6 +905,11 @@ bool migration_in_postcopy(MigrationState *s)
return (s->state == MIGRATION_STATUS_POSTCOPY_ACTIVE);
}
bool migration_in_postcopy_after_devices(MigrationState *s)
{
return migration_in_postcopy(s) && s->postcopy_after_devices;
}
MigrationState *migrate_init(const MigrationParams *params)
{
MigrationState *s = migrate_get_current();
@ -930,6 +935,7 @@ MigrationState *migrate_init(const MigrationParams *params)
s->setup_time = 0;
s->dirty_sync_count = 0;
s->start_postcopy = false;
s->postcopy_after_devices = false;
s->migration_thread_running = false;
s->last_req_rb = NULL;
@ -1489,6 +1495,14 @@ static int postcopy_start(MigrationState *ms, bool *old_vm_running)
goto fail_closefb;
}
qemu_fclose(fb);
/* Send a notify to give a chance for anything that needs to happen
* at the transition to postcopy and after the device state; in particular
* spice needs to trigger a transition now
*/
ms->postcopy_after_devices = true;
notifier_list_notify(&migration_state_notifiers, ms);
ms->downtime = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - time_at_stop;
qemu_mutex_unlock_iothread();

View File

@ -1051,6 +1051,7 @@ DEF("spice", HAS_ARG, QEMU_OPTION_spice,
" [,streaming-video=[off|all|filter]][,disable-copy-paste]\n"
" [,disable-agent-file-xfer][,agent-mouse=[on|off]]\n"
" [,playback-compression=[on|off]][,seamless-migration=[on|off]]\n"
" [,gl=[on|off]]\n"
" enable spice\n"
" at least one of {port, tls-port} is mandatory\n",
QEMU_ARCH_ALL)
@ -1142,6 +1143,9 @@ Enable/disable audio stream compression (using celt 0.5.1). Default is on.
@item seamless-migration=[on|off]
Enable/disable spice seamless migration. Default is off.
@item gl=[on|off]
Enable/disable OpenGL context. Default is off.
@end table
ETEXI

View File

@ -1,6 +1,8 @@
#include "qemu/osdep.h"
#include <glob.h>
#include <dirent.h>
#include "config-host.h"
#include "ui/egl-helpers.h"
EGLDisplay *qemu_egl_display;
@ -20,6 +22,133 @@ static int egl_debug;
/* ---------------------------------------------------------------------- */
#ifdef CONFIG_OPENGL_DMABUF
int qemu_egl_rn_fd;
struct gbm_device *qemu_egl_rn_gbm_dev;
EGLContext qemu_egl_rn_ctx;
int qemu_egl_rendernode_open(void)
{
DIR *dir;
struct dirent *e;
int r, fd;
char *p;
dir = opendir("/dev/dri");
if (!dir) {
return -1;
}
fd = -1;
while ((e = readdir(dir))) {
if (e->d_type != DT_CHR) {
continue;
}
if (strncmp(e->d_name, "renderD", 7)) {
continue;
}
r = asprintf(&p, "/dev/dri/%s", e->d_name);
if (r < 0) {
return -1;
}
r = open(p, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK);
if (r < 0) {
free(p);
continue;
}
fd = r;
free(p);
break;
}
closedir(dir);
if (fd < 0) {
return -1;
}
return fd;
}
int egl_rendernode_init(void)
{
qemu_egl_rn_fd = -1;
qemu_egl_rn_fd = qemu_egl_rendernode_open();
if (qemu_egl_rn_fd == -1) {
fprintf(stderr, "egl: no drm render node available\n");
goto err;
}
qemu_egl_rn_gbm_dev = gbm_create_device(qemu_egl_rn_fd);
if (!qemu_egl_rn_gbm_dev) {
fprintf(stderr, "egl: gbm_create_device failed\n");
goto err;
}
qemu_egl_init_dpy((EGLNativeDisplayType)qemu_egl_rn_gbm_dev, false, false);
if (!epoxy_has_egl_extension(qemu_egl_display,
"EGL_KHR_surfaceless_context")) {
fprintf(stderr, "egl: EGL_KHR_surfaceless_context not supported\n");
goto err;
}
if (!epoxy_has_egl_extension(qemu_egl_display,
"EGL_MESA_image_dma_buf_export")) {
fprintf(stderr, "egl: EGL_MESA_image_dma_buf_export not supported\n");
goto err;
}
qemu_egl_rn_ctx = qemu_egl_init_ctx();
if (!qemu_egl_rn_ctx) {
fprintf(stderr, "egl: egl_init_ctx failed\n");
goto err;
}
return 0;
err:
if (qemu_egl_rn_gbm_dev) {
gbm_device_destroy(qemu_egl_rn_gbm_dev);
}
if (qemu_egl_rn_fd != -1) {
close(qemu_egl_rn_fd);
}
return -1;
}
int egl_get_fd_for_texture(uint32_t tex_id, EGLint *stride, EGLint *fourcc)
{
EGLImageKHR image;
EGLint num_planes, fd;
image = eglCreateImageKHR(qemu_egl_display, eglGetCurrentContext(),
EGL_GL_TEXTURE_2D_KHR,
(EGLClientBuffer)(unsigned long)tex_id,
NULL);
if (!image) {
return -1;
}
eglExportDMABUFImageQueryMESA(qemu_egl_display, image, fourcc,
&num_planes, NULL);
if (num_planes != 1) {
eglDestroyImageKHR(qemu_egl_display, image);
return -1;
}
eglExportDMABUFImageMESA(qemu_egl_display, image, &fd, stride, NULL);
eglDestroyImageKHR(qemu_egl_display, image);
return fd;
}
#endif /* CONFIG_OPENGL_DMABUF */
/* ---------------------------------------------------------------------- */
EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, Window win)
{
EGLSurface esurface;

View File

@ -494,9 +494,14 @@ static QemuOptsList qemu_spice_opts = {
},{
.name = "playback-compression",
.type = QEMU_OPT_BOOL,
}, {
},{
.name = "seamless-migration",
.type = QEMU_OPT_BOOL,
#ifdef HAVE_SPICE_GL
},{
.name = "gl",
.type = QEMU_OPT_BOOL,
#endif
},
{ /* end of list */ }
},
@ -568,7 +573,8 @@ static void migration_state_notifier(Notifier *notifier, void *data)
if (migration_in_setup(s)) {
spice_server_migrate_start(spice_server);
} else if (migration_has_finished(s)) {
} else if (migration_has_finished(s) ||
migration_in_postcopy_after_devices(s)) {
spice_server_migrate_end(spice_server, true);
spice_have_target_host = false;
} else if (migration_has_failed(s)) {
@ -819,6 +825,14 @@ void qemu_spice_init(void)
#if SPICE_SERVER_VERSION >= 0x000c02
qemu_spice_register_ports();
#endif
#ifdef HAVE_SPICE_GL
if (qemu_opt_get_bool(opts, "gl", 0)) {
if (egl_rendernode_init() == 0) {
display_opengl = 1;
}
}
#endif
}
int qemu_spice_add_interface(SpiceBaseInstance *sin)

View File

@ -460,6 +460,13 @@ void qemu_spice_display_switch(SimpleSpiceDisplay *ssd,
memset(&ssd->dirty, 0, sizeof(ssd->dirty));
ssd->notify++;
qemu_mutex_lock(&ssd->lock);
if (ssd->cursor) {
g_free(ssd->ptr_define);
ssd->ptr_define = qemu_spice_create_cursor_update(ssd, ssd->cursor, 0);
}
qemu_mutex_unlock(&ssd->lock);
}
static void qemu_spice_cursor_refresh_unlocked(SimpleSpiceDisplay *ssd)
@ -467,8 +474,6 @@ static void qemu_spice_cursor_refresh_unlocked(SimpleSpiceDisplay *ssd)
if (ssd->cursor) {
assert(ssd->dcl.con);
dpy_cursor_define(ssd->dcl.con, ssd->cursor);
cursor_put(ssd->cursor);
ssd->cursor = NULL;
}
if (ssd->mouse_x != -1 && ssd->mouse_y != -1) {
assert(ssd->dcl.con);
@ -563,7 +568,7 @@ static int interface_get_command(QXLInstance *sin, QXLCommandExt *ext)
static int interface_req_cmd_notification(QXLInstance *sin)
{
dprint(1, "%s/%d:\n", __func__, sin->id);
dprint(2, "%s/%d:\n", __func__, sin->id);
return 1;
}
@ -616,7 +621,7 @@ static int interface_get_cursor_command(QXLInstance *sin, QXLCommandExt *ext)
static int interface_req_cursor_notification(QXLInstance *sin)
{
dprint(1, "%s:\n", __FUNCTION__);
dprint(2, "%s:\n", __func__);
return 1;
}
@ -645,9 +650,23 @@ static void interface_update_area_complete(QXLInstance *sin,
/* called from spice server thread context only */
static void interface_async_complete(QXLInstance *sin, uint64_t cookie_token)
{
/* should never be called, used in qxl native mode only */
fprintf(stderr, "%s: abort()\n", __func__);
abort();
QXLCookie *cookie = (QXLCookie *)(uintptr_t)cookie_token;
switch (cookie->type) {
#ifdef HAVE_SPICE_GL
case QXL_COOKIE_TYPE_GL_DRAW_DONE:
{
SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
qemu_bh_schedule(ssd->gl_unblock_bh);
break;
}
#endif
default:
/* should never be called, used in qxl native mode only */
fprintf(stderr, "%s: abort()\n", __func__);
abort();
}
g_free(cookie);
}
static void interface_set_client_capabilities(QXLInstance *sin,
@ -750,6 +769,11 @@ static void display_mouse_define(DisplayChangeListener *dcl,
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
qemu_mutex_lock(&ssd->lock);
if (c) {
cursor_get(c);
}
cursor_put(ssd->cursor);
ssd->cursor = c;
ssd->hot_x = c->hot_x;
ssd->hot_y = c->hot_y;
g_free(ssd->ptr_move);
@ -769,20 +793,128 @@ static const DisplayChangeListenerOps display_listener_ops = {
.dpy_cursor_define = display_mouse_define,
};
#ifdef HAVE_SPICE_GL
static void qemu_spice_gl_block(SimpleSpiceDisplay *ssd, bool block)
{
uint64_t timeout;
if (block) {
timeout = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
timeout += 1000; /* one sec */
timer_mod(ssd->gl_unblock_timer, timeout);
} else {
timer_del(ssd->gl_unblock_timer);
}
graphic_hw_gl_block(ssd->dcl.con, block);
}
static void qemu_spice_gl_unblock_bh(void *opaque)
{
SimpleSpiceDisplay *ssd = opaque;
qemu_spice_gl_block(ssd, false);
}
static void qemu_spice_gl_block_timer(void *opaque)
{
fprintf(stderr, "WARNING: spice: no gl-draw-done within one second\n");
}
static QEMUGLContext qemu_spice_gl_create_context(DisplayChangeListener *dcl,
QEMUGLParams *params)
{
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
qemu_egl_rn_ctx);
return qemu_egl_create_context(dcl, params);
}
static void qemu_spice_gl_scanout(DisplayChangeListener *dcl,
uint32_t tex_id,
bool y_0_top,
uint32_t x, uint32_t y,
uint32_t w, uint32_t h)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
EGLint stride = 0, fourcc = 0;
int fd = -1;
if (tex_id) {
fd = egl_get_fd_for_texture(tex_id, &stride, &fourcc);
if (fd < 0) {
fprintf(stderr, "%s: failed to get fd for texture\n", __func__);
return;
}
dprint(1, "%s: %dx%d (stride %d, fourcc 0x%x)\n", __func__,
w, h, stride, fourcc);
} else {
dprint(1, "%s: no texture (no framebuffer)\n", __func__);
}
assert(!tex_id || fd >= 0);
/* note: spice server will close the fd */
spice_qxl_gl_scanout(&ssd->qxl, fd,
surface_width(ssd->ds),
surface_height(ssd->ds),
stride, fourcc, y_0_top);
}
static void qemu_spice_gl_update(DisplayChangeListener *dcl,
uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
uint64_t cookie;
dprint(2, "%s: %dx%d+%d+%d\n", __func__, w, h, x, y);
qemu_spice_gl_block(ssd, true);
cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0);
spice_qxl_gl_draw_async(&ssd->qxl, x, y, w, h, cookie);
}
static const DisplayChangeListenerOps display_listener_gl_ops = {
.dpy_name = "spice-egl",
.dpy_gfx_update = display_update,
.dpy_gfx_switch = display_switch,
.dpy_gfx_check_format = qemu_pixman_check_format,
.dpy_refresh = display_refresh,
.dpy_mouse_set = display_mouse_set,
.dpy_cursor_define = display_mouse_define,
.dpy_gl_ctx_create = qemu_spice_gl_create_context,
.dpy_gl_ctx_destroy = qemu_egl_destroy_context,
.dpy_gl_ctx_make_current = qemu_egl_make_context_current,
.dpy_gl_ctx_get_current = qemu_egl_get_current_context,
.dpy_gl_scanout = qemu_spice_gl_scanout,
.dpy_gl_update = qemu_spice_gl_update,
};
#endif /* HAVE_SPICE_GL */
static void qemu_spice_display_init_one(QemuConsole *con)
{
SimpleSpiceDisplay *ssd = g_new0(SimpleSpiceDisplay, 1);
qemu_spice_display_init_common(ssd);
ssd->dcl.ops = &display_listener_ops;
#ifdef HAVE_SPICE_GL
if (display_opengl) {
ssd->dcl.ops = &display_listener_gl_ops;
ssd->dmabuf_fd = -1;
ssd->gl_unblock_bh = qemu_bh_new(qemu_spice_gl_unblock_bh, ssd);
ssd->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
qemu_spice_gl_block_timer, ssd);
}
#endif
ssd->dcl.con = con;
ssd->qxl.base.sif = &dpy_interface.base;
qemu_spice_add_display_interface(&ssd->qxl, con);
assert(ssd->worker);
qemu_spice_create_host_memslot(ssd);
ssd->dcl.ops = &display_listener_ops;
ssd->dcl.con = con;
register_displaychangelistener(&ssd->dcl);
}