aboutsummaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/egl-helpers.c129
-rw-r--r--ui/spice-core.c18
-rw-r--r--ui/spice-display.c152
3 files changed, 287 insertions, 12 deletions
diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c
index 4c83834615..54be44ccea 100644
--- a/ui/egl-helpers.c
+++ b/ui/egl-helpers.c
@@ -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;
diff --git a/ui/spice-core.c b/ui/spice-core.c
index 4dbd99ab19..a68a665a9a 100644
--- a/ui/spice-core.c
+++ b/ui/spice-core.c
@@ -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)
diff --git a/ui/spice-display.c b/ui/spice-display.c
index 8a5b3258bd..242ab5f468 100644
--- a/ui/spice-display.c
+++ b/ui/spice-display.c
@@ -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);
}