From 9cfa7ab939f27ffca845ffb9a28c3fa1856c92e7 Mon Sep 17 00:00:00 2001 From: Philippe Voinov Date: Fri, 5 May 2017 15:39:52 +0200 Subject: [PATCH 01/10] ui: Support non-zero minimum values for absolute input axes This patch refactors ui/input.c to support absolute axis minimum values other than 0. All dependent calls to qemu_input_queue_abs have been updated to explicitly supply 0 as the axis minimum value. Signed-off-by: Philippe Voinov Message-id: 20170505133952.29885-1-philippevoinov@gmail.com Signed-off-by: Gerd Hoffmann --- hw/input/virtio-input-hid.c | 6 ++++-- include/ui/input.h | 11 +++++++---- ui/cocoa.m | 4 ++-- ui/gtk.c | 4 ++-- ui/input.c | 31 ++++++++++++++++++++++--------- ui/sdl.c | 4 ++-- ui/sdl2.c | 4 ++-- ui/spice-input.c | 4 ++-- ui/vnc.c | 4 ++-- 9 files changed, 45 insertions(+), 27 deletions(-) diff --git a/hw/input/virtio-input-hid.c b/hw/input/virtio-input-hid.c index 3ee0c1814a..46c038110c 100644 --- a/hw/input/virtio-input-hid.c +++ b/hw/input/virtio-input-hid.c @@ -484,12 +484,14 @@ static struct virtio_input_config virtio_tablet_config[] = { .select = VIRTIO_INPUT_CFG_ABS_INFO, .subsel = ABS_X, .size = sizeof(virtio_input_absinfo), - .u.abs.max = const_le32(INPUT_EVENT_ABS_SIZE - 1), + .u.abs.min = const_le32(INPUT_EVENT_ABS_MIN), + .u.abs.max = const_le32(INPUT_EVENT_ABS_MAX), },{ .select = VIRTIO_INPUT_CFG_ABS_INFO, .subsel = ABS_Y, .size = sizeof(virtio_input_absinfo), - .u.abs.max = const_le32(INPUT_EVENT_ABS_SIZE - 1), + .u.abs.min = const_le32(INPUT_EVENT_ABS_MIN), + .u.abs.max = const_le32(INPUT_EVENT_ABS_MAX), }, { /* end of list */ }, }; diff --git a/include/ui/input.h b/include/ui/input.h index d06a12dd4c..3cfd0f3363 100644 --- a/include/ui/input.h +++ b/include/ui/input.h @@ -8,7 +8,8 @@ #define INPUT_EVENT_MASK_REL (1<con, INPUT_AXIS_X, p.x, screen.width); - qemu_input_queue_abs(dcl->con, INPUT_AXIS_Y, screen.height - p.y, screen.height); + qemu_input_queue_abs(dcl->con, INPUT_AXIS_X, p.x, 0, screen.width); + qemu_input_queue_abs(dcl->con, INPUT_AXIS_Y, screen.height - p.y, 0, screen.height); } } else { qemu_input_queue_rel(dcl->con, INPUT_AXIS_X, (int)[event deltaX]); diff --git a/ui/gtk.c b/ui/gtk.c index 7479ceef35..0213ad0efc 100644 --- a/ui/gtk.c +++ b/ui/gtk.c @@ -912,9 +912,9 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion, return TRUE; } qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_X, x, - surface_width(vc->gfx.ds)); + 0, surface_width(vc->gfx.ds)); qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, y, - surface_height(vc->gfx.ds)); + 0, surface_height(vc->gfx.ds)); qemu_input_event_sync(); } else if (s->last_set && s->ptr_owner == vc) { qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, x - s->last_x); diff --git a/ui/input.c b/ui/input.c index 830f912f99..290ca9f54d 100644 --- a/ui/input.c +++ b/ui/input.c @@ -166,6 +166,11 @@ void qmp_input_send_event(bool has_device, const char *device, qemu_input_event_sync(); } +static int qemu_input_transform_invert_abs_value(int value) +{ + return (int64_t)INPUT_EVENT_ABS_MAX - value + INPUT_EVENT_ABS_MIN; +} + static void qemu_input_transform_abs_rotate(InputEvent *evt) { InputMoveEvent *move = evt->u.abs.data; @@ -175,16 +180,16 @@ static void qemu_input_transform_abs_rotate(InputEvent *evt) move->axis = INPUT_AXIS_Y; } else if (move->axis == INPUT_AXIS_Y) { move->axis = INPUT_AXIS_X; - move->value = INPUT_EVENT_ABS_SIZE - 1 - move->value; + move->value = qemu_input_transform_invert_abs_value(move->value); } break; case 180: - move->value = INPUT_EVENT_ABS_SIZE - 1 - move->value; + move->value = qemu_input_transform_invert_abs_value(move->value); break; case 270: if (move->axis == INPUT_AXIS_X) { move->axis = INPUT_AXIS_Y; - move->value = INPUT_EVENT_ABS_SIZE - 1 - move->value; + move->value = qemu_input_transform_invert_abs_value(move->value); } else if (move->axis == INPUT_AXIS_Y) { move->axis = INPUT_AXIS_X; } @@ -467,12 +472,17 @@ bool qemu_input_is_absolute(void) return (s != NULL) && (s->handler->mask & INPUT_EVENT_MASK_ABS); } -int qemu_input_scale_axis(int value, int size_in, int size_out) +int qemu_input_scale_axis(int value, + int min_in, int max_in, + int min_out, int max_out) { - if (size_in < 2) { - return size_out / 2; + int64_t range_in = (int64_t)max_in - min_in; + int64_t range_out = (int64_t)max_out - min_out; + + if (range_in < 1) { + return min_out + range_out / 2; } - return (int64_t)value * (size_out - 1) / (size_in - 1); + return ((int64_t)value - min_in) * range_out / range_in + min_out; } InputEvent *qemu_input_event_new_move(InputEventKind kind, @@ -496,10 +506,13 @@ void qemu_input_queue_rel(QemuConsole *src, InputAxis axis, int value) qapi_free_InputEvent(evt); } -void qemu_input_queue_abs(QemuConsole *src, InputAxis axis, int value, int size) +void qemu_input_queue_abs(QemuConsole *src, InputAxis axis, int value, + int min_in, int max_in) { InputEvent *evt; - int scaled = qemu_input_scale_axis(value, size, INPUT_EVENT_ABS_SIZE); + int scaled = qemu_input_scale_axis(value, min_in, max_in, + INPUT_EVENT_ABS_MIN, + INPUT_EVENT_ABS_MAX); evt = qemu_input_event_new_move(INPUT_EVENT_KIND_ABS, axis, scaled); qemu_input_event_send(src, evt); qapi_free_InputEvent(evt); diff --git a/ui/sdl.c b/ui/sdl.c index 37c21a00fb..b35a67855f 100644 --- a/ui/sdl.c +++ b/ui/sdl.c @@ -490,9 +490,9 @@ static void sdl_send_mouse_event(int dx, int dy, int x, int y, int state) if (qemu_input_is_absolute()) { qemu_input_queue_abs(dcl->con, INPUT_AXIS_X, x, - real_screen->w); + 0, real_screen->w); qemu_input_queue_abs(dcl->con, INPUT_AXIS_Y, y, - real_screen->h); + 0, real_screen->h); } else { if (guest_cursor) { x -= guest_x; diff --git a/ui/sdl2.c b/ui/sdl2.c index faf9bdff5c..21de05200e 100644 --- a/ui/sdl2.c +++ b/ui/sdl2.c @@ -298,8 +298,8 @@ static void sdl_send_mouse_event(struct sdl2_console *scon, int dx, int dy, } } } - qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_X, off_x + x, max_w); - qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_Y, off_y + y, max_h); + qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_X, off_x + x, 0, max_w); + qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_Y, off_y + y, 0, max_h); } else { if (guest_cursor) { x -= guest_x; diff --git a/ui/spice-input.c b/ui/spice-input.c index 8eeebdbb2e..86293dd2ce 100644 --- a/ui/spice-input.c +++ b/ui/spice-input.c @@ -172,8 +172,8 @@ static void tablet_position(SpiceTabletInstance* sin, int x, int y, QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet); spice_update_buttons(pointer, 0, buttons_state); - qemu_input_queue_abs(NULL, INPUT_AXIS_X, x, pointer->width); - qemu_input_queue_abs(NULL, INPUT_AXIS_Y, y, pointer->height); + qemu_input_queue_abs(NULL, INPUT_AXIS_X, x, 0, pointer->width); + qemu_input_queue_abs(NULL, INPUT_AXIS_Y, y, 0, pointer->height); qemu_input_event_sync(); } diff --git a/ui/vnc.c b/ui/vnc.c index 9c4edcdbf5..601f9ec539 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -1556,8 +1556,8 @@ static void pointer_event(VncState *vs, int button_mask, int x, int y) } if (vs->absolute) { - qemu_input_queue_abs(con, INPUT_AXIS_X, x, width); - qemu_input_queue_abs(con, INPUT_AXIS_Y, y, height); + qemu_input_queue_abs(con, INPUT_AXIS_X, x, 0, width); + qemu_input_queue_abs(con, INPUT_AXIS_Y, y, 0, height); } else if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE)) { qemu_input_queue_rel(con, INPUT_AXIS_X, x - 0x7FFF); qemu_input_queue_rel(con, INPUT_AXIS_Y, y - 0x7FFF); From d755defd5d779d96a9a204cdd559458798c4a36a Mon Sep 17 00:00:00 2001 From: Philippe Voinov Date: Fri, 5 May 2017 15:42:31 +0200 Subject: [PATCH 02/10] ui: input-linux: Add absolute event support This patch adds support for absolute pointer events to the input-linux subsystem. This support was omitted from the original input-linux patch, however most of the code required for it is already in place. Support for absolute events is especially useful for guests with vga passthrough. Since they have a physical monitor, none of normal channels for sending video output (vnc, etc) are used, meaning they also can't be used to send absolute input events. This leaves QMP as the only option to send absolute input into vga passthrough guests, which is not its intended use and is not efficient. This patch allows, for example, uinput to be used to create virtual absolute input devices. This lets you build external systems which share physical input devices between guests. Without absolute input capability, such external systems can't seamlessly share pointer devices between guests. Signed-off-by: Philippe Voinov Message-id: 20170505134231.30210-1-philippevoinov@gmail.com Signed-off-by: Gerd Hoffmann --- ui/input-linux.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/ui/input-linux.c b/ui/input-linux.c index dc0613ca1f..49d52a69cc 100644 --- a/ui/input-linux.c +++ b/ui/input-linux.c @@ -169,6 +169,10 @@ struct InputLinux { bool has_abs_x; int num_keys; int num_btns; + int abs_x_min; + int abs_x_max; + int abs_y_min; + int abs_y_max; struct input_event event; int read_offset; @@ -314,6 +318,18 @@ static void input_linux_handle_mouse(InputLinux *il, struct input_event *event) break; } break; + case EV_ABS: + switch (event->code) { + case ABS_X: + qemu_input_queue_abs(NULL, INPUT_AXIS_X, event->value, + il->abs_x_min, il->abs_x_max); + break; + case ABS_Y: + qemu_input_queue_abs(NULL, INPUT_AXIS_Y, event->value, + il->abs_y_min, il->abs_y_max); + break; + } + break; case EV_SYN: qemu_input_event_sync(); if (il->wheel != 0) { @@ -351,7 +367,7 @@ static void input_linux_event(void *opaque) if (il->num_keys) { input_linux_handle_keyboard(il, &il->event); } - if (il->has_rel_x && il->num_btns) { + if ((il->has_rel_x || il->has_abs_x) && il->num_btns) { input_linux_handle_mouse(il, &il->event); } } @@ -364,6 +380,7 @@ static void input_linux_complete(UserCreatable *uc, Error **errp) uint8_t keymap[KEY_CNT / 8], keystate[KEY_CNT / 8]; unsigned int i; int rc, ver; + struct input_absinfo absinfo; if (!il->evdev) { error_setg(errp, "no input device specified"); @@ -402,6 +419,12 @@ static void input_linux_complete(UserCreatable *uc, Error **errp) rc = ioctl(il->fd, EVIOCGBIT(EV_ABS, sizeof(absmap)), &absmap); if (absmap & (1 << ABS_X)) { il->has_abs_x = true; + rc = ioctl(il->fd, EVIOCGABS(ABS_X), &absinfo); + il->abs_x_min = absinfo.minimum; + il->abs_x_max = absinfo.maximum; + rc = ioctl(il->fd, EVIOCGABS(ABS_Y), &absinfo); + il->abs_y_min = absinfo.minimum; + il->abs_y_max = absinfo.maximum; } } From c19f4fbce1c2293b7a9bddadddd7a1b69953f534 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 5 May 2017 12:40:56 +0200 Subject: [PATCH 03/10] virtio-gpu: move virtio_gpu_gl_block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move to virtio-gpu-3d.c where all the other virgl code lives too. Signed-off-by: Gerd Hoffmann Reviewed-by: Philippe Mathieu-Daudé Message-id: 20170505104101.30589-2-kraxel@redhat.com --- hw/display/virtio-gpu-3d.c | 16 ++++++++++++++++ hw/display/virtio-gpu.c | 18 ++---------------- include/hw/virtio/virtio-gpu.h | 1 + 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/hw/display/virtio-gpu-3d.c b/hw/display/virtio-gpu-3d.c index f49b7fe8cd..8c106a662d 100644 --- a/hw/display/virtio-gpu-3d.c +++ b/hw/display/virtio-gpu-3d.c @@ -600,6 +600,22 @@ void virtio_gpu_virgl_reset(VirtIOGPU *g) } } +void virtio_gpu_gl_block(void *opaque, bool block) +{ + VirtIOGPU *g = opaque; + + if (block) { + g->renderer_blocked++; + } else { + g->renderer_blocked--; + } + assert(g->renderer_blocked >= 0); + + if (g->renderer_blocked == 0) { + virtio_gpu_process_cmdq(g); + } +} + int virtio_gpu_virgl_init(VirtIOGPU *g) { int ret; diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c index e1056f34df..cfb5dfa336 100644 --- a/hw/display/virtio-gpu.c +++ b/hw/display/virtio-gpu.c @@ -929,28 +929,14 @@ static int virtio_gpu_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info) return 0; } -static void virtio_gpu_gl_block(void *opaque, bool block) -{ - VirtIOGPU *g = opaque; - - if (block) { - g->renderer_blocked++; - } else { - g->renderer_blocked--; - } - assert(g->renderer_blocked >= 0); - - if (g->renderer_blocked == 0) { - virtio_gpu_process_cmdq(g); - } -} - const GraphicHwOps virtio_gpu_ops = { .invalidate = virtio_gpu_invalidate_display, .gfx_update = virtio_gpu_update_display, .text_update = virtio_gpu_text_update, .ui_info = virtio_gpu_ui_info, +#ifdef CONFIG_VIRGL .gl_block = virtio_gpu_gl_block, +#endif }; static const VMStateDescription vmstate_virtio_gpu_scanout = { diff --git a/include/hw/virtio/virtio-gpu.h b/include/hw/virtio/virtio-gpu.h index f3ffdceca4..83f474ffc3 100644 --- a/include/hw/virtio/virtio-gpu.h +++ b/include/hw/virtio/virtio-gpu.h @@ -169,6 +169,7 @@ void virtio_gpu_virgl_process_cmd(VirtIOGPU *g, struct virtio_gpu_ctrl_command *cmd); void virtio_gpu_virgl_fence_poll(VirtIOGPU *g); void virtio_gpu_virgl_reset(VirtIOGPU *g); +void virtio_gpu_gl_block(void *opaque, bool block); int virtio_gpu_virgl_init(VirtIOGPU *g); #endif From 9f728c7940dfade366fed01d8f43f1e5283f95d8 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 5 May 2017 12:40:57 +0200 Subject: [PATCH 04/10] egl-helpers: drop support for gles and debug logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leftover from the early opengl days. Unused now, so delete the dead code. Signed-off-by: Gerd Hoffmann Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Marc-André Lureau Message-id: 20170505104101.30589-3-kraxel@redhat.com --- include/ui/egl-helpers.h | 2 +- ui/egl-helpers.c | 52 ++++------------------------------------ ui/gtk-egl.c | 2 +- 3 files changed, 7 insertions(+), 49 deletions(-) diff --git a/include/ui/egl-helpers.h b/include/ui/egl-helpers.h index 88a13e827b..fec7da14a5 100644 --- a/include/ui/egl-helpers.h +++ b/include/ui/egl-helpers.h @@ -21,7 +21,7 @@ int egl_get_fd_for_texture(uint32_t tex_id, EGLint *stride, EGLint *fourcc); EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, Window win); -int qemu_egl_init_dpy(EGLNativeDisplayType dpy, bool gles, bool debug); +int qemu_egl_init_dpy(EGLNativeDisplayType dpy); EGLContext qemu_egl_init_ctx(void); #endif /* EGL_HELPERS_H */ diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c index b7b6b2e3cc..a3d7c3d7f5 100644 --- a/ui/egl-helpers.c +++ b/ui/egl-helpers.c @@ -26,18 +26,6 @@ EGLConfig qemu_egl_config; /* ---------------------------------------------------------------------- */ -static bool egl_gles; -static int egl_debug; - -#define egl_dbg(_x ...) \ - do { \ - if (egl_debug) { \ - fprintf(stderr, "egl: " _x); \ - } \ - } while (0); - -/* ---------------------------------------------------------------------- */ - #ifdef CONFIG_OPENGL_DMABUF int qemu_egl_rn_fd; @@ -105,7 +93,7 @@ int egl_rendernode_init(const char *rendernode) goto err; } - qemu_egl_init_dpy((EGLNativeDisplayType)qemu_egl_rn_gbm_dev, false, false); + qemu_egl_init_dpy((EGLNativeDisplayType)qemu_egl_rn_gbm_dev); if (!epoxy_has_egl_extension(qemu_egl_display, "EGL_KHR_surfaceless_context")) { @@ -171,8 +159,6 @@ EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, Window win) EGLSurface esurface; EGLBoolean b; - egl_dbg("eglCreateWindowSurface (x11 win id 0x%lx) ...\n", - (unsigned long) win); esurface = eglCreateWindowSurface(qemu_egl_display, qemu_egl_config, (EGLNativeWindowType)win, NULL); @@ -242,7 +228,7 @@ static EGLDisplay qemu_egl_get_display(void *native) return dpy; } -int qemu_egl_init_dpy(EGLNativeDisplayType dpy, bool gles, bool debug) +int qemu_egl_init_dpy(EGLNativeDisplayType dpy) { static const EGLint conf_att_gl[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, @@ -253,56 +239,34 @@ int qemu_egl_init_dpy(EGLNativeDisplayType dpy, bool gles, bool debug) EGL_ALPHA_SIZE, 0, EGL_NONE, }; - static const EGLint conf_att_gles[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE, 5, - EGL_GREEN_SIZE, 5, - EGL_BLUE_SIZE, 5, - EGL_ALPHA_SIZE, 0, - EGL_NONE, - }; EGLint major, minor; EGLBoolean b; EGLint n; - if (debug) { - egl_debug = 1; - setenv("EGL_LOG_LEVEL", "debug", true); - setenv("LIBGL_DEBUG", "verbose", true); - } - - egl_dbg("qemu_egl_get_display (dpy %p) ...\n", dpy); qemu_egl_display = qemu_egl_get_display(dpy); if (qemu_egl_display == EGL_NO_DISPLAY) { error_report("egl: eglGetDisplay failed"); return -1; } - egl_dbg("eglInitialize ...\n"); b = eglInitialize(qemu_egl_display, &major, &minor); if (b == EGL_FALSE) { error_report("egl: eglInitialize failed"); return -1; } - egl_dbg("eglBindAPI ...\n"); - b = eglBindAPI(gles ? EGL_OPENGL_ES_API : EGL_OPENGL_API); + b = eglBindAPI(EGL_OPENGL_API); if (b == EGL_FALSE) { error_report("egl: eglBindAPI failed"); return -1; } - egl_dbg("eglChooseConfig ...\n"); - b = eglChooseConfig(qemu_egl_display, - gles ? conf_att_gles : conf_att_gl, + b = eglChooseConfig(qemu_egl_display, conf_att_gl, &qemu_egl_config, 1, &n); if (b == EGL_FALSE || n != 1) { error_report("egl: eglChooseConfig failed"); return -1; } - - egl_gles = gles; return 0; } @@ -311,17 +275,11 @@ EGLContext qemu_egl_init_ctx(void) static const EGLint ctx_att_gl[] = { EGL_NONE }; - static const EGLint ctx_att_gles[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - EGLContext ectx; EGLBoolean b; - egl_dbg("eglCreateContext ...\n"); ectx = eglCreateContext(qemu_egl_display, qemu_egl_config, EGL_NO_CONTEXT, - egl_gles ? ctx_att_gles : ctx_att_gl); + ctx_att_gl); if (ectx == EGL_NO_CONTEXT) { error_report("egl: eglCreateContext failed"); return NULL; diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c index d53288f027..89492c0e02 100644 --- a/ui/gtk-egl.c +++ b/ui/gtk-egl.c @@ -246,7 +246,7 @@ void gtk_egl_init(void) GdkDisplay *gdk_display = gdk_display_get_default(); Display *x11_display = gdk_x11_display_get_xdisplay(gdk_display); - if (qemu_egl_init_dpy(x11_display, false, false) < 0) { + if (qemu_egl_init_dpy(x11_display) < 0) { return; } From e1913dbb58e7a4ab0c2dd088cc3b397b8daed8c5 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 5 May 2017 12:40:58 +0200 Subject: [PATCH 05/10] egl-helpers: fix display init for x11 When running on gtk we need X11 platform not mesa platform. Create separate functions for mesa and x11 so we can keep the egl #ifdef mess local to egl-helpers.c Fixes: 0ea1523fb6703aa0dcd65e66b59e96fec028e60a Signed-off-by: Gerd Hoffmann Message-id: 20170505104101.30589-4-kraxel@redhat.com --- include/ui/egl-helpers.h | 3 ++- ui/egl-helpers.c | 34 ++++++++++++++++++++++++++-------- ui/gtk-egl.c | 2 +- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/include/ui/egl-helpers.h b/include/ui/egl-helpers.h index fec7da14a5..c785d60e91 100644 --- a/include/ui/egl-helpers.h +++ b/include/ui/egl-helpers.h @@ -21,7 +21,8 @@ int egl_get_fd_for_texture(uint32_t tex_id, EGLint *stride, EGLint *fourcc); EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, Window win); -int qemu_egl_init_dpy(EGLNativeDisplayType dpy); +int qemu_egl_init_dpy_x11(EGLNativeDisplayType dpy); +int qemu_egl_init_dpy_mesa(EGLNativeDisplayType dpy); EGLContext qemu_egl_init_ctx(void); #endif /* EGL_HELPERS_H */ diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c index a3d7c3d7f5..ec2e325e21 100644 --- a/ui/egl-helpers.c +++ b/ui/egl-helpers.c @@ -93,7 +93,7 @@ int egl_rendernode_init(const char *rendernode) goto err; } - qemu_egl_init_dpy((EGLNativeDisplayType)qemu_egl_rn_gbm_dev); + qemu_egl_init_dpy_mesa((EGLNativeDisplayType)qemu_egl_rn_gbm_dev); if (!epoxy_has_egl_extension(qemu_egl_display, "EGL_KHR_surfaceless_context")) { @@ -206,20 +206,19 @@ EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, Window win) * platform extensions (EGL_KHR_platform_gbm and friends) yet it doesn't seem * like mesa will be able to advertise these (even though it can do EGL 1.5). */ -static EGLDisplay qemu_egl_get_display(void *native) +static EGLDisplay qemu_egl_get_display(EGLNativeDisplayType native, + EGLenum platform) { EGLDisplay dpy = EGL_NO_DISPLAY; -#ifdef EGL_MESA_platform_gbm /* In practise any EGL 1.5 implementation would support the EXT extension */ if (epoxy_has_egl_extension(NULL, "EGL_EXT_platform_base")) { PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplayEXT = (void *) eglGetProcAddress("eglGetPlatformDisplayEXT"); - if (getPlatformDisplayEXT) { - dpy = getPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, native, NULL); + if (getPlatformDisplayEXT && platform != 0) { + dpy = getPlatformDisplayEXT(platform, native, NULL); } } -#endif if (dpy == EGL_NO_DISPLAY) { /* fallback */ @@ -228,7 +227,8 @@ static EGLDisplay qemu_egl_get_display(void *native) return dpy; } -int qemu_egl_init_dpy(EGLNativeDisplayType dpy) +static int qemu_egl_init_dpy(EGLNativeDisplayType dpy, + EGLenum platform) { static const EGLint conf_att_gl[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, @@ -243,7 +243,7 @@ int qemu_egl_init_dpy(EGLNativeDisplayType dpy) EGLBoolean b; EGLint n; - qemu_egl_display = qemu_egl_get_display(dpy); + qemu_egl_display = qemu_egl_get_display(dpy, platform); if (qemu_egl_display == EGL_NO_DISPLAY) { error_report("egl: eglGetDisplay failed"); return -1; @@ -270,6 +270,24 @@ int qemu_egl_init_dpy(EGLNativeDisplayType dpy) return 0; } +int qemu_egl_init_dpy_x11(EGLNativeDisplayType dpy) +{ +#ifdef EGL_KHR_platform_x11 + return qemu_egl_init_dpy(dpy, EGL_PLATFORM_X11_KHR); +#else + return qemu_egl_init_dpy(dpy, 0); +#endif +} + +int qemu_egl_init_dpy_mesa(EGLNativeDisplayType dpy) +{ +#ifdef EGL_MESA_platform_gbm + return qemu_egl_init_dpy(dpy, EGL_PLATFORM_GBM_MESA); +#else + return qemu_egl_init_dpy(dpy, 0); +#endif +} + EGLContext qemu_egl_init_ctx(void) { static const EGLint ctx_att_gl[] = { diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c index 89492c0e02..cf48cca259 100644 --- a/ui/gtk-egl.c +++ b/ui/gtk-egl.c @@ -246,7 +246,7 @@ void gtk_egl_init(void) GdkDisplay *gdk_display = gdk_display_get_default(); Display *x11_display = gdk_x11_display_get_xdisplay(gdk_display); - if (qemu_egl_init_dpy(x11_display) < 0) { + if (qemu_egl_init_dpy_x11(x11_display) < 0) { return; } From 151c8e608efc29e4dde4a0dbc2e79ebbc86c319c Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 5 May 2017 12:40:59 +0200 Subject: [PATCH 06/10] egl-helpers: add missing error check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code didn't check for qemu_egl_init_dpy_mesa() failures, add it. Signed-off-by: Gerd Hoffmann Reviewed-by: Philippe Mathieu-Daudé Message-id: 20170505104101.30589-5-kraxel@redhat.com --- ui/egl-helpers.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c index ec2e325e21..b50225158b 100644 --- a/ui/egl-helpers.c +++ b/ui/egl-helpers.c @@ -80,6 +80,7 @@ static int qemu_egl_rendernode_open(const char *rendernode) int egl_rendernode_init(const char *rendernode) { qemu_egl_rn_fd = -1; + int rc; qemu_egl_rn_fd = qemu_egl_rendernode_open(rendernode); if (qemu_egl_rn_fd == -1) { @@ -93,7 +94,11 @@ int egl_rendernode_init(const char *rendernode) goto err; } - qemu_egl_init_dpy_mesa((EGLNativeDisplayType)qemu_egl_rn_gbm_dev); + rc = qemu_egl_init_dpy_mesa((EGLNativeDisplayType)qemu_egl_rn_gbm_dev); + if (rc != 0) { + /* qemu_egl_init_dpy_mesa reports error */ + goto err; + } if (!epoxy_has_egl_extension(qemu_egl_display, "EGL_KHR_surfaceless_context")) { From bc8c946f7274543ca3ed35482de4e554daccfac6 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 5 May 2017 12:41:00 +0200 Subject: [PATCH 07/10] egl: explicitly ask for core context Signed-off-by: Gerd Hoffmann Message-id: 20170505104101.30589-6-kraxel@redhat.com --- ui/egl-context.c | 7 ++++--- ui/egl-helpers.c | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ui/egl-context.c b/ui/egl-context.c index 3a02b68d1a..2161969abe 100644 --- a/ui/egl-context.c +++ b/ui/egl-context.c @@ -7,9 +7,10 @@ QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl, { EGLContext ctx; EGLint ctx_att[] = { - EGL_CONTEXT_CLIENT_VERSION, params->major_ver, - EGL_CONTEXT_MINOR_VERSION_KHR, params->minor_ver, - EGL_NONE + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + EGL_CONTEXT_CLIENT_VERSION, params->major_ver, + EGL_CONTEXT_MINOR_VERSION_KHR, params->minor_ver, + EGL_NONE }; ctx = eglCreateContext(qemu_egl_display, qemu_egl_config, diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c index b50225158b..4a4d3370ee 100644 --- a/ui/egl-helpers.c +++ b/ui/egl-helpers.c @@ -296,6 +296,7 @@ int qemu_egl_init_dpy_mesa(EGLNativeDisplayType dpy) EGLContext qemu_egl_init_ctx(void) { static const EGLint ctx_att_gl[] = { + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, EGL_NONE }; EGLContext ectx; From bb1599b64c8f94fb2bd745d20f128e11543d891d Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 5 May 2017 12:41:01 +0200 Subject: [PATCH 08/10] opengl: add egl-headless display Add egl-headless user interface. It doesn't provide a real user interface, it only provides opengl support using drm render nodes. It will copy back the bits rendered by the guest using virgl back to a DisplaySurface and kick the usual display update code paths, so spice and vnc and screendump can pick it up. Use it this way: qemu -display egl-headless -vnc $display qemu -display egl-headless -spice gl=off,$args Note that you should prefer native spice opengl support (-spice gl=on) if possible because that delivers better performance. Signed-off-by: Gerd Hoffmann Message-id: 20170505104101.30589-7-kraxel@redhat.com --- include/ui/console.h | 3 + ui/Makefile.objs | 1 + ui/egl-headless.c | 158 +++++++++++++++++++++++++++++++++++++++++++ vl.c | 16 +++++ 4 files changed, 178 insertions(+) create mode 100644 ui/egl-headless.c diff --git a/include/ui/console.h b/include/ui/console.h index d759338816..7262bef6d3 100644 --- a/include/ui/console.h +++ b/include/ui/console.h @@ -527,4 +527,7 @@ static inline void early_gtk_display_init(int opengl) } #endif +/* egl-headless.c */ +void egl_headless_init(void); + #endif diff --git a/ui/Makefile.objs b/ui/Makefile.objs index 27566b32f1..aac6ae8bef 100644 --- a/ui/Makefile.objs +++ b/ui/Makefile.objs @@ -33,6 +33,7 @@ common-obj-y += shader.o common-obj-y += console-gl.o common-obj-y += egl-helpers.o common-obj-y += egl-context.o +common-obj-y += egl-headless.o ifeq ($(CONFIG_GTK_GL),y) common-obj-$(CONFIG_GTK) += gtk-gl-area.o else diff --git a/ui/egl-headless.c b/ui/egl-headless.c new file mode 100644 index 0000000000..d8d800f8a6 --- /dev/null +++ b/ui/egl-headless.c @@ -0,0 +1,158 @@ +#include "qemu/osdep.h" +#include "qemu-common.h" +#include "sysemu/sysemu.h" +#include "ui/console.h" +#include "ui/egl-helpers.h" +#include "ui/egl-context.h" + +typedef struct egl_dpy { + DisplayChangeListener dcl; + DisplaySurface *ds; + int width, height; + GLuint texture; + GLuint framebuffer; + GLuint blit_texture; + GLuint blit_framebuffer; + bool y_0_top; +} egl_dpy; + +static void egl_refresh(DisplayChangeListener *dcl) +{ + graphic_hw_update(dcl->con); +} + +static void egl_gfx_update(DisplayChangeListener *dcl, + int x, int y, int w, int h) +{ +} + +static void egl_gfx_switch(DisplayChangeListener *dcl, + struct DisplaySurface *new_surface) +{ + egl_dpy *edpy = container_of(dcl, egl_dpy, dcl); + + edpy->ds = new_surface; +} + +static void egl_scanout_disable(DisplayChangeListener *dcl) +{ + egl_dpy *edpy = container_of(dcl, egl_dpy, dcl); + + edpy->texture = 0; + /* XXX: delete framebuffers here ??? */ +} + +static void egl_scanout_texture(DisplayChangeListener *dcl, + uint32_t backing_id, + bool backing_y_0_top, + uint32_t backing_width, + uint32_t backing_height, + uint32_t x, uint32_t y, + uint32_t w, uint32_t h) +{ + egl_dpy *edpy = container_of(dcl, egl_dpy, dcl); + + edpy->texture = backing_id; + edpy->y_0_top = backing_y_0_top; + + /* source framebuffer */ + if (!edpy->framebuffer) { + glGenFramebuffers(1, &edpy->framebuffer); + } + glBindFramebuffer(GL_FRAMEBUFFER_EXT, edpy->framebuffer); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, edpy->texture, 0); + + /* dest framebuffer */ + if (!edpy->blit_framebuffer) { + glGenFramebuffers(1, &edpy->blit_framebuffer); + glGenTextures(1, &edpy->blit_texture); + edpy->width = 0; + edpy->height = 0; + } + if (edpy->width != backing_width || edpy->height != backing_height) { + edpy->width = backing_width; + edpy->height = backing_height; + glBindTexture(GL_TEXTURE_2D, edpy->blit_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + edpy->width, edpy->height, + 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + glBindFramebuffer(GL_FRAMEBUFFER_EXT, edpy->blit_framebuffer); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, edpy->blit_texture, 0); + } +} + +static void egl_scanout_flush(DisplayChangeListener *dcl, + uint32_t x, uint32_t y, + uint32_t w, uint32_t h) +{ + egl_dpy *edpy = container_of(dcl, egl_dpy, dcl); + GLuint y1, y2; + + if (!edpy->texture || !edpy->ds) { + return; + } + assert(surface_width(edpy->ds) == edpy->width); + assert(surface_height(edpy->ds) == edpy->height); + assert(surface_format(edpy->ds) == PIXMAN_x8r8g8b8); + + /* blit framebuffer, flip if needed */ + glBindFramebuffer(GL_READ_FRAMEBUFFER, edpy->framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, edpy->blit_framebuffer); + glViewport(0, 0, edpy->width, edpy->height); + y1 = edpy->y_0_top ? edpy->height : 0; + y2 = edpy->y_0_top ? 0 : edpy->height; + glBlitFramebuffer(0, y1, edpy->width, y2, + 0, 0, edpy->width, edpy->height, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + + /* read pixels to surface */ + glBindFramebuffer(GL_READ_FRAMEBUFFER, edpy->blit_framebuffer); + glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); + glReadPixels(0, 0, edpy->width, edpy->height, + GL_BGRA, GL_UNSIGNED_BYTE, surface_data(edpy->ds)); + + /* notify about updates */ + dpy_gfx_update(edpy->dcl.con, x, y, w, h); +} + +static const DisplayChangeListenerOps egl_ops = { + .dpy_name = "egl-headless", + .dpy_refresh = egl_refresh, + .dpy_gfx_update = egl_gfx_update, + .dpy_gfx_switch = egl_gfx_switch, + + .dpy_gl_ctx_create = qemu_egl_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_disable = egl_scanout_disable, + .dpy_gl_scanout_texture = egl_scanout_texture, + .dpy_gl_update = egl_scanout_flush, +}; + +void egl_headless_init(void) +{ + QemuConsole *con; + egl_dpy *edpy; + int idx; + + if (egl_rendernode_init(NULL) < 0) { + error_report("egl: render node init failed"); + exit(1); + } + + for (idx = 0;; idx++) { + con = qemu_console_lookup_by_index(idx); + if (!con || !qemu_console_is_graphic(con)) { + break; + } + + edpy = g_new0(egl_dpy, 1); + edpy->dcl.con = con; + edpy->dcl.ops = &egl_ops; + register_displaychangelistener(&edpy->dcl); + } +} diff --git a/vl.c b/vl.c index 58023fca02..aca99e1c77 100644 --- a/vl.c +++ b/vl.c @@ -2050,6 +2050,7 @@ typedef enum DisplayType { DT_SDL, DT_COCOA, DT_GTK, + DT_EGL, DT_NONE, } DisplayType; @@ -2127,6 +2128,15 @@ static DisplayType select_display(const char *p) error_report("VNC requires a display argument vnc="); exit(1); } + } else if (strstart(p, "egl-headless", &opts)) { +#ifdef CONFIG_OPENGL + request_opengl = 1; + display_opengl = 1; + display = DT_EGL; +#else + fprintf(stderr, "egl support is disabled\n"); + exit(1); +#endif } else if (strstart(p, "curses", &opts)) { #ifdef CONFIG_CURSES display = DT_CURSES; @@ -4659,6 +4669,12 @@ int main(int argc, char **argv, char **envp) qemu_spice_display_init(); } +#ifdef CONFIG_OPENGL + if (display_type == DT_EGL) { + egl_headless_init(); + } +#endif + if (foreach_device_config(DEV_GDB, gdbserver_start) < 0) { exit(1); } From 761d0f97a40a3a6b6a0f2fb75eea47a0cea42d14 Mon Sep 17 00:00:00 2001 From: Wei Qi Date: Fri, 7 Apr 2017 14:58:58 +0800 Subject: [PATCH 09/10] vnc: simple clean up It is unnecessary to assign 'packed_bytes' to 'estimated_bytes', because 'estimated_bytes' unused after assignment. Signed-off-by: Wei Qi Reviewed-by: Sahid Orentino Ferdjaoui Signed-off-by: Gerd Hoffmann --- ui/vnc-enc-zrle.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/vnc-enc-zrle.c b/ui/vnc-enc-zrle.c index 5489870e70..fd63d4f688 100644 --- a/ui/vnc-enc-zrle.c +++ b/ui/vnc-enc-zrle.c @@ -163,7 +163,6 @@ static void zrle_choose_palette_rle(VncState *vs, int w, int h, if (packed_bytes < estimated_bytes) { *use_rle = false; *use_palette = true; - estimated_bytes = packed_bytes; } } } From 7c9209e7bfb8c09ab5a4cadaa84928d146874a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Mon, 13 Mar 2017 15:33:25 +0100 Subject: [PATCH 10/10] vnc: replace hweight_long() with ctpopl() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ctpopl() has a better implementation than hweight_long() and ui/vnc.c being the last user of hweight_long(), we can simply remove it. Signed-off-by: Cédric Le Goater Reviewed-by: Peter Maydell Message-id: 1489415605-13105-1-git-send-email-clg@kaod.org Signed-off-by: Gerd Hoffmann --- include/qemu/bitops.h | 10 ---------- ui/vnc.c | 6 +++--- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/include/qemu/bitops.h b/include/qemu/bitops.h index 1881284cb5..3f0926cf40 100644 --- a/include/qemu/bitops.h +++ b/include/qemu/bitops.h @@ -201,16 +201,6 @@ static inline unsigned long find_first_zero_bit(const unsigned long *addr, return find_next_zero_bit(addr, size, 0); } -static inline unsigned long hweight_long(unsigned long w) -{ - unsigned long count; - - for (count = 0; w; w >>= 1) { - count += w & 1; - } - return count; -} - /** * rol8 - rotate an 8-bit value left * @word: value to rotate diff --git a/ui/vnc.c b/ui/vnc.c index 601f9ec539..47b49c7318 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -2061,15 +2061,15 @@ static void set_pixel_format(VncState *vs, int bits_per_pixel, } vs->client_pf.rmax = red_max ? red_max : 0xFF; - vs->client_pf.rbits = hweight_long(red_max); + vs->client_pf.rbits = ctpopl(red_max); vs->client_pf.rshift = red_shift; vs->client_pf.rmask = red_max << red_shift; vs->client_pf.gmax = green_max ? green_max : 0xFF; - vs->client_pf.gbits = hweight_long(green_max); + vs->client_pf.gbits = ctpopl(green_max); vs->client_pf.gshift = green_shift; vs->client_pf.gmask = green_max << green_shift; vs->client_pf.bmax = blue_max ? blue_max : 0xFF; - vs->client_pf.bbits = hweight_long(blue_max); + vs->client_pf.bbits = ctpopl(blue_max); vs->client_pf.bshift = blue_shift; vs->client_pf.bmask = blue_max << blue_shift; vs->client_pf.bits_per_pixel = bits_per_pixel;