Path: blob/21.2-virgl/src/gallium/drivers/r600/compute_memory_pool.c
4570 views
/*1* Permission is hereby granted, free of charge, to any person obtaining a2* copy of this software and associated documentation files (the "Software"),3* to deal in the Software without restriction, including without limitation4* on the rights to use, copy, modify, merge, publish, distribute, sub5* license, and/or sell copies of the Software, and to permit persons to whom6* the Software is furnished to do so, subject to the following conditions:7*8* The above copyright notice and this permission notice (including the next9* paragraph) shall be included in all copies or substantial portions of the10* Software.11*12* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR13* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,14* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL15* THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,16* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR17* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE18* USE OR OTHER DEALINGS IN THE SOFTWARE.19*20* Authors:21* Adam Rak <[email protected]>22*/2324#include "pipe/p_defines.h"25#include "pipe/p_state.h"26#include "pipe/p_context.h"27#include "util/u_blitter.h"28#include "util/list.h"29#include "util/u_transfer.h"30#include "util/u_surface.h"31#include "util/u_pack_color.h"32#include "util/u_math.h"33#include "util/u_memory.h"34#include "util/u_inlines.h"35#include "util/u_framebuffer.h"36#include "r600_shader.h"37#include "r600_pipe.h"38#include "r600_formats.h"39#include "compute_memory_pool.h"40#include "evergreen_compute.h"41#include "evergreen_compute_internal.h"42#include <inttypes.h>4344#define ITEM_ALIGNMENT 10244546/* A few forward declarations of static functions */47static void compute_memory_shadow(struct compute_memory_pool* pool,48struct pipe_context *pipe, int device_to_host);4950static void compute_memory_defrag(struct compute_memory_pool *pool,51struct pipe_resource *src, struct pipe_resource *dst,52struct pipe_context *pipe);5354static int compute_memory_promote_item(struct compute_memory_pool *pool,55struct compute_memory_item *item, struct pipe_context *pipe,56int64_t allocated);5758static void compute_memory_move_item(struct compute_memory_pool *pool,59struct pipe_resource *src, struct pipe_resource *dst,60struct compute_memory_item *item, uint64_t new_start_in_dw,61struct pipe_context *pipe);6263static void compute_memory_transfer(struct compute_memory_pool* pool,64struct pipe_context * pipe, int device_to_host,65struct compute_memory_item* chunk, void* data,66int offset_in_chunk, int size);6768/**69* Creates a new pool.70*/71struct compute_memory_pool* compute_memory_pool_new(72struct r600_screen * rscreen)73{74struct compute_memory_pool* pool = (struct compute_memory_pool*)75CALLOC(sizeof(struct compute_memory_pool), 1);76if (!pool)77return NULL;7879COMPUTE_DBG(rscreen, "* compute_memory_pool_new()\n");8081pool->screen = rscreen;82pool->item_list = (struct list_head *)83CALLOC(sizeof(struct list_head), 1);84pool->unallocated_list = (struct list_head *)85CALLOC(sizeof(struct list_head), 1);86list_inithead(pool->item_list);87list_inithead(pool->unallocated_list);88return pool;89}9091/**92* Initializes the pool with a size of \a initial_size_in_dw.93* \param pool The pool to be initialized.94* \param initial_size_in_dw The initial size.95* \see compute_memory_grow_defrag_pool96*/97static void compute_memory_pool_init(struct compute_memory_pool * pool,98unsigned initial_size_in_dw)99{100101COMPUTE_DBG(pool->screen, "* compute_memory_pool_init() initial_size_in_dw = %u\n",102initial_size_in_dw);103104pool->size_in_dw = initial_size_in_dw;105pool->bo = r600_compute_buffer_alloc_vram(pool->screen,106pool->size_in_dw * 4);107}108109/**110* Frees all stuff in the pool and the pool struct itself too.111*/112void compute_memory_pool_delete(struct compute_memory_pool* pool)113{114COMPUTE_DBG(pool->screen, "* compute_memory_pool_delete()\n");115free(pool->shadow);116r600_resource_reference(&pool->bo, NULL);117/* In theory, all of the items were freed in compute_memory_free.118* Just delete the list heads119*/120free(pool->item_list);121free(pool->unallocated_list);122/* And then the pool itself */123free(pool);124}125126/**127* Reallocates and defragments the pool, conserves data.128* \returns -1 if it fails, 0 otherwise129* \see compute_memory_finalize_pending130*/131static int compute_memory_grow_defrag_pool(struct compute_memory_pool *pool,132struct pipe_context *pipe, int new_size_in_dw)133{134new_size_in_dw = align(new_size_in_dw, ITEM_ALIGNMENT);135136COMPUTE_DBG(pool->screen, "* compute_memory_grow_defrag_pool() "137"new_size_in_dw = %d (%d bytes)\n",138new_size_in_dw, new_size_in_dw * 4);139140assert(new_size_in_dw >= pool->size_in_dw);141142if (!pool->bo) {143compute_memory_pool_init(pool, MAX2(new_size_in_dw, 1024 * 16));144} else {145struct r600_resource *temp = NULL;146147temp = r600_compute_buffer_alloc_vram(pool->screen, new_size_in_dw * 4);148149if (temp != NULL) {150struct pipe_resource *src = (struct pipe_resource *)pool->bo;151struct pipe_resource *dst = (struct pipe_resource *)temp;152153COMPUTE_DBG(pool->screen, " Growing and defragmenting the pool "154"using a temporary resource\n");155156compute_memory_defrag(pool, src, dst, pipe);157158/* Release the old buffer */159r600_resource_reference(&pool->bo, NULL);160pool->bo = temp;161pool->size_in_dw = new_size_in_dw;162}163else {164COMPUTE_DBG(pool->screen, " The creation of the temporary resource failed\n"165" Falling back to using 'shadow'\n");166167compute_memory_shadow(pool, pipe, 1);168pool->shadow = realloc(pool->shadow, new_size_in_dw * 4);169if (pool->shadow == NULL)170return -1;171172pool->size_in_dw = new_size_in_dw;173/* Release the old buffer */174r600_resource_reference(&pool->bo, NULL);175pool->bo = r600_compute_buffer_alloc_vram(pool->screen, pool->size_in_dw * 4);176compute_memory_shadow(pool, pipe, 0);177178if (pool->status & POOL_FRAGMENTED) {179struct pipe_resource *src = (struct pipe_resource *)pool->bo;180compute_memory_defrag(pool, src, src, pipe);181}182}183}184185return 0;186}187188/**189* Copy pool from device to host, or host to device.190* \param device_to_host 1 for device->host, 0 for host->device191* \see compute_memory_grow_defrag_pool192*/193static void compute_memory_shadow(struct compute_memory_pool* pool,194struct pipe_context * pipe, int device_to_host)195{196struct compute_memory_item chunk;197198COMPUTE_DBG(pool->screen, "* compute_memory_shadow() device_to_host = %d\n",199device_to_host);200201chunk.id = 0;202chunk.start_in_dw = 0;203chunk.size_in_dw = pool->size_in_dw;204compute_memory_transfer(pool, pipe, device_to_host, &chunk,205pool->shadow, 0, pool->size_in_dw*4);206}207208/**209* Moves all the items marked for promotion from the \a unallocated_list210* to the \a item_list.211* \return -1 if it fails, 0 otherwise212* \see evergreen_set_global_binding213*/214int compute_memory_finalize_pending(struct compute_memory_pool* pool,215struct pipe_context * pipe)216{217struct compute_memory_item *item, *next;218219int64_t allocated = 0;220int64_t unallocated = 0;221int64_t last_pos;222223int err = 0;224225COMPUTE_DBG(pool->screen, "* compute_memory_finalize_pending()\n");226227LIST_FOR_EACH_ENTRY(item, pool->item_list, link) {228COMPUTE_DBG(pool->screen, " + list: offset = %"PRIi64" id = %"PRIi64" size = %"PRIi64" "229"(%"PRIi64" bytes)\n", item->start_in_dw, item->id,230item->size_in_dw, item->size_in_dw * 4);231}232233/* Calculate the total allocated size */234LIST_FOR_EACH_ENTRY(item, pool->item_list, link) {235allocated += align(item->size_in_dw, ITEM_ALIGNMENT);236}237238/* Calculate the total unallocated size of the items that239* will be promoted to the pool */240LIST_FOR_EACH_ENTRY(item, pool->unallocated_list, link) {241if (item->status & ITEM_FOR_PROMOTING)242unallocated += align(item->size_in_dw, ITEM_ALIGNMENT);243}244245if (unallocated == 0) {246return 0;247}248249if (pool->size_in_dw < allocated + unallocated) {250err = compute_memory_grow_defrag_pool(pool, pipe, allocated + unallocated);251if (err == -1)252return -1;253}254else if (pool->status & POOL_FRAGMENTED) {255struct pipe_resource *src = (struct pipe_resource *)pool->bo;256compute_memory_defrag(pool, src, src, pipe);257}258259/* After defragmenting the pool, allocated is equal to the first available260* position for new items in the pool */261last_pos = allocated;262263/* Loop through all the unallocated items, check if they are marked264* for promoting, allocate space for them and add them to the item_list. */265LIST_FOR_EACH_ENTRY_SAFE(item, next, pool->unallocated_list, link) {266if (item->status & ITEM_FOR_PROMOTING) {267err = compute_memory_promote_item(pool, item, pipe, last_pos);268item->status &= ~ITEM_FOR_PROMOTING;269270last_pos += align(item->size_in_dw, ITEM_ALIGNMENT);271272if (err == -1)273return -1;274}275}276277return 0;278}279280/**281* Defragments the pool, so that there's no gap between items.282* \param pool The pool to be defragmented283* \param src The origin resource284* \param dst The destination resource285* \see compute_memory_grow_defrag_pool and compute_memory_finalize_pending286*/287static void compute_memory_defrag(struct compute_memory_pool *pool,288struct pipe_resource *src, struct pipe_resource *dst,289struct pipe_context *pipe)290{291struct compute_memory_item *item;292int64_t last_pos;293294COMPUTE_DBG(pool->screen, "* compute_memory_defrag()\n");295296last_pos = 0;297LIST_FOR_EACH_ENTRY(item, pool->item_list, link) {298if (src != dst || item->start_in_dw != last_pos) {299assert(last_pos <= item->start_in_dw);300301compute_memory_move_item(pool, src, dst,302item, last_pos, pipe);303}304305last_pos += align(item->size_in_dw, ITEM_ALIGNMENT);306}307308pool->status &= ~POOL_FRAGMENTED;309}310311/**312* Moves an item from the \a unallocated_list to the \a item_list.313* \param item The item that will be promoted.314* \return -1 if it fails, 0 otherwise315* \see compute_memory_finalize_pending316*/317static int compute_memory_promote_item(struct compute_memory_pool *pool,318struct compute_memory_item *item, struct pipe_context *pipe,319int64_t start_in_dw)320{321struct pipe_screen *screen = (struct pipe_screen *)pool->screen;322struct r600_context *rctx = (struct r600_context *)pipe;323struct pipe_resource *src = (struct pipe_resource *)item->real_buffer;324struct pipe_resource *dst = (struct pipe_resource *)pool->bo;325struct pipe_box box;326327COMPUTE_DBG(pool->screen, "* compute_memory_promote_item()\n"328" + Promoting Item: %"PRIi64" , starting at: %"PRIi64" (%"PRIi64" bytes) "329"size: %"PRIi64" (%"PRIi64" bytes)\n\t\t\tnew start: %"PRIi64" (%"PRIi64" bytes)\n",330item->id, item->start_in_dw, item->start_in_dw * 4,331item->size_in_dw, item->size_in_dw * 4,332start_in_dw, start_in_dw * 4);333334/* Remove the item from the unallocated list */335list_del(&item->link);336337/* Add it back to the item_list */338list_addtail(&item->link, pool->item_list);339item->start_in_dw = start_in_dw;340341if (src) {342u_box_1d(0, item->size_in_dw * 4, &box);343344rctx->b.b.resource_copy_region(pipe,345dst, 0, item->start_in_dw * 4, 0 ,0,346src, 0, &box);347348/* We check if the item is mapped for reading.349* In this case, we need to keep the temporary buffer 'alive'350* because it is possible to keep a map active for reading351* while a kernel (that reads from it) executes */352if (!(item->status & ITEM_MAPPED_FOR_READING)) {353pool->screen->b.b.resource_destroy(screen, src);354item->real_buffer = NULL;355}356}357358return 0;359}360361/**362* Moves an item from the \a item_list to the \a unallocated_list.363* \param item The item that will be demoted364* \see r600_compute_global_transfer_map365*/366void compute_memory_demote_item(struct compute_memory_pool *pool,367struct compute_memory_item *item, struct pipe_context *pipe)368{369struct r600_context *rctx = (struct r600_context *)pipe;370struct pipe_resource *src = (struct pipe_resource *)pool->bo;371struct pipe_resource *dst;372struct pipe_box box;373374COMPUTE_DBG(pool->screen, "* compute_memory_demote_item()\n"375" + Demoting Item: %"PRIi64", starting at: %"PRIi64" (%"PRIi64" bytes) "376"size: %"PRIi64" (%"PRIi64" bytes)\n", item->id, item->start_in_dw,377item->start_in_dw * 4, item->size_in_dw, item->size_in_dw * 4);378379/* First, we remove the item from the item_list */380list_del(&item->link);381382/* Now we add it to the unallocated list */383list_addtail(&item->link, pool->unallocated_list);384385/* We check if the intermediate buffer exists, and if it386* doesn't, we create it again */387if (item->real_buffer == NULL) {388item->real_buffer = r600_compute_buffer_alloc_vram(389pool->screen, item->size_in_dw * 4);390}391392dst = (struct pipe_resource *)item->real_buffer;393394/* We transfer the memory from the item in the pool to the395* temporary buffer */396u_box_1d(item->start_in_dw * 4, item->size_in_dw * 4, &box);397398rctx->b.b.resource_copy_region(pipe,399dst, 0, 0, 0, 0,400src, 0, &box);401402/* Remember to mark the buffer as 'pending' by setting start_in_dw to -1 */403item->start_in_dw = -1;404405if (item->link.next != pool->item_list) {406pool->status |= POOL_FRAGMENTED;407}408}409410/**411* Moves the item \a item forward from the resource \a src to the412* resource \a dst at \a new_start_in_dw413*414* This function assumes two things:415* 1) The item is \b only moved forward, unless src is different from dst416* 2) The item \b won't change it's position inside the \a item_list417*418* \param item The item that will be moved419* \param new_start_in_dw The new position of the item in \a item_list420* \see compute_memory_defrag421*/422static void compute_memory_move_item(struct compute_memory_pool *pool,423struct pipe_resource *src, struct pipe_resource *dst,424struct compute_memory_item *item, uint64_t new_start_in_dw,425struct pipe_context *pipe)426{427struct pipe_screen *screen = (struct pipe_screen *)pool->screen;428struct r600_context *rctx = (struct r600_context *)pipe;429struct pipe_box box;430431COMPUTE_DBG(pool->screen, "* compute_memory_move_item()\n"432" + Moving item %"PRIi64" from %"PRIi64" (%"PRIi64" bytes) to %"PRIu64" (%"PRIu64" bytes)\n",433item->id, item->start_in_dw, item->start_in_dw * 4,434new_start_in_dw, new_start_in_dw * 4);435436if (pool->item_list != item->link.prev) {437ASSERTED struct compute_memory_item *prev;438prev = container_of(item->link.prev, struct compute_memory_item, link);439assert(prev->start_in_dw + prev->size_in_dw <= new_start_in_dw);440}441442u_box_1d(item->start_in_dw * 4, item->size_in_dw * 4, &box);443444/* If the ranges don't overlap, or we are copying from one resource445* to another, we can just copy the item directly */446if (src != dst || new_start_in_dw + item->size_in_dw <= item->start_in_dw) {447448rctx->b.b.resource_copy_region(pipe,449dst, 0, new_start_in_dw * 4, 0, 0,450src, 0, &box);451} else {452/* The ranges overlap, we will try first to use an intermediate453* resource to move the item */454struct pipe_resource *tmp = (struct pipe_resource *)455r600_compute_buffer_alloc_vram(pool->screen, item->size_in_dw * 4);456457if (tmp != NULL) {458rctx->b.b.resource_copy_region(pipe,459tmp, 0, 0, 0, 0,460src, 0, &box);461462box.x = 0;463464rctx->b.b.resource_copy_region(pipe,465dst, 0, new_start_in_dw * 4, 0, 0,466tmp, 0, &box);467468pool->screen->b.b.resource_destroy(screen, tmp);469470} else {471/* The allocation of the temporary resource failed,472* falling back to use mappings */473uint32_t *map;474int64_t offset;475struct pipe_transfer *trans;476477offset = item->start_in_dw - new_start_in_dw;478479u_box_1d(new_start_in_dw * 4, (offset + item->size_in_dw) * 4, &box);480481map = pipe->buffer_map(pipe, src, 0, PIPE_MAP_READ_WRITE,482&box, &trans);483484assert(map);485assert(trans);486487memmove(map, map + offset, item->size_in_dw * 4);488489pipe->buffer_unmap(pipe, trans);490}491}492493item->start_in_dw = new_start_in_dw;494}495496/**497* Frees the memory associated to the item with id \a id from the pool.498* \param id The id of the item to be freed.499*/500void compute_memory_free(struct compute_memory_pool* pool, int64_t id)501{502struct compute_memory_item *item, *next;503struct pipe_screen *screen = (struct pipe_screen *)pool->screen;504struct pipe_resource *res;505506COMPUTE_DBG(pool->screen, "* compute_memory_free() id + %"PRIi64" \n", id);507508LIST_FOR_EACH_ENTRY_SAFE(item, next, pool->item_list, link) {509510if (item->id == id) {511512if (item->link.next != pool->item_list) {513pool->status |= POOL_FRAGMENTED;514}515516list_del(&item->link);517518if (item->real_buffer) {519res = (struct pipe_resource *)item->real_buffer;520pool->screen->b.b.resource_destroy(521screen, res);522}523524free(item);525526return;527}528}529530LIST_FOR_EACH_ENTRY_SAFE(item, next, pool->unallocated_list, link) {531532if (item->id == id) {533list_del(&item->link);534535if (item->real_buffer) {536res = (struct pipe_resource *)item->real_buffer;537pool->screen->b.b.resource_destroy(538screen, res);539}540541free(item);542543return;544}545}546547fprintf(stderr, "Internal error, invalid id %"PRIi64" "548"for compute_memory_free\n", id);549550assert(0 && "error");551}552553/**554* Creates pending allocations for new items, these items are555* placed in the unallocated_list.556* \param size_in_dw The size, in double words, of the new item.557* \return The new item558* \see r600_compute_global_buffer_create559*/560struct compute_memory_item* compute_memory_alloc(561struct compute_memory_pool* pool,562int64_t size_in_dw)563{564struct compute_memory_item *new_item = NULL;565566COMPUTE_DBG(pool->screen, "* compute_memory_alloc() size_in_dw = %"PRIi64" (%"PRIi64" bytes)\n",567size_in_dw, 4 * size_in_dw);568569new_item = (struct compute_memory_item *)570CALLOC(sizeof(struct compute_memory_item), 1);571if (!new_item)572return NULL;573574new_item->size_in_dw = size_in_dw;575new_item->start_in_dw = -1; /* mark pending */576new_item->id = pool->next_id++;577new_item->pool = pool;578new_item->real_buffer = NULL;579580list_addtail(&new_item->link, pool->unallocated_list);581582COMPUTE_DBG(pool->screen, " + Adding item %p id = %"PRIi64" size = %"PRIi64" (%"PRIi64" bytes)\n",583new_item, new_item->id, new_item->size_in_dw,584new_item->size_in_dw * 4);585return new_item;586}587588/**589* Transfer data host<->device, offset and size is in bytes.590* \param device_to_host 1 for device->host, 0 for host->device.591* \see compute_memory_shadow592*/593static void compute_memory_transfer(594struct compute_memory_pool* pool,595struct pipe_context * pipe,596int device_to_host,597struct compute_memory_item* chunk,598void* data,599int offset_in_chunk,600int size)601{602int64_t aligned_size = pool->size_in_dw;603struct pipe_resource* gart = (struct pipe_resource*)pool->bo;604int64_t internal_offset = chunk->start_in_dw*4 + offset_in_chunk;605606struct pipe_transfer *xfer;607uint32_t *map;608609assert(gart);610611COMPUTE_DBG(pool->screen, "* compute_memory_transfer() device_to_host = %d, "612"offset_in_chunk = %d, size = %d\n", device_to_host,613offset_in_chunk, size);614615if (device_to_host) {616map = pipe->buffer_map(pipe, gart, 0, PIPE_MAP_READ,617&(struct pipe_box) { .width = aligned_size * 4,618.height = 1, .depth = 1 }, &xfer);619assert(xfer);620assert(map);621memcpy(data, map + internal_offset, size);622pipe->buffer_unmap(pipe, xfer);623} else {624map = pipe->buffer_map(pipe, gart, 0, PIPE_MAP_WRITE,625&(struct pipe_box) { .width = aligned_size * 4,626.height = 1, .depth = 1 }, &xfer);627assert(xfer);628assert(map);629memcpy(map + internal_offset, data, size);630pipe->buffer_unmap(pipe, xfer);631}632}633634635