summaryrefslogtreecommitdiffstats
path: root/video/out/vo_vaapi.c
diff options
context:
space:
mode:
Diffstat (limited to 'video/out/vo_vaapi.c')
-rw-r--r--video/out/vo_vaapi.c1054
1 files changed, 1054 insertions, 0 deletions
diff --git a/video/out/vo_vaapi.c b/video/out/vo_vaapi.c
new file mode 100644
index 0000000000..48eac61b9b
--- /dev/null
+++ b/video/out/vo_vaapi.c
@@ -0,0 +1,1054 @@
+/*
+ * VA API output module
+ *
+ * Copyright (C) 2008-2009 Splitted-Desktop Systems
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <assert.h>
+#include <stdarg.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <va/va_x11.h>
+
+#include <libavutil/common.h>
+#include <libavcodec/vaapi.h>
+
+#include "config.h"
+#include "mpvcore/mp_msg.h"
+#include "video/out/vo.h"
+#include "video/memcpy_pic.h"
+#include "sub/sub.h"
+#include "sub/img_convert.h"
+#include "x11_common.h"
+
+#include "video/vfcap.h"
+#include "video/mp_image.h"
+#include "video/vaapi.h"
+#include "video/decode/dec_video.h"
+
+#define STR_FOURCC(fcc) \
+ (const char[]){(fcc), (fcc) >> 8u, (fcc) >> 16u, (fcc) >> 24u, 0}
+
+struct vaapi_surface {
+ VASurfaceID id; // VA_INVALID_ID if unallocated
+ int w, h, va_format; // parameters of allocated image (0/0/-1 unallocated)
+ VAImage image; // used for sofwtare decoding case
+ bool is_bound; // image bound to the surface?
+ bool is_used; // referenced by a mp_image
+ bool is_dead; // used, but deallocate VA objects as soon as possible
+ int order; // for LRU allocation
+
+ // convenience shortcut for mp_image deallocation callback
+ struct priv *p;
+};
+
+struct vaapi_osd_image {
+ int w, h;
+ VAImage image;
+ VASubpictureID subpic_id;
+ bool is_used;
+};
+
+struct vaapi_subpic {
+ VASubpictureID id;
+ int src_x, src_y, src_w, src_h;
+ int dst_x, dst_y, dst_w, dst_h;
+};
+
+struct vaapi_osd_part {
+ bool active;
+ int bitmap_pos_id;
+ struct vaapi_osd_image image;
+ struct vaapi_subpic subpic;
+ struct osd_conv_cache *conv_cache;
+};
+
+#define MAX_OUTPUT_SURFACES 2
+
+struct priv {
+ struct mp_log *log;
+ struct vo *vo;
+ VADisplay display;
+ struct mp_vaapi_ctx mpvaapi;
+
+ struct mp_image_params image_params;
+ struct mp_rect src_rect;
+ struct mp_rect dst_rect;
+ struct mp_osd_res screen_osd_res;
+
+ struct mp_image *output_surfaces[MAX_OUTPUT_SURFACES];
+ struct mp_image *swdec_surfaces[MAX_OUTPUT_SURFACES];
+
+ int output_surface;
+ int visible_surface;
+ int deint;
+ int deint_type;
+ int scaling;
+ int force_scaled_osd;
+
+ VAImageFormat osd_format; // corresponds to OSD_VA_FORMAT
+ struct vaapi_osd_part osd_parts[MAX_OSD_PARTS];
+ bool osd_screen;
+
+ int num_video_surfaces;
+ struct vaapi_surface **video_surfaces;
+ int video_surface_lru_counter;
+
+ VAImageFormat *va_image_formats;
+ int va_num_image_formats;
+ VAImageFormat *va_subpic_formats;
+ unsigned int *va_subpic_flags;
+ int va_num_subpic_formats;
+ VADisplayAttribute *va_display_attrs;
+ int va_num_display_attrs;
+};
+
+#define OSD_VA_FORMAT VA_FOURCC_BGRA
+
+static const bool osd_formats[SUBBITMAP_COUNT] = {
+ // Actually BGRA, but only on little endian.
+ // This will break on big endian, I think.
+ [SUBBITMAP_RGBA_STR] = true,
+};
+
+struct fmtentry {
+ uint32_t va;
+ int mp;
+};
+static struct fmtentry va_to_imgfmt[] = {
+ {VA_FOURCC('Y','V','1','2'), IMGFMT_420P},
+ {VA_FOURCC('I','4','2','0'), IMGFMT_420P},
+ {VA_FOURCC('I','Y','U','V'), IMGFMT_420P},
+ {VA_FOURCC('N','V','1','2'), IMGFMT_NV12},
+ // Note: not sure about endian issues (the mp formats are byte-addressed)
+ {VA_FOURCC_RGBA, IMGFMT_RGBA},
+ {VA_FOURCC_BGRA, IMGFMT_BGRA},
+ // Untested.
+ //{VA_FOURCC_UYVY, IMGFMT_UYVY},
+ //{VA_FOURCC_YUY2, IMGFMT_YUYV},
+ {0}
+};
+
+
+static int va_fourcc_to_imgfmt(uint32_t fourcc)
+{
+ for (int n = 0; va_to_imgfmt[n].mp; n++) {
+ if (va_to_imgfmt[n].va == fourcc)
+ return va_to_imgfmt[n].mp;
+ }
+ return 0;
+}
+
+static VAImageFormat *VAImageFormat_from_imgfmt(struct priv *p, int format)
+{
+ for (int i = 0; i < p->va_num_image_formats; i++) {
+ if (va_fourcc_to_imgfmt(p->va_image_formats[i].fourcc) == format)
+ return &p->va_image_formats[i];
+ }
+ return NULL;
+}
+
+static struct vaapi_surface *to_vaapi_surface(struct priv *p,
+ struct mp_image *img)
+{
+ if (!img || !IMGFMT_IS_VAAPI(img->imgfmt))
+ return NULL;
+ // Note: we _could_ use planes[1] or planes[2] to store a vaapi_surface
+ // pointer, but I just don't trust libavcodec enough.
+ VASurfaceID id = (uintptr_t)img->planes[3];
+ for (int n = 0; n < p->num_video_surfaces; n++) {
+ struct vaapi_surface *s = p->video_surfaces[n];
+ if (s->id == id)
+ return s;
+ }
+ return NULL;
+}
+
+static struct vaapi_surface *alloc_vaapi_surface(struct priv *p, int w, int h,
+ int va_format)
+{
+ VAStatus status;
+
+ VASurfaceID id = VA_INVALID_ID;
+ status = vaCreateSurfaces(p->display, w, h, va_format, 1, &id);
+ if (!check_va_status(status, "vaCreateSurfaces()"))
+ return NULL;
+
+ struct vaapi_surface *surface = NULL;
+ for (int n = 0; n < p->num_video_surfaces; n++) {
+ struct vaapi_surface *s = p->video_surfaces[n];
+ if (s->id == VA_INVALID_ID) {
+ surface = s;
+ break;
+ }
+ }
+ if (!surface) {
+ surface = talloc_ptrtype(NULL, surface);
+ MP_TARRAY_APPEND(p, p->video_surfaces, p->num_video_surfaces, surface);
+ }
+
+ *surface = (struct vaapi_surface) {
+ .id = id,
+ .image = { .image_id = VA_INVALID_ID, .buf = VA_INVALID_ID },
+ .w = w,
+ .h = h,
+ .va_format = va_format,
+ .p = p,
+ };
+ return surface;
+}
+
+static void destroy_vaapi_surface(struct priv *p, struct vaapi_surface *s)
+{
+ if (!s || s->id == VA_INVALID_ID)
+ return;
+ assert(!s->is_used);
+
+ if (s->image.image_id != VA_INVALID_ID)
+ vaDestroyImage(p->display, s->image.image_id);
+ vaDestroySurfaces(p->display, &s->id, 1);
+ s->id = VA_INVALID_ID;
+ s->w = 0;
+ s->h = 0;
+ s->va_format = -1;
+}
+
+static struct vaapi_surface *get_vaapi_surface(struct priv *p, int w, int h,
+ int va_format)
+{
+ struct vaapi_surface *best = NULL;
+
+ for (int n = 0; n < p->num_video_surfaces; n++) {
+ struct vaapi_surface *s = p->video_surfaces[n];
+ if (!s->is_used && s->w == w && s->h == h && s->va_format == va_format) {
+ if (!best || best->order > s->order)
+ best = s;
+ }
+ }
+
+ if (!best)
+ best = alloc_vaapi_surface(p, w, h, va_format);
+
+ if (best) {
+ best->is_used = true;
+ best->order = ++p->video_surface_lru_counter;
+ }
+ return best;
+}
+
+static void release_video_surface(void *ptr)
+{
+ struct vaapi_surface *surface = ptr;
+ surface->is_used = false;
+ if (surface->is_dead)
+ destroy_vaapi_surface(surface->p, surface);
+}
+
+static struct mp_image *get_surface(struct mp_vaapi_ctx *ctx, int va_rt_format,
+ int mp_format, int w, int h)
+{
+ assert(IMGFMT_IS_VAAPI(mp_format));
+
+ struct vo *vo = ctx->priv;
+ struct priv *p = vo->priv;
+
+ struct mp_image img = {0};
+ mp_image_setfmt(&img, mp_format);
+ mp_image_set_size(&img, w, h);
+
+ struct vaapi_surface *surface = get_vaapi_surface(p, w, h, va_rt_format);
+ if (!surface)
+ return NULL;
+
+ // libavcodec probably wants it at [0] and [3]
+ // [1] and [2] are possibly free for own use.
+ for (int n = 0; n < 4; n++)
+ img.planes[n] = (void *)(uintptr_t)surface->id;
+
+ return mp_image_new_custom_ref(&img, surface, release_video_surface);
+}
+
+// This should be called only by code that is going to preallocate surfaces
+// (and by uninit). Otherwise, hw decoder init might get confused by
+// accidentally releasing hw decoder preallocated surfaces.
+static void flush_surfaces(struct mp_vaapi_ctx *ctx)
+{
+ struct vo *vo = ctx->priv;
+ struct priv *p = vo->priv;
+
+ for (int n = 0; n < p->num_video_surfaces; n++) {
+ struct vaapi_surface *s = p->video_surfaces[n];
+ if (s->is_used) {
+ s->is_dead = true;
+ } else {
+ destroy_vaapi_surface(p, s);
+ }
+ }
+}
+
+static void flush_output_surfaces(struct priv *p)
+{
+ for (int n = 0; n < MAX_OUTPUT_SURFACES; n++) {
+ talloc_free(p->output_surfaces[n]);
+ p->output_surfaces[n] = NULL;
+ }
+ p->output_surface = 0;
+ p->visible_surface = 0;
+}
+
+// See flush_surfaces() remarks - the same applies.
+static void free_video_specific(struct priv *p)
+{
+ flush_output_surfaces(p);
+
+ for (int n = 0; n < MAX_OUTPUT_SURFACES; n++) {
+ talloc_free(p->swdec_surfaces[n]);
+ p->swdec_surfaces[n] = NULL;
+ }
+
+ flush_surfaces(&p->mpvaapi);
+}
+
+static int alloc_swdec_surfaces(struct priv *p, int w, int h, int format)
+{
+ VAStatus status;
+
+ free_video_specific(p);
+
+ VAImageFormat *image_format = VAImageFormat_from_imgfmt(p, format);
+ if (!image_format)
+ return -1;
+ for (int i = 0; i < MAX_OUTPUT_SURFACES; i++) {
+ // WTF: no mapping from VAImageFormat -> VA_RT_FORMAT_
+ struct mp_image *img =
+ get_surface(&p->mpvaapi, VA_RT_FORMAT_YUV420, IMGFMT_VAAPI, w, h);
+ struct vaapi_surface *s = to_vaapi_surface(p, img);
+ if (!s)
+ return -1;
+
+ if (s->image.image_id != VA_INVALID_ID) {
+ vaDestroyImage(p->display, s->image.image_id);
+ s->image.image_id = VA_INVALID_ID;
+ }
+
+ status = vaDeriveImage(p->display, s->id, &s->image);
+ if (status == VA_STATUS_SUCCESS) {
+ /* vaDeriveImage() is supported, check format */
+ if (s->image.format.fourcc == image_format->fourcc &&
+ s->image.width == w && s->image.height == h)
+ {
+ s->is_bound = true;
+ MP_VERBOSE(p, "Using vaDeriveImage()\n");
+ } else {
+ vaDestroyImage(p->display, s->image.image_id);
+ s->image.image_id = VA_INVALID_ID;
+ status = VA_STATUS_ERROR_OPERATION_FAILED;
+ }
+ }
+ if (status != VA_STATUS_SUCCESS) {
+ status = vaCreateImage(p->display, image_format, w, h, &s->image);
+ if (!check_va_status(status, "vaCreateImage()")) {
+ talloc_free(img);
+ return -1;
+ }
+ }
+ p->swdec_surfaces[i] = img;
+ }
+ return 0;
+}
+
+static void resize(struct priv *p)
+{
+ vo_get_src_dst_rects(p->vo, &p->src_rect, &p->dst_rect, &p->screen_osd_res);
+
+ // It's not clear whether this is needed; maybe not.
+ //vo_x11_clearwindow(p->vo, p->vo->x11->window);
+
+ p->vo->want_redraw = true;
+}
+
+static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
+{
+ struct priv *p = vo->priv;
+
+ vo_x11_config_vo_window(vo, NULL, vo->dx, vo->dy, vo->dwidth, vo->dheight,
+ flags, "vaapi");
+
+ if (!IMGFMT_IS_VAAPI(params->imgfmt)) {
+ if (alloc_swdec_surfaces(p, params->w, params->h, params->imgfmt) < 0)
+ return -1;
+ }
+
+ p->image_params = *params;
+ resize(p);
+ return 0;
+}
+
+static int query_format(struct vo *vo, uint32_t format)
+{
+ struct priv *p = vo->priv;
+
+ if (IMGFMT_IS_VAAPI(format) || VAImageFormat_from_imgfmt(p, format))
+ return VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW;
+
+ return 0;
+}
+
+static inline int get_field_flags(struct priv *p, int i, int flags)
+{
+ return (p->deint && (flags & MP_IMGFIELD_INTERLACED) ?
+ (((!!(flags & MP_IMGFIELD_TOP_FIRST)) ^ i) == 0 ?
+ VA_BOTTOM_FIELD : VA_TOP_FIELD) : VA_FRAME_PICTURE);
+}
+
+static inline int get_colorspace_flags(struct priv *p)
+{
+#if USE_VAAPI_COLORSPACE
+ switch (p->image_params.colorspace) {
+ case MP_CSP_BT_601: return VA_SRC_BT601;
+ case MP_CSP_BT_709: return VA_SRC_BT709;
+ case MP_CSP_SMPTE_240M: return VA_SRC_SMPTE_240;
+ }
+#endif
+ return 0;
+}
+
+static bool render_to_screen(struct priv *p, struct mp_image *mpi)
+{
+ bool res = true;
+ VAStatus status;
+
+ struct vaapi_surface *surface = to_vaapi_surface(p, mpi);
+ if (!surface)
+ return false;
+
+ for (int n = 0; n < MAX_OSD_PARTS; n++) {
+ struct vaapi_osd_part *part = &p->osd_parts[n];
+ if (part->active) {
+ struct vaapi_subpic *sp = &part->subpic;
+ int flags = 0;
+ if (p->osd_screen)
+ flags |= VA_SUBPICTURE_DESTINATION_IS_SCREEN_COORD;
+ status = vaAssociateSubpicture2(p->display,
+ sp->id, &surface->id, 1,
+ sp->src_x, sp->src_y,
+ sp->src_w, sp->src_h,
+ sp->dst_x, sp->dst_y,
+ sp->dst_w, sp->dst_h,
+ flags);
+ check_va_status(status, "vaAssociateSubpicture()");
+ }
+ }
+
+ for (int i = 0; i <= !!(p->deint > 1); i++) {
+ unsigned int flags = (get_field_flags(p, i, mpi->fields) |
+ get_colorspace_flags(p) |
+ p->scaling);
+ status = vaPutSurface(p->display,
+ surface->id,
+ p->vo->x11->window,
+ p->src_rect.x0,
+ p->src_rect.y0,
+ p->src_rect.x1 - p->src_rect.x0,
+ p->src_rect.y1 - p->src_rect.y0,
+ p->dst_rect.x0,
+ p->dst_rect.y0,
+ p->dst_rect.x1 - p->dst_rect.x0,
+ p->dst_rect.y1 - p->dst_rect.y0,
+ NULL, 0,
+ flags);
+ if (!check_va_status(status, "vaPutSurface()"))
+ res = false;
+ }
+
+ for (int n = 0; n < MAX_OSD_PARTS; n++) {
+ struct vaapi_osd_part *part = &p->osd_parts[n];
+ if (part->active) {
+ struct vaapi_subpic *sp = &part->subpic;
+ status = vaDeassociateSubpicture(p->display, sp->id,
+ &surface->id, 1);
+ check_va_status(status, "vaDeassociateSubpicture()");
+ }
+ }
+
+ return res;
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+
+ p->visible_surface = p->output_surface;
+ render_to_screen(p, p->output_surfaces[p->output_surface]);
+ p->output_surface = (p->output_surface + 1) % MAX_OUTPUT_SURFACES;
+}
+
+static int map_image(struct priv *p, VAImage *va_image, int mpfmt,
+ struct mp_image *dst)
+{
+ VAStatus status;
+
+ if (mpfmt != va_fourcc_to_imgfmt(va_image->format.fourcc))
+ return -1;
+
+ void *image_data = NULL;
+ status = vaMapBuffer(p->display, va_image->buf, &image_data);
+ if (!check_va_status(status, "vaMapBuffer()"))
+ return -1;
+
+ *dst = (struct mp_image) {0};
+ mp_image_setfmt(dst, mpfmt);
+ mp_image_set_size(dst, va_image->width, va_image->height);
+
+ for (int p = 0; p < va_image->num_planes; p++) {
+ dst->stride[p] = va_image->pitches[p];
+ dst->planes[p] = (uint8_t *)image_data + va_image->offsets[p];
+ }
+
+ if (va_image->format.fourcc == VA_FOURCC('Y','V','1','2')) {
+ FFSWAP(unsigned int, dst->stride[1], dst->stride[2]);
+ FFSWAP(uint8_t *, dst->planes[1], dst->planes[2]);
+ }
+
+ return 0;
+}
+
+static int unmap_image(struct priv *p, VAImage *va_image)
+{
+ VAStatus status;
+
+ status = vaUnmapBuffer(p->display, va_image->buf);
+ return check_va_status(status, "vaUnmapBuffer()") ? 0 : -1;
+}
+
+static int upload_surface(struct priv *p, struct vaapi_surface *va_surface,
+ struct mp_image *mpi)
+{
+ VAStatus status;
+
+ if (va_surface->image.image_id == VA_INVALID_ID)
+ return -1;
+
+ struct mp_image img;
+ if (map_image(p, &va_surface->image, mpi->imgfmt, &img) < 0)
+ return -1;
+ mp_image_copy(&img, mpi);
+ unmap_image(p, &va_surface->image);
+
+ if (!va_surface->is_bound) {
+ status = vaPutImage2(p->display, va_surface->id,
+ va_surface->image.image_id,
+ 0, 0, mpi->w, mpi->h,
+ 0, 0, mpi->w, mpi->h);
+ if (!check_va_status(status, "vaPutImage()"))
+ return -1;
+ }
+
+ return 0;
+}
+
+static int try_get_surface(struct priv *p, VAImageFormat *fmt,
+ struct vaapi_surface *va_surface,
+ VAImage *out_image)
+{
+ VAStatus status;
+
+ status = vaSyncSurface(p->display, va_surface->id);
+ if (!check_va_status(status, "vaSyncSurface()"))
+ return -2;
+
+ int w = va_surface->w;
+ int h = va_surface->h;
+
+ status = vaCreateImage(p->display, fmt, w, h, out_image);
+ if (!check_va_status(status, "vaCreateImage()"))
+ return -2;
+
+ status = vaGetImage(p->display, va_surface->id, 0, 0, w, h,
+ out_image->image_id);
+ if (status != VA_STATUS_SUCCESS) {
+ vaDestroyImage(p->display, out_image->image_id);
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct mp_image *download_surface(struct priv *p,
+ struct vaapi_surface *va_surface)
+{
+ // We have no clue which format will work, so try them all.
+ // This code is just for screenshots, so it's ok not to cache the right
+ // format (to prevent unnecessary work), and we don't attempt to use
+ // vaDeriveImage() for direct access either.
+ for (int i = 0; i < p->va_num_image_formats; i++) {
+ VAImageFormat *fmt = &p->va_image_formats[i];
+ int mpfmt = va_fourcc_to_imgfmt(fmt->fourcc);
+ if (!mpfmt)
+ continue;
+ VAImage image;
+ int r = try_get_surface(p, fmt, va_surface, &image);
+ if (r == -1)
+ continue;
+ if (r < 0)
+ return NULL;
+
+ struct mp_image *res = NULL;
+ struct mp_image tmp;
+ if (map_image(p, &image, mpfmt, &tmp) >= 0) {
+ res = mp_image_alloc(mpfmt, tmp.w, tmp.h);
+ mp_image_copy(res, &tmp);
+ unmap_image(p, &image);
+ }
+ vaDestroyImage(p->display, image.image_id);
+ return res;
+ }
+
+ MP_ERR(p, "failed to get surface data.\n");
+ return NULL;
+}
+
+static void draw_image(struct vo *vo, mp_image_t *mpi)
+{
+ struct priv *p = vo->priv;
+
+ if (!IMGFMT_IS_VAAPI(mpi->imgfmt)) {
+ struct mp_image *surface = p->swdec_surfaces[p->output_surface];
+ struct vaapi_surface *va_surface = to_vaapi_surface(p, surface);
+ if (!va_surface)
+ return;
+ if (upload_surface(p, va_surface, mpi) < 0)
+ return;
+ mp_image_copy_attributes(surface, mpi);
+ mpi = surface;
+ }
+
+ mp_image_setrefp(&p->output_surfaces[p->output_surface], mpi);
+}
+
+static struct mp_image *get_screenshot(struct priv *p)
+{
+ struct vaapi_surface *va_surface =
+ to_vaapi_surface(p, p->output_surfaces[p->visible_surface]);
+ if (!va_surface)
+ return NULL;
+ struct mp_image *img = download_surface(p, va_surface);
+ if (!img)
+ return NULL;
+ struct mp_image_params params = p->image_params;
+ params.imgfmt = img->imgfmt;
+ mp_image_set_params(img, &params);
+ return img;
+}
+
+static bool redraw_frame(struct priv *p)
+{
+ p->output_surface = p->visible_surface;
+ return render_to_screen(p, p->output_surfaces[p->output_surface]);
+}
+
+static void free_subpicture(struct priv *p, struct vaapi_osd_image *img)
+{
+ if (img->image.image_id != VA_INVALID_ID)
+ vaDestroyImage(p->display, img->image.image_id);
+ if (img->subpic_id != VA_INVALID_ID)
+ vaDestroySubpicture(p->display, img->subpic_id);
+ img->image.image_id = VA_INVALID_ID;
+ img->subpic_id = VA_INVALID_ID;
+}
+
+static int new_subpicture(struct priv *p, int w, int h,
+ struct vaapi_osd_image *out)
+{
+ VAStatus status;
+
+ free_subpicture(p, out);
+
+ struct vaapi_osd_image m = {
+ .image = {.image_id = VA_INVALID_ID, .buf = VA_INVALID_ID},
+ .subpic_id = VA_INVALID_ID,
+ .w = w,
+ .h = h,
+ };
+
+ status = vaCreateImage(p->display, &p->osd_format, w, h, &m.image);
+ if (!check_va_status(status, "vaCreateImage()"))
+ goto error;
+ status = vaCreateSubpicture(p->display, m.image.image_id, &m.subpic_id);
+ if (!check_va_status(status, "vaCreateSubpicture()"))
+ goto error;
+
+ *out = m;
+ return 0;
+
+error:
+ free_subpicture(p, &m);
+ MP_ERR(p, "failed to allocate OSD sub-picture of size %dx%d.\n", w, h);
+ return -1;
+}
+
+static void draw_osd_cb(void *pctx, struct sub_bitmaps *imgs)
+{
+ struct priv *p = pctx;
+
+ struct vaapi_osd_part *part = &p->osd_parts[imgs->render_index];
+ if (imgs->bitmap_pos_id != part->bitmap_pos_id) {
+ part->bitmap_pos_id = imgs->bitmap_pos_id;
+
+ osd_scale_rgba(part->conv_cache, imgs);
+
+ struct mp_rect bb;
+ if (!mp_sub_bitmaps_bb(imgs, &bb))
+ goto error;
+
+ // Prevent filtering artifacts on borders
+ int pad = 2;
+
+ int w = bb.x1 - bb.x0;
+ int h = bb.y1 - bb.y0;
+ if (part->image.w < w + pad || part->image.h < h + pad) {
+ int sw = MP_ALIGN_UP(w + pad, 64);
+ int sh = MP_ALIGN_UP(h + pad, 64);
+ if (new_subpicture(p, sw, sh, &part->image) < 0)
+ goto error;
+ }
+
+ struct vaapi_osd_image *img = &part->image;
+ struct mp_image vaimg;
+ if (map_image(p, &img->image, IMGFMT_BGRA, &vaimg) < 0)
+ goto error;
+
+ // Clear borders and regions uncovered by sub-bitmaps
+ mp_image_clear(&vaimg, 0, 0, w + pad, h + pad);
+
+ for (int n = 0; n < imgs->num_parts; n++) {
+ struct sub_bitmap *sub = &imgs->parts[n];
+
+ // Note: nothing guarantees that the sub-bitmaps don't overlap.
+ // But in all currently existing cases, they don't.
+ // We simply hope that this won't change, and nobody will
+ // ever notice our little shortcut here.
+
+ size_t dst = (sub->y - bb.y0) * vaimg.stride[0] +
+ (sub->x - bb.x0) * 4;
+
+ memcpy_pic(vaimg.planes[0] + dst, sub->bitmap, sub->w * 4, sub->h,
+ vaimg.stride[0], sub->stride);
+ }
+
+ if (unmap_image(p, &img->image) < 0)
+ goto error;
+
+ part->subpic = (struct vaapi_subpic) {
+ .id = img->subpic_id,
+ .src_x = 0, .src_y = 0,
+ .src_w = w, .src_h = h,
+ .dst_x = bb.x0, .dst_y = bb.y0,
+ .dst_w = w, .dst_h = h,
+ };
+ }
+
+ part->active = true;
+
+error:
+ ;
+}
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+ struct priv *p = vo->priv;
+
+ if (!p->osd_format.fourcc)
+ return;
+
+ struct mp_osd_res vid_res = {
+ .w = p->image_params.w,
+ .h = p->image_params.h,
+ .display_par = 1.0 / vo->aspdat.par,
+ .video_par = vo->aspdat.par,
+ };
+
+ struct mp_osd_res *res;
+ if (p->osd_screen) {
+ res = &p->screen_osd_res;
+ } else {
+ res = &vid_res;
+ }
+
+ for (int n = 0; n < MAX_OSD_PARTS; n++)
+ p->osd_parts[n].active = false;
+ osd_draw(osd, *res, osd->vo_pts, 0, osd_formats, draw_osd_cb, p);
+}
+
+static int get_displayattribtype(const char *name)
+{
+ if (!strcasecmp(name, "brightness"))
+ return VADisplayAttribBrightness;
+ else if (!strcasecmp(name, "contrast"))
+ return VADisplayAttribContrast;
+ else if (!strcasecmp(name, "saturation"))
+ return VADisplayAttribSaturation;
+ else if (!strcasecmp(name, "hue"))
+ return VADisplayAttribHue;
+ return -1;
+}
+
+static VADisplayAttribute *get_display_attribute(struct priv *p,
+ const char *name)
+{
+ int type = get_displayattribtype(name);
+ for (int n = 0; n < p->va_num_display_attrs; n++) {
+ VADisplayAttribute *attr = &p->va_display_attrs[n];
+ if (attr->type == type)
+ return attr;
+ }
+ return NULL;
+}
+
+static int get_equalizer(struct priv *p, const char *name, int *value)
+{
+ VADisplayAttribute * const attr = get_display_attribute(p, name);
+
+ if (!attr || !(attr->flags & VA_DISPLAY_ATTRIB_GETTABLE))
+ return VO_NOTIMPL;
+
+ /* normalize to -100 .. 100 range */
+ int r = attr->max_value - attr->min_value;
+ if (r == 0)
+ return VO_NOTIMPL;
+ *value = ((attr->value - attr->min_value) * 200) / r - 100;
+ return VO_TRUE;
+}
+
+static int set_equalizer(struct priv *p, const char *name, int value)
+{
+ VADisplayAttribute * const attr = get_display_attribute(p, name);
+ VAStatus status;
+
+ if (!attr || !(attr->flags & VA_DISPLAY_ATTRIB_SETTABLE))
+ return VO_NOTIMPL;
+
+ /* normalize to attribute value range */
+ int r = attr->max_value - attr->min_value;
+ if (r == 0)
+ return VO_NOTIMPL;
+ attr->value = ((value + 100) * r) / 200 + attr->min_value;
+
+ status = vaSetDisplayAttributes(p->display, attr, 1);
+ if (!check_va_status(status, "vaSetDisplayAttributes()"))
+ return VO_FALSE;
+ return VO_TRUE;
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ struct priv *p = vo->priv;
+
+ switch (request) {
+ case VOCTRL_GET_DEINTERLACE:
+ *(int*)data = !!p->deint;
+ return VO_TRUE;
+ case VOCTRL_SET_DEINTERLACE:
+ p->deint = *(int*)data ? p->deint_type : 0;
+ return VO_TRUE;
+ case VOCTRL_GET_HWDEC_INFO: {
+ struct mp_hwdec_info *arg = data;
+ arg->vaapi_ctx = &p->mpvaapi;
+ return true;
+ }
+ case VOCTRL_SET_EQUALIZER: {
+ struct voctrl_set_equalizer_args *eq = data;
+ return set_equalizer(p, eq->name, eq->value);
+ }
+ case VOCTRL_GET_EQUALIZER: {
+ struct voctrl_get_equalizer_args *eq = data;
+ return get_equalizer(p, eq->name, eq->valueptr);
+ }
+ case VOCTRL_REDRAW_FRAME:
+ return redraw_frame(p);
+ case VOCTRL_SCREENSHOT: {
+ struct voctrl_screenshot_args *args = data;
+ args->out_image = get_screenshot(p);
+ return true;
+ }
+ case VOCTRL_GET_PANSCAN:
+ return VO_TRUE;
+ case VOCTRL_SET_PANSCAN:
+ resize(p);
+ return VO_TRUE;
+ }
+
+ int events = 0;
+ int r = vo_x11_control(vo, &events, request, data);
+ if (events & VO_EVENT_RESIZE)
+ resize(p);
+ if (events & VO_EVENT_EXPOSE)
+ vo->want_redraw = true;
+ return r;
+}
+
+static void uninit(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+
+ free_video_specific(p);
+
+ for (int n = 0; n < p->num_video_surfaces; n++) {
+ struct vaapi_surface *surface = p->video_surfaces[n];
+ // Nothing is allowed to reference HW surfaces past VO lifetime.
+ assert(!surface->is_used);
+ talloc_free(surface);
+ }
+ p->num_video_surfaces = 0;
+
+ for (int n = 0; n < MAX_OSD_PARTS; n++) {
+ struct vaapi_osd_part *part = &p->osd_parts[n];
+ free_subpicture(p, &part->image);
+ }
+
+ if (p->display) {
+ vaTerminate(p->display);
+ p->display = NULL;
+ }
+
+ vo_x11_uninit(vo);
+}
+
+static int preinit(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ p->vo = vo;
+ p->log = vo->log;
+
+ VAStatus status;
+
+ if (!vo_x11_init(vo))
+ return -1;
+
+ p->display = vaGetDisplay(vo->x11->display);
+ if (!p->display)
+ return -1;
+
+ int major_version, minor_version;
+ status = vaInitialize(p->display, &major_version, &minor_version);
+ if (!check_va_status(status, "vaInitialize()"))
+ return -1;
+ MP_VERBOSE(vo, "VA API version %d.%d\n", major_version, minor_version);
+
+ p->mpvaapi.display = p->display;
+ p->mpvaapi.priv = vo;
+ p->mpvaapi.flush = flush_surfaces;
+ p->mpvaapi.get_surface = get_surface;
+
+ int max_image_formats = vaMaxNumImageFormats(p->display);
+ p->va_image_formats = talloc_array(vo, VAImageFormat, max_image_formats);
+ status = vaQueryImageFormats(p->display, p->va_image_formats,
+ &p->va_num_image_formats);
+ if (!check_va_status(status, "vaQueryImageFormats()"))
+ return -1;
+ MP_VERBOSE(vo, "%d image formats available:\n", p->va_num_image_formats);
+ for (int i = 0; i < p->va_num_image_formats; i++)
+ MP_VERBOSE(vo, " %s\n", STR_FOURCC(p->va_image_formats[i].fourcc));
+
+ int max_subpic_formats = vaMaxNumSubpictureFormats(p->display);
+ p->va_subpic_formats = talloc_array(vo, VAImageFormat, max_subpic_formats);
+ p->va_subpic_flags = talloc_array(vo, unsigned int, max_subpic_formats);
+ status = vaQuerySubpictureFormats(p->display,
+ p->va_subpic_formats,
+ p->va_subpic_flags,
+ &p->va_num_subpic_formats);
+ if (!check_va_status(status, "vaQuerySubpictureFormats()"))
+ p->va_num_subpic_formats = 0;
+ MP_VERBOSE(vo, "%d subpicture formats available:\n",
+ p->va_num_subpic_formats);
+
+ for (int i = 0; i < p->va_num_subpic_formats; i++) {
+ MP_VERBOSE(vo, " %s, flags 0x%x\n",
+ STR_FOURCC(p->va_subpic_formats[i].fourcc),
+ p->va_subpic_flags[i]);
+ if (p->va_subpic_formats[i].fourcc == OSD_VA_FORMAT) {
+ p->osd_format = p->va_subpic_formats[i];
+ if (!p->force_scaled_osd) {
+ p->osd_screen =
+ p->va_subpic_flags[i] & VA_SUBPICTURE_DESTINATION_IS_SCREEN_COORD;
+ }
+ }
+ }
+
+ if (!p->osd_format.fourcc)
+ MP_ERR(vo, "OSD format not supported. Disabling OSD.\n");
+
+ for (int n = 0; n < MAX_OSD_PARTS; n++) {
+ struct vaapi_osd_part *part = &p->osd_parts[n];
+ part->image.image.image_id = VA_INVALID_ID;
+ part->image.subpic_id = VA_INVALID_ID;
+ part->conv_cache = talloc_steal(vo, osd_conv_cache_new());
+ }
+
+ int max_display_attrs = vaMaxNumDisplayAttributes(p->display);
+ p->va_display_attrs = talloc_array(vo, VADisplayAttribute, max_display_attrs);
+ if (p->va_display_attrs) {
+ status = vaQueryDisplayAttributes(p->display, p->va_display_attrs,
+ &p->va_num_display_attrs);
+ if (!check_va_status(status, "vaQueryDisplayAttributes()"))
+ p->va_num_display_attrs = 0;
+ }
+ return 0;
+}
+
+#define OPT_BASE_STRUCT struct priv
+
+const struct vo_driver video_out_vaapi = {
+ .info = &(const vo_info_t) {
+ "VA API with X11",
+ "vaapi",
+ "Gwenole Beauchesne <gbeauchesne@splitted-desktop.com> and others",
+ ""
+ },
+ .preinit = preinit,
+ .query_format = query_format,
+ .reconfig = reconfig,
+ .control = control,
+ .draw_image = draw_image,
+ .draw_osd = draw_osd,
+ .flip_page = flip_page,
+ .uninit = uninit,
+ .priv_size = sizeof(struct priv),
+ .priv_defaults = &(const struct priv) {
+ .scaling = VA_FILTER_SCALING_DEFAULT,
+ .deint_type = 2,
+ .deint = 0,
+ },
+ .options = (const struct m_option[]) {
+#if USE_VAAPI_SCALING
+ OPT_CHOICE("scaling", scaling, 0,
+ ({"default", VA_FILTER_SCALING_DEFAULT},
+ {"fast", VA_FILTER_SCALING_FAST},
+ {"hq", VA_FILTER_SCALING_HQ},
+ {"nla", VA_FILTER_SCALING_NL_ANAMORPHIC})),
+#endif
+ OPT_CHOICE("deint", deint_type, 0,
+ ({"no", 0},
+ {"first-field", 1},
+ {"bob", 2})),
+ OPT_