[PATCH v3 12/20] virtio-gpu: Add virtio_gpu_set_scanout_blob

Vivek Kasireddy posted 20 patches 4 years, 9 months ago
[PATCH v3 12/20] virtio-gpu: Add virtio_gpu_set_scanout_blob
Posted by Vivek Kasireddy 4 years, 9 months ago
This API allows Qemu to set the blob allocated by the Guest as
the scanout buffer. If Opengl support is available, then the
scanout buffer would be submitted as a dmabuf to the UI; if not,
a pixman image is created from the scanout buffer and is
submitted to the UI via the display surface.

Based-on-patch-by: Gerd Hoffmann <kraxel@redhat.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
---
 hw/display/trace-events              |   1 +
 hw/display/virtio-gpu.c              | 105 +++++++++++++++++++++++++--
 include/hw/virtio/virtio-gpu-bswap.h |   7 ++
 3 files changed, 105 insertions(+), 8 deletions(-)

diff --git a/hw/display/trace-events b/hw/display/trace-events
index 99e5256aac..96fe1ea3de 100644
--- a/hw/display/trace-events
+++ b/hw/display/trace-events
@@ -40,6 +40,7 @@ virtio_gpu_features(bool virgl) "virgl %d"
 virtio_gpu_cmd_get_display_info(void) ""
 virtio_gpu_cmd_get_edid(uint32_t scanout) "scanout %d"
 virtio_gpu_cmd_set_scanout(uint32_t id, uint32_t res, uint32_t w, uint32_t h, uint32_t x, uint32_t y) "id %d, res 0x%x, w %d, h %d, x %d, y %d"
+virtio_gpu_cmd_set_scanout_blob(uint32_t id, uint32_t res, uint32_t w, uint32_t h, uint32_t x, uint32_t y) "id %d, res 0x%x, w %d, h %d, x %d, y %d"
 virtio_gpu_cmd_res_create_2d(uint32_t res, uint32_t fmt, uint32_t w, uint32_t h) "res 0x%x, fmt 0x%x, w %d, h %d"
 virtio_gpu_cmd_res_create_3d(uint32_t res, uint32_t fmt, uint32_t w, uint32_t h, uint32_t d) "res 0x%x, fmt 0x%x, w %d, h %d, d %d"
 virtio_gpu_cmd_res_create_blob(uint32_t res, uint64_t size) "res 0x%x, size %" PRId64
diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
index f96f7590b1..b8695ac7d5 100644
--- a/hw/display/virtio-gpu.c
+++ b/hw/display/virtio-gpu.c
@@ -405,7 +405,9 @@ static void virtio_gpu_resource_destroy(VirtIOGPU *g,
         }
     }
 
-    pixman_image_unref(res->image);
+    if (res->image) {
+        pixman_image_unref(res->image);
+    }
     virtio_gpu_cleanup_mapping(g, res);
     QTAILQ_REMOVE(&g->reslist, res, next);
     g->hostmem -= res->hostmem;
@@ -494,6 +496,7 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g,
 {
     struct virtio_gpu_simple_resource *res;
     struct virtio_gpu_resource_flush rf;
+    struct virtio_gpu_scanout *scanout;
     pixman_region16_t flush_region;
     int i;
 
@@ -504,16 +507,28 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g,
 
     res = virtio_gpu_find_check_resource(g, rf.resource_id, false,
                                          __func__, &cmd->error);
-    if (!res || res->blob) {
+    if (!res) {
         return;
     }
 
-    if (rf.r.x > res->width ||
+    if (res->blob && display_opengl) {
+        for (i = 0; i < g->parent_obj.conf.max_outputs; i++) {
+            scanout = &g->parent_obj.scanout[i];
+            if (scanout->resource_id == res->resource_id) {
+                dpy_gl_update(scanout->con, 0, 0, scanout->width,
+                              scanout->height);
+                return;
+            }
+        }
+    }
+
+    if (!res->blob &&
+        (rf.r.x > res->width ||
         rf.r.y > res->height ||
         rf.r.width > res->width ||
         rf.r.height > res->height ||
         rf.r.x + rf.r.width > res->width ||
-        rf.r.y + rf.r.height > res->height) {
+        rf.r.y + rf.r.height > res->height)) {
         qemu_log_mask(LOG_GUEST_ERROR, "%s: flush bounds outside resource"
                       " bounds for resource %d: %d %d %d %d vs %d %d\n",
                       __func__, rf.resource_id, rf.r.x, rf.r.y,
@@ -525,7 +540,6 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g,
     pixman_region_init_rect(&flush_region,
                             rf.r.x, rf.r.y, rf.r.width, rf.r.height);
     for (i = 0; i < g->parent_obj.conf.max_outputs; i++) {
-        struct virtio_gpu_scanout *scanout;
         pixman_region16_t region, finalregion;
         pixman_box16_t *extents;
 
@@ -616,10 +630,23 @@ static void virtio_gpu_do_set_scanout(VirtIOGPU *g,
     }
 
     g->parent_obj.enable = 1;
-    data = (uint8_t *)pixman_image_get_data(res->image);
+
+    if (res->blob) {
+        if (display_opengl) {
+            if (!virtio_gpu_update_dmabuf(g, scanout_id, res, fb)) {
+                virtio_gpu_update_scanout(g, scanout_id, res, r);
+                return;
+            }
+        }
+
+        data = res->blob;
+    } else {
+        data = (uint8_t *)pixman_image_get_data(res->image);
+    }
 
     /* create a surface for this scanout */
-    if (!scanout->ds ||
+    if ((res->blob && !display_opengl) ||
+        !scanout->ds ||
         surface_data(scanout->ds) != data + fb->offset ||
         scanout->width != r->width ||
         scanout->height != r->height) {
@@ -683,6 +710,61 @@ static void virtio_gpu_set_scanout(VirtIOGPU *g,
                               &fb, res, &ss.r, &cmd->error);
 }
 
+static void virtio_gpu_set_scanout_blob(VirtIOGPU *g,
+                                        struct virtio_gpu_ctrl_command *cmd)
+{
+    struct virtio_gpu_simple_resource *res;
+    struct virtio_gpu_framebuffer fb = { 0 };
+    struct virtio_gpu_set_scanout_blob ss;
+    uint64_t fbend;
+
+    VIRTIO_GPU_FILL_CMD(ss);
+    virtio_gpu_scanout_blob_bswap(&ss);
+    trace_virtio_gpu_cmd_set_scanout_blob(ss.scanout_id, ss.resource_id,
+                                          ss.r.width, ss.r.height, ss.r.x,
+                                          ss.r.y);
+
+    if (ss.resource_id == 0) {
+        virtio_gpu_disable_scanout(g, ss.scanout_id);
+        return;
+    }
+
+    res = virtio_gpu_find_check_resource(g, ss.resource_id, true,
+                                         __func__, &cmd->error);
+    if (!res) {
+        return;
+    }
+
+    fb.format = virtio_gpu_get_pixman_format(ss.format);
+    if (!fb.format) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: host couldn't handle guest format %d\n",
+                      __func__, ss.format);
+        cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+        return;
+    }
+
+    fb.bytes_pp = DIV_ROUND_UP(PIXMAN_FORMAT_BPP(fb.format), 8);
+    fb.width = ss.width;
+    fb.height = ss.height;
+    fb.stride = ss.strides[0];
+    fb.offset = ss.offsets[0] + ss.r.x * fb.bytes_pp + ss.r.y * fb.stride;
+
+    fbend = fb.offset;
+    fbend += fb.stride * (ss.r.height - 1);
+    fbend += fb.bytes_pp * ss.r.width;
+    if (fbend > res->blob_size) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: fb end out of range\n",
+                      __func__);
+        cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+        return;
+    }
+
+    virtio_gpu_do_set_scanout(g, ss.scanout_id,
+                              &fb, res, &ss.r, &cmd->error);
+}
+
 int virtio_gpu_create_mapping_iov(VirtIOGPU *g,
                                   uint32_t nr_entries, uint32_t offset,
                                   struct virtio_gpu_ctrl_command *cmd,
@@ -858,7 +940,7 @@ void virtio_gpu_simple_process_cmd(VirtIOGPU *g,
     case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D:
         virtio_gpu_resource_create_2d(g, cmd);
         break;
-  case VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB:
+    case VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB:
         if (!virtio_gpu_blob_enabled(g->parent_obj.conf)) {
             cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
             break;
@@ -877,6 +959,13 @@ void virtio_gpu_simple_process_cmd(VirtIOGPU *g,
     case VIRTIO_GPU_CMD_SET_SCANOUT:
         virtio_gpu_set_scanout(g, cmd);
         break;
+    case VIRTIO_GPU_CMD_SET_SCANOUT_BLOB:
+        if (!virtio_gpu_blob_enabled(g->parent_obj.conf)) {
+            cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+            break;
+        }
+        virtio_gpu_set_scanout_blob(g, cmd);
+        break;
     case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING:
         virtio_gpu_resource_attach_backing(g, cmd);
         break;
diff --git a/include/hw/virtio/virtio-gpu-bswap.h b/include/hw/virtio/virtio-gpu-bswap.h
index d23ac5cc4a..e2bee8f595 100644
--- a/include/hw/virtio/virtio-gpu-bswap.h
+++ b/include/hw/virtio/virtio-gpu-bswap.h
@@ -68,4 +68,11 @@ virtio_gpu_create_blob_bswap(struct virtio_gpu_resource_create_blob *cblob)
     le64_to_cpus(&cblob->size);
 }
 
+static inline void
+virtio_gpu_scanout_blob_bswap(struct virtio_gpu_set_scanout_blob *ssb)
+{
+    virtio_gpu_bswap_32(ssb, sizeof(*ssb) - sizeof(ssb->offsets[3]));
+    le32_to_cpus(&ssb->offsets[3]);
+}
+
 #endif
-- 
2.30.2


Re: [PATCH v3 12/20] virtio-gpu: Add virtio_gpu_set_scanout_blob
Posted by Gerd Hoffmann 4 years, 9 months ago
  Hi,

> -    pixman_image_unref(res->image);
> +    if (res->image) {
> +        pixman_image_unref(res->image);
> +    }

There is qemu_pixman_image_unref().

Like pixman_image_unref except that it also accepts (and ignores) NULL
pointers.

>      virtio_gpu_cleanup_mapping(g, res);
>      QTAILQ_REMOVE(&g->reslist, res, next);
>      g->hostmem -= res->hostmem;
> @@ -494,6 +496,7 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g,
>  {
>      struct virtio_gpu_simple_resource *res;
>      struct virtio_gpu_resource_flush rf;
> +    struct virtio_gpu_scanout *scanout;
>      pixman_region16_t flush_region;
>      int i;
>  
> @@ -504,16 +507,28 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g,
>  
>      res = virtio_gpu_find_check_resource(g, rf.resource_id, false,
>                                           __func__, &cmd->error);
> -    if (!res || res->blob) {
> +    if (!res) {
>          return;
>      }
>  
> -    if (rf.r.x > res->width ||
> +    if (res->blob && display_opengl) {

console_has_gl(scanout->con)

> +    if (!res->blob &&
> +        (rf.r.x > res->width ||
>          rf.r.y > res->height ||
>          rf.r.width > res->width ||
>          rf.r.height > res->height ||
>          rf.r.x + rf.r.width > res->width ||
> -        rf.r.y + rf.r.height > res->height) {
> +        rf.r.y + rf.r.height > res->height)) {
>          qemu_log_mask(LOG_GUEST_ERROR, "%s: flush bounds outside resource"
>                        " bounds for resource %d: %d %d %d %d vs %d %d\n",
>                        __func__, rf.resource_id, rf.r.x, rf.r.y,

Indent needs fixing.
Do we need sanity checks for the res->blob == true case?  I think so ...

>      g->parent_obj.enable = 1;
> -    data = (uint8_t *)pixman_image_get_data(res->image);
> +
> +    if (res->blob) {
> +        if (display_opengl) {

Again console_has_gl(scanout->con)

> +            if (!virtio_gpu_update_dmabuf(g, scanout_id, res, fb)) {
> +                virtio_gpu_update_scanout(g, scanout_id, res, r);
> +                return;
> +            }
> +        }
> +
> +        data = res->blob;
> +    } else {
> +        data = (uint8_t *)pixman_image_get_data(res->image);
> +    }
>  
>      /* create a surface for this scanout */
> -    if (!scanout->ds ||
> +    if ((res->blob && !display_opengl) ||

And again.

take care,
  Gerd


RE: [PATCH v3 12/20] virtio-gpu: Add virtio_gpu_set_scanout_blob
Posted by Kasireddy, Vivek 4 years, 9 months ago
Hi Gerd,

> > -    pixman_image_unref(res->image);
> > +    if (res->image) {
> > +        pixman_image_unref(res->image);
> > +    }
> 
> There is qemu_pixman_image_unref().
> 
> Like pixman_image_unref except that it also accepts (and ignores) NULL
> pointers.
[Kasireddy, Vivek] Made the change in v4.

> 
> >      virtio_gpu_cleanup_mapping(g, res);
> >      QTAILQ_REMOVE(&g->reslist, res, next);
> >      g->hostmem -= res->hostmem;
> > @@ -494,6 +496,7 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g,
> >  {
> >      struct virtio_gpu_simple_resource *res;
> >      struct virtio_gpu_resource_flush rf;
> > +    struct virtio_gpu_scanout *scanout;
> >      pixman_region16_t flush_region;
> >      int i;
> >
> > @@ -504,16 +507,28 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g,
> >
> >      res = virtio_gpu_find_check_resource(g, rf.resource_id, false,
> >                                           __func__, &cmd->error);
> > -    if (!res || res->blob) {
> > +    if (!res) {
> >          return;
> >      }
> >
> > -    if (rf.r.x > res->width ||
> > +    if (res->blob && display_opengl) {
> 
> console_has_gl(scanout->con)
[Kasireddy, Vivek] Made this change here and in other places in v4.

> 
> > +    if (!res->blob &&
> > +        (rf.r.x > res->width ||
> >          rf.r.y > res->height ||
> >          rf.r.width > res->width ||
> >          rf.r.height > res->height ||
> >          rf.r.x + rf.r.width > res->width ||
> > -        rf.r.y + rf.r.height > res->height) {
> > +        rf.r.y + rf.r.height > res->height)) {
> >          qemu_log_mask(LOG_GUEST_ERROR, "%s: flush bounds outside resource"
> >                        " bounds for resource %d: %d %d %d %d vs %d %d\n",
> >                        __func__, rf.resource_id, rf.r.x, rf.r.y,
> 
> Indent needs fixing.
> Do we need sanity checks for the res->blob == true case?  I think so ...
[Kasireddy, Vivek] If a resource is a blob, it would not have valid width and height and
instead only have valid blob_size; hence, the sanity checks would not be applicable.

Thanks,
Vivek

> 
> >      g->parent_obj.enable = 1;
> > -    data = (uint8_t *)pixman_image_get_data(res->image);
> > +
> > +    if (res->blob) {
> > +        if (display_opengl) {
> 
> Again console_has_gl(scanout->con)
> 
> > +            if (!virtio_gpu_update_dmabuf(g, scanout_id, res, fb)) {
> > +                virtio_gpu_update_scanout(g, scanout_id, res, r);
> > +                return;
> > +            }
> > +        }
> > +
> > +        data = res->blob;
> > +    } else {
> > +        data = (uint8_t *)pixman_image_get_data(res->image);
> > +    }
> >
> >      /* create a surface for this scanout */
> > -    if (!scanout->ds ||
> > +    if ((res->blob && !display_opengl) ||
> 
> And again.
> 
> take care,
>   Gerd