virgl: Use copy transfers for textures
[mesa.git] / src / gallium / drivers / virgl / virgl_resource.c
index 52197b8017532b395c22fdbe4cf2c44690d84efb..897e0c16c7ef2ce1850ea7e42468e22bd659b709 100644 (file)
@@ -23,6 +23,7 @@
 #include "util/u_format.h"
 #include "util/u_inlines.h"
 #include "util/u_memory.h"
+#include "util/u_upload_mgr.h"
 #include "virgl_context.h"
 #include "virgl_resource.h"
 #include "virgl_screen.h"
  *
  *  - synchronization is disabled
  *  - the resource is not referenced by the current cmdbuf
- *  - the current cmdbuf has no draw/compute command that accesses the
- *    resource (XXX there are also clear or blit commands)
- *  - the transfer is to an undefined region and we can assume the current
- *    cmdbuf has no command that accesses the region (XXX we cannot just check
- *    for overlapping transfers)
  */
-bool virgl_res_needs_flush(struct virgl_context *vctx,
-                           struct virgl_transfer *trans)
+static bool virgl_res_needs_flush(struct virgl_context *vctx,
+                                  struct virgl_transfer *trans)
 {
    struct virgl_winsys *vws = virgl_screen(vctx->base.screen)->vws;
    struct virgl_resource *res = virgl_resource(trans->base.resource);
@@ -50,32 +46,6 @@ bool virgl_res_needs_flush(struct virgl_context *vctx,
    if (!vws->res_is_referenced(vws, vctx->cbuf, res->hw_res))
       return false;
 
-   if (res->clean_mask & (1 << trans->base.level)) {
-      /* XXX Consider
-       *
-       *   glCopyBufferSubData(src, dst, ...);
-       *   glBufferSubData(src, ...);
-       *
-       * at the beginning of a cmdbuf.  glBufferSubData will be incorrectly
-       * reordered before glCopyBufferSubData.
-       */
-      if (vctx->num_draws == 0 && vctx->num_compute == 0)
-         return false;
-
-      /* XXX Consider
-       *
-       *   glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 3, data1);
-       *   glFlush();
-       *   glDrawArrays(GL_TRIANGLES, 0, 3);
-       *   glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 3, data2);
-       *   glDrawArrays(GL_TRIANGLES, 0, 3);
-       *
-       * Both draws will see data2.
-       */
-      if (!virgl_transfer_queue_is_queued(&vctx->queue, trans))
-         return false;
-   }
-
    return true;
 }
 
@@ -89,11 +59,12 @@ bool virgl_res_needs_flush(struct virgl_context *vctx,
  * PIPE_TRANSFER_READ becomes irrelevant.  PIPE_TRANSFER_UNSYNCHRONIZED and
  * PIPE_TRANSFER_FLUSH_EXPLICIT are also irrelevant.
  */
-bool virgl_res_needs_readback(struct virgl_context *vctx,
-                              struct virgl_resource *res,
-                              unsigned usage, unsigned level)
+static bool virgl_res_needs_readback(struct virgl_context *vctx,
+                                     struct virgl_resource *res,
+                                     unsigned usage, unsigned level)
 {
-   if (usage & PIPE_TRANSFER_DISCARD_RANGE)
+   if (usage & (PIPE_TRANSFER_DISCARD_RANGE |
+                PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE))
       return false;
 
    if (res->clean_mask & (1 << level))
@@ -102,6 +73,129 @@ bool virgl_res_needs_readback(struct virgl_context *vctx,
    return true;
 }
 
+enum virgl_transfer_map_type
+virgl_resource_transfer_prepare(struct virgl_context *vctx,
+                                struct virgl_transfer *xfer)
+{
+   struct virgl_screen *vs = virgl_screen(vctx->base.screen);
+   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)
+      return VIRGL_TRANSFER_MAP_ERROR;
+
+   /* We break the logic down into four steps
+    *
+    * step 1: determine the required operations independently
+    * step 2: look for chances to skip the operations
+    * step 3: resolve dependencies between the operations
+    * step 4: execute the operations
+    */
+
+   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;
+
+   /* When the transfer range consists of only uninitialized data, we can
+    * assume the GPU is not accessing the range and readback is unnecessary.
+    * We can proceed as if PIPE_TRANSFER_UNSYNCHRONIZED and
+    * PIPE_TRANSFER_DISCARD_RANGE are set.
+    */
+   if (res->u.b.target == PIPE_BUFFER &&
+         !util_ranges_intersect(&res->valid_buffer_range, xfer->base.box.x,
+            xfer->base.box.x + xfer->base.box.width)) {
+      flush = false;
+      readback = false;
+      wait = false;
+      copy_transfer = false;
+   }
+
+   /* When performing a copy transfer there is no need to flush or wait for
+    * the target resource.
+    */
+   if (copy_transfer) {
+      flush = false;
+      wait = false;
+   }
+
+   /* readback has some implications */
+   if (readback) {
+      /* Readback is yet another command and is transparent to the state
+       * trackers.  It should be waited for in all cases, including when
+       * PIPE_TRANSFER_UNSYNCHRONIZED is set.
+       */
+      wait = true;
+
+      /* When the transfer queue has pending writes to this transfer's region,
+       * we have to flush before readback.
+       */
+      if (!flush && virgl_transfer_queue_is_queued(&vctx->queue, xfer))
+         flush = true;
+   }
+
+   /* XXX This is incorrect and will be removed.  Consider
+    *
+    *   glTexImage2D(..., data1);
+    *   glDrawArrays();
+    *   glFlush();
+    *   glTexImage2D(..., data2);
+    *
+    * readback and flush are both false in the second glTexImage2D call.  The
+    * draw call might end up seeing data2.  Same applies to buffers with
+    * glBufferSubData.
+    */
+   wait = flush || readback;
+
+   if (flush)
+      vctx->base.flush(&vctx->base, NULL, 0);
+
+   /* If we are not allowed to block, and we know that we will have to wait,
+    * either because the resource is busy, or because it will become busy due
+    * to a readback, return early to avoid performing an incomplete
+    * transfer_get. Such an incomplete transfer_get may finish at any time,
+    * during which another unsynchronized map could write to the resource
+    * contents, leaving the contents in an undefined state.
+    */
+   if ((xfer->base.usage & PIPE_TRANSFER_DONTBLOCK) &&
+       (readback || (wait && vws->resource_is_busy(vws, res->hw_res))))
+      return VIRGL_TRANSFER_MAP_ERROR;
+
+   if (readback) {
+      vws->transfer_get(vws, res->hw_res, &xfer->base.box, xfer->base.stride,
+                        xfer->l_stride, xfer->offset, xfer->base.level);
+   }
+
+   if (wait)
+      vws->resource_wait(vws, res->hw_res);
+
+   if (copy_transfer)
+      map_type = VIRGL_TRANSFER_MAP_STAGING;
+
+   return map_type;
+}
+
 static struct pipe_resource *virgl_resource_create(struct pipe_screen *screen,
                                                    const struct pipe_resource *templ)
 {
@@ -112,7 +206,7 @@ static struct pipe_resource *virgl_resource_create(struct pipe_screen *screen,
    res->u.b = *templ;
    res->u.b.screen = &vs->base;
    pipe_reference_init(&res->u.b.reference, 1);
-   vbind = pipe_to_virgl_bind(vs, templ->bind);
+   vbind = pipe_to_virgl_bind(vs, templ->bind, templ->flags);
    virgl_resource_layout(&res->u.b, &res->metadata);
    res->hw_res = vs->vws->resource_create(vs->vws, templ->target,
                                           templ->format, vbind,
@@ -130,10 +224,12 @@ static struct pipe_resource *virgl_resource_create(struct pipe_screen *screen,
 
    res->clean_mask = (1 << VR_MAX_TEXTURE_2D_LEVELS) - 1;
 
-   if (templ->target == PIPE_BUFFER)
+   if (templ->target == PIPE_BUFFER) {
+      util_range_init(&res->valid_buffer_range);
       virgl_buffer_init(res);
-   else
+   } else {
       virgl_texture_init(res);
+   }
 
    return &res->u.b;
 
@@ -197,7 +293,8 @@ static bool virgl_buffer_transfer_extend(struct pipe_context *ctx,
    dummy_trans.offset = box->x;
 
    flush = virgl_res_needs_flush(vctx, &dummy_trans);
-   if (flush)
+   if (flush && util_ranges_intersect(&vbuf->valid_buffer_range,
+                                      box->x, box->x + box->width))
       return false;
 
    queued = virgl_transfer_queue_extend(&vctx->queue, &dummy_trans);
@@ -205,6 +302,7 @@ static bool virgl_buffer_transfer_extend(struct pipe_context *ctx,
       return false;
 
    memcpy(queued->hw_res_map + dummy_trans.offset, data, box->width);
+   util_range_add(&vbuf->valid_buffer_range, box->x, box->x + box->width);
 
    return true;
 }
@@ -330,6 +428,8 @@ virgl_resource_create_transfer(struct slab_child_pool *pool,
    trans->base.layer_stride = metadata->layer_stride[level];
    trans->offset = offset;
    util_range_init(&trans->range);
+   trans->copy_src_res = NULL;
+   trans->copy_src_offset = 0;
 
    if (trans->base.resource->target != PIPE_TEXTURE_3D &&
        trans->base.resource->target != PIPE_TEXTURE_CUBE &&
@@ -346,6 +446,7 @@ virgl_resource_create_transfer(struct slab_child_pool *pool,
 void virgl_resource_destroy_transfer(struct slab_child_pool *pool,
                                      struct virgl_transfer *trans)
 {
+   pipe_resource_reference(&trans->copy_src_res, NULL);
    util_range_destroy(&trans->range);
    slab_free(pool, trans);
 }
@@ -355,6 +456,10 @@ void virgl_resource_destroy(struct pipe_screen *screen,
 {
    struct virgl_screen *vs = virgl_screen(screen);
    struct virgl_resource *res = virgl_resource(resource);
+
+   if (res->u.b.target == PIPE_BUFFER)
+      util_range_destroy(&res->valid_buffer_range);
+
    vs->vws->resource_unref(vs->vws, res->hw_res);
    FREE(res);
 }
@@ -383,3 +488,104 @@ void virgl_resource_dirty(struct virgl_resource *res, uint32_t level)
          res->clean_mask &= ~(1 << level);
    }
 }
+
+/* Calculate the minimum size of the memory required to service a resource
+ * transfer map. Also return the stride and layer_stride for the corresponding
+ * layout.
+ */
+static unsigned virgl_transfer_map_size(struct virgl_transfer *vtransfer,
+                                        unsigned *out_stride,
+                                        unsigned *out_layer_stride)
+{
+   struct pipe_resource *pres = vtransfer->base.resource;
+   struct pipe_box *box = &vtransfer->base.box;
+   unsigned stride;
+   unsigned layer_stride;
+   unsigned size;
+
+   assert(out_stride);
+   assert(out_layer_stride);
+
+   stride = util_format_get_stride(pres->format, box->width);
+   layer_stride = util_format_get_2d_size(pres->format, stride, box->height);
+
+   if (pres->target == PIPE_TEXTURE_CUBE ||
+       pres->target == PIPE_TEXTURE_CUBE_ARRAY ||
+       pres->target == PIPE_TEXTURE_3D ||
+       pres->target == PIPE_TEXTURE_2D_ARRAY) {
+      size = box->depth * layer_stride;
+   } else if (pres->target == PIPE_TEXTURE_1D_ARRAY) {
+      size = box->depth * stride;
+   } else {
+      size = layer_stride;
+   }
+
+   *out_stride = stride;
+   *out_layer_stride = layer_stride;
+
+   return size;
+}
+
+/* Maps a region from the transfer uploader to service the transfer. */
+void *virgl_transfer_uploader_map(struct virgl_context *vctx,
+                                  struct virgl_transfer *vtransfer)
+{
+   struct virgl_resource *vres = virgl_resource(vtransfer->base.resource);
+   unsigned size;
+   unsigned align_offset;
+   unsigned stride;
+   unsigned layer_stride;
+   void *map_addr;
+
+   assert(vctx->transfer_uploader);
+   assert(!vctx->transfer_uploader_in_use);
+
+   size = virgl_transfer_map_size(vtransfer, &stride, &layer_stride);
+
+   /* For buffers we need to ensure that the start of the buffer would be
+    * aligned to VIRGL_MAP_BUFFER_ALIGNMENT, even if our transfer doesn't
+    * actually include it. To achieve this we may need to allocate a slightly
+    * larger range from the upload buffer, and later update the uploader
+    * resource offset and map address to point to the requested x coordinate
+    * within that range.
+    *
+    * 0       A       2A      3A
+    * |-------|---bbbb|bbbbb--|
+    *             |--------|    ==> size
+    *         |---|             ==> align_offset
+    *         |------------|    ==> allocation of size + align_offset
+    */
+   align_offset = vres->u.b.target == PIPE_BUFFER ?
+                  vtransfer->base.box.x % VIRGL_MAP_BUFFER_ALIGNMENT :
+                  0;
+
+   u_upload_alloc(vctx->transfer_uploader, 0, size + align_offset,
+                  VIRGL_MAP_BUFFER_ALIGNMENT,
+                  &vtransfer->copy_src_offset,
+                  &vtransfer->copy_src_res, &map_addr);
+   if (map_addr) {
+      /* Update source offset and address to point to the requested x coordinate
+       * if we have an align_offset (see above for more information). */
+      vtransfer->copy_src_offset += align_offset;
+      map_addr += align_offset;
+
+      /* Mark as dirty, since we are updating the host side resource
+       * without going through the corresponding guest side resource, and
+       * hence the two will diverge.
+       */
+      virgl_resource_dirty(vres, vtransfer->base.level);
+
+      /* The pointer returned by u_upload_alloc already has +offset
+       * applied. */
+      vctx->transfer_uploader_in_use = true;
+
+      /* We are using the minimum required size to hold the contents,
+       * possibly using a layout different from the layout of the resource,
+       * so update the transfer strides accordingly.
+       */
+      vtransfer->base.stride = stride;
+      vtransfer->base.layer_stride = layer_stride;
+   }
+
+   return map_addr;
+}