Path: blob/21.2-virgl/src/freedreno/vulkan/tu_cs.c
4565 views
/*1* Copyright © 2019 Google LLC2*3* Permission is hereby granted, free of charge, to any person obtaining a4* copy of this software and associated documentation files (the "Software"),5* to deal in the Software without restriction, including without limitation6* the rights to use, copy, modify, merge, publish, distribute, sublicense,7* and/or sell copies of the Software, and to permit persons to whom the8* Software is furnished to do so, subject to the following conditions:9*10* The above copyright notice and this permission notice (including the next11* paragraph) shall be included in all copies or substantial portions of the12* Software.13*14* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR15* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,16* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL17* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER18* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING19* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER20* DEALINGS IN THE SOFTWARE.21*/2223#include "tu_cs.h"2425/**26* Initialize a command stream.27*/28void29tu_cs_init(struct tu_cs *cs,30struct tu_device *device,31enum tu_cs_mode mode,32uint32_t initial_size)33{34assert(mode != TU_CS_MODE_EXTERNAL);3536memset(cs, 0, sizeof(*cs));3738cs->device = device;39cs->mode = mode;40cs->next_bo_size = initial_size;41}4243/**44* Initialize a command stream as a wrapper to an external buffer.45*/46void47tu_cs_init_external(struct tu_cs *cs, uint32_t *start, uint32_t *end)48{49memset(cs, 0, sizeof(*cs));5051cs->mode = TU_CS_MODE_EXTERNAL;52cs->start = cs->reserved_end = cs->cur = start;53cs->end = end;54}5556/**57* Finish and release all resources owned by a command stream.58*/59void60tu_cs_finish(struct tu_cs *cs)61{62for (uint32_t i = 0; i < cs->bo_count; ++i) {63tu_bo_finish(cs->device, cs->bos[i]);64free(cs->bos[i]);65}6667free(cs->entries);68free(cs->bos);69}7071/**72* Get the offset of the command packets emitted since the last call to73* tu_cs_add_entry.74*/75static uint32_t76tu_cs_get_offset(const struct tu_cs *cs)77{78assert(cs->bo_count);79return cs->start - (uint32_t *) cs->bos[cs->bo_count - 1]->map;80}8182/*83* Allocate and add a BO to a command stream. Following command packets will84* be emitted to the new BO.85*/86static VkResult87tu_cs_add_bo(struct tu_cs *cs, uint32_t size)88{89/* no BO for TU_CS_MODE_EXTERNAL */90assert(cs->mode != TU_CS_MODE_EXTERNAL);9192/* no dangling command packet */93assert(tu_cs_is_empty(cs));9495/* grow cs->bos if needed */96if (cs->bo_count == cs->bo_capacity) {97uint32_t new_capacity = MAX2(4, 2 * cs->bo_capacity);98struct tu_bo **new_bos =99realloc(cs->bos, new_capacity * sizeof(struct tu_bo *));100if (!new_bos)101return VK_ERROR_OUT_OF_HOST_MEMORY;102103cs->bo_capacity = new_capacity;104cs->bos = new_bos;105}106107struct tu_bo *new_bo = malloc(sizeof(struct tu_bo));108if (!new_bo)109return VK_ERROR_OUT_OF_HOST_MEMORY;110111VkResult result =112tu_bo_init_new(cs->device, new_bo, size * sizeof(uint32_t),113TU_BO_ALLOC_GPU_READ_ONLY | TU_BO_ALLOC_ALLOW_DUMP);114if (result != VK_SUCCESS) {115free(new_bo);116return result;117}118119result = tu_bo_map(cs->device, new_bo);120if (result != VK_SUCCESS) {121tu_bo_finish(cs->device, new_bo);122free(new_bo);123return result;124}125126cs->bos[cs->bo_count++] = new_bo;127128cs->start = cs->cur = cs->reserved_end = (uint32_t *) new_bo->map;129cs->end = cs->start + new_bo->size / sizeof(uint32_t);130131return VK_SUCCESS;132}133134/**135* Reserve an IB entry.136*/137static VkResult138tu_cs_reserve_entry(struct tu_cs *cs)139{140/* entries are only for TU_CS_MODE_GROW */141assert(cs->mode == TU_CS_MODE_GROW);142143/* grow cs->entries if needed */144if (cs->entry_count == cs->entry_capacity) {145uint32_t new_capacity = MAX2(4, cs->entry_capacity * 2);146struct tu_cs_entry *new_entries =147realloc(cs->entries, new_capacity * sizeof(struct tu_cs_entry));148if (!new_entries)149return VK_ERROR_OUT_OF_HOST_MEMORY;150151cs->entry_capacity = new_capacity;152cs->entries = new_entries;153}154155return VK_SUCCESS;156}157158/**159* Add an IB entry for the command packets emitted since the last call to this160* function.161*/162static void163tu_cs_add_entry(struct tu_cs *cs)164{165/* entries are only for TU_CS_MODE_GROW */166assert(cs->mode == TU_CS_MODE_GROW);167168/* disallow empty entry */169assert(!tu_cs_is_empty(cs));170171/*172* because we disallow empty entry, tu_cs_add_bo and tu_cs_reserve_entry173* must both have been called174*/175assert(cs->bo_count);176assert(cs->entry_count < cs->entry_capacity);177178/* add an entry for [cs->start, cs->cur] */179cs->entries[cs->entry_count++] = (struct tu_cs_entry) {180.bo = cs->bos[cs->bo_count - 1],181.size = tu_cs_get_size(cs) * sizeof(uint32_t),182.offset = tu_cs_get_offset(cs) * sizeof(uint32_t),183};184185cs->start = cs->cur;186}187188/**189* same behavior as tu_cs_emit_call but without the indirect190*/191VkResult192tu_cs_add_entries(struct tu_cs *cs, struct tu_cs *target)193{194VkResult result;195196assert(cs->mode == TU_CS_MODE_GROW);197assert(target->mode == TU_CS_MODE_GROW);198199if (!tu_cs_is_empty(cs))200tu_cs_add_entry(cs);201202for (unsigned i = 0; i < target->entry_count; i++) {203result = tu_cs_reserve_entry(cs);204if (result != VK_SUCCESS)205return result;206cs->entries[cs->entry_count++] = target->entries[i];207}208209return VK_SUCCESS;210}211212/**213* Begin (or continue) command packet emission. This does nothing but sanity214* checks currently. \a cs must not be in TU_CS_MODE_SUB_STREAM mode.215*/216void217tu_cs_begin(struct tu_cs *cs)218{219assert(cs->mode != TU_CS_MODE_SUB_STREAM);220assert(tu_cs_is_empty(cs));221}222223/**224* End command packet emission. This adds an IB entry when \a cs is in225* TU_CS_MODE_GROW mode.226*/227void228tu_cs_end(struct tu_cs *cs)229{230assert(cs->mode != TU_CS_MODE_SUB_STREAM);231232if (cs->mode == TU_CS_MODE_GROW && !tu_cs_is_empty(cs))233tu_cs_add_entry(cs);234}235236/**237* Begin command packet emission to a sub-stream. \a cs must be in238* TU_CS_MODE_SUB_STREAM mode.239*240* Return \a sub_cs which is in TU_CS_MODE_EXTERNAL mode. tu_cs_begin and241* tu_cs_reserve_space are implied and \a sub_cs is ready for command packet242* emission.243*/244VkResult245tu_cs_begin_sub_stream(struct tu_cs *cs, uint32_t size, struct tu_cs *sub_cs)246{247assert(cs->mode == TU_CS_MODE_SUB_STREAM);248assert(size);249250VkResult result = tu_cs_reserve_space(cs, size);251if (result != VK_SUCCESS)252return result;253254tu_cs_init_external(sub_cs, cs->cur, cs->reserved_end);255tu_cs_begin(sub_cs);256result = tu_cs_reserve_space(sub_cs, size);257assert(result == VK_SUCCESS);258259return VK_SUCCESS;260}261262/**263* Allocate count*size dwords, aligned to size dwords.264* \a cs must be in TU_CS_MODE_SUB_STREAM mode.265*266*/267VkResult268tu_cs_alloc(struct tu_cs *cs,269uint32_t count,270uint32_t size,271struct tu_cs_memory *memory)272{273assert(cs->mode == TU_CS_MODE_SUB_STREAM);274assert(size && size <= 1024);275276if (!count)277return VK_SUCCESS;278279/* TODO: smarter way to deal with alignment? */280281VkResult result = tu_cs_reserve_space(cs, count * size + (size-1));282if (result != VK_SUCCESS)283return result;284285struct tu_bo *bo = cs->bos[cs->bo_count - 1];286size_t offset = align(tu_cs_get_offset(cs), size);287288memory->map = bo->map + offset * sizeof(uint32_t);289memory->iova = bo->iova + offset * sizeof(uint32_t);290291cs->start = cs->cur = (uint32_t*) bo->map + offset + count * size;292293return VK_SUCCESS;294}295296/**297* End command packet emission to a sub-stream. \a sub_cs becomes invalid298* after this call.299*300* Return an IB entry for the sub-stream. The entry has the same lifetime as301* \a cs.302*/303struct tu_cs_entry304tu_cs_end_sub_stream(struct tu_cs *cs, struct tu_cs *sub_cs)305{306assert(cs->mode == TU_CS_MODE_SUB_STREAM);307assert(cs->bo_count);308assert(sub_cs->start == cs->cur && sub_cs->end == cs->reserved_end);309tu_cs_sanity_check(sub_cs);310311tu_cs_end(sub_cs);312313cs->cur = sub_cs->cur;314315struct tu_cs_entry entry = {316.bo = cs->bos[cs->bo_count - 1],317.size = tu_cs_get_size(cs) * sizeof(uint32_t),318.offset = tu_cs_get_offset(cs) * sizeof(uint32_t),319};320321cs->start = cs->cur;322323return entry;324}325326/**327* Reserve space from a command stream for \a reserved_size uint32_t values.328* This never fails when \a cs has mode TU_CS_MODE_EXTERNAL.329*/330VkResult331tu_cs_reserve_space(struct tu_cs *cs, uint32_t reserved_size)332{333if (tu_cs_get_space(cs) < reserved_size) {334if (cs->mode == TU_CS_MODE_EXTERNAL) {335unreachable("cannot grow external buffer");336return VK_ERROR_OUT_OF_HOST_MEMORY;337}338339/* add an entry for the exiting command packets */340if (!tu_cs_is_empty(cs)) {341/* no direct command packet for TU_CS_MODE_SUB_STREAM */342assert(cs->mode != TU_CS_MODE_SUB_STREAM);343344tu_cs_add_entry(cs);345}346347if (cs->cond_flags) {348/* Subtract one here to account for the DWORD field itself. */349*cs->cond_dwords = cs->cur - cs->cond_dwords - 1;350351/* space for CP_COND_REG_EXEC in next bo */352reserved_size += 3;353}354355/* switch to a new BO */356uint32_t new_size = MAX2(cs->next_bo_size, reserved_size);357VkResult result = tu_cs_add_bo(cs, new_size);358if (result != VK_SUCCESS)359return result;360361/* if inside a condition, emit a new CP_COND_REG_EXEC */362if (cs->cond_flags) {363cs->reserved_end = cs->cur + reserved_size;364365tu_cs_emit_pkt7(cs, CP_COND_REG_EXEC, 2);366tu_cs_emit(cs, cs->cond_flags);367368cs->cond_dwords = cs->cur;369370/* Emit dummy DWORD field here */371tu_cs_emit(cs, CP_COND_REG_EXEC_1_DWORDS(0));372}373374/* double the size for the next bo, also there is an upper375* bound on IB size, which appears to be 0x0fffff376*/377new_size = MIN2(new_size << 1, 0x0fffff);378if (cs->next_bo_size < new_size)379cs->next_bo_size = new_size;380}381382assert(tu_cs_get_space(cs) >= reserved_size);383cs->reserved_end = cs->cur + reserved_size;384385if (cs->mode == TU_CS_MODE_GROW) {386/* reserve an entry for the next call to this function or tu_cs_end */387return tu_cs_reserve_entry(cs);388}389390return VK_SUCCESS;391}392393/**394* Reset a command stream to its initial state. This discards all comand395* packets in \a cs, but does not necessarily release all resources.396*/397void398tu_cs_reset(struct tu_cs *cs)399{400if (cs->mode == TU_CS_MODE_EXTERNAL) {401assert(!cs->bo_count && !cs->entry_count);402cs->reserved_end = cs->cur = cs->start;403return;404}405406for (uint32_t i = 0; i + 1 < cs->bo_count; ++i) {407tu_bo_finish(cs->device, cs->bos[i]);408free(cs->bos[i]);409}410411if (cs->bo_count) {412cs->bos[0] = cs->bos[cs->bo_count - 1];413cs->bo_count = 1;414415cs->start = cs->cur = cs->reserved_end = (uint32_t *) cs->bos[0]->map;416cs->end = cs->start + cs->bos[0]->size / sizeof(uint32_t);417}418419cs->entry_count = 0;420}421422423