summaryrefslogtreecommitdiffstats
path: root/video/out/vo_vaapi.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2013-08-09 14:01:30 +0200
committerwm4 <wm4@nowhere>2013-08-12 01:12:02 +0200
commit2827295703c74e3c119df9a435aa856e268c2ea9 (patch)
tree7464b1630d76e84f1abdf53680544a74b7dab300 /video/out/vo_vaapi.c
parentc7da4ba74469bc6c404c396340ffadc748535f6e (diff)
downloadmpv-2827295703c74e3c119df9a435aa856e268c2ea9.tar.bz2
mpv-2827295703c74e3c119df9a435aa856e268c2ea9.tar.xz
video: add vaapi decode and output support
This is based on the MPlayer VA API patches. To be exact it's based on a very stripped down version of commit f1ad459a263f8537f6c from git://gitorious.org/vaapi/mplayer.git. This doesn't contain useless things like benchmarking hacks and the demo code for GLX interop. Also, unlike in the original patch, decoding and video output are split into separate source files (the separation between decoding and display also makes pixel format hacks unnecessary). On the other hand, some features not present in the original patch were added, like screenshot support. VA API is rather bad for actual video output. Dealing with older libva versions or the completely broken vdpau backend doesn't help. OSD is low quality and should be rather slow. In some cases, only either OSD or subtitles can be shown at the same time (because OSD is drawn first, OSD is prefered). Also, libva can't decide whether it accepts straight or premultiplied alpha for OSD sub-pictures: the vdpau backend seems to assume premultiplied, while a native vaapi driver uses straight. So I picked straight alpha. It doesn't matter much, because the blending code for straight alpha I added to img_convert.c is probably buggy, and ASS subtitles might be blended incorrectly. Really good video output with VA API would probably use OpenGL and the GL interop features, but at this point you might just use vo_opengl. (Patches for making HW decoding with vo_opengl have a chance of being accepted.) Despite these issues, decoding seems to work ok. I still got tearing on the Intel system I tested (Intel(R) Core(TM) i3-2350M). It was also tested with the vdpau vaapi wrapper on a nvidia system; however this was rather broken. (Fortunately, there is no reason to use mpv's VAAPI support over native VDPAU.)
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_