qemu-cr16/ui/spice-display.c
Vivek Kasireddy 376d4b22e4 ui/spice: Enable gl=on option for non-local or remote clients
Newer versions of Spice server should be able to accept dmabuf
fds from Qemu for clients that are connected via the network.
In other words, when this option is enabled, Qemu would share
a dmabuf fd with Spice which would encode and send the data
associated with the fd to a client that could be located on
a different machine.

Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: Marc-André Lureau <marcandre.lureau@redhat.com>
Cc: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Cc: Frediano Ziglio <freddy77@gmail.com>
Cc: Dongwon Kim <dongwon.kim@intel.com>
Cc: Michael Scherle <michael.scherle@rz.uni-freiburg.de>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
Message-Id: <20250617043546.1022779-3-vivek.kasireddy@intel.com>
2025-07-14 14:51:45 +04:00

1282 lines
41 KiB
C

/*
* Copyright (C) 2010 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 or
* (at your option) version 3 of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "ui/qemu-spice.h"
#include "qemu/error-report.h"
#include "qemu/timer.h"
#include "qemu/lockable.h"
#include "qemu/main-loop.h"
#include "qemu/option.h"
#include "qemu/queue.h"
#include "ui/console.h"
#include "trace.h"
#include "ui/spice-display.h"
#include "standard-headers/drm/drm_fourcc.h"
bool spice_opengl;
bool spice_remote_client;
int qemu_spice_rect_is_empty(const QXLRect* r)
{
return r->top == r->bottom || r->left == r->right;
}
void qemu_spice_rect_union(QXLRect *dest, const QXLRect *r)
{
if (qemu_spice_rect_is_empty(r)) {
return;
}
if (qemu_spice_rect_is_empty(dest)) {
*dest = *r;
return;
}
dest->top = MIN(dest->top, r->top);
dest->left = MIN(dest->left, r->left);
dest->bottom = MAX(dest->bottom, r->bottom);
dest->right = MAX(dest->right, r->right);
}
QXLCookie *qxl_cookie_new(int type, uint64_t io)
{
QXLCookie *cookie;
cookie = g_malloc0(sizeof(*cookie));
cookie->type = type;
cookie->io = io;
return cookie;
}
void qemu_spice_add_memslot(SimpleSpiceDisplay *ssd, QXLDevMemSlot *memslot,
qxl_async_io async)
{
trace_qemu_spice_add_memslot(ssd->qxl.id, memslot->slot_id,
memslot->virt_start, memslot->virt_end,
async);
if (async != QXL_SYNC) {
spice_qxl_add_memslot_async(&ssd->qxl, memslot,
(uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
QXL_IO_MEMSLOT_ADD_ASYNC));
} else {
spice_qxl_add_memslot(&ssd->qxl, memslot);
}
}
void qemu_spice_del_memslot(SimpleSpiceDisplay *ssd, uint32_t gid, uint32_t sid)
{
trace_qemu_spice_del_memslot(ssd->qxl.id, gid, sid);
spice_qxl_del_memslot(&ssd->qxl, gid, sid);
}
void qemu_spice_create_primary_surface(SimpleSpiceDisplay *ssd, uint32_t id,
QXLDevSurfaceCreate *surface,
qxl_async_io async)
{
trace_qemu_spice_create_primary_surface(ssd->qxl.id, id, surface, async);
if (async != QXL_SYNC) {
spice_qxl_create_primary_surface_async(&ssd->qxl, id, surface,
(uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
QXL_IO_CREATE_PRIMARY_ASYNC));
} else {
spice_qxl_create_primary_surface(&ssd->qxl, id, surface);
}
}
void qemu_spice_destroy_primary_surface(SimpleSpiceDisplay *ssd,
uint32_t id, qxl_async_io async)
{
trace_qemu_spice_destroy_primary_surface(ssd->qxl.id, id, async);
if (async != QXL_SYNC) {
spice_qxl_destroy_primary_surface_async(&ssd->qxl, id,
(uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
QXL_IO_DESTROY_PRIMARY_ASYNC));
} else {
spice_qxl_destroy_primary_surface(&ssd->qxl, id);
}
}
void qemu_spice_wakeup(SimpleSpiceDisplay *ssd)
{
trace_qemu_spice_wakeup(ssd->qxl.id);
spice_qxl_wakeup(&ssd->qxl);
}
static void qemu_spice_create_one_update(SimpleSpiceDisplay *ssd,
QXLRect *rect)
{
SimpleSpiceUpdate *update;
QXLDrawable *drawable;
QXLImage *image;
QXLCommand *cmd;
int bw, bh;
struct timespec time_space;
pixman_image_t *dest;
trace_qemu_spice_create_update(
rect->left, rect->right,
rect->top, rect->bottom);
update = g_malloc0(sizeof(*update));
drawable = &update->drawable;
image = &update->image;
cmd = &update->ext.cmd;
bw = rect->right - rect->left;
bh = rect->bottom - rect->top;
update->bitmap = g_malloc(bw * bh * 4);
drawable->bbox = *rect;
drawable->clip.type = SPICE_CLIP_TYPE_NONE;
drawable->effect = QXL_EFFECT_OPAQUE;
drawable->release_info.id = (uintptr_t)(&update->ext);
drawable->type = QXL_DRAW_COPY;
drawable->surfaces_dest[0] = -1;
drawable->surfaces_dest[1] = -1;
drawable->surfaces_dest[2] = -1;
clock_gettime(CLOCK_MONOTONIC, &time_space);
/* time in milliseconds from epoch. */
drawable->mm_time = time_space.tv_sec * 1000
+ time_space.tv_nsec / 1000 / 1000;
drawable->u.copy.rop_descriptor = SPICE_ROPD_OP_PUT;
drawable->u.copy.src_bitmap = (uintptr_t)image;
drawable->u.copy.src_area.right = bw;
drawable->u.copy.src_area.bottom = bh;
QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_DEVICE, ssd->unique++);
image->descriptor.type = SPICE_IMAGE_TYPE_BITMAP;
image->bitmap.flags = QXL_BITMAP_DIRECT | QXL_BITMAP_TOP_DOWN;
image->bitmap.stride = bw * 4;
image->descriptor.width = image->bitmap.x = bw;
image->descriptor.height = image->bitmap.y = bh;
image->bitmap.data = (uintptr_t)(update->bitmap);
image->bitmap.palette = 0;
image->bitmap.format = SPICE_BITMAP_FMT_32BIT;
dest = pixman_image_create_bits(PIXMAN_LE_x8r8g8b8, bw, bh,
(void *)update->bitmap, bw * 4);
pixman_image_composite(PIXMAN_OP_SRC, ssd->surface, NULL, ssd->mirror,
rect->left, rect->top, 0, 0,
rect->left, rect->top, bw, bh);
pixman_image_composite(PIXMAN_OP_SRC, ssd->mirror, NULL, dest,
rect->left, rect->top, 0, 0,
0, 0, bw, bh);
pixman_image_unref(dest);
cmd->type = QXL_CMD_DRAW;
cmd->data = (uintptr_t)drawable;
QTAILQ_INSERT_TAIL(&ssd->updates, update, next);
}
static void qemu_spice_create_update(SimpleSpiceDisplay *ssd)
{
static const int blksize = 32;
int blocks = DIV_ROUND_UP(surface_width(ssd->ds), blksize);
g_autofree int *dirty_top = NULL;
int y, yoff1, yoff2, x, xoff, blk, bw;
int bpp = surface_bytes_per_pixel(ssd->ds);
uint8_t *guest, *mirror;
if (qemu_spice_rect_is_empty(&ssd->dirty)) {
return;
};
dirty_top = g_new(int, blocks);
for (blk = 0; blk < blocks; blk++) {
dirty_top[blk] = -1;
}
guest = surface_data(ssd->ds);
mirror = (void *)pixman_image_get_data(ssd->mirror);
for (y = ssd->dirty.top; y < ssd->dirty.bottom; y++) {
yoff1 = y * surface_stride(ssd->ds);
yoff2 = y * pixman_image_get_stride(ssd->mirror);
for (x = ssd->dirty.left; x < ssd->dirty.right; x += blksize) {
xoff = x * bpp;
blk = x / blksize;
bw = MIN(blksize, ssd->dirty.right - x);
if (memcmp(guest + yoff1 + xoff,
mirror + yoff2 + xoff,
bw * bpp) == 0) {
if (dirty_top[blk] != -1) {
QXLRect update = {
.top = dirty_top[blk],
.bottom = y,
.left = x,
.right = x + bw,
};
qemu_spice_create_one_update(ssd, &update);
dirty_top[blk] = -1;
}
} else {
if (dirty_top[blk] == -1) {
dirty_top[blk] = y;
}
}
}
}
for (x = ssd->dirty.left; x < ssd->dirty.right; x += blksize) {
blk = x / blksize;
bw = MIN(blksize, ssd->dirty.right - x);
if (dirty_top[blk] != -1) {
QXLRect update = {
.top = dirty_top[blk],
.bottom = ssd->dirty.bottom,
.left = x,
.right = x + bw,
};
qemu_spice_create_one_update(ssd, &update);
dirty_top[blk] = -1;
}
}
memset(&ssd->dirty, 0, sizeof(ssd->dirty));
}
static SimpleSpiceCursor*
qemu_spice_create_cursor_update(SimpleSpiceDisplay *ssd,
QEMUCursor *c,
bool on)
{
size_t size = c ? c->width * c->height * 4 : 0;
SimpleSpiceCursor *update;
QXLCursorCmd *ccmd;
QXLCursor *cursor;
QXLCommand *cmd;
update = g_malloc0(sizeof(*update) + size);
ccmd = &update->cmd;
cursor = &update->cursor;
cmd = &update->ext.cmd;
if (c) {
ccmd->type = QXL_CURSOR_SET;
ccmd->u.set.position.x = ssd->ptr_x + ssd->hot_x;
ccmd->u.set.position.y = ssd->ptr_y + ssd->hot_y;
ccmd->u.set.visible = true;
ccmd->u.set.shape = (uintptr_t)cursor;
cursor->header.unique = ssd->unique++;
cursor->header.type = SPICE_CURSOR_TYPE_ALPHA;
cursor->header.width = c->width;
cursor->header.height = c->height;
cursor->header.hot_spot_x = c->hot_x;
cursor->header.hot_spot_y = c->hot_y;
cursor->data_size = size;
cursor->chunk.data_size = size;
memcpy(cursor->chunk.data, c->data, size);
} else if (!on) {
ccmd->type = QXL_CURSOR_HIDE;
} else {
ccmd->type = QXL_CURSOR_MOVE;
ccmd->u.position.x = ssd->ptr_x + ssd->hot_x;
ccmd->u.position.y = ssd->ptr_y + ssd->hot_y;
}
ccmd->release_info.id = (uintptr_t)(&update->ext);
cmd->type = QXL_CMD_CURSOR;
cmd->data = (uintptr_t)ccmd;
return update;
}
/*
* Called from spice server thread context (via interface_release_resource)
* We do *not* hold the global qemu mutex here, so extra care is needed
* when calling qemu functions. QEMU interfaces used:
* - g_free (underlying glibc free is re-entrant).
*/
void qemu_spice_destroy_update(SimpleSpiceDisplay *sdpy, SimpleSpiceUpdate *update)
{
g_free(update->bitmap);
g_free(update);
}
void qemu_spice_create_host_memslot(SimpleSpiceDisplay *ssd)
{
QXLDevMemSlot memslot;
memset(&memslot, 0, sizeof(memslot));
memslot.slot_group_id = MEMSLOT_GROUP_HOST;
memslot.virt_end = ~0;
qemu_spice_add_memslot(ssd, &memslot, QXL_SYNC);
}
void qemu_spice_create_host_primary(SimpleSpiceDisplay *ssd)
{
QXLDevSurfaceCreate surface;
uint64_t surface_size;
memset(&surface, 0, sizeof(surface));
surface_size = (uint64_t) surface_width(ssd->ds) *
surface_height(ssd->ds) * 4;
assert(surface_size > 0);
assert(surface_size < INT_MAX);
if (ssd->bufsize < surface_size) {
ssd->bufsize = surface_size;
g_free(ssd->buf);
ssd->buf = g_malloc(ssd->bufsize);
}
surface.format = SPICE_SURFACE_FMT_32_xRGB;
surface.width = surface_width(ssd->ds);
surface.height = surface_height(ssd->ds);
surface.stride = -surface.width * 4;
surface.mouse_mode = true;
surface.flags = 0;
surface.type = 0;
surface.mem = (uintptr_t)ssd->buf;
surface.group_id = MEMSLOT_GROUP_HOST;
qemu_spice_create_primary_surface(ssd, 0, &surface, QXL_SYNC);
}
void qemu_spice_destroy_host_primary(SimpleSpiceDisplay *ssd)
{
qemu_spice_destroy_primary_surface(ssd, 0, QXL_SYNC);
}
void qemu_spice_display_init_common(SimpleSpiceDisplay *ssd)
{
qemu_mutex_init(&ssd->lock);
QTAILQ_INIT(&ssd->updates);
ssd->mouse_x = -1;
ssd->mouse_y = -1;
if (ssd->num_surfaces == 0) {
ssd->num_surfaces = 1024;
}
}
/* display listener callbacks */
void qemu_spice_display_update(SimpleSpiceDisplay *ssd,
int x, int y, int w, int h)
{
QXLRect update_area;
trace_qemu_spice_display_update(ssd->qxl.id, x, y, w, h);
update_area.left = x,
update_area.right = x + w;
update_area.top = y;
update_area.bottom = y + h;
if (qemu_spice_rect_is_empty(&ssd->dirty)) {
ssd->notify++;
}
qemu_spice_rect_union(&ssd->dirty, &update_area);
}
void qemu_spice_display_switch(SimpleSpiceDisplay *ssd,
DisplaySurface *surface)
{
SimpleSpiceUpdate *update;
bool need_destroy;
if (ssd->surface &&
surface_width(surface) == pixman_image_get_width(ssd->surface) &&
surface_height(surface) == pixman_image_get_height(ssd->surface) &&
surface_format(surface) == pixman_image_get_format(ssd->surface)) {
/* no-resize fast path: just swap backing store */
trace_qemu_spice_display_surface(ssd->qxl.id,
surface_width(surface),
surface_height(surface),
true);
qemu_mutex_lock(&ssd->lock);
ssd->ds = surface;
pixman_image_unref(ssd->surface);
ssd->surface = pixman_image_ref(ssd->ds->image);
qemu_mutex_unlock(&ssd->lock);
qemu_spice_display_update(ssd, 0, 0,
surface_width(surface),
surface_height(surface));
return;
}
/* full mode switch */
trace_qemu_spice_display_surface(ssd->qxl.id,
surface_width(surface),
surface_height(surface),
false);
memset(&ssd->dirty, 0, sizeof(ssd->dirty));
if (ssd->surface) {
pixman_image_unref(ssd->surface);
ssd->surface = NULL;
pixman_image_unref(ssd->mirror);
ssd->mirror = NULL;
}
qemu_mutex_lock(&ssd->lock);
need_destroy = (ssd->ds != NULL);
ssd->ds = surface;
while ((update = QTAILQ_FIRST(&ssd->updates)) != NULL) {
QTAILQ_REMOVE(&ssd->updates, update, next);
qemu_spice_destroy_update(ssd, update);
}
qemu_mutex_unlock(&ssd->lock);
if (need_destroy) {
qemu_spice_destroy_host_primary(ssd);
}
if (ssd->ds) {
ssd->surface = pixman_image_ref(ssd->ds->image);
ssd->mirror = qemu_pixman_mirror_create(surface_format(ssd->ds),
ssd->ds->image);
qemu_spice_create_host_primary(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, false);
}
qemu_mutex_unlock(&ssd->lock);
}
void qemu_spice_cursor_refresh_bh(void *opaque)
{
SimpleSpiceDisplay *ssd = opaque;
qemu_mutex_lock(&ssd->lock);
if (ssd->cursor) {
QEMUCursor *c = ssd->cursor;
assert(ssd->dcl.con);
cursor_ref(c);
qemu_mutex_unlock(&ssd->lock);
dpy_cursor_define(ssd->dcl.con, c);
qemu_mutex_lock(&ssd->lock);
cursor_unref(c);
}
if (ssd->mouse_x != -1 && ssd->mouse_y != -1) {
int x, y;
assert(ssd->dcl.con);
x = ssd->mouse_x;
y = ssd->mouse_y;
ssd->mouse_x = -1;
ssd->mouse_y = -1;
qemu_mutex_unlock(&ssd->lock);
dpy_mouse_set(ssd->dcl.con, x, y, true);
} else {
qemu_mutex_unlock(&ssd->lock);
}
}
void qemu_spice_display_refresh(SimpleSpiceDisplay *ssd)
{
graphic_hw_update(ssd->dcl.con);
WITH_QEMU_LOCK_GUARD(&ssd->lock) {
if (QTAILQ_EMPTY(&ssd->updates) && ssd->ds) {
qemu_spice_create_update(ssd);
ssd->notify++;
}
}
trace_qemu_spice_display_refresh(ssd->qxl.id, ssd->notify);
if (ssd->notify) {
ssd->notify = 0;
qemu_spice_wakeup(ssd);
}
}
/* spice display interface callbacks */
#if SPICE_HAS_ATTACHED_WORKER
static void interface_attached_worker(QXLInstance *sin)
{
/* nothing to do */
}
#else
static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker)
{
/* nothing to do */
}
#endif
static void interface_set_compression_level(QXLInstance *sin, int level)
{
/* nothing to do */
}
static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info)
{
SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
info->memslot_gen_bits = MEMSLOT_GENERATION_BITS;
info->memslot_id_bits = MEMSLOT_SLOT_BITS;
info->num_memslots = NUM_MEMSLOTS;
info->num_memslots_groups = NUM_MEMSLOTS_GROUPS;
info->internal_groupslot_id = 0;
info->qxl_ram_size = 16 * 1024 * 1024;
info->n_surfaces = ssd->num_surfaces;
}
static int interface_get_command(QXLInstance *sin, QXLCommandExt *ext)
{
SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
SimpleSpiceUpdate *update;
int ret = false;
qemu_mutex_lock(&ssd->lock);
update = QTAILQ_FIRST(&ssd->updates);
if (update != NULL) {
QTAILQ_REMOVE(&ssd->updates, update, next);
*ext = update->ext;
ret = true;
}
qemu_mutex_unlock(&ssd->lock);
return ret;
}
static int interface_req_cmd_notification(QXLInstance *sin)
{
return 1;
}
static void interface_release_resource(QXLInstance *sin,
QXLReleaseInfoExt rext)
{
SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
SimpleSpiceUpdate *update;
SimpleSpiceCursor *cursor;
QXLCommandExt *ext;
if (!rext.info) {
return;
}
ext = (void *)(intptr_t)(rext.info->id);
switch (ext->cmd.type) {
case QXL_CMD_DRAW:
update = container_of(ext, SimpleSpiceUpdate, ext);
qemu_spice_destroy_update(ssd, update);
break;
case QXL_CMD_CURSOR:
cursor = container_of(ext, SimpleSpiceCursor, ext);
g_free(cursor);
break;
default:
g_assert_not_reached();
}
}
static int interface_get_cursor_command(QXLInstance *sin, QXLCommandExt *ext)
{
SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
int ret;
QEMU_LOCK_GUARD(&ssd->lock);
if (ssd->ptr_define) {
*ext = ssd->ptr_define->ext;
ssd->ptr_define = NULL;
ret = true;
} else if (ssd->ptr_move) {
*ext = ssd->ptr_move->ext;
ssd->ptr_move = NULL;
ret = true;
} else {
ret = false;
}
return ret;
}
static int interface_req_cursor_notification(QXLInstance *sin)
{
return 1;
}
static void interface_notify_update(QXLInstance *sin, uint32_t update_id)
{
fprintf(stderr, "%s: abort()\n", __func__);
abort();
}
static int interface_flush_resources(QXLInstance *sin)
{
fprintf(stderr, "%s: abort()\n", __func__);
abort();
return 0;
}
static void interface_update_area_complete(QXLInstance *sin,
uint32_t surface_id,
QXLRect *dirty, uint32_t num_updated_rects)
{
/* should never be called, used in qxl native mode only */
fprintf(stderr, "%s: abort()\n", __func__);
abort();
}
/* called from spice server thread context only */
static void interface_async_complete(QXLInstance *sin, uint64_t cookie_token)
{
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;
}
case QXL_COOKIE_TYPE_IO:
if (cookie->io == QXL_IO_MONITORS_CONFIG_ASYNC) {
g_free(cookie->u.data);
}
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,
uint8_t client_present,
uint8_t caps[58])
{
/* nothing to do */
}
static int interface_client_monitors_config(QXLInstance *sin,
VDAgentMonitorsConfig *mc)
{
SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
QemuUIInfo info;
int head;
if (!dpy_ui_info_supported(ssd->dcl.con)) {
return 0; /* == not supported by guest */
}
if (!mc) {
return 1;
}
info = *dpy_get_ui_info(ssd->dcl.con);
head = qemu_console_get_index(ssd->dcl.con);
if (mc->num_of_monitors > head) {
info.width = mc->monitors[head].width;
info.height = mc->monitors[head].height;
#if SPICE_SERVER_VERSION >= 0x000e04 /* release 0.14.4 */
if (mc->flags & VD_AGENT_CONFIG_MONITORS_FLAG_PHYSICAL_SIZE) {
VDAgentMonitorMM *mm = (void *)&mc->monitors[mc->num_of_monitors];
info.width_mm = mm[head].width;
info.height_mm = mm[head].height;
}
#endif
}
trace_qemu_spice_ui_info(ssd->qxl.id, info.width, info.height);
dpy_set_ui_info(ssd->dcl.con, &info, false);
return 1;
}
static const QXLInterface dpy_interface = {
.base.type = SPICE_INTERFACE_QXL,
.base.description = "qemu simple display",
.base.major_version = SPICE_INTERFACE_QXL_MAJOR,
.base.minor_version = SPICE_INTERFACE_QXL_MINOR,
#if SPICE_HAS_ATTACHED_WORKER
.attached_worker = interface_attached_worker,
#else
.attache_worker = interface_attach_worker,
#endif
.set_compression_level = interface_set_compression_level,
.get_init_info = interface_get_init_info,
/* the callbacks below are called from spice server thread context */
.get_command = interface_get_command,
.req_cmd_notification = interface_req_cmd_notification,
.release_resource = interface_release_resource,
.get_cursor_command = interface_get_cursor_command,
.req_cursor_notification = interface_req_cursor_notification,
.notify_update = interface_notify_update,
.flush_resources = interface_flush_resources,
.async_complete = interface_async_complete,
.update_area_complete = interface_update_area_complete,
.set_client_capabilities = interface_set_client_capabilities,
.client_monitors_config = interface_client_monitors_config,
};
static void display_update(DisplayChangeListener *dcl,
int x, int y, int w, int h)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
qemu_spice_display_update(ssd, x, y, w, h);
}
static void display_switch(DisplayChangeListener *dcl,
DisplaySurface *surface)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
qemu_spice_display_switch(ssd, surface);
}
static void display_refresh(DisplayChangeListener *dcl)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
qemu_spice_display_refresh(ssd);
}
static void display_mouse_set(DisplayChangeListener *dcl,
int x, int y, bool on)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
qemu_mutex_lock(&ssd->lock);
ssd->ptr_x = x;
ssd->ptr_y = y;
g_free(ssd->ptr_move);
ssd->ptr_move = qemu_spice_create_cursor_update(ssd, NULL, on);
qemu_mutex_unlock(&ssd->lock);
qemu_spice_wakeup(ssd);
}
static void display_mouse_define(DisplayChangeListener *dcl,
QEMUCursor *c)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
qemu_mutex_lock(&ssd->lock);
cursor_ref(c);
cursor_unref(ssd->cursor);
ssd->cursor = c;
ssd->hot_x = c->hot_x;
ssd->hot_y = c->hot_y;
g_free(ssd->ptr_move);
ssd->ptr_move = NULL;
g_free(ssd->ptr_define);
ssd->ptr_define = qemu_spice_create_cursor_update(ssd, c, false);
qemu_mutex_unlock(&ssd->lock);
qemu_spice_wakeup(ssd);
}
static const DisplayChangeListenerOps display_listener_ops = {
.dpy_name = "spice",
.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,
};
#ifdef HAVE_SPICE_GL
static void qemu_spice_gl_monitor_config(SimpleSpiceDisplay *ssd,
int x, int y, int w, int h)
{
QXLMonitorsConfig *config;
QXLCookie *cookie;
config = g_malloc0(sizeof(QXLMonitorsConfig) + sizeof(QXLHead));
config->count = 1;
config->max_allowed = 1;
config->heads[0].x = x;
config->heads[0].y = y;
config->heads[0].width = w;
config->heads[0].height = h;
cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO,
QXL_IO_MONITORS_CONFIG_ASYNC);
cookie->u.data = config;
spice_qxl_monitors_config_async(&ssd->qxl,
(uintptr_t)config,
MEMSLOT_GROUP_HOST,
(uintptr_t)cookie);
}
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)
{
warn_report("spice: no gl-draw-done within one second");
}
static void spice_gl_refresh(DisplayChangeListener *dcl)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
uint64_t cookie;
if (!ssd->ds || qemu_console_is_gl_blocked(ssd->dcl.con)) {
return;
}
graphic_hw_update(dcl->con);
if (ssd->gl_updates && ssd->have_surface) {
qemu_spice_gl_block(ssd, true);
glFlush();
cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0);
spice_qxl_gl_draw_async(&ssd->qxl, 0, 0,
surface_width(ssd->ds),
surface_height(ssd->ds),
cookie);
ssd->gl_updates = 0;
}
}
static void spice_gl_update(DisplayChangeListener *dcl,
int x, int y, int w, int h)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
surface_gl_update_texture(ssd->gls, ssd->ds, x, y, w, h);
ssd->gl_updates++;
}
static void spice_server_gl_scanout(QXLInstance *qxl,
const int *fd,
uint32_t width, uint32_t height,
const uint32_t *offset,
const uint32_t *stride,
uint32_t num_planes, uint32_t format,
uint64_t modifier, int y_0_top)
{
#ifdef HAVE_SPICE_QXL_GL_SCANOUT2
spice_qxl_gl_scanout2(qxl, fd, width, height, offset, stride,
num_planes, format, modifier, y_0_top);
#else
if (num_planes <= 1) {
spice_qxl_gl_scanout(qxl, fd[0], width, height, stride[0], format, y_0_top);
} else {
error_report("SPICE server does not support multi plane GL scanout");
}
#endif
}
static void spice_gl_switch(DisplayChangeListener *dcl,
struct DisplaySurface *new_surface)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
if (ssd->ds) {
surface_gl_destroy_texture(ssd->gls, ssd->ds);
}
ssd->ds = new_surface;
if (ssd->ds) {
uint32_t offset[DMABUF_MAX_PLANES], stride[DMABUF_MAX_PLANES];
int fd[DMABUF_MAX_PLANES], num_planes, fourcc;
uint64_t modifier;
surface_gl_create_texture(ssd->gls, ssd->ds);
if (!egl_dmabuf_export_texture(ssd->ds->texture,
fd,
(EGLint *)offset,
(EGLint *)stride,
&fourcc,
&num_planes,
&modifier)) {
surface_gl_destroy_texture(ssd->gls, ssd->ds);
return;
}
trace_qemu_spice_gl_surface(ssd->qxl.id,
surface_width(ssd->ds),
surface_height(ssd->ds),
fourcc);
/* note: spice server will close the fd */
spice_server_gl_scanout(&ssd->qxl, fd,
surface_width(ssd->ds),
surface_height(ssd->ds),
offset, stride, num_planes,
fourcc, modifier, false);
ssd->have_surface = true;
ssd->have_scanout = false;
qemu_spice_gl_monitor_config(ssd, 0, 0,
surface_width(ssd->ds),
surface_height(ssd->ds));
}
}
static QEMUGLContext qemu_spice_gl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
qemu_egl_rn_ctx);
return qemu_egl_create_context(dgc, params);
}
static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
trace_qemu_spice_gl_scanout_disable(ssd->qxl.id);
spice_server_gl_scanout(&ssd->qxl, NULL, 0, 0, NULL, NULL, 0, DRM_FORMAT_INVALID,
DRM_FORMAT_MOD_INVALID, false);
qemu_spice_gl_monitor_config(ssd, 0, 0, 0, 0);
ssd->have_surface = false;
ssd->have_scanout = false;
}
static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
uint32_t tex_id,
bool y_0_top,
uint32_t backing_width,
uint32_t backing_height,
uint32_t x, uint32_t y,
uint32_t w, uint32_t h,
void *d3d_tex2d)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
EGLint offset[DMABUF_MAX_PLANES], stride[DMABUF_MAX_PLANES], fourcc = 0;
int fd[DMABUF_MAX_PLANES], num_planes;
uint64_t modifier;
assert(tex_id);
if (!egl_dmabuf_export_texture(tex_id, fd, offset, stride, &fourcc,
&num_planes, &modifier)) {
fprintf(stderr, "%s: failed to export dmabuf for texture\n", __func__);
return;
}
trace_qemu_spice_gl_scanout_texture(ssd->qxl.id, w, h, fourcc);
/* note: spice server will close the fd */
spice_server_gl_scanout(&ssd->qxl, fd, backing_width, backing_height,
(uint32_t *)offset, (uint32_t *)stride, num_planes,
fourcc, modifier, y_0_top);
qemu_spice_gl_monitor_config(ssd, x, y, w, h);
ssd->have_surface = false;
ssd->have_scanout = true;
}
static void qemu_spice_gl_scanout_dmabuf(DisplayChangeListener *dcl,
QemuDmaBuf *dmabuf)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
ssd->guest_dmabuf = dmabuf;
ssd->guest_dmabuf_refresh = true;
ssd->have_surface = false;
ssd->have_scanout = true;
}
static void qemu_spice_gl_cursor_dmabuf(DisplayChangeListener *dcl,
QemuDmaBuf *dmabuf, bool have_hot,
uint32_t hot_x, uint32_t hot_y)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
uint32_t width, height, texture;
ssd->have_hot = have_hot;
ssd->hot_x = hot_x;
ssd->hot_y = hot_y;
trace_qemu_spice_gl_cursor(ssd->qxl.id, dmabuf != NULL, have_hot);
if (dmabuf) {
egl_dmabuf_import_texture(dmabuf);
texture = qemu_dmabuf_get_texture(dmabuf);
if (!texture) {
return;
}
width = qemu_dmabuf_get_width(dmabuf);
height = qemu_dmabuf_get_height(dmabuf);
egl_fb_setup_for_tex(&ssd->cursor_fb, width, height, texture, false);
} else {
egl_fb_destroy(&ssd->cursor_fb);
}
}
static void qemu_spice_gl_cursor_position(DisplayChangeListener *dcl,
uint32_t pos_x, uint32_t pos_y)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
qemu_mutex_lock(&ssd->lock);
ssd->ptr_x = pos_x;
ssd->ptr_y = pos_y;
qemu_mutex_unlock(&ssd->lock);
}
static void qemu_spice_gl_release_dmabuf(DisplayChangeListener *dcl,
QemuDmaBuf *dmabuf)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
if (ssd->guest_dmabuf == dmabuf) {
ssd->guest_dmabuf = NULL;
ssd->guest_dmabuf_refresh = false;
}
egl_dmabuf_release_texture(dmabuf);
}
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);
EGLint fourcc = 0;
bool render_cursor = false;
bool y_0_top = false; /* FIXME */
uint64_t cookie;
uint32_t width, height, texture;
if (!ssd->have_scanout) {
return;
}
if (ssd->cursor_fb.texture) {
render_cursor = true;
}
if (ssd->render_cursor != render_cursor) {
ssd->render_cursor = render_cursor;
ssd->guest_dmabuf_refresh = true;
egl_fb_destroy(&ssd->blit_fb);
}
if (ssd->guest_dmabuf_refresh) {
QemuDmaBuf *dmabuf = ssd->guest_dmabuf;
width = qemu_dmabuf_get_width(dmabuf);
height = qemu_dmabuf_get_height(dmabuf);
if (render_cursor) {
egl_dmabuf_import_texture(dmabuf);
texture = qemu_dmabuf_get_texture(dmabuf);
if (!texture) {
return;
}
/* source framebuffer */
egl_fb_setup_for_tex(&ssd->guest_fb, width, height,
texture, false);
/* dest framebuffer */
if (ssd->blit_fb.width != width ||
ssd->blit_fb.height != height) {
int fds[DMABUF_MAX_PLANES], num_planes;
uint32_t offsets[DMABUF_MAX_PLANES], strides[DMABUF_MAX_PLANES];
uint64_t modifier;
trace_qemu_spice_gl_render_dmabuf(ssd->qxl.id, width,
height);
egl_fb_destroy(&ssd->blit_fb);
egl_fb_setup_new_tex(&ssd->blit_fb,
width, height);
if (!egl_dmabuf_export_texture(ssd->blit_fb.texture, fds,
(EGLint *)offsets, (EGLint *)strides,
&fourcc, &num_planes, &modifier)) {
fprintf(stderr,
"%s: failed to export dmabuf for texture\n", __func__);
return;
}
spice_server_gl_scanout(&ssd->qxl, fds, width, height, offsets, strides,
num_planes, fourcc, modifier, false);
}
} else {
int fds[DMABUF_MAX_PLANES];
int noffsets, nstrides;
const uint32_t *offsets = qemu_dmabuf_get_offsets(dmabuf, &noffsets);
const uint32_t *strides = qemu_dmabuf_get_strides(dmabuf, &nstrides);
uint32_t num_planes = qemu_dmabuf_get_num_planes(dmabuf);
assert(noffsets >= num_planes);
assert(nstrides >= num_planes);
fourcc = qemu_dmabuf_get_fourcc(dmabuf);
y_0_top = qemu_dmabuf_get_y0_top(dmabuf);
qemu_dmabuf_dup_fds(dmabuf, fds, DMABUF_MAX_PLANES);
trace_qemu_spice_gl_forward_dmabuf(ssd->qxl.id, width, height);
/* note: spice server will close the fd, so hand over a dup */
spice_server_gl_scanout(&ssd->qxl, fds, width, height,
offsets, strides, num_planes,
fourcc,
qemu_dmabuf_get_modifier(dmabuf),
y_0_top);
}
qemu_spice_gl_monitor_config(ssd, 0, 0, width, height);
ssd->guest_dmabuf_refresh = false;
}
if (render_cursor) {
int ptr_x, ptr_y;
qemu_mutex_lock(&ssd->lock);
ptr_x = ssd->ptr_x;
ptr_y = ssd->ptr_y;
qemu_mutex_unlock(&ssd->lock);
egl_texture_blit(ssd->gls, &ssd->blit_fb, &ssd->guest_fb,
!y_0_top);
egl_texture_blend(ssd->gls, &ssd->blit_fb, &ssd->cursor_fb,
!y_0_top, ptr_x, ptr_y, 1.0, 1.0);
glFlush();
}
trace_qemu_spice_gl_update(ssd->qxl.id, w, h, x, y);
qemu_spice_gl_block(ssd, true);
glFlush();
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 = spice_gl_update,
.dpy_gfx_switch = spice_gl_switch,
.dpy_gfx_check_format = console_gl_check_format,
.dpy_refresh = spice_gl_refresh,
.dpy_mouse_set = display_mouse_set,
.dpy_cursor_define = display_mouse_define,
.dpy_gl_scanout_disable = qemu_spice_gl_scanout_disable,
.dpy_gl_scanout_texture = qemu_spice_gl_scanout_texture,
.dpy_gl_scanout_dmabuf = qemu_spice_gl_scanout_dmabuf,
.dpy_gl_cursor_dmabuf = qemu_spice_gl_cursor_dmabuf,
.dpy_gl_cursor_position = qemu_spice_gl_cursor_position,
.dpy_gl_release_dmabuf = qemu_spice_gl_release_dmabuf,
.dpy_gl_update = qemu_spice_gl_update,
};
static bool
qemu_spice_is_compatible_dcl(DisplayGLCtx *dgc,
DisplayChangeListener *dcl)
{
return dcl->ops == &display_listener_gl_ops;
}
static const DisplayGLCtxOps gl_ctx_ops = {
.dpy_gl_ctx_is_compatible_dcl = qemu_spice_is_compatible_dcl,
.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,
};
#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 (spice_opengl) {
ssd->dcl.ops = &display_listener_gl_ops;
ssd->dgc.ops = &gl_ctx_ops;
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);
ssd->gls = qemu_gl_init_shader();
ssd->have_surface = false;
ssd->have_scanout = false;
}
#endif
ssd->dcl.con = con;
ssd->qxl.base.sif = &dpy_interface.base;
qemu_spice_add_display_interface(&ssd->qxl, con);
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
Error *err = NULL;
char device_address[256] = "";
if (qemu_console_fill_device_address(con, device_address, 256, &err)) {
spice_qxl_set_device_info(&ssd->qxl,
device_address,
qemu_console_get_head(con),
1);
} else {
error_report_err(err);
}
#endif
qemu_spice_create_host_memslot(ssd);
if (spice_opengl) {
qemu_console_set_display_gl_ctx(con, &ssd->dgc);
}
register_displaychangelistener(&ssd->dcl);
}
void qemu_spice_display_init(void)
{
QemuOptsList *olist = qemu_find_opts("spice");
QemuOpts *opts = QTAILQ_FIRST(&olist->head);
QemuConsole *spice_con, *con;
const char *str;
int i;
str = qemu_opt_get(opts, "display");
if (str) {
int head = qemu_opt_get_number(opts, "head", 0);
Error *err = NULL;
spice_con = qemu_console_lookup_by_device_name(str, head, &err);
if (err) {
error_report("Failed to lookup display/head");
exit(1);
}
} else {
spice_con = NULL;
}
for (i = 0;; i++) {
con = qemu_console_lookup_by_index(i);
if (!con || !qemu_console_is_graphic(con)) {
break;
}
if (qemu_spice_have_display_interface(con)) {
continue;
}
if (spice_con != NULL && spice_con != con) {
continue;
}
qemu_spice_display_init_one(con);
}
qemu_spice_display_init_done();
}