virgl: better support for PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE
authorChia-I Wu <olvaffe@gmail.com>
Fri, 10 May 2019 04:44:33 +0000 (21:44 -0700)
committerChia-I Wu <olvaffe@gmail.com>
Mon, 17 Jun 2019 16:36:31 +0000 (09:36 -0700)
When the resource to be mapped is busy and the backing storage can
be discarded, reallocate the backing storage to avoid waiting.

In this new path, we allocate a new buffer, emit a state change,
write, and add the transfer to the queue .  In the
PIPE_TRANSFER_DISCARD_RANGE path, we suballocate a staging buffer,
write, and emit a copy_transfer (which may allocate, memcpy, and
blit internally).  The win might not always be clear.  But another
win comes from that the new path clears res->valid_buffer_range and
does not clear res->clean_mask.  This makes it much more preferable
in scenarios such as

  access = enough_space ? GL_MAP_UNSYNCHRONIZED_BIT :
                          GL_MAP_INVALIDATE_BUFFER_BIT;
  glMapBufferRange(..., GL_MAP_WRITE_BIT | access);
  memcpy(...); // append new data
  glUnmapBuffer(...);

Signed-off-by: Chia-I Wu <olvaffe@gmail.com>
Reviewed-by: Alexandros Frantzis <alexandros.frantzis@collabora.com>
src/gallium/drivers/virgl/virgl_buffer.c
src/gallium/drivers/virgl/virgl_resource.c
src/gallium/drivers/virgl/virgl_resource.h
src/gallium/drivers/virgl/virgl_texture.c

index ddb632db48334b399f404904f646a1a5a44388b2..ed001722ab47ac938ff86b8c81f0ca9b10dd6129 100644 (file)
@@ -47,6 +47,13 @@ static void *virgl_buffer_transfer_map(struct pipe_context *ctx,
 
    map_type = virgl_resource_transfer_prepare(vctx, trans);
    switch (map_type) {
+   case VIRGL_TRANSFER_MAP_REALLOC:
+      if (!virgl_resource_realloc(vctx, vbuf)) {
+         map_addr = NULL;
+         break;
+      }
+      vs->vws->resource_reference(vs->vws, &trans->hw_res, vbuf->hw_res);
+      /* fall through */
    case VIRGL_TRANSFER_MAP_HW_RES:
       trans->hw_res_map = vs->vws->resource_map(vs->vws, vbuf->hw_res);
       if (trans->hw_res_map)
index 59afcca968ec6ffbd5218abeb944e3e5402dafaf..9b908afd8520c2242acab4570d8a767392402760 100644 (file)
@@ -87,13 +87,9 @@ virgl_resource_transfer_prepare(struct virgl_context *vctx,
    struct virgl_winsys *vws = vs->vws;
    struct virgl_resource *res = virgl_resource(xfer->base.resource);
    enum virgl_transfer_map_type map_type = VIRGL_TRANSFER_MAP_HW_RES;
-   bool unsynchronized = xfer->base.usage & PIPE_TRANSFER_UNSYNCHRONIZED;
-   bool discard = xfer->base.usage & (PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE |
-                                      PIPE_TRANSFER_DISCARD_RANGE);
    bool flush;
    bool readback;
    bool wait;
-   bool copy_transfer;
 
    /* there is no way to map the host storage currently */
    if (xfer->base.usage & PIPE_TRANSFER_MAP_DIRECTLY)
@@ -110,19 +106,10 @@ virgl_resource_transfer_prepare(struct virgl_context *vctx,
    flush = virgl_res_needs_flush(vctx, xfer);
    readback = virgl_res_needs_readback(vctx, res, xfer->base.usage,
                                        xfer->base.level);
-
-   /* Check if we should perform a copy transfer through the transfer_uploader. */
-   copy_transfer = discard &&
-                   !readback &&
-                   !unsynchronized &&
-                   vctx->transfer_uploader &&
-                   !vctx->transfer_uploader_in_use &&
-                   (flush || vws->resource_is_busy(vws, res->hw_res));
-
    /* We need to wait for all cmdbufs, current or previous, that access the
     * resource to finish unless synchronization is disabled.
     */
-   wait = !unsynchronized;
+   wait = !(xfer->base.usage & PIPE_TRANSFER_UNSYNCHRONIZED);
 
    /* When the transfer range consists of only uninitialized data, we can
     * assume the GPU is not accessing the range and readback is unnecessary.
@@ -135,17 +122,42 @@ virgl_resource_transfer_prepare(struct virgl_context *vctx,
       flush = false;
       readback = false;
       wait = false;
-      copy_transfer = false;
    }
 
-   /* When performing a copy transfer there is no need wait for the target
-    * resource. There is normally no need to flush either, unless the amount of
-    * memory we are using for staging resources starts growing, in which case
-    * we want to flush to keep our memory consumption in check.
+   /* When the resource is busy but its content can be discarded, we can
+    * replace its HW resource or use a staging buffer to avoid waiting.
     */
-   if (copy_transfer) {
-      flush = (vctx->queued_staging_res_size > VIRGL_QUEUED_STAGING_RES_SIZE_LIMIT);
-      wait = false;
+   if (wait && (xfer->base.usage & (PIPE_TRANSFER_DISCARD_RANGE |
+                                    PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE))) {
+      const bool can_realloc =
+         (xfer->base.usage & PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE) &&
+         virgl_can_rebind_resource(vctx, &res->u.b);
+      const bool can_staging = vctx->transfer_uploader &&
+                               !vctx->transfer_uploader_in_use;
+
+      /* discard implies no readback */
+      assert(!readback);
+
+      if (can_realloc || can_staging) {
+         /* Both map types have some costs.  Do them only when the resource is
+          * (or will be) busy for real.  Otherwise, set wait to false.
+          */
+         wait = (flush || vws->resource_is_busy(vws, res->hw_res));
+         if (wait) {
+            map_type = (can_realloc) ?
+               VIRGL_TRANSFER_MAP_REALLOC :
+               VIRGL_TRANSFER_MAP_STAGING;
+            wait = false;
+
+            /* There is normally no need to flush either, unless the amount of
+             * memory we are using for staging resources starts growing, in
+             * which case we want to flush to keep our memory consumption in
+             * check.
+             */
+            flush = (vctx->queued_staging_res_size >
+               VIRGL_QUEUED_STAGING_RES_SIZE_LIMIT);
+         }
+      }
    }
 
    /* readback has some implications */
@@ -185,9 +197,6 @@ virgl_resource_transfer_prepare(struct virgl_context *vctx,
    if (wait)
       vws->resource_wait(vws, res->hw_res);
 
-   if (copy_transfer)
-      map_type = VIRGL_TRANSFER_MAP_STAGING;
-
    return map_type;
 }
 
@@ -598,3 +607,39 @@ void *virgl_transfer_uploader_map(struct virgl_context *vctx,
 
    return map_addr;
 }
+
+bool
+virgl_resource_realloc(struct virgl_context *vctx, struct virgl_resource *res)
+{
+   struct virgl_screen *vs = virgl_screen(vctx->base.screen);
+   const struct pipe_resource *templ = &res->u.b;
+   unsigned vbind;
+   struct virgl_hw_res *hw_res;
+
+   vbind = pipe_to_virgl_bind(vs, templ->bind, templ->flags);
+   hw_res = vs->vws->resource_create(vs->vws,
+                                     templ->target,
+                                     templ->format,
+                                     vbind,
+                                     templ->width0,
+                                     templ->height0,
+                                     templ->depth0,
+                                     templ->array_size,
+                                     templ->last_level,
+                                     templ->nr_samples,
+                                     res->metadata.total_size);
+   if (!hw_res)
+      return false;
+
+   vs->vws->resource_reference(vs->vws, &res->hw_res, NULL);
+   res->hw_res = hw_res;
+
+   util_range_set_empty(&res->valid_buffer_range);
+
+   /* count toward the staging resource size limit */
+   vctx->queued_staging_res_size += res->metadata.total_size;
+
+   virgl_rebind_resource(vctx, &res->u.b);
+
+   return true;
+}
index 86eb2322513da7973f0dd4e71bc7ad7354b1dfe7..c3374fbef70456f843adfe3cc90b97ce68f9a3e7 100644 (file)
@@ -73,10 +73,14 @@ struct virgl_resource {
 enum virgl_transfer_map_type {
    VIRGL_TRANSFER_MAP_ERROR = -1,
    VIRGL_TRANSFER_MAP_HW_RES,
+
    /* Map a range of a staging buffer. The updated contents should be transferred
     * with a copy transfer.
     */
    VIRGL_TRANSFER_MAP_STAGING,
+
+   /* Reallocate the underlying virgl_hw_res. */
+   VIRGL_TRANSFER_MAP_REALLOC,
 };
 
 struct virgl_transfer {
@@ -187,4 +191,8 @@ void virgl_resource_dirty(struct virgl_resource *res, uint32_t level);
 void *virgl_transfer_uploader_map(struct virgl_context *vctx,
                                   struct virgl_transfer *vtransfer);
 
+bool
+virgl_resource_realloc(struct virgl_context *vctx,
+                       struct virgl_resource *res);
+
 #endif
index 1de6d4f17dbfee15b16a8c121d4a8c4976cfb7b6..3c5737c5d17cc1ce7aaba5f79f123ae27048766e 100644 (file)
@@ -134,6 +134,13 @@ static void *texture_transfer_map_plain(struct pipe_context *ctx,
 
    map_type = virgl_resource_transfer_prepare(vctx, trans);
    switch (map_type) {
+   case VIRGL_TRANSFER_MAP_REALLOC:
+      if (!virgl_resource_realloc(vctx, vtex)) {
+         map_addr = NULL;
+         break;
+      }
+      vws->resource_reference(vws, &trans->hw_res, vtex->hw_res);
+      /* fall through */
    case VIRGL_TRANSFER_MAP_HW_RES:
       trans->hw_res_map = vws->resource_map(vws, vtex->hw_res);
       if (trans->hw_res_map)