[Spice-devel] [PATCH RFC EXP] remote Virgl support

Frediano Ziglio fziglio at redhat.com
Tue Jun 28 14:46:29 UTC 2016


This patch is really hacky and mainly intended to try to use the
current spice-protocol to make Virgl remote.
It does in a fast (as code lines) way:
- extract the images from scanouts;
- fed these images to normal flow (using display_channel_process_draw)
  so making possible to check streaming flow or just image compression
  using Qemu parameters (or some other hacks is not very recent).

First problem: it works only on Intel i915 family.
I tried to use DRM directly finding many post where mmap were used.
This was a big mistake. Current cards use some internal memory
arrangement for textures or the texture memory could be in an area
not mmap-able. Using DRM you can allocate a buffer (PBO) mmap-able
but however there is no portable (among card types) way to copy the
texture on this buffer. At the end I think would be much better to use
EGL like Qemu does so will work on any card, probably faster.

Second problem: multiple clients (local and remote) are not supported
at all, basically if any remote client is detected all dialog will use
old (not passing scanouts) protocol.

Third problem: the output queue is not handled correctly. There are some
limitation based on RedDrawable number however RedDrawable existence
check is not good as RedDrawables can be retained for different
reason (Drawable, Glz dictionary and streaming) so for instance if you
use Glz you could end up with blank screen. Also the streaming code
should limit the queue so looks like some kind of duplication.

Would be really good to have some kind of lazy extraction to extract
the texture as late as possible (possibly getting a more recent
scanout as possible or making possible for streaming code to fed
vaapi in the future).

Looks like the protocol for some reason is very latency dependent.

To force internet socket listening and different video compression
I changed code instead of patching Qemu properly (patch for video codecs
is already in master, just lazy to recompile it or have a no packages
Qemu).

You can find the full patchset at 
https://cgit.freedesktop.org/~fziglio/spice-server/log/?h=virgl
(probably not worth looking at it).

Any comment, suggestion are welcome.
---
 server/Makefile.am       |   2 +
 server/dcc.c             |   3 +-
 server/display-channel.c | 121 +++++++++++++++++++++++++++++++++++++++++++----
 server/display-channel.h |  10 ++--
 server/egl.c             | 103 ++++++++++++++++++++++++++++++++++++++++
 server/egl.h             |  25 ++++++++++
 server/red-worker.c      |  10 ++--
 server/reds.c            |   8 +++-
 8 files changed, 265 insertions(+), 17 deletions(-)
 create mode 100644 server/egl.c
 create mode 100644 server/egl.h

diff --git a/server/Makefile.am b/server/Makefile.am
index 921b082..0291426 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -147,6 +147,8 @@ libserver_la_SOURCES =				\
 	dcc.c					\
 	dcc-send.c					\
 	dcc.h					\
+	egl.c					\
+	egl.h					\
 	display-limits.h			\
 	image-encoders.c					\
 	image-encoders.h					\
diff --git a/server/dcc.c b/server/dcc.c
index 45c3ca8..d5cfe4c 100644
--- a/server/dcc.c
+++ b/server/dcc.c
@@ -590,7 +590,8 @@ RedPipeItem *dcc_gl_draw_item_new(RedChannelClient *rcc, void *data, int num)
     RedGlDrawItem *item = spice_new(RedGlDrawItem, 1);
     spice_return_val_if_fail(item != NULL, NULL);
 
-    if (!red_channel_client_test_remote_cap(rcc, SPICE_DISPLAY_CAP_GL_SCANOUT)) {
+    if (!reds_stream_is_plain_unix(rcc->stream) ||
+        !red_channel_client_test_remote_cap(rcc, SPICE_DISPLAY_CAP_GL_SCANOUT)) {
         spice_printerr("FIXME: client does not support GL scanout");
         red_channel_client_disconnect(rcc);
         return NULL;
diff --git a/server/display-channel.c b/server/display-channel.c
index 073d45e..5a5f235 100644
--- a/server/display-channel.c
+++ b/server/display-channel.c
@@ -19,10 +19,13 @@
 #endif
 
 #include "display-channel.h"
+#include "egl.h"
 
 static void drawable_draw(DisplayChannel *display, Drawable *drawable);
 static Drawable *display_channel_drawable_try_new(DisplayChannel *display,
-                                                  int process_commands_generation);
+                                                  uint32_t process_commands_generation);
+static RedDrawable *get_dummy_drawable(DisplayChannel *display, int w, int h, uint8_t *data, uint32_t data_size);
+static RedDrawable *get_dummy_gl_drawable(DisplayChannel *display);
 
 uint32_t display_channel_generate_uid(DisplayChannel *display)
 {
@@ -1069,6 +1072,8 @@ static void display_channel_add_drawable(DisplayChannel *display, Drawable *draw
         add_to_pipe = current_add(display, ring, drawable);
     }
 
+    // TODO handle differently dummy events ??
+    // on destroy decrement gl_count ??
     if (add_to_pipe)
         pipes_add_drawable(display, drawable);
 
@@ -1079,7 +1084,7 @@ static void display_channel_add_drawable(DisplayChannel *display, Drawable *draw
 }
 
 void display_channel_process_draw(DisplayChannel *display, RedDrawable *red_drawable,
-                                  int process_commands_generation)
+                                  uint32_t process_commands_generation)
 {
     Drawable *drawable =
         display_channel_get_drawable(display, red_drawable->effect, red_drawable,
@@ -1258,7 +1263,7 @@ static void drawables_init(DisplayChannel *display)
  * @return pointer to uninitialized Drawable or NULL on failure
  */
 static Drawable *display_channel_drawable_try_new(DisplayChannel *display,
-                                                  int process_commands_generation)
+                                                  uint32_t process_commands_generation)
 {
     Drawable *drawable;
 
@@ -2002,9 +2007,36 @@ void display_channel_update_compression(DisplayChannel *display, DisplayChannelC
     spice_info("zlib-over-glz %s", display->enable_zlib_glz_wrap ? "enabled" : "disabled");
 }
 
-void display_channel_gl_scanout(DisplayChannel *display)
+static gboolean display_channel_gl_handle_remote(DisplayChannel *display, uint32_t process_commands_generation)
 {
-    red_channel_pipes_new_add_push(RED_CHANNEL(display), dcc_gl_scanout_item_new, NULL);
+    RedChannelClient *rcc;
+    GList *link, *next;
+
+    // check if all channel are remote
+    FOREACH_CLIENT(display, link, next, rcc) {
+        if (!reds_stream_is_plain_unix(rcc->stream) ||
+            !red_channel_client_test_remote_cap(rcc, SPICE_DISPLAY_CAP_GL_SCANOUT)) {
+            if (display->gl_dummy_count > 5) {
+                return TRUE;
+            }
+            RedDrawable *red_drawable = get_dummy_gl_drawable(display);
+            if (red_drawable) {
+                // FIXME no flow control, check pipe size!
+                display_channel_process_draw(display, red_drawable, process_commands_generation);
+                red_drawable_unref(red_drawable);
+            }
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+
+void display_channel_gl_scanout(DisplayChannel *display, uint32_t process_commands_generation)
+{
+    if (!display_channel_gl_handle_remote(display, process_commands_generation)) {
+        red_channel_pipes_new_add_push(RED_CHANNEL(display), dcc_gl_scanout_item_new, NULL);
+    }
 }
 
 static void set_gl_draw_async_count(DisplayChannel *display, int num)
@@ -2018,13 +2050,15 @@ static void set_gl_draw_async_count(DisplayChannel *display, int num)
     }
 }
 
-void display_channel_gl_draw(DisplayChannel *display, SpiceMsgDisplayGlDraw *draw)
+void display_channel_gl_draw(DisplayChannel *display, SpiceMsgDisplayGlDraw *draw, uint32_t process_commands_generation)
 {
-    int num;
+    int num = 0;
 
     spice_return_if_fail(display->gl_draw_async_count == 0);
 
-    num = red_channel_pipes_new_add_push(RED_CHANNEL(display), dcc_gl_draw_item_new, draw);
+    if (!display_channel_gl_handle_remote(display, process_commands_generation)) {
+        num = red_channel_pipes_new_add_push(RED_CHANNEL(display), dcc_gl_draw_item_new, draw);
+    }
     set_gl_draw_async_count(display, num);
 }
 
@@ -2032,3 +2066,74 @@ void display_channel_gl_draw_done(DisplayChannel *display)
 {
     set_gl_draw_async_count(display, display->gl_draw_async_count - 1);
 }
+
+/**
+ * Returns a RedDrawable with a bitmap image pointing to the data image
+ * we provide
+ */
+static RedDrawable *get_dummy_drawable(DisplayChannel *display, int w, int h, uint8_t *data, uint32_t data_size)
+{
+    SpiceChunks *chunks = spice_chunks_new(1);
+    chunks->flags = SPICE_CHUNKS_FLAGS_FREE;
+    chunks->data_size = data_size;
+    chunks->chunk[0].data = data;
+    chunks->chunk[0].len = data_size;
+
+    SpiceImage *image = spice_new0(SpiceImage, 1);
+    image->descriptor.type = SPICE_IMAGE_TYPE_BITMAP;
+    image->descriptor.width = w;
+    image->descriptor.height = h;
+    image->u.bitmap.x = w;
+    image->u.bitmap.y = h;
+    image->u.bitmap.format = SPICE_BITMAP_FMT_32BIT; // 16BIT,24BIT 32BIT RGBA ???
+    image->u.bitmap.flags = 0; //SPICE_STREAM_FLAGS_TOP_DOWN; // or TOP_DOWN ???
+    image->u.bitmap.stride = w * 4; // ???
+    image->u.bitmap.data = chunks;
+
+    RedDrawable *red = spice_new0(RedDrawable, 1);
+    red->refs = 1;
+    display->gl_dummy_count++;
+    red->release_info_ext.info = (void *) display;
+    red->effect = QXL_EFFECT_OPAQUE;
+    red->type = QXL_DRAW_COPY;
+    red->bbox.bottom = h; // -1 ??
+    red->bbox.right = w; // -1 ??
+    red->surface_deps[0] = -1;
+    red->surface_deps[1] = -1;
+    red->surface_deps[2] = -1;
+    red->u.copy.src_area = red->bbox;
+    red->u.copy.rop_descriptor = SPICE_ROPD_OP_PUT;
+    red->u.copy.src_bitmap = image;
+
+    return red;
+}
+
+static RedDrawable *get_dummy_gl_drawable(DisplayChannel *display)
+{
+    RedDrawable *red_drawable = NULL;
+    QXLInstance* qxl = display->common.qxl;
+
+    SpiceMsgDisplayGlScanoutUnix *scanout = red_qxl_get_gl_scanout(qxl);
+    if (scanout != NULL) {
+#if 0
+        printf("GL image format %4.4s flags %u stride %u width %u height %u\n",
+               (char*) &scanout->drm_fourcc_format, scanout->flags,
+               scanout->stride, scanout->width, scanout->height);
+#endif
+
+        size_t size;
+        void *data = get_scanout_raw_data(scanout, &size);
+        spice_assert(data);
+        if (data) {
+            red_drawable = get_dummy_drawable(display, scanout->width, scanout->height, data, size);
+            spice_assert(red_drawable);
+        }
+    }
+    red_qxl_put_gl_scanout(qxl, scanout);
+    return red_drawable;
+}
+
+void display_channel_release_dummy(DisplayChannel *display)
+{
+    display->gl_dummy_count--;
+}
diff --git a/server/display-channel.h b/server/display-channel.h
index f090d99..a394971 100644
--- a/server/display-channel.h
+++ b/server/display-channel.h
@@ -194,6 +194,7 @@ struct DisplayChannel {
     ImageCache image_cache;
 
     int gl_draw_async_count;
+    int gl_dummy_count;
 
 /* TODO: some day unify this, make it more runtime.. */
     stat_info_t add_stat;
@@ -273,16 +274,17 @@ void                       display_channel_destroy_surfaces          (DisplayCha
 uint32_t                   display_channel_generate_uid              (DisplayChannel *display);
 void                       display_channel_process_draw              (DisplayChannel *display,
                                                                       RedDrawable *red_drawable,
-                                                                      int process_commands_generation);
+                                                                      uint32_t process_commands_generation);
 void                       display_channel_process_surface_cmd       (DisplayChannel *display,
                                                                       RedSurfaceCmd *surface,
                                                                       int loadvm);
 void                       display_channel_update_compression        (DisplayChannel *display,
                                                                       DisplayChannelClient *dcc);
-void                       display_channel_gl_scanout                (DisplayChannel *display);
-void                       display_channel_gl_draw                   (DisplayChannel *display,
-                                                                      SpiceMsgDisplayGlDraw *draw);
+void display_channel_gl_scanout(DisplayChannel *display, uint32_t process_commands_generation);
+void display_channel_gl_draw(DisplayChannel *display, SpiceMsgDisplayGlDraw *draw,
+                             uint32_t process_commands_generation);
 void                       display_channel_gl_draw_done              (DisplayChannel *display);
+void display_channel_release_dummy(DisplayChannel *display);
 
 static inline int validate_surface(DisplayChannel *display, uint32_t surface_id)
 {
diff --git a/server/egl.c b/server/egl.c
new file mode 100644
index 0000000..858a8f8
--- /dev/null
+++ b/server/egl.c
@@ -0,0 +1,103 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2016 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <drm/drm.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <byteswap.h>
+#include <libdrm/drm.h>
+#include <libdrm/i915_drm.h>
+
+#include <common/macros.h>
+
+#include "egl.h"
+
+static int drm_fd = -1;
+
+SPICE_CONSTRUCTOR_FUNC(drm_global_init)
+{
+    // FIXME not constant !!!
+    const char *p = "/dev/dri/renderD128";
+    drm_fd = open(p, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK);
+}
+
+static void
+convert_tile(uint8_t *data, uint32_t width, uint32_t height, uint32_t stride)
+{
+    unsigned int i, j, k, z;
+    // TODO
+    uint8_t *out = spice_malloc(width * 4 * height);
+    uint32_t *psrc, *pdst;
+
+    for (z = 0; z < height; z += 32) {
+        for (k = 0; k < width; k += 32) {
+            // handle a 32x32 block
+            for (j = 0; j < 8; ++j) {
+                psrc = (uint32_t *) (data + z * stride + j * 32 * 4 * 4 + k * 32 * 4);
+                pdst = (uint32_t *) (out + (height - 1 - z) * width * 4 + j * 4 * 4 + k * 4);
+                for (i = 0; i < 32; ++i) {
+#define DO_PIXEL(n) do { pdst[n] = bswap_32(psrc[n]) >> 8; } while(0)
+                    DO_PIXEL(0);
+                    DO_PIXEL(1);
+                    DO_PIXEL(2);
+                    DO_PIXEL(3);
+#undef DO_PIXEL
+                    psrc += 4;
+                    pdst -= width;
+                }
+            }
+        }
+    }
+
+    memcpy(data, out, width * 4 * height);
+    free(out);
+}
+
+#define ROUND_UP(n, m) (((n) + (m) - 1) & -(m))
+
+void *get_scanout_raw_data(SpiceMsgDisplayGlScanoutUnix *scanout, size_t *data_size)
+{
+    struct drm_prime_handle h = { scanout->drm_dma_buf_fd, 0, scanout->drm_dma_buf_fd };
+    if (ioctl(drm_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &h)) {
+        return NULL;
+    }
+
+    size_t tex_size = ROUND_UP(scanout->stride, 128) * ROUND_UP(scanout->height, 32);
+    uint8_t *data = valloc(tex_size);
+
+    struct drm_i915_gem_pread pread = { h.handle, 0, 0, tex_size, (uintptr_t) data };
+    if (ioctl(drm_fd, DRM_IOCTL_I915_GEM_PREAD, &pread)) {
+        free(data);
+        return NULL;
+    }
+
+    convert_tile(data, scanout->width, scanout->height, scanout->stride);
+    *data_size = scanout->width * 4 * scanout->height;
+    return data;
+}
diff --git a/server/egl.h b/server/egl.h
new file mode 100644
index 0000000..e48fce8
--- /dev/null
+++ b/server/egl.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2016 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef EGL_H_
+#define EGL_H_
+
+#include <common/messages.h>
+
+void *get_scanout_raw_data(SpiceMsgDisplayGlScanoutUnix *scanout, size_t *data_size);
+
+#endif /* EGL_H_ */
diff --git a/server/red-worker.c b/server/red-worker.c
index 9238632..6f86101 100644
--- a/server/red-worker.c
+++ b/server/red-worker.c
@@ -128,7 +128,11 @@ void red_drawable_unref(RedDrawable *red_drawable)
     if (--red_drawable->refs) {
         return;
     }
-    red_qxl_release_resource(red_drawable->qxl, red_drawable->release_info_ext);
+    if (red_drawable->qxl) {
+        red_qxl_release_resource(red_drawable->qxl, red_drawable->release_info_ext);
+    } else {
+        display_channel_release_dummy((DisplayChannel *) red_drawable->release_info_ext.info);
+    }
     red_put_drawable(red_drawable);
     free(red_drawable);
 }
@@ -1122,7 +1126,7 @@ void handle_dev_gl_scanout(void *opaque, void *payload)
 {
     RedWorker *worker = opaque;
 
-    display_channel_gl_scanout(worker->display_channel);
+    display_channel_gl_scanout(worker->display_channel, worker->process_display_generation);
 }
 
 static
@@ -1131,7 +1135,7 @@ void handle_dev_gl_draw_async(void *opaque, void *payload)
     RedWorker *worker = opaque;
     SpiceMsgDisplayGlDraw *draw = payload;
 
-    display_channel_gl_draw(worker->display_channel, draw);
+    display_channel_gl_draw(worker->display_channel, draw, worker->process_display_generation);
 }
 
 static int loadvm_command(RedWorker *worker, QXLCommandExt *ext)
diff --git a/server/reds.c b/server/reds.c
index fdf62e5..f767ebc 100644
--- a/server/reds.c
+++ b/server/reds.c
@@ -35,6 +35,7 @@
 #include <errno.h>
 #include <ctype.h>
 #include <stdbool.h>
+#include <sys/stat.h>
 
 #include <openssl/err.h>
 
@@ -2600,6 +2601,7 @@ static int reds_init_socket(const char *addr, int portnr, int family)
             perror("bind");
             return -1;
         }
+        chmod(addr, 0666);
 
         goto listen;
     }
@@ -2690,6 +2692,10 @@ void reds_set_client_mm_time_latency(RedsState *reds, RedClient *client, uint32_
 
 static int reds_init_net(RedsState *reds)
 {
+    reds->config->spice_port = 5900;
+    reds->config->spice_family = AF_INET;
+    g_strlcpy(reds->config->spice_addr, "0.0.0.0", sizeof(reds->config->spice_addr));
+
     if (reds->config->spice_port != -1 || reds->config->spice_family == AF_UNIX) {
         reds->listen_socket = reds_init_socket(reds->config->spice_addr, reds->config->spice_port, reds->config->spice_family);
         if (-1 == reds->listen_socket) {
@@ -3499,7 +3505,7 @@ err:
 }
 
 static const char default_renderer[] = "sw";
-static const char default_video_codecs[] = "spice:mjpeg;gstreamer:mjpeg;gstreamer:h264;gstreamer:vp8";
+static const char default_video_codecs[] = "gstreamer:h264;gstreamer:vp8";
 
 /* new interface */
 SPICE_GNUC_VISIBLE SpiceServer *spice_server_new(void)
-- 
2.7.4



More information about the Spice-devel mailing list