X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=src%2Fgallium%2Fdrivers%2Fnouveau%2Fnouveau_buffer.c;h=49ff100c4ece47b5006cf4c0cd6872242dd74bb1;hb=5e04526399ca5d249cc5ec600aa6546356abccbb;hp=95905a8776f750891f91b2ca9398233984016f39;hpb=0e5bf8565106833e1a678ebdae81fdf1657391c9;p=mesa.git diff --git a/src/gallium/drivers/nouveau/nouveau_buffer.c b/src/gallium/drivers/nouveau/nouveau_buffer.c index 95905a8776f..49ff100c4ec 100644 --- a/src/gallium/drivers/nouveau/nouveau_buffer.c +++ b/src/gallium/drivers/nouveau/nouveau_buffer.c @@ -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) { @@ -210,7 +221,9 @@ nouveau_transfer_write(struct nouveau_context *nv, struct nouveau_transfer *tx, 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) { @@ -283,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) { @@ -317,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) { @@ -324,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, @@ -350,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; @@ -364,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) @@ -376,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); @@ -383,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); @@ -396,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 @@ -403,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 @@ -412,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); @@ -431,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) @@ -452,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)) @@ -495,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); } @@ -554,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: @@ -593,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: @@ -624,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; }