virtgpu_virgl: use blobs NV12 encoder input
Use blob buffers for encoder NV12 input, even when software access is
required. This is helpful for ARCVM, since the v4l2_codec2 stack
sometimes needs to do a format conversion in the guest as the
virtio-video encoder only supports NV12/I420 input.
Supporting this requires knowing the host buffer layout before creating
the blob resource. This is done by creating a temporary resource to
query the layout. To avoid the overhead of querying the host every time
a buffer is created, the allocator process keeps a cache of the most
recently used buffer formats and their host layout.
Creating temporary resources to discover host buffer parameters is a bit
of a cludge. However, since virtgpu_virgl will eventually be deprecated
in favor of virtgpu_cross_domain, a self-contained and simple approach
like this gives some nice performance gains on low end devices.
TEST=decode-edit-encode workflows
TEST=Cts{NativeHardware,Camera,Graphics,Video}TestCases on volteer
BUG=b:203380807, b:232531771
Change-Id: Ibda500862b42680ba898ba689e1600ebe5d258bd
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/minigbm/+/3256451
Auto-Submit: David Stevens <stevensd@chromium.org>
Reviewed-by: Lepton Wu <lepton@chromium.org>
Reviewed-by: Yiwei Zhang <zzyiwei@chromium.org>
Commit-Queue: Yiwei Zhang <zzyiwei@chromium.org>
Tested-by: David Stevens <stevensd@chromium.org>
This commit is contained in:
parent
fc3146f5b7
commit
7eb9e826a7
3 changed files with 229 additions and 22 deletions
|
|
@ -601,3 +601,49 @@ void drv_resolve_format_and_use_flags_helper(struct driver *drv, uint32_t format
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void lru_remove_entry(struct lru_entry *entry) {
|
||||
entry->prev->next = entry->next;
|
||||
entry->next->prev = entry->prev;
|
||||
}
|
||||
|
||||
static void lru_link_entry(struct lru *lru, struct lru_entry *entry) {
|
||||
struct lru_entry *head = &lru->head;
|
||||
entry->prev = head;
|
||||
entry->next = head->next;
|
||||
|
||||
head->next->prev = entry;
|
||||
head->next = entry;
|
||||
}
|
||||
|
||||
struct lru_entry *lru_find(struct lru *lru, bool (*eq)(struct lru_entry *e, void *data), void *data) {
|
||||
struct lru_entry *head = &lru->head;
|
||||
struct lru_entry *cur = head->next;
|
||||
|
||||
while (cur != head) {
|
||||
if (eq(cur, data)) {
|
||||
lru_remove_entry(cur);
|
||||
lru_link_entry(lru, cur);
|
||||
return cur;
|
||||
}
|
||||
cur = cur->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void lru_insert(struct lru *lru, struct lru_entry *entry) {
|
||||
if (lru->count == lru->max) {
|
||||
lru_remove_entry(lru->head.prev);
|
||||
} else {
|
||||
lru->count++;
|
||||
}
|
||||
lru_link_entry(lru, entry);
|
||||
}
|
||||
|
||||
void lru_init(struct lru *lru, int max) {
|
||||
lru->head.next = &lru->head;
|
||||
lru->head.prev = &lru->head;
|
||||
lru->count = 0;
|
||||
lru->max = max;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,4 +48,19 @@ void drv_resolve_format_and_use_flags_helper(struct driver *drv, uint32_t format
|
|||
uint64_t use_flags, uint32_t *out_format,
|
||||
uint64_t *out_use_flags);
|
||||
|
||||
struct lru_entry {
|
||||
struct lru_entry *next;
|
||||
struct lru_entry *prev;
|
||||
};
|
||||
|
||||
struct lru {
|
||||
struct lru_entry head;
|
||||
int count;
|
||||
int max;
|
||||
};
|
||||
|
||||
struct lru_entry *lru_find(struct lru *lru, bool (*eq)(struct lru_entry *e, void *data), void *data);
|
||||
void lru_insert(struct lru *lru, struct lru_entry *entry);
|
||||
void lru_init(struct lru *lru, int max);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
190
virtgpu_virgl.c
190
virtgpu_virgl.c
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
|
@ -54,11 +55,23 @@ static const uint32_t texture_source_formats[] = {
|
|||
|
||||
extern struct virtgpu_param params[];
|
||||
|
||||
struct virgl_blob_metadata_cache {
|
||||
struct lru_entry entry;
|
||||
struct bo_metadata meta;
|
||||
};
|
||||
|
||||
#define lru_entry_to_metadata(entry) ((struct virgl_blob_metadata_cache *)(void*)(entry))
|
||||
|
||||
#define MAX_CACHED_FORMATS 128
|
||||
|
||||
struct virgl_priv {
|
||||
int caps_is_v2;
|
||||
union virgl_caps caps;
|
||||
int host_gbm_enabled;
|
||||
atomic_int next_blob_id;
|
||||
|
||||
pthread_mutex_t host_blob_format_lock;
|
||||
struct lru virgl_blob_metadata_cache;
|
||||
};
|
||||
|
||||
static uint32_t translate_format(uint32_t drm_fourcc)
|
||||
|
|
@ -593,7 +606,12 @@ static int virgl_init(struct driver *drv)
|
|||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
int ret = pthread_mutex_init(&priv->host_blob_format_lock, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
drv->priv = priv;
|
||||
lru_init(&priv->virgl_blob_metadata_cache, MAX_CACHED_FORMATS);
|
||||
|
||||
virgl_init_params_and_caps(drv);
|
||||
|
||||
|
|
@ -678,41 +696,56 @@ static void virgl_close(struct driver *drv)
|
|||
drv->priv = NULL;
|
||||
}
|
||||
|
||||
static int virgl_bo_create_blob(struct driver *drv, struct bo *bo)
|
||||
static uint32_t blob_flags_from_use_flags(uint32_t use_flags)
|
||||
{
|
||||
int ret;
|
||||
uint32_t stride;
|
||||
uint32_t cur_blob_id;
|
||||
uint32_t cmd[VIRGL_PIPE_RES_CREATE_SIZE + 1] = { 0 };
|
||||
struct drm_virtgpu_resource_create_blob drm_rc_blob = { 0 };
|
||||
struct virgl_priv *priv = (struct virgl_priv *)drv->priv;
|
||||
|
||||
uint32_t blob_flags = VIRTGPU_BLOB_FLAG_USE_SHAREABLE;
|
||||
if (bo->meta.use_flags & (BO_USE_SW_MASK | BO_USE_GPU_DATA_BUFFER))
|
||||
if (use_flags & (BO_USE_SW_MASK | BO_USE_GPU_DATA_BUFFER))
|
||||
blob_flags |= VIRTGPU_BLOB_FLAG_USE_MAPPABLE;
|
||||
|
||||
// For now, all blob use cases are cross device. When we add wider
|
||||
// support for blobs, we can revisit making this unconditional.
|
||||
blob_flags |= VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE;
|
||||
|
||||
return blob_flags;
|
||||
}
|
||||
|
||||
static bool virgl_blob_metadata_eq(struct lru_entry *entry, void *data)
|
||||
{
|
||||
struct virgl_blob_metadata_cache *e = lru_entry_to_metadata(entry);
|
||||
struct bo_metadata *meta = data;
|
||||
uint32_t virgl_format1 = translate_format(e->meta.format);
|
||||
uint32_t virgl_format2 = translate_format(meta->format);
|
||||
|
||||
return e->meta.height == meta->height && e->meta.width == meta->width &&
|
||||
e->meta.use_flags == meta->use_flags && virgl_format1 == virgl_format2;
|
||||
}
|
||||
|
||||
static int virgl_blob_do_create(struct driver *drv, uint32_t width, uint32_t height,
|
||||
uint32_t use_flags, uint32_t virgl_format,
|
||||
uint32_t total_size, uint32_t *bo_handle)
|
||||
{
|
||||
int ret;
|
||||
uint32_t cur_blob_id;
|
||||
uint32_t cmd[VIRGL_PIPE_RES_CREATE_SIZE + 1] = { 0 };
|
||||
struct drm_virtgpu_resource_create_blob drm_rc_blob = { 0 };
|
||||
struct virgl_priv *priv = (struct virgl_priv *)drv->priv;
|
||||
uint32_t virgl_bind_flags = compute_virgl_bind_flags(use_flags);
|
||||
uint32_t blob_flags = blob_flags_from_use_flags(use_flags);
|
||||
|
||||
cur_blob_id = atomic_fetch_add(&priv->next_blob_id, 1);
|
||||
stride = drv_stride_from_format(bo->meta.format, bo->meta.width, 0);
|
||||
drv_bo_from_format(bo, stride, bo->meta.height, bo->meta.format);
|
||||
bo->meta.total_size = ALIGN(bo->meta.total_size, PAGE_SIZE);
|
||||
bo->meta.tiling = blob_flags;
|
||||
|
||||
cmd[0] = VIRGL_CMD0(VIRGL_CCMD_PIPE_RESOURCE_CREATE, 0, VIRGL_PIPE_RES_CREATE_SIZE);
|
||||
cmd[VIRGL_PIPE_RES_CREATE_TARGET] = PIPE_TEXTURE_2D;
|
||||
cmd[VIRGL_PIPE_RES_CREATE_WIDTH] = bo->meta.width;
|
||||
cmd[VIRGL_PIPE_RES_CREATE_HEIGHT] = bo->meta.height;
|
||||
cmd[VIRGL_PIPE_RES_CREATE_FORMAT] = translate_format(bo->meta.format);
|
||||
cmd[VIRGL_PIPE_RES_CREATE_BIND] = compute_virgl_bind_flags(bo->meta.use_flags);
|
||||
cmd[VIRGL_PIPE_RES_CREATE_WIDTH] = width;
|
||||
cmd[VIRGL_PIPE_RES_CREATE_HEIGHT] = height;
|
||||
cmd[VIRGL_PIPE_RES_CREATE_FORMAT] = virgl_format;
|
||||
cmd[VIRGL_PIPE_RES_CREATE_BIND] = virgl_bind_flags;
|
||||
cmd[VIRGL_PIPE_RES_CREATE_DEPTH] = 1;
|
||||
cmd[VIRGL_PIPE_RES_CREATE_BLOB_ID] = cur_blob_id;
|
||||
|
||||
drm_rc_blob.cmd = (uint64_t)&cmd;
|
||||
drm_rc_blob.cmd_size = 4 * (VIRGL_PIPE_RES_CREATE_SIZE + 1);
|
||||
drm_rc_blob.size = bo->meta.total_size;
|
||||
drm_rc_blob.size = total_size;
|
||||
drm_rc_blob.blob_mem = VIRTGPU_BLOB_MEM_HOST3D;
|
||||
drm_rc_blob.blob_flags = blob_flags;
|
||||
drm_rc_blob.blob_id = cur_blob_id;
|
||||
|
|
@ -723,8 +756,117 @@ static int virgl_bo_create_blob(struct driver *drv, struct bo *bo)
|
|||
return -errno;
|
||||
}
|
||||
|
||||
*bo_handle = drm_rc_blob.bo_handle;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Queries the host layout for the requested buffer metadata.
|
||||
//
|
||||
// Of particular interest is total_size. This value is passed to the kernel when creating
|
||||
// a buffer via drm_virtgpu_resource_create_blob.size, to specify how much "vram" to
|
||||
// allocate for use when exposing the host buffer to the guest. As such, we need to know
|
||||
// this value before allocating a buffer to ensure that the full host buffer is actually
|
||||
// visible to the guest.
|
||||
//
|
||||
// Note that we can't reuse these test buffers as actual allocations because our guess for
|
||||
// total_size is insufficient if width!=stride or padding!=0.
|
||||
static int virgl_blob_get_host_format(struct driver *drv, struct bo_metadata *meta)
|
||||
{
|
||||
struct virgl_priv *priv = (struct virgl_priv *)drv->priv;
|
||||
int num_planes = drv_num_planes_from_format(meta->format);
|
||||
|
||||
pthread_mutex_lock(&priv->host_blob_format_lock);
|
||||
if (meta->format == DRM_FORMAT_R8) {
|
||||
meta->offsets[0] = 0;
|
||||
meta->sizes[0] = meta->width;
|
||||
meta->strides[0] = meta->width;
|
||||
meta->total_size = meta->width;
|
||||
} else {
|
||||
uint32_t virgl_format = translate_format(meta->format);
|
||||
struct virgl_blob_metadata_cache *entry;
|
||||
|
||||
entry = lru_entry_to_metadata(lru_find(
|
||||
&priv->virgl_blob_metadata_cache, virgl_blob_metadata_eq, meta));
|
||||
|
||||
if (!entry) {
|
||||
uint32_t total_size = 0;
|
||||
for (int i = 0; i < num_planes; i++) {
|
||||
uint32_t stride =
|
||||
drv_stride_from_format(meta->format, meta->width, i);
|
||||
total_size +=
|
||||
drv_size_from_format(meta->format, stride, meta->height, i);
|
||||
}
|
||||
|
||||
uint32_t handle;
|
||||
int ret = virgl_blob_do_create(drv, meta->width, meta->height,
|
||||
meta->use_flags, virgl_format,
|
||||
total_size, &handle);
|
||||
if (ret) {
|
||||
pthread_mutex_unlock(&priv->host_blob_format_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct drm_virtgpu_resource_info_cros info = { 0 };
|
||||
info.bo_handle = handle;
|
||||
info.type = VIRTGPU_RESOURCE_INFO_TYPE_EXTENDED;
|
||||
int info_ret =
|
||||
drmIoctl(drv->fd, DRM_IOCTL_VIRTGPU_RESOURCE_INFO_CROS, &info);
|
||||
|
||||
struct drm_gem_close gem_close = { 0 };
|
||||
gem_close.handle = handle;
|
||||
int close_ret = drmIoctl(drv->fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
|
||||
if (close_ret)
|
||||
drv_loge("DRM_IOCTL_GEM_CLOSE failed (handle=%x) error %d\n",
|
||||
handle, close_ret);
|
||||
|
||||
if (info_ret) {
|
||||
pthread_mutex_unlock(&priv->host_blob_format_lock);
|
||||
drv_loge("Getting resource info failed with %s\n", strerror(errno));
|
||||
return info_ret;
|
||||
}
|
||||
|
||||
entry = calloc(1, sizeof(*entry));
|
||||
entry->meta = *meta;
|
||||
|
||||
for (int i = 0; i < num_planes; i++) {
|
||||
entry->meta.strides[i] = info.strides[i];
|
||||
entry->meta.sizes[i] = info.strides[i] *
|
||||
drv_height_from_format(meta->format, meta->height, i);
|
||||
entry->meta.offsets[i] = info.offsets[i];
|
||||
}
|
||||
entry->meta.total_size = entry->meta.offsets[num_planes - 1] +
|
||||
entry->meta.sizes[num_planes - 1];
|
||||
|
||||
lru_insert(&priv->virgl_blob_metadata_cache, &entry->entry);
|
||||
}
|
||||
|
||||
memcpy(meta->offsets, entry->meta.offsets, sizeof(meta->offsets));
|
||||
memcpy(meta->sizes, entry->meta.sizes, sizeof(meta->sizes));
|
||||
memcpy(meta->strides, entry->meta.strides, sizeof(meta->strides));
|
||||
meta->total_size = entry->meta.total_size;
|
||||
}
|
||||
pthread_mutex_unlock(&priv->host_blob_format_lock);
|
||||
|
||||
meta->total_size = ALIGN(meta->total_size, PAGE_SIZE);
|
||||
meta->tiling = blob_flags_from_use_flags(meta->use_flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int virgl_bo_create_blob(struct driver *drv, struct bo *bo)
|
||||
{
|
||||
int ret;
|
||||
uint32_t virgl_format = translate_format(bo->meta.format);
|
||||
uint32_t bo_handle;
|
||||
|
||||
virgl_blob_get_host_format(drv, &bo->meta);
|
||||
ret = virgl_blob_do_create(drv, bo->meta.width, bo->meta.height, bo->meta.use_flags,
|
||||
virgl_format, bo->meta.total_size, &bo_handle);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (uint32_t plane = 0; plane < bo->meta.num_planes; plane++)
|
||||
bo->handles[plane].u32 = drm_rc_blob.bo_handle;
|
||||
bo->handles[plane].u32 = bo_handle;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -754,9 +896,13 @@ static bool should_use_blob(struct driver *drv, uint32_t format, uint64_t use_fl
|
|||
return true;
|
||||
case DRM_FORMAT_YVU420_ANDROID:
|
||||
case DRM_FORMAT_NV12:
|
||||
// Knowing buffer metadata at buffer creation isn't yet supported, so buffers
|
||||
// can't be properly mapped into the guest.
|
||||
return (use_flags & BO_USE_SW_MASK) == 0;
|
||||
// Zero copy buffers are exposed for guest software access via a persistent
|
||||
// mapping, with no flush/invalidate messages. However, the virtio-video
|
||||
// device relies transfers to/from the host waiting on implicit fences in
|
||||
// the host kernel to synchronize with hardware output. As such, we can only
|
||||
// use zero copy if the guest doesn't need software access or if we're encoder
|
||||
// input.
|
||||
return (use_flags & BO_USE_SW_MASK) == 0 || (use_flags & BO_USE_HW_VIDEO_ENCODER);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue