From 1fece5fa5f8284076d923be6937d9115055a533e Mon Sep 17 00:00:00 2001 From: Chia-I Wu Date: Thu, 9 May 2019 21:44:33 -0700 Subject: [PATCH] virgl: better support for PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE 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 Reviewed-by: Alexandros Frantzis --- src/gallium/drivers/virgl/virgl_buffer.c | 7 ++ src/gallium/drivers/virgl/virgl_resource.c | 95 ++++++++++++++++------ src/gallium/drivers/virgl/virgl_resource.h | 8 ++ src/gallium/drivers/virgl/virgl_texture.c | 7 ++ 4 files changed, 92 insertions(+), 25 deletions(-) diff --git a/src/gallium/drivers/virgl/virgl_buffer.c b/src/gallium/drivers/virgl/virgl_buffer.c index ddb632db483..ed001722ab4 100644 --- a/src/gallium/drivers/virgl/virgl_buffer.c +++ b/src/gallium/drivers/virgl/virgl_buffer.c @@ -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) diff --git a/src/gallium/drivers/virgl/virgl_resource.c b/src/gallium/drivers/virgl/virgl_resource.c index 59afcca968e..9b908afd852 100644 --- a/src/gallium/drivers/virgl/virgl_resource.c +++ b/src/gallium/drivers/virgl/virgl_resource.c @@ -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; +} diff --git a/src/gallium/drivers/virgl/virgl_resource.h b/src/gallium/drivers/virgl/virgl_resource.h index 86eb2322513..c3374fbef70 100644 --- a/src/gallium/drivers/virgl/virgl_resource.h +++ b/src/gallium/drivers/virgl/virgl_resource.h @@ -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 diff --git a/src/gallium/drivers/virgl/virgl_texture.c b/src/gallium/drivers/virgl/virgl_texture.c index 1de6d4f17db..3c5737c5d17 100644 --- a/src/gallium/drivers/virgl/virgl_texture.c +++ b/src/gallium/drivers/virgl/virgl_texture.c @@ -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) -- 2.30.2