blob: 2353ad5042e597a9afff21c7754dc6b1bdc4a0c9 [file] [log] [blame]
/*
* Copyright 2018 Chromium.
*
* 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
* on 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 AUTHOR(S) AND/OR THEIR SUPPLIERS 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.
*/
#include "util/u_box.h"
#include "util/u_inlines.h"
#include "virtio-gpu/virgl_protocol.h"
#include "virgl_context.h"
#include "virgl_screen.h"
#include "virgl_encode.h"
#include "virgl_resource.h"
#include "virgl_transfer_queue.h"
struct list_action_args
{
void *data;
struct virgl_transfer *queued;
struct virgl_transfer *current;
};
typedef bool (*compare_transfers_t)(struct virgl_transfer *queued,
struct virgl_transfer *current);
typedef void (*list_action_t)(struct virgl_transfer_queue *queue,
struct list_action_args *args);
struct list_iteration_args
{
void *data;
list_action_t action;
compare_transfers_t compare;
struct virgl_transfer *current;
};
static int
transfer_dim(const struct virgl_transfer *xfer)
{
switch (xfer->base.resource->target) {
case PIPE_BUFFER:
case PIPE_TEXTURE_1D:
return 1;
case PIPE_TEXTURE_2D:
case PIPE_TEXTURE_RECT:
return 2;
default:
return 3;
}
}
static void
box_min_max(const struct pipe_box *box, int dim, int *min, int *max)
{
switch (dim) {
case 0:
if (box->width > 0) {
*min = box->x;
*max = box->x + box->width;
} else {
*max = box->x;
*min = box->x + box->width;
}
break;
case 1:
if (box->height > 0) {
*min = box->y;
*max = box->y + box->height;
} else {
*max = box->y;
*min = box->y + box->height;
}
break;
default:
if (box->depth > 0) {
*min = box->z;
*max = box->z + box->depth;
} else {
*max = box->z;
*min = box->z + box->depth;
}
break;
}
}
static bool
transfer_overlap(const struct virgl_transfer *xfer,
const struct virgl_hw_res *hw_res,
unsigned level,
const struct pipe_box *box,
bool include_touching)
{
const int dim_count = transfer_dim(xfer);
if (xfer->hw_res != hw_res || xfer->base.level != level)
return false;
for (int dim = 0; dim < dim_count; dim++) {
int xfer_min;
int xfer_max;
int box_min;
int box_max;
box_min_max(&xfer->base.box, dim, &xfer_min, &xfer_max);
box_min_max(box, dim, &box_min, &box_max);
if (include_touching) {
/* touching is considered overlapping */
if (xfer_min > box_max || xfer_max < box_min)
return false;
} else {
/* touching is not considered overlapping */
if (xfer_min >= box_max || xfer_max <= box_min)
return false;
}
}
return true;
}
static struct virgl_transfer *
virgl_transfer_queue_find_overlap(const struct virgl_transfer_queue *queue,
const struct virgl_hw_res *hw_res,
unsigned level,
const struct pipe_box *box,
bool include_touching)
{
struct virgl_transfer *xfer;
LIST_FOR_EACH_ENTRY(xfer, &queue->transfer_list, queue_link) {
if (transfer_overlap(xfer, hw_res, level, box, include_touching))
return xfer;
}
return NULL;
}
static bool transfers_intersect(struct virgl_transfer *queued,
struct virgl_transfer *current)
{
return transfer_overlap(queued, current->hw_res, current->base.level,
&current->base.box, true);
}
static void remove_transfer(struct virgl_transfer_queue *queue,
struct virgl_transfer *queued)
{
list_del(&queued->queue_link);
virgl_resource_destroy_transfer(queue->vctx, queued);
}
static void replace_unmapped_transfer(struct virgl_transfer_queue *queue,
struct list_action_args *args)
{
struct virgl_transfer *current = args->current;
struct virgl_transfer *queued = args->queued;
u_box_union_2d(&current->base.box, &current->base.box, &queued->base.box);
current->offset = current->base.box.x;
remove_transfer(queue, queued);
queue->num_dwords -= (VIRGL_TRANSFER3D_SIZE + 1);
}
static void transfer_put(struct virgl_transfer_queue *queue,
struct list_action_args *args)
{
struct virgl_transfer *queued = args->queued;
queue->vs->vws->transfer_put(queue->vs->vws, queued->hw_res,
&queued->base.box,
queued->base.stride, queued->l_stride,
queued->offset, queued->base.level);
remove_transfer(queue, queued);
}
static void transfer_write(struct virgl_transfer_queue *queue,
struct list_action_args *args)
{
struct virgl_transfer *queued = args->queued;
struct virgl_cmd_buf *buf = args->data;
// Takes a reference on the HW resource, which is released after
// the exec buffer command.
virgl_encode_transfer(queue->vs, buf, queued, VIRGL_TRANSFER_TO_HOST);
remove_transfer(queue, queued);
}
static void compare_and_perform_action(struct virgl_transfer_queue *queue,
struct list_iteration_args *iter)
{
struct list_action_args args;
struct virgl_transfer *queued, *tmp;
memset(&args, 0, sizeof(args));
args.current = iter->current;
args.data = iter->data;
LIST_FOR_EACH_ENTRY_SAFE(queued, tmp, &queue->transfer_list, queue_link) {
if (iter->compare(queued, iter->current)) {
args.queued = queued;
iter->action(queue, &args);
}
}
}
static void perform_action(struct virgl_transfer_queue *queue,
struct list_iteration_args *iter)
{
struct list_action_args args;
struct virgl_transfer *queued, *tmp;
memset(&args, 0, sizeof(args));
args.data = iter->data;
LIST_FOR_EACH_ENTRY_SAFE(queued, tmp, &queue->transfer_list, queue_link) {
args.queued = queued;
iter->action(queue, &args);
}
}
static void add_internal(struct virgl_transfer_queue *queue,
struct virgl_transfer *transfer)
{
uint32_t dwords = VIRGL_TRANSFER3D_SIZE + 1;
if (queue->tbuf) {
if (queue->num_dwords + dwords >= VIRGL_MAX_TBUF_DWORDS) {
struct list_iteration_args iter;
struct virgl_winsys *vws = queue->vs->vws;
memset(&iter, 0, sizeof(iter));
iter.action = transfer_write;
iter.data = queue->tbuf;
perform_action(queue, &iter);
vws->submit_cmd(vws, queue->tbuf, NULL);
queue->num_dwords = 0;
}
}
list_addtail(&transfer->queue_link, &queue->transfer_list);
queue->num_dwords += dwords;
}
void virgl_transfer_queue_init(struct virgl_transfer_queue *queue,
struct virgl_context *vctx)
{
struct virgl_screen *vs = virgl_screen(vctx->base.screen);
queue->vs = vs;
queue->vctx = vctx;
queue->num_dwords = 0;
list_inithead(&queue->transfer_list);
if ((vs->caps.caps.v2.capability_bits & VIRGL_CAP_TRANSFER) &&
vs->vws->supports_encoded_transfers)
queue->tbuf = vs->vws->cmd_buf_create(vs->vws, VIRGL_MAX_TBUF_DWORDS);
else
queue->tbuf = NULL;
}
void virgl_transfer_queue_fini(struct virgl_transfer_queue *queue)
{
struct virgl_winsys *vws = queue->vs->vws;
struct list_iteration_args iter;
memset(&iter, 0, sizeof(iter));
iter.action = transfer_put;
perform_action(queue, &iter);
if (queue->tbuf)
vws->cmd_buf_destroy(queue->tbuf);
queue->vs = NULL;
queue->vctx = NULL;
queue->tbuf = NULL;
queue->num_dwords = 0;
}
int virgl_transfer_queue_unmap(struct virgl_transfer_queue *queue,
struct virgl_transfer *transfer)
{
struct list_iteration_args iter;
/* We don't support copy transfers in the transfer queue. */
assert(!transfer->copy_src_hw_res);
/* Attempt to merge multiple intersecting transfers into a single one. */
if (transfer->base.resource->target == PIPE_BUFFER) {
memset(&iter, 0, sizeof(iter));
iter.current = transfer;
iter.compare = transfers_intersect;
iter.action = replace_unmapped_transfer;
compare_and_perform_action(queue, &iter);
}
add_internal(queue, transfer);
return 0;
}
int virgl_transfer_queue_clear(struct virgl_transfer_queue *queue,
struct virgl_cmd_buf *cbuf)
{
struct list_iteration_args iter;
memset(&iter, 0, sizeof(iter));
if (queue->tbuf) {
uint32_t prior_num_dwords = cbuf->cdw;
cbuf->cdw = 0;
iter.action = transfer_write;
iter.data = cbuf;
perform_action(queue, &iter);
virgl_encode_end_transfers(cbuf);
cbuf->cdw = prior_num_dwords;
} else {
iter.action = transfer_put;
perform_action(queue, &iter);
}
queue->num_dwords = 0;
return 0;
}
bool virgl_transfer_queue_is_queued(struct virgl_transfer_queue *queue,
struct virgl_transfer *transfer)
{
return virgl_transfer_queue_find_overlap(queue,
transfer->hw_res,
transfer->base.level,
&transfer->base.box,
false);
}
bool
virgl_transfer_queue_extend_buffer(struct virgl_transfer_queue *queue,
const struct virgl_hw_res *hw_res,
unsigned offset, unsigned size,
const void *data)
{
struct virgl_transfer *queued;
struct pipe_box box;
u_box_1d(offset, size, &box);
queued = virgl_transfer_queue_find_overlap(queue, hw_res, 0, &box, true);
if (!queued)
return false;
assert(queued->base.resource->target == PIPE_BUFFER);
assert(queued->hw_res_map);
memcpy(queued->hw_res_map + offset, data, size);
u_box_union_2d(&queued->base.box, &queued->base.box, &box);
queued->offset = queued->base.box.x;
return true;
}