diff options
Diffstat (limited to 'video')
-rw-r--r-- | video/decode/dec_video.h | 1 | ||||
-rw-r--r-- | video/decode/lavc.h | 1 | ||||
-rw-r--r-- | video/decode/vaapi.c | 414 | ||||
-rw-r--r-- | video/decode/vd_lavc.c | 4 | ||||
-rw-r--r-- | video/fmt-conversion.c | 5 | ||||
-rw-r--r-- | video/img_format.c | 3 | ||||
-rw-r--r-- | video/img_format.h | 14 | ||||
-rw-r--r-- | video/out/vo.c | 4 | ||||
-rw-r--r-- | video/out/vo.h | 2 | ||||
-rw-r--r-- | video/out/vo_vaapi.c | 1054 | ||||
-rw-r--r-- | video/vaapi.h | 73 |
11 files changed, 1572 insertions, 3 deletions
diff --git a/video/decode/dec_video.h b/video/decode/dec_video.h index 4ba052afd1..021abaaf22 100644 --- a/video/decode/dec_video.h +++ b/video/decode/dec_video.h @@ -51,6 +51,7 @@ extern int divx_quality; // The VO can set the context pointer for supported APIs. struct mp_hwdec_info { struct mp_vdpau_ctx *vdpau_ctx; + struct mp_vaapi_ctx *vaapi_ctx; }; #endif /* MPLAYER_DEC_VIDEO_H */ diff --git a/video/decode/lavc.h b/video/decode/lavc.h index 94973cb2d8..4252034b2c 100644 --- a/video/decode/lavc.h +++ b/video/decode/lavc.h @@ -17,6 +17,7 @@ enum hwdec_type { HWDEC_VDPAU = 1, HWDEC_VDA = 2, HWDEC_CRYSTALHD = 3, + HWDEC_VAAPI = 4, }; typedef struct lavc_ctx { diff --git a/video/decode/vaapi.c b/video/decode/vaapi.c new file mode 100644 index 0000000000..baa13588c9 --- /dev/null +++ b/video/decode/vaapi.c @@ -0,0 +1,414 @@ +/* + * This file is part of mpv. + * + * With some chunks from original MPlayer VAAPI patch: + * Copyright (C) 2008-2009 Splitted-Desktop Systems + * + * mpv 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. + * + * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stddef.h> +#include <assert.h> + +#include <libavcodec/avcodec.h> +#include <libavcodec/vaapi.h> +#include <libavutil/common.h> + +#include "lavc.h" +#include "mpvcore/mp_common.h" +#include "mpvcore/av_common.h" +#include "video/fmt-conversion.h" +#include "video/vaapi.h" +#include "video/decode/dec_video.h" + +/* + * The VAAPI decoder can work only with surfaces passed to the decoder at + * creation time. This means all surfaces have to be created in advance. + * So, additionally to the maximum number of reference frames, we need + * surfaces for: + * - 1 decode frame + * - decoding 1 frame ahead (done by generic playback code) + * - keeping the reference to the previous frame (done by vo_vaapi.c) + * Note that redundant additional surfaces also might allow for some + * buffering (i.e. not trying to reuse a surface while it's busy). + */ +#define ADDTIONAL_SURFACES 3 + +// Magic number taken from original MPlayer vaapi patch. +#define MAX_DECODER_SURFACES 21 + +#define MAX_SURFACES (MAX_DECODER_SURFACES + ADDTIONAL_SURFACES) + +struct priv { + struct mp_vaapi_ctx *ctx; + VADisplay display; + + // libavcodec shared struct + struct vaapi_context *va_context; + struct vaapi_context va_context_storage; + + int format, w, h; + VASurfaceID surfaces[MAX_SURFACES]; +}; + +struct profile_entry { + enum AVCodecID av_codec; + int ff_profile; + VAProfile va_profile; + int maxrefs; +}; + +#define PE(av_codec_id, ff_profile, va_dcoder_profile, maxrefs) \ + {AV_CODEC_ID_ ## av_codec_id, \ + FF_PROFILE_ ## ff_profile, \ + VAProfile ## va_dcoder_profile, \ + maxrefs} + +static const struct profile_entry profiles[] = { + PE(MPEG2VIDEO, MPEG2_SIMPLE, MPEG2Simple, 2), + PE(MPEG2VIDEO, UNKNOWN, MPEG2Main, 2), + PE(H264, H264_BASELINE, H264Baseline, 16), + PE(H264, H264_CONSTRAINED_BASELINE, H264ConstrainedBaseline, 16), + PE(H264, H264_MAIN, H264Main, 16), + PE(H264, UNKNOWN, H264High, 16), + PE(WMV3, VC1_SIMPLE, VC1Simple, 2), + PE(WMV3, VC1_MAIN, VC1Main, 2), + PE(WMV3, UNKNOWN, VC1Advanced, 2), + PE(VC1, VC1_SIMPLE, VC1Simple, 2), + PE(VC1, VC1_MAIN, VC1Main, 2), + PE(VC1, UNKNOWN, VC1Advanced, 2), + // No idea whether these are correct + PE(MPEG4, MPEG4_SIMPLE, MPEG4Simple, 2), + PE(MPEG4, MPEG4_MAIN, MPEG4Main, 2), + PE(MPEG4, UNKNOWN, MPEG4AdvancedSimple, 2), +}; + +static const struct profile_entry *find_codec(enum AVCodecID id, int ff_profile) +{ + for (int n = 0; n < MP_ARRAY_SIZE(profiles); n++) { + if (profiles[n].av_codec == id && + (profiles[n].ff_profile == ff_profile || + profiles[n].ff_profile == FF_PROFILE_UNKNOWN)) + { + return &profiles[n]; + } + } + return NULL; +} + + +static const char *str_va_profile(VAProfile profile) +{ + switch (profile) { +#define PROFILE(profile) \ + case VAProfile##profile: return "VAProfile" #profile + PROFILE(MPEG2Simple); + PROFILE(MPEG2Main); + PROFILE(MPEG4Simple); + PROFILE(MPEG4AdvancedSimple); + PROFILE(MPEG4Main); + PROFILE(H264Baseline); + PROFILE(H264Main); + PROFILE(H264High); + PROFILE(VC1Simple); + PROFILE(VC1Main); + PROFILE(VC1Advanced); +#undef PROFILE + } + return "<unknown>"; +} + +static int find_entrypoint(int format, VAEntrypoint *ep, int num_ep) +{ + int entrypoint = -1; + switch (format) { + case IMGFMT_VAAPI: entrypoint = VAEntrypointVLD; break; + case IMGFMT_VAAPI_MPEG2_IDCT: entrypoint = VAEntrypointIDCT; break; + case IMGFMT_VAAPI_MPEG2_MOCO: entrypoint = VAEntrypointMoComp; break; + } + for (int n = 0; n < num_ep; n++) { + if (ep[n] == entrypoint) + return entrypoint; + } + return -1; +} + +static int is_direct_mapping(VADisplay display) +{ + VADisplayAttribute attr; + VAStatus status; + +#if VA_CHECK_VERSION(0,34,0) + attr.type = VADisplayAttribRenderMode; + attr.flags = VA_DISPLAY_ATTRIB_GETTABLE; + + status = vaGetDisplayAttributes(display, &attr, 1); + if (status == VA_STATUS_SUCCESS) + return !(attr.value & (VA_RENDER_MODE_LOCAL_OVERLAY| + VA_RENDER_MODE_EXTERNAL_OVERLAY)); +#else + /* If the driver doesn't make a copy of the VA surface for + display, then we have to retain it until it's no longer the + visible surface. In other words, if the driver is using + DirectSurface mode, we don't want to decode the new surface + into the previous one that was used for display. */ + attr.type = VADisplayAttribDirectSurface; + attr.flags = VA_DISPLAY_ATTRIB_GETTABLE; + + status = vaGetDisplayAttributes(display, &attr, 1); + if (status == VA_STATUS_SUCCESS) + return !attr.value; +#endif + return 0; +} + +// Make vo_vaapi.c pool the required number of surfaces. +// This is very touchy: vo_vaapi.c must not free surfaces while we decode, +// and we must allocate only surfaces that were passed to the decoder on +// creation. +// We achieve this by deleting all previous surfaces, then allocate every +// surface needed. Then we free these surfaces, and rely on the fact that +// vo_vaapi.c keeps the released surfaces in the pool, and only allocates +// new surfaces out of that pool. +static int preallocate_surfaces(struct lavc_ctx *ctx, int va_rt_format, int num) +{ + struct priv *p = ctx->hwdec_priv; + int res = -1; + + struct mp_image *tmp_surfaces[MAX_SURFACES] = {0}; + + p->ctx->flush(p->ctx); // free previously allocated surfaces + + for (int n = 0; n < num; n++) { + tmp_surfaces[n] = p->ctx->get_surface(p->ctx, va_rt_format, p->format, + p->w, p->h); + if (!tmp_surfaces[n]) + goto done; + p->surfaces[n] = (uintptr_t)tmp_surfaces[n]->planes[3]; + } + res = 0; + +done: + for (int n = 0; n < num; n++) + talloc_free(tmp_surfaces[n]); + return res; +} + +static void destroy_decoder(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + + if (p->va_context->context_id != VA_INVALID_ID) { + vaDestroyContext(p->display, p->va_context->context_id); + p->va_context->context_id = VA_INVALID_ID; + } + + if (p->va_context->config_id != VA_INVALID_ID) { + vaDestroyConfig(p->display, p->va_context->config_id); + p->va_context->config_id = VA_INVALID_ID; + } + + for (int n = 0; n < MAX_SURFACES; n++) + p->surfaces[n] = VA_INVALID_ID; +} + +static int create_decoder(struct lavc_ctx *ctx) +{ + void *tmp = talloc_new(NULL); + + struct priv *p = ctx->hwdec_priv; + VAStatus status; + int res = -1; + + assert(IMGFMT_IS_VAAPI(p->format)); + + destroy_decoder(ctx); + + const struct profile_entry *pe = find_codec(ctx->avctx->codec_id, + ctx->avctx->profile); + if (!pe) { + mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Unknown codec!\n"); + goto error; + } + + int num_profiles = vaMaxNumProfiles(p->display); + VAProfile *va_profiles = talloc_zero_array(tmp, VAProfile, num_profiles); + status = vaQueryConfigProfiles(p->display, va_profiles, &num_profiles); + if (!check_va_status(status, "vaQueryConfigProfiles()")) + goto error; + mp_msg(MSGT_VO, MSGL_DBG2, "[vaapi] %d profiles available:\n", num_profiles); + for (int i = 0; i < num_profiles; i++) + mp_msg(MSGT_VO, MSGL_DBG2, " %s\n", str_va_profile(va_profiles[i])); + + bool profile_found = false; + for (int i = 0; i < num_profiles; i++) { + if (pe->va_profile == va_profiles[i]) { + profile_found = true; + break; + } + } + if (!profile_found) { + mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Profile '%s' not available.\n", + str_va_profile(pe->va_profile)); + goto error; + } + + int num_surfaces = pe->maxrefs; + if (!is_direct_mapping(p->display)) { + mp_msg(MSGT_VO, MSGL_V, "[vaapi] No direct mapping.\n"); + // Note: not sure why it has to be *=2 rather than +=1. + num_surfaces *= 2; + } + num_surfaces = MPMIN(num_surfaces, MAX_DECODER_SURFACES) + ADDTIONAL_SURFACES; + + if (num_surfaces > MAX_SURFACES) { + mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Internal error: too many surfaces.\n"); + goto error; + } + + if (preallocate_surfaces(ctx, VA_RT_FORMAT_YUV420, num_surfaces) < 0) { + mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Could not allocate surfaces.\n"); + goto error; + } + + int num_ep = vaMaxNumEntrypoints(p->display); + VAEntrypoint *ep = talloc_zero_array(tmp, VAEntrypoint, num_ep); + status = vaQueryConfigEntrypoints(p->display, pe->va_profile, ep, &num_ep); + if (!check_va_status(status, "vaQueryConfigEntrypoints()")) + goto error; + + VAEntrypoint entrypoint = find_entrypoint(p->format, ep, num_ep); + if (entrypoint < 0) { + mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Could not find VA entrypoint.\n"); + goto error; + } + + VAConfigAttrib attrib = { + .type = VAConfigAttribRTFormat, + }; + status = vaGetConfigAttributes(p->display, pe->va_profile, entrypoint, + &attrib, 1); + if (!check_va_status(status, "vaGetConfigAttributes()")) + goto error; + if ((attrib.value & VA_RT_FORMAT_YUV420) == 0) { + mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Chroma format not supported.\n"); + goto error; + } + + status = vaCreateConfig(p->display, pe->va_profile, entrypoint, &attrib, 1, + &p->va_context->config_id); + if (!check_va_status(status, "vaCreateConfig()")) + goto error; + + status = vaCreateContext(p->display, p->va_context->config_id, + p->w, p->h, VA_PROGRESSIVE, + p->surfaces, num_surfaces, + &p->va_context->context_id); + if (!check_va_status(status, "vaCreateContext()")) + goto error; + + res = 0; +error: + talloc_free(tmp); + return res; +} + +static struct mp_image *allocate_image(struct lavc_ctx *ctx, AVFrame *frame) +{ + struct priv *p = ctx->hwdec_priv; + int format = pixfmt2imgfmt(frame->format); + + if (!IMGFMT_IS_VAAPI(format)) + return NULL; + + // frame->width/height lie. Using them breaks with non-mod 16 video. + int w = ctx->avctx->width; + int h = ctx->avctx->height; + + if (format != p->format || w != p->w || h != p->h || + p->va_context->context_id == VA_INVALID_ID) + { + p->format = format; + p->w = w; + p->h = h; + if (create_decoder(ctx) < 0) + return NULL; + } + + struct mp_image *img = p->ctx->get_surface(p->ctx, VA_RT_FORMAT_YUV420, + format, p->w, p->h); + if (img) { + for (int n = 0; n < MAX_SURFACES; n++) { + if (p->surfaces[n] == (uintptr_t)img->planes[3]) + return img; + } + talloc_free(img); + } + mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Insufficient number of surfaces.\n"); + return NULL; +} + +static void uninit(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + + if (!p) + return; + + destroy_decoder(ctx); + + talloc_free(p); + ctx->hwdec_priv = NULL; +} + +static int init(struct lavc_ctx *ctx) +{ + struct priv *p = talloc_ptrtype(NULL, p); + *p = (struct priv) { + .ctx = ctx->hwdec_info->vaapi_ctx, + .va_context = &p->va_context_storage, + }; + ctx->hwdec_priv = p; + + p->display = p->ctx->display; + + p->va_context->display = p->display; + p->va_context->config_id = VA_INVALID_ID; + p->va_context->context_id = VA_INVALID_ID; + + ctx->avctx->hwaccel_context = p->va_context; + + return 0; +} + + +static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, + const char *decoder) +{ + if (!info || !info->vaapi_ctx) + return HWDEC_ERR_NO_CTX; + if (!find_codec(mp_codec_to_av_codec_id(decoder), FF_PROFILE_UNKNOWN)) + return HWDEC_ERR_NO_CODEC; + return 0; +} + +const struct vd_lavc_hwdec mp_vd_lavc_vaapi = { + .type = HWDEC_VAAPI, + .image_formats = (const int[]) {IMGFMT_VAAPI, IMGFMT_VAAPI_MPEG2_IDCT, + IMGFMT_VAAPI_MPEG2_MOCO, 0}, + .probe = probe, + .init = init, + .uninit = uninit, + .allocate_image = allocate_image, +}; diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c index 2fc7a1ea4c..639e46ebcc 100644 --- a/video/decode/vd_lavc.c +++ b/video/decode/vd_lavc.c @@ -84,6 +84,7 @@ const m_option_t lavc_decode_opts_conf[] = { const struct vd_lavc_hwdec mp_vd_lavc_vdpau; const struct vd_lavc_hwdec mp_vd_lavc_vdpau_old; +const struct vd_lavc_hwdec mp_vd_lavc_vaapi; static const struct vd_lavc_hwdec mp_vd_lavc_crystalhd = { .type = HWDEC_CRYSTALHD, @@ -113,6 +114,9 @@ static const struct vd_lavc_hwdec *hwdec_list[] = { #endif // CONFIG_VDPAU &mp_vd_lavc_vda, &mp_vd_lavc_crystalhd, +#if CONFIG_VAAPI + &mp_vd_lavc_vaapi, +#endif NULL }; diff --git a/video/fmt-conversion.c b/video/fmt-conversion.c index 509ff31535..842d95ce89 100644 --- a/video/fmt-conversion.c +++ b/video/fmt-conversion.c @@ -183,6 +183,11 @@ static const struct { // map to an arbitrary but existing vdpau format {IMGFMT_VDPAU, PIX_FMT_VDPAU_H264}, #endif + + {IMGFMT_VAAPI, PIX_FMT_VAAPI_VLD}, + {IMGFMT_VAAPI_MPEG2_IDCT, PIX_FMT_VAAPI_IDCT}, + {IMGFMT_VAAPI_MPEG2_MOCO, PIX_FMT_VAAPI_MOCO}, + {0, PIX_FMT_NONE} }; diff --git a/video/img_format.c b/video/img_format.c index 9422fbdd27..56d5ddc412 100644 --- a/video/img_format.c +++ b/video/img_format.c @@ -119,6 +119,9 @@ struct mp_imgfmt_entry mp_imgfmt_list[] = { FMT("vdpau_vc1", IMGFMT_VDPAU_VC1) FMT("vdpau_mpeg4", IMGFMT_VDPAU_MPEG4) FMT("vdpau", IMGFMT_VDPAU) + FMT("vaapi", IMGFMT_VAAPI) + FMT("vaapi_mpeg2_idct", IMGFMT_VAAPI_MPEG2_IDCT) + FMT("vaapi_mpeg2_moco", IMGFMT_VAAPI_MPEG2_MOCO) {0} }; diff --git a/video/img_format.h b/video/img_format.h index 7261971e43..e83f5ec25b 100644 --- a/video/img_format.h +++ b/video/img_format.h @@ -87,7 +87,6 @@ enum mp_imgfmt { IMGFMT_START = 1000, // Planar YUV formats - IMGFMT_444P, // 1x1 IMGFMT_422P, // 2x1 IMGFMT_440P, // 1x2 @@ -253,6 +252,14 @@ enum mp_imgfmt { IMGFMT_VDPAU_FIRST = IMGFMT_VDPAU, IMGFMT_VDPAU_LAST = IMGFMT_VDPAU_MPEG4, + IMGFMT_VAAPI, + IMGFMT_VAAPI_MPEG2_IDCT, + IMGFMT_VAAPI_MPEG2_MOCO, + + IMGFMT_VAAPI_FIRST = IMGFMT_VAAPI, + IMGFMT_VAAPI_LAST = IMGFMT_VAAPI_MPEG2_MOCO, + + IMGFMT_END, // Redundant format aliases for native endian access @@ -328,7 +335,10 @@ static inline bool IMGFMT_IS_RGB(unsigned int fmt) #define IMGFMT_IS_VDPAU(fmt) \ (((fmt) >= IMGFMT_VDPAU_FIRST) && ((fmt) <= IMGFMT_VDPAU_LAST)) -#define IMGFMT_IS_HWACCEL(fmt) IMGFMT_IS_VDPAU(fmt) +#define IMGFMT_IS_VAAPI(fmt) \ + (((fmt) >= IMGFMT_VAAPI_FIRST) && ((fmt) <= IMGFMT_VAAPI_LAST)) + +#define IMGFMT_IS_HWACCEL(fmt) (IMGFMT_IS_VDPAU(fmt) || IMGFMT_IS_VAAPI(fmt)) struct mp_imgfmt_entry { diff --git a/video/out/vo.c b/video/out/vo.c index 5a1b6f6fcd..592dfe6243 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -61,6 +61,7 @@ extern struct vo_driver video_out_direct3d; extern struct vo_driver video_out_direct3d_shaders; extern struct vo_driver video_out_sdl; extern struct vo_driver video_out_corevideo; +extern struct vo_driver video_out_vaapi; const struct vo_driver *video_out_drivers[] = { @@ -86,6 +87,9 @@ const struct vo_driver *video_out_drivers[] = #ifdef CONFIG_GL &video_out_opengl_old, #endif +#ifdef CONFIG_VAAPI + &video_out_vaapi, +#endif #ifdef CONFIG_X11 &video_out_x11, #endif diff --git a/video/out/vo.h b/video/out/vo.h index 4052d11773..8bb1bd5617 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -50,7 +50,7 @@ enum mp_voctrl { VOCTRL_SET_EQUALIZER, // struct voctrl_set_equalizer_args* VOCTRL_GET_EQUALIZER, // struct voctrl_get_equalizer_args* - /* for vdpau hardware decoding */ + /* for hardware decoding */ VOCTRL_GET_HWDEC_INFO, // struct mp_hwdec_info* VOCTRL_NEWFRAME, 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) |