nvc0: expose 4 vertex streams, use stream ids in xfb
[mesa.git] / src / gallium / drivers / nouveau / nouveau_buffer.c
index 02bc6f0414632a8add372328d00314d707e6b34b..49ff100c4ece47b5006cf4c0cd6872242dd74bb1 100644 (file)
@@ -69,6 +69,8 @@ nouveau_buffer_allocate(struct nouveau_screen *screen,
    if (buf->bo)
       buf->address = buf->bo->offset + buf->offset;
 
+   util_range_set_empty(&buf->valid_buffer_range);
+
    return TRUE;
 }
 
@@ -124,11 +126,17 @@ nouveau_buffer_destroy(struct pipe_screen *pscreen,
    nouveau_fence_ref(NULL, &res->fence);
    nouveau_fence_ref(NULL, &res->fence_wr);
 
+   util_range_destroy(&res->valid_buffer_range);
+
    FREE(res);
 
    NOUVEAU_DRV_STAT(nouveau_screen(pscreen), buf_obj_current_count, -1);
 }
 
+/* Set up a staging area for the transfer. This is either done in "regular"
+ * system memory if the driver supports push_data (nv50+) and the data is
+ * small enough (and permit_pb == true), or in GART memory.
+ */
 static uint8_t *
 nouveau_transfer_staging(struct nouveau_context *nv,
                          struct nouveau_transfer *tx, boolean permit_pb)
@@ -155,7 +163,10 @@ nouveau_transfer_staging(struct nouveau_context *nv,
    return tx->map;
 }
 
-/* Maybe just migrate to GART right away if we actually need to do this. */
+/* Copies data from the resource into the the transfer's temporary GART
+ * buffer. Also updates buf->data if present.
+ *
+ * Maybe just migrate to GART right away if we actually need to do this. */
 static boolean
 nouveau_transfer_read(struct nouveau_context *nv, struct nouveau_transfer *tx)
 {
@@ -205,9 +216,14 @@ nouveau_transfer_write(struct nouveau_context *nv, struct nouveau_transfer *tx,
                   base, size / 4, (const uint32_t *)data);
    else
       nv->push_data(nv, buf->bo, buf->offset + base, buf->domain, size, data);
-}
 
+   nouveau_fence_ref(nv->screen->fence.current, &buf->fence);
+   nouveau_fence_ref(nv->screen->fence.current, &buf->fence_wr);
+}
 
+/* Does a CPU wait for the buffer's backing data to become reliably accessible
+ * for write/read by waiting on the buffer's relevant fences.
+ */
 static INLINE boolean
 nouveau_buffer_sync(struct nv04_resource *buf, unsigned rw)
 {
@@ -280,6 +296,7 @@ nouveau_buffer_transfer_del(struct nouveau_context *nv,
    }
 }
 
+/* Creates a cache in system memory of the buffer data. */
 static boolean
 nouveau_buffer_cache(struct nouveau_context *nv, struct nv04_resource *buf)
 {
@@ -289,6 +306,7 @@ nouveau_buffer_cache(struct nouveau_context *nv, struct nv04_resource *buf)
    tx.base.box.x = 0;
    tx.base.box.width = buf->base.width0;
    tx.bo = NULL;
+   tx.map = NULL;
 
    if (!buf->data)
       if (!nouveau_buffer_malloc(buf))
@@ -313,6 +331,10 @@ nouveau_buffer_cache(struct nouveau_context *nv, struct nv04_resource *buf)
 #define NOUVEAU_TRANSFER_DISCARD \
    (PIPE_TRANSFER_DISCARD_RANGE | PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE)
 
+/* Checks whether it is possible to completely discard the memory backing this
+ * resource. This can be useful if we would otherwise have to wait for a read
+ * operation to complete on this data.
+ */
 static INLINE boolean
 nouveau_buffer_should_discard(struct nv04_resource *buf, unsigned usage)
 {
@@ -320,9 +342,34 @@ nouveau_buffer_should_discard(struct nv04_resource *buf, unsigned usage)
       return FALSE;
    if (unlikely(buf->base.bind & PIPE_BIND_SHARED))
       return FALSE;
+   if (unlikely(usage & PIPE_TRANSFER_PERSISTENT))
+      return FALSE;
    return buf->mm && nouveau_buffer_busy(buf, PIPE_TRANSFER_WRITE);
 }
 
+/* Returns a pointer to a memory area representing a window into the
+ * resource's data.
+ *
+ * This may or may not be the _actual_ memory area of the resource. However
+ * when calling nouveau_buffer_transfer_unmap, if it wasn't the actual memory
+ * area, the contents of the returned map are copied over to the resource.
+ *
+ * The usage indicates what the caller plans to do with the map:
+ *
+ *   WRITE means that the user plans to write to it
+ *
+ *   READ means that the user plans on reading from it
+ *
+ *   DISCARD_WHOLE_RESOURCE means that the whole resource is going to be
+ *   potentially overwritten, and even if it isn't, the bits that aren't don't
+ *   need to be maintained.
+ *
+ *   DISCARD_RANGE means that all the data in the specified range is going to
+ *   be overwritten.
+ *
+ * The strategy for determining what kind of memory area to return is complex,
+ * see comments inside of the function.
+ */
 static void *
 nouveau_buffer_transfer_map(struct pipe_context *pipe,
                             struct pipe_resource *resource,
@@ -346,13 +393,33 @@ nouveau_buffer_transfer_map(struct pipe_context *pipe,
    if (usage & PIPE_TRANSFER_WRITE)
       NOUVEAU_DRV_STAT(nv->screen, buf_transfers_wr, 1);
 
+   /* If we are trying to write to an uninitialized range, the user shouldn't
+    * care what was there before. So we can treat the write as if the target
+    * range were being discarded. Furthermore, since we know that even if this
+    * buffer is busy due to GPU activity, because the contents were
+    * uninitialized, the GPU can't care what was there, and so we can treat
+    * the write as being unsynchronized.
+    */
+   if ((usage & PIPE_TRANSFER_WRITE) &&
+       !util_ranges_intersect(&buf->valid_buffer_range, box->x, box->x + box->width))
+      usage |= PIPE_TRANSFER_DISCARD_RANGE | PIPE_TRANSFER_UNSYNCHRONIZED;
+
+   if (usage & PIPE_TRANSFER_PERSISTENT)
+      usage |= PIPE_TRANSFER_UNSYNCHRONIZED;
+
    if (buf->domain == NOUVEAU_BO_VRAM) {
       if (usage & NOUVEAU_TRANSFER_DISCARD) {
+         /* Set up a staging area for the user to write to. It will be copied
+          * back into VRAM on unmap. */
          if (usage & PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE)
             buf->status &= NOUVEAU_BUFFER_STATUS_REALLOC_MASK;
          nouveau_transfer_staging(nv, tx, TRUE);
       } else {
          if (buf->status & NOUVEAU_BUFFER_STATUS_GPU_WRITING) {
+            /* The GPU is currently writing to this buffer. Copy its current
+             * contents to a staging area in the GART. This is necessary since
+             * not the whole area being mapped is being discarded.
+             */
             if (buf->data) {
                align_free(buf->data);
                buf->data = NULL;
@@ -360,6 +427,8 @@ nouveau_buffer_transfer_map(struct pipe_context *pipe,
             nouveau_transfer_staging(nv, tx, FALSE);
             nouveau_transfer_read(nv, tx);
          } else {
+            /* The buffer is currently idle. Create a staging area for writes,
+             * and make sure that the cached data is up-to-date. */
             if (usage & PIPE_TRANSFER_WRITE)
                nouveau_transfer_staging(nv, tx, TRUE);
             if (!buf->data)
@@ -372,6 +441,8 @@ nouveau_buffer_transfer_map(struct pipe_context *pipe,
       return buf->data + box->x;
    }
 
+   /* At this point, buf->domain == GART */
+
    if (nouveau_buffer_should_discard(buf, usage)) {
       int ref = buf->base.reference.count - 1;
       nouveau_buffer_reallocate(nv->screen, buf, buf->domain);
@@ -379,6 +450,12 @@ nouveau_buffer_transfer_map(struct pipe_context *pipe,
          nv->invalidate_resource_storage(nv, &buf->base, ref);
    }
 
+   /* Note that nouveau_bo_map ends up doing a nouveau_bo_wait with the
+    * relevant flags. If buf->mm is set, that means this resource is part of a
+    * larger slab bo that holds multiple resources. So in that case, don't
+    * wait on the whole slab and instead use the logic below to return a
+    * reasonable buffer for that case.
+    */
    ret = nouveau_bo_map(buf->bo,
                         buf->mm ? 0 : nouveau_screen_transfer_flags(usage),
                         nv->client);
@@ -392,6 +469,10 @@ nouveau_buffer_transfer_map(struct pipe_context *pipe,
    if ((usage & PIPE_TRANSFER_UNSYNCHRONIZED) || !buf->mm)
       return map;
 
+   /* If the GPU is currently reading/writing this buffer, we shouldn't
+    * interfere with its progress. So instead we either wait for the GPU to
+    * complete its operation, or set up a staging area to perform our work in.
+    */
    if (nouveau_buffer_busy(buf, usage & PIPE_TRANSFER_READ_WRITE)) {
       if (unlikely(usage & PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE)) {
          /* Discarding was not possible, must sync because
@@ -399,6 +480,8 @@ nouveau_buffer_transfer_map(struct pipe_context *pipe,
          nouveau_buffer_sync(buf, usage & PIPE_TRANSFER_READ_WRITE);
       } else
       if (usage & PIPE_TRANSFER_DISCARD_RANGE) {
+         /* The whole range is being discarded, so it doesn't matter what was
+          * there before. No need to copy anything over. */
          nouveau_transfer_staging(nv, tx, TRUE);
          map = tx->map;
       } else
@@ -408,6 +491,8 @@ nouveau_buffer_transfer_map(struct pipe_context *pipe,
          else
             nouveau_buffer_sync(buf, usage & PIPE_TRANSFER_READ_WRITE);
       } else {
+         /* It is expected that the returned buffer be a representation of the
+          * data in question, so we must copy it over from the buffer. */
          nouveau_transfer_staging(nv, tx, TRUE);
          if (tx->map)
             memcpy(tx->map, map, box->width);
@@ -427,10 +512,22 @@ nouveau_buffer_transfer_flush_region(struct pipe_context *pipe,
                                      const struct pipe_box *box)
 {
    struct nouveau_transfer *tx = nouveau_transfer(transfer);
+   struct nv04_resource *buf = nv04_resource(transfer->resource);
+
    if (tx->map)
       nouveau_transfer_write(nouveau_context(pipe), tx, box->x, box->width);
+
+   util_range_add(&buf->valid_buffer_range,
+                  tx->base.box.x + box->x,
+                  tx->base.box.x + box->x + box->width);
 }
 
+/* Unmap stage of the transfer. If it was a WRITE transfer and the map that
+ * was returned was not the real resource's data, this needs to transfer the
+ * data back to the resource.
+ *
+ * Also marks vbo dirty based on the buffer's binding
+ */
 static void
 nouveau_buffer_transfer_unmap(struct pipe_context *pipe,
                               struct pipe_transfer *transfer)
@@ -448,9 +545,10 @@ nouveau_buffer_transfer_unmap(struct pipe_context *pipe,
          /* make sure we invalidate dedicated caches */
          if (bind & (PIPE_BIND_VERTEX_BUFFER | PIPE_BIND_INDEX_BUFFER))
             nv->vbo_dirty = TRUE;
-         if (bind & (PIPE_BIND_CONSTANT_BUFFER))
-            nv->cb_dirty = TRUE;
       }
+
+      util_range_add(&buf->valid_buffer_range,
+                     tx->base.box.x, tx->base.box.x + tx->base.box.width);
    }
 
    if (!tx->bo && (tx->base.usage & PIPE_TRANSFER_WRITE))
@@ -491,6 +589,8 @@ nouveau_copy_buffer(struct nouveau_context *nv,
                                 &dst->base, 0, dstx, 0, 0,
                                 &src->base, 0, &src_box);
    }
+
+   util_range_add(&dst->valid_buffer_range, dstx, dstx + size);
 }
 
 
@@ -550,12 +650,14 @@ nouveau_buffer_create(struct pipe_screen *pscreen,
    pipe_reference_init(&buffer->base.reference, 1);
    buffer->base.screen = pscreen;
 
-   if (buffer->base.bind &
-       (screen->vidmem_bindings & screen->sysmem_bindings)) {
+   if (buffer->base.flags & (PIPE_RESOURCE_FLAG_MAP_PERSISTENT |
+                             PIPE_RESOURCE_FLAG_MAP_COHERENT)) {
+      buffer->domain = NOUVEAU_BO_GART;
+   } else if (buffer->base.bind &
+              (screen->vidmem_bindings & screen->sysmem_bindings)) {
       switch (buffer->base.usage) {
       case PIPE_USAGE_DEFAULT:
       case PIPE_USAGE_IMMUTABLE:
-      case PIPE_USAGE_STATIC:
          buffer->domain = NOUVEAU_BO_VRAM;
          break;
       case PIPE_USAGE_DYNAMIC:
@@ -589,6 +691,8 @@ nouveau_buffer_create(struct pipe_screen *pscreen,
 
    NOUVEAU_DRV_STAT(screen, buf_obj_current_count, 1);
 
+   util_range_init(&buffer->valid_buffer_range);
+
    return &buffer->base;
 
 fail:
@@ -620,6 +724,9 @@ nouveau_user_buffer_create(struct pipe_screen *pscreen, void *ptr,
    buffer->data = ptr;
    buffer->status = NOUVEAU_BUFFER_STATUS_USER_MEMORY;
 
+   util_range_init(&buffer->valid_buffer_range);
+   util_range_add(&buffer->valid_buffer_range, 0, bytes);
+
    return &buffer->base;
 }
 
@@ -690,6 +797,7 @@ nouveau_buffer_migrate(struct nouveau_context *nv,
       tx.base.box.x = 0;
       tx.base.box.width = buf->base.width0;
       tx.bo = NULL;
+      tx.map = NULL;
       if (!nouveau_transfer_staging(nv, &tx, FALSE))
          return FALSE;
       nouveau_transfer_write(nv, &tx, 0, tx.base.box.width);