diff options
Diffstat (limited to 'src/gallium/drivers/etnaviv/etnaviv_transfer.c')
-rw-r--r-- | src/gallium/drivers/etnaviv/etnaviv_transfer.c | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/src/gallium/drivers/etnaviv/etnaviv_transfer.c b/src/gallium/drivers/etnaviv/etnaviv_transfer.c new file mode 100644 index 00000000000..1a5aa7fc043 --- /dev/null +++ b/src/gallium/drivers/etnaviv/etnaviv_transfer.c @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2012-2015 Etnaviv Project + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sub license, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Wladimir J. van der Laan <[email protected]> + */ + +#include "etnaviv_transfer.h" +#include "etnaviv_clear_blit.h" +#include "etnaviv_context.h" +#include "etnaviv_debug.h" + +#include "pipe/p_defines.h" +#include "pipe/p_format.h" +#include "pipe/p_screen.h" +#include "pipe/p_state.h" +#include "util/u_format.h" +#include "util/u_inlines.h" +#include "util/u_memory.h" +#include "util/u_surface.h" +#include "util/u_transfer.h" + +/* Compute offset into a 1D/2D/3D buffer of a certain box. + * This box must be aligned to the block width and height of the + * underlying format. */ +static inline size_t +etna_compute_offset(enum pipe_format format, const struct pipe_box *box, + size_t stride, size_t layer_stride) +{ + return box->z * layer_stride + + box->y / util_format_get_blockheight(format) * stride + + box->x / util_format_get_blockwidth(format) * + util_format_get_blocksize(format); +} + +static void +etna_transfer_unmap(struct pipe_context *pctx, struct pipe_transfer *ptrans) +{ + struct etna_context *ctx = etna_context(pctx); + struct etna_transfer *trans = etna_transfer(ptrans); + struct etna_resource *rsc = etna_resource(ptrans->resource); + + /* XXX + * When writing to a resource that is already in use, replace the resource + * with a completely new buffer + * and free the old one using a fenced free. + * The most tricky case to implement will be: tiled or supertiled surface, + * partial write, target not aligned to 4/64. */ + assert(ptrans->level <= rsc->base.last_level); + + if (rsc->texture && !etna_resource_newer(rsc, etna_resource(rsc->texture))) + rsc = etna_resource(rsc->texture); /* switch to using the texture resource */ + + if (ptrans->usage & PIPE_TRANSFER_WRITE) { + if (trans->rsc) { + /* We have a temporary resource due to either tile status or + * tiling format. Write back the updated buffer contents. + * FIXME: we need to invalidate the tile status. */ + etna_copy_resource(pctx, ptrans->resource, trans->rsc, ptrans->level, + trans->rsc->last_level); + } else if (trans->staging) { + /* map buffer object */ + struct etna_resource_level *res_level = &rsc->levels[ptrans->level]; + void *mapped = etna_bo_map(rsc->bo) + res_level->offset; + + if (rsc->layout == ETNA_LAYOUT_LINEAR || rsc->layout == ETNA_LAYOUT_TILED) { + if (rsc->layout == ETNA_LAYOUT_TILED && !util_format_is_compressed(rsc->base.format)) { + etna_texture_tile( + mapped + ptrans->box.z * res_level->layer_stride, + trans->staging, ptrans->box.x, ptrans->box.y, + res_level->stride, ptrans->box.width, ptrans->box.height, + ptrans->stride, util_format_get_blocksize(rsc->base.format)); + } else { /* non-tiled or compressed format */ + util_copy_box(mapped, rsc->base.format, res_level->stride, + res_level->layer_stride, ptrans->box.x, + ptrans->box.y, ptrans->box.z, ptrans->box.width, + ptrans->box.height, ptrans->box.depth, + trans->staging, ptrans->stride, + ptrans->layer_stride, 0, 0, 0 /* src x,y,z */); + } + } else { + BUG("unsupported tiling %i", rsc->layout); + } + + FREE(trans->staging); + } + + rsc->seqno++; + etna_bo_cpu_fini(rsc->bo); + + if (rsc->base.bind & PIPE_BIND_SAMPLER_VIEW) { + /* XXX do we need to flush the CPU cache too or start a write barrier + * to make sure the GPU sees it? */ + ctx->dirty |= ETNA_DIRTY_TEXTURE_CACHES; + } + } + + pipe_resource_reference(&trans->rsc, NULL); + pipe_resource_reference(&ptrans->resource, NULL); + slab_free(&ctx->transfer_pool, trans); +} + +static void * +etna_transfer_map(struct pipe_context *pctx, struct pipe_resource *prsc, + unsigned level, + unsigned usage, + const struct pipe_box *box, + struct pipe_transfer **out_transfer) +{ + struct etna_context *ctx = etna_context(pctx); + struct etna_resource *rsc = etna_resource(prsc); + struct etna_transfer *trans; + struct pipe_transfer *ptrans; + enum pipe_format format = prsc->format; + + trans = slab_alloc(&ctx->transfer_pool); + if (!trans) + return NULL; + + /* slab_alloc() doesn't zero */ + memset(trans, 0, sizeof(*trans)); + + ptrans = &trans->base; + pipe_resource_reference(&ptrans->resource, prsc); + ptrans->level = level; + ptrans->usage = usage; + ptrans->box = *box; + + assert(level <= prsc->last_level); + + if (rsc->texture && !etna_resource_newer(rsc, etna_resource(rsc->texture))) { + /* We have a texture resource which is the same age or newer than the + * render resource. Use the texture resource, which avoids bouncing + * pixels between the two resources, and we can de-tile it in s/w. */ + rsc = etna_resource(rsc->texture); + } else if (rsc->ts_bo || + (rsc->layout != ETNA_LAYOUT_LINEAR && + util_format_get_blocksize(format) > 1 && + /* HALIGN 4 resources are incompatible with the resolve engine, + * so fall back to using software to detile this resource. */ + rsc->halign != TEXTURE_HALIGN_FOUR)) { + /* If the surface has tile status, we need to resolve it first. + * The strategy we implement here is to use the RS to copy the + * depth buffer, filling in the "holes" where the tile status + * indicates that it's clear. We also do this for tiled + * resources, but only if the RS can blit them. */ + if (usage & PIPE_TRANSFER_MAP_DIRECTLY) { + slab_free(&ctx->transfer_pool, trans); + BUG("unsupported transfer flags %#x with tile status/tiled layout", usage); + return NULL; + } + + if (prsc->depth0 > 1) { + slab_free(&ctx->transfer_pool, trans); + BUG("resource has depth >1 with tile status"); + return NULL; + } + + struct pipe_resource templ = *prsc; + templ.nr_samples = 0; + templ.bind = PIPE_BIND_RENDER_TARGET; + + trans->rsc = etna_resource_alloc(pctx->screen, ETNA_LAYOUT_LINEAR, &templ); + if (!trans->rsc) { + slab_free(&ctx->transfer_pool, trans); + return NULL; + } + + etna_copy_resource(pctx, trans->rsc, prsc, level, trans->rsc->last_level); + + /* Switch to using the temporary resource instead */ + rsc = etna_resource(trans->rsc); + } + + struct etna_resource_level *res_level = &rsc->levels[level]; + + /* Always sync if we have the temporary resource. The PIPE_TRANSFER_READ + * case could be optimised if we knew whether the resource has outstanding + * rendering. */ + if (usage & PIPE_TRANSFER_READ || trans->rsc) + etna_resource_wait(pctx, rsc); + + /* XXX we don't handle PIPE_TRANSFER_FLUSH_EXPLICIT; this flag can be ignored + * when mapping in-place, + * but when not in place we need to fire off the copy operation in + * transfer_flush_region (currently + * a no-op) instead of unmap. Need to handle this to support + * ARB_map_buffer_range extension at least. + */ + /* XXX we don't take care of current operations on the resource; which can + be, at some point in the pipeline + which is not yet executed: + + - bound as surface + - bound through vertex buffer + - bound through index buffer + - bound in sampler view + - used in clear_render_target / clear_depth_stencil operation + - used in blit + - used in resource_copy_region + + How do other drivers record this information over course of the rendering + pipeline? + Is it necessary at all? Only in case we want to provide a fast path and + map the resource directly + (and for PIPE_TRANSFER_MAP_DIRECTLY) and we don't want to force a sync. + We also need to know whether the resource is in use to determine if a sync + is needed (or just do it + always, but that comes at the expense of performance). + + A conservative approximation without too much overhead would be to mark + all resources that have + been bound at some point as busy. A drawback would be that accessing + resources that have + been bound but are no longer in use for a while still carry a performance + penalty. On the other hand, + the program could be using PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE or + PIPE_TRANSFER_UNSYNCHRONIZED to + avoid this in the first place... + + A) We use an in-pipe copy engine, and queue the copy operation after unmap + so that the copy + will be performed when all current commands have been executed. + Using the RS is possible, not sure if always efficient. This can also + do any kind of tiling for us. + Only possible when PIPE_TRANSFER_DISCARD_RANGE is set. + B) We discard the entire resource (or at least, the mipmap level) and + allocate new memory for it. + Only possible when mapping the entire resource or + PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE is set. + */ + + /* No need to allocate a buffer for copying if the resource is not in use, + * and no tiling is needed, can just return a direct pointer. + */ + bool in_place = rsc->layout == ETNA_LAYOUT_LINEAR || + (rsc->layout == ETNA_LAYOUT_TILED && + util_format_is_compressed(prsc->format)); + + /* Ignore PIPE_TRANSFER_UNSYNCHRONIZED and PIPE_TRANSFER_DONTBLOCK here. + * It appears that Gallium operates the index/vertex buffers in a + * circular fashion, and the CPU can catch up with the GPU and starts + * overwriting yet-to-be-processed entries, causing rendering corruption. */ + uint32_t prep_flags = 0; + + if (usage & PIPE_TRANSFER_READ) + prep_flags |= DRM_ETNA_PREP_READ; + if (usage & PIPE_TRANSFER_WRITE) + prep_flags |= DRM_ETNA_PREP_WRITE; + + if (etna_bo_cpu_prep(rsc->bo, prep_flags)) + goto fail_prep; + + /* map buffer object */ + void *mapped = etna_bo_map(rsc->bo); + if (!mapped) + goto fail; + + *out_transfer = ptrans; + + if (in_place) { + ptrans->stride = res_level->stride; + ptrans->layer_stride = res_level->layer_stride; + + return mapped + res_level->offset + + etna_compute_offset(prsc->format, box, res_level->stride, + res_level->layer_stride); + } else { + unsigned divSizeX = util_format_get_blockwidth(format); + unsigned divSizeY = util_format_get_blockheight(format); + + /* No direct mappings of tiled, since we need to manually + * tile/untile. + */ + if (usage & PIPE_TRANSFER_MAP_DIRECTLY) + goto fail; + + mapped += res_level->offset; + ptrans->stride = align(box->width, divSizeX) * util_format_get_blocksize(format); /* row stride in bytes */ + ptrans->layer_stride = align(box->height, divSizeY) * ptrans->stride; + size_t size = ptrans->layer_stride * box->depth; + + trans->staging = MALLOC(size); + if (!trans->staging) + goto fail; + + if (usage & PIPE_TRANSFER_READ) { + /* untile or copy resource for reading */ + if (rsc->layout == ETNA_LAYOUT_LINEAR || rsc->layout == ETNA_LAYOUT_TILED) { + if (rsc->layout == ETNA_LAYOUT_TILED && !util_format_is_compressed(rsc->base.format)) { + etna_texture_untile(trans->staging, + mapped + ptrans->box.z * res_level->layer_stride, + ptrans->box.x, ptrans->box.y, res_level->stride, + ptrans->box.width, ptrans->box.height, ptrans->stride, + util_format_get_blocksize(rsc->base.format)); + } else { /* non-tiled or compressed format */ + util_copy_box(trans->staging, rsc->base.format, ptrans->stride, + ptrans->layer_stride, 0, 0, 0, /* dst x,y,z */ + ptrans->box.width, ptrans->box.height, + ptrans->box.depth, mapped, res_level->stride, + res_level->layer_stride, ptrans->box.x, + ptrans->box.y, ptrans->box.z); + } + } else /* TODO supertiling */ + { + BUG("unsupported tiling %i for reading", rsc->layout); + } + } + + return trans->staging; + } + +fail: + etna_bo_cpu_fini(rsc->bo); +fail_prep: + etna_transfer_unmap(pctx, ptrans); + return NULL; +} + +static void +etna_transfer_flush_region(struct pipe_context *pctx, + struct pipe_transfer *transfer, + const struct pipe_box *box) +{ + /* NOOP for now */ +} + +void +etna_transfer_init(struct pipe_context *pctx) +{ + pctx->transfer_map = etna_transfer_map; + pctx->transfer_flush_region = etna_transfer_flush_region; + pctx->transfer_unmap = etna_transfer_unmap; + pctx->buffer_subdata = u_default_buffer_subdata; + pctx->texture_subdata = u_default_texture_subdata; +} |