diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
commit | 51de1d8436100f725f3576aefa24a2bd2057bc28 (patch) | |
tree | c6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /video/out/vo_vdpau.c | |
parent | Initial commit. (diff) | |
download | mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.tar.xz mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.zip |
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'video/out/vo_vdpau.c')
-rw-r--r-- | video/out/vo_vdpau.c | 1139 |
1 files changed, 1139 insertions, 0 deletions
diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c new file mode 100644 index 0000000..d6b261f --- /dev/null +++ b/video/out/vo_vdpau.c @@ -0,0 +1,1139 @@ +/* + * VDPAU video output driver + * + * Copyright (C) 2008 NVIDIA (Rajib Mahapatra <rmahapatra@nvidia.com>) + * Copyright (C) 2009 Uoti Urpala + * + * This file is part of mpv. + * + * 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/>. + */ + +/* + * Actual decoding is done in video/decode/vdpau.c + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <limits.h> +#include <assert.h> + +#include "video/vdpau.h" +#include "video/vdpau_mixer.h" +#include "video/hwdec.h" +#include "common/msg.h" +#include "options/options.h" +#include "mpv_talloc.h" +#include "vo.h" +#include "x11_common.h" +#include "video/csputils.h" +#include "sub/osd.h" +#include "options/m_option.h" +#include "video/mp_image.h" +#include "osdep/timer.h" + +// Returns x + a, but wrapped around to the range [0, m) +// a must be within [-m, m], x within [0, m) +#define WRAP_ADD(x, a, m) ((a) < 0 \ + ? ((x)+(a)+(m) < (m) ? (x)+(a)+(m) : (x)+(a)) \ + : ((x)+(a) < (m) ? (x)+(a) : (x)+(a)-(m))) + + +/* number of video and output surfaces */ +#define MAX_OUTPUT_SURFACES 15 + +/* Pixelformat used for output surfaces */ +#define OUTPUT_RGBA_FORMAT VDP_RGBA_FORMAT_B8G8R8A8 + +/* + * Global variable declaration - VDPAU specific + */ + +struct vdpctx { + struct mp_vdpau_ctx *mpvdp; + struct vdp_functions *vdp; + VdpDevice vdp_device; + uint64_t preemption_counter; + + struct m_color colorkey; + + VdpPresentationQueueTarget flip_target; + VdpPresentationQueue flip_queue; + + VdpOutputSurface output_surfaces[MAX_OUTPUT_SURFACES]; + int num_output_surfaces; + VdpOutputSurface black_pixel; + VdpOutputSurface rotation_surface; + + struct mp_image *current_image; + int64_t current_pts; + int current_duration; + + int output_surface_w, output_surface_h; + int rotation; + + bool force_yuv; + struct mp_vdpau_mixer *video_mixer; + bool pullup; + float denoise; + float sharpen; + int hqscaling; + bool chroma_deint; + int flip_offset_window; + int flip_offset_fs; + int64_t flip_offset_us; + + VdpRect src_rect_vid; + VdpRect out_rect_vid; + struct mp_osd_res osd_rect; + VdpBool supports_a8; + + int surface_num; // indexes output_surfaces + int query_surface_num; + VdpTime recent_vsync_time; + float user_fps; + bool composite_detect; + int vsync_interval; + uint64_t last_queue_time; + uint64_t queue_time[MAX_OUTPUT_SURFACES]; + uint64_t last_ideal_time; + bool dropped_frame; + uint64_t dropped_time; + uint32_t vid_width, vid_height; + uint32_t image_format; + VdpYCbCrFormat vdp_pixel_format; + bool rgb_mode; + + // OSD + struct osd_bitmap_surface { + VdpRGBAFormat format; + VdpBitmapSurface surface; + uint32_t surface_w, surface_h; + // List of surfaces to be rendered + struct osd_target { + VdpRect source; + VdpRect dest; + VdpColor color; + } *targets; + int targets_size; + int render_count; + int change_id; + } osd_surfaces[MAX_OSD_PARTS]; +}; + +static bool status_ok(struct vo *vo); + +static int video_to_output_surface(struct vo *vo, struct mp_image *mpi) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpTime dummy; + VdpStatus vdp_st; + + VdpOutputSurface output_surface = vc->output_surfaces[vc->surface_num]; + VdpRect *output_rect = &vc->out_rect_vid; + VdpRect *video_rect = &vc->src_rect_vid; + + vdp_st = vdp->presentation_queue_block_until_surface_idle(vc->flip_queue, + output_surface, + &dummy); + CHECK_VDP_WARNING(vo, "Error when calling " + "vdp_presentation_queue_block_until_surface_idle"); + + // Clear the borders between video and window (if there are any). + // For some reason, video_mixer_render doesn't need it for YUV. + // Also, if there is nothing to render, at least clear the screen. + if (vc->rgb_mode || !mpi || mpi->params.rotate != 0) { + int flags = VDP_OUTPUT_SURFACE_RENDER_ROTATE_0; + vdp_st = vdp->output_surface_render_output_surface(output_surface, + NULL, vc->black_pixel, + NULL, NULL, NULL, + flags); + CHECK_VDP_WARNING(vo, "Error clearing screen"); + } + + if (!mpi) + return -1; + + struct mp_vdpau_mixer_frame *frame = mp_vdpau_mixed_frame_get(mpi); + struct mp_vdpau_mixer_opts opts = {0}; + if (frame) + opts = frame->opts; + + // Apply custom vo_vdpau suboptions. + opts.chroma_deint |= vc->chroma_deint; + opts.pullup |= vc->pullup; + opts.denoise = MPCLAMP(opts.denoise + vc->denoise, 0, 1); + opts.sharpen = MPCLAMP(opts.sharpen + vc->sharpen, -1, 1); + if (vc->hqscaling) + opts.hqscaling = vc->hqscaling; + + if (mpi->params.rotate != 0) { + int flags; + VdpRect r_rect; + switch (mpi->params.rotate) { + case 90: + r_rect.y0 = output_rect->x0; + r_rect.y1 = output_rect->x1; + r_rect.x0 = output_rect->y0; + r_rect.x1 = output_rect->y1; + flags = VDP_OUTPUT_SURFACE_RENDER_ROTATE_90; + break; + case 180: + r_rect.x0 = output_rect->x0; + r_rect.x1 = output_rect->x1; + r_rect.y0 = output_rect->y0; + r_rect.y1 = output_rect->y1; + flags = VDP_OUTPUT_SURFACE_RENDER_ROTATE_180; + break; + case 270: + r_rect.y0 = output_rect->x0; + r_rect.y1 = output_rect->x1; + r_rect.x0 = output_rect->y0; + r_rect.x1 = output_rect->y1; + flags = VDP_OUTPUT_SURFACE_RENDER_ROTATE_270; + break; + default: + MP_ERR(vo, "Unsupported rotation angle: %u\n", mpi->params.rotate); + return -1; + } + + mp_vdpau_mixer_render(vc->video_mixer, &opts, vc->rotation_surface, + &r_rect, mpi, video_rect); + vdp_st = vdp->output_surface_render_output_surface(output_surface, + output_rect, + vc->rotation_surface, + &r_rect, + NULL, + NULL, + flags); + CHECK_VDP_WARNING(vo, "Error rendering rotated frame"); + } else { + mp_vdpau_mixer_render(vc->video_mixer, &opts, output_surface, + output_rect, mpi, video_rect); + } + return 0; +} + +static void forget_frames(struct vo *vo, bool seek_reset) +{ + struct vdpctx *vc = vo->priv; + + if (!seek_reset) + mp_image_unrefp(&vc->current_image); + + vc->dropped_frame = false; +} + +static int s_size(int max, int s, int disp) +{ + disp = MPMAX(1, disp); + return MPMIN(max, MPMAX(s, disp)); +} + +static void resize(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + struct mp_rect src_rect; + struct mp_rect dst_rect; + vo_get_src_dst_rects(vo, &src_rect, &dst_rect, &vc->osd_rect); + vc->out_rect_vid.x0 = dst_rect.x0; + vc->out_rect_vid.x1 = dst_rect.x1; + vc->out_rect_vid.y0 = dst_rect.y0; + vc->out_rect_vid.y1 = dst_rect.y1; + if (vo->params->rotate == 90 || vo->params->rotate == 270) { + vc->src_rect_vid.y0 = src_rect.x0; + vc->src_rect_vid.y1 = src_rect.x1; + vc->src_rect_vid.x0 = src_rect.y0; + vc->src_rect_vid.x1 = src_rect.y1; + } else { + vc->src_rect_vid.x0 = src_rect.x0; + vc->src_rect_vid.x1 = src_rect.x1; + vc->src_rect_vid.y0 = src_rect.y0; + vc->src_rect_vid.y1 = src_rect.y1; + } + + VdpBool ok; + uint32_t max_w, max_h; + vdp_st = vdp->output_surface_query_capabilities(vc->vdp_device, + OUTPUT_RGBA_FORMAT, + &ok, &max_w, &max_h); + if (vdp_st != VDP_STATUS_OK || !ok) + return; + + vc->flip_offset_us = vo->opts->fullscreen ? + 1000LL * vc->flip_offset_fs : + 1000LL * vc->flip_offset_window; + vo_set_queue_params(vo, vc->flip_offset_us * 1000, 1); + + if (vc->output_surface_w < vo->dwidth || vc->output_surface_h < vo->dheight || + vc->rotation != vo->params->rotate) + { + vc->output_surface_w = s_size(max_w, vc->output_surface_w, vo->dwidth); + vc->output_surface_h = s_size(max_h, vc->output_surface_h, vo->dheight); + // Creation of output_surfaces + for (int i = 0; i < vc->num_output_surfaces; i++) + if (vc->output_surfaces[i] != VDP_INVALID_HANDLE) { + vdp_st = vdp->output_surface_destroy(vc->output_surfaces[i]); + CHECK_VDP_WARNING(vo, "Error when calling " + "vdp_output_surface_destroy"); + } + for (int i = 0; i < vc->num_output_surfaces; i++) { + vdp_st = vdp->output_surface_create(vc->vdp_device, + OUTPUT_RGBA_FORMAT, + vc->output_surface_w, + vc->output_surface_h, + &vc->output_surfaces[i]); + CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_create"); + MP_DBG(vo, "vdpau out create: %u\n", + vc->output_surfaces[i]); + } + if (vc->rotation_surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->output_surface_destroy(vc->rotation_surface); + CHECK_VDP_WARNING(vo, "Error when calling " + "vdp_output_surface_destroy"); + vc->rotation_surface = VDP_INVALID_HANDLE; + } + if (vo->params->rotate == 90 || vo->params->rotate == 270) { + vdp_st = vdp->output_surface_create(vc->vdp_device, + OUTPUT_RGBA_FORMAT, + vc->output_surface_h, + vc->output_surface_w, + &vc->rotation_surface); + } else if (vo->params->rotate == 180) { + vdp_st = vdp->output_surface_create(vc->vdp_device, + OUTPUT_RGBA_FORMAT, + vc->output_surface_w, + vc->output_surface_h, + &vc->rotation_surface); + } + CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_create"); + MP_DBG(vo, "vdpau rotation surface create: %u\n", + vc->rotation_surface); + } + vc->rotation = vo->params->rotate; + vo->want_redraw = true; +} + +static int win_x11_init_vdpau_flip_queue(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + struct vo_x11_state *x11 = vo->x11; + VdpStatus vdp_st; + + if (vc->flip_target == VDP_INVALID_HANDLE) { + vdp_st = vdp->presentation_queue_target_create_x11(vc->vdp_device, + x11->window, + &vc->flip_target); + CHECK_VDP_ERROR(vo, "Error when calling " + "vdp_presentation_queue_target_create_x11"); + } + + /* Empirically this seems to be the first call which fails when we + * try to reinit after preemption while the user is still switched + * from X to a virtual terminal (creating the vdp_device initially + * succeeds, as does creating the flip_target above). This is + * probably not guaranteed behavior. + */ + if (vc->flip_queue == VDP_INVALID_HANDLE) { + vdp_st = vdp->presentation_queue_create(vc->vdp_device, vc->flip_target, + &vc->flip_queue); + CHECK_VDP_ERROR(vo, "Error when calling vdp_presentation_queue_create"); + } + + if (vc->colorkey.a > 0) { + VdpColor color = { + .red = vc->colorkey.r / 255.0, + .green = vc->colorkey.g / 255.0, + .blue = vc->colorkey.b / 255.0, + .alpha = 0, + }; + vdp_st = vdp->presentation_queue_set_background_color(vc->flip_queue, + &color); + CHECK_VDP_WARNING(vo, "Error setting colorkey"); + } + + if (vc->composite_detect && vo_x11_screen_is_composited(vo)) { + MP_INFO(vo, "Compositing window manager detected. Assuming timing info " + "is inaccurate.\n"); + vc->user_fps = -1; + } + + return 0; +} + +// Free everything specific to a certain video file +static void free_video_specific(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + + forget_frames(vo, false); + + if (vc->black_pixel != VDP_INVALID_HANDLE) { + vdp_st = vdp->output_surface_destroy(vc->black_pixel); + CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_destroy"); + } + vc->black_pixel = VDP_INVALID_HANDLE; +} + +static int initialize_vdpau_objects(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + + mp_vdpau_get_format(vc->image_format, NULL, &vc->vdp_pixel_format); + + vc->video_mixer->initialized = false; + + if (win_x11_init_vdpau_flip_queue(vo) < 0) + return -1; + + if (vc->black_pixel == VDP_INVALID_HANDLE) { + vdp_st = vdp->output_surface_create(vc->vdp_device, OUTPUT_RGBA_FORMAT, + 1, 1, &vc->black_pixel); + CHECK_VDP_ERROR(vo, "Allocating clearing surface"); + const char data[4] = {0}; + vdp_st = vdp->output_surface_put_bits_native(vc->black_pixel, + (const void*[]){data}, + (uint32_t[]){4}, NULL); + CHECK_VDP_ERROR(vo, "Initializing clearing surface"); + } + + forget_frames(vo, false); + resize(vo); + return 0; +} + +static void mark_vdpau_objects_uninitialized(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + forget_frames(vo, false); + vc->black_pixel = VDP_INVALID_HANDLE; + vc->flip_queue = VDP_INVALID_HANDLE; + vc->flip_target = VDP_INVALID_HANDLE; + for (int i = 0; i < MAX_OUTPUT_SURFACES; i++) + vc->output_surfaces[i] = VDP_INVALID_HANDLE; + vc->rotation_surface = VDP_INVALID_HANDLE; + vc->vdp_device = VDP_INVALID_HANDLE; + for (int i = 0; i < MAX_OSD_PARTS; i++) { + struct osd_bitmap_surface *sfc = &vc->osd_surfaces[i]; + sfc->change_id = 0; + *sfc = (struct osd_bitmap_surface){ + .surface = VDP_INVALID_HANDLE, + }; + } + vc->output_surface_w = vc->output_surface_h = -1; +} + +static bool check_preemption(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + int r = mp_vdpau_handle_preemption(vc->mpvdp, &vc->preemption_counter); + if (r < 1) { + mark_vdpau_objects_uninitialized(vo); + if (r < 0) + return false; + vc->vdp_device = vc->mpvdp->vdp_device; + if (initialize_vdpau_objects(vo) < 0) + return false; + } + return true; +} + +static bool status_ok(struct vo *vo) +{ + return vo->config_ok && check_preemption(vo); +} + +/* + * connect to X server, create and map window, initialize all + * VDPAU objects, create different surfaces etc. + */ +static int reconfig(struct vo *vo, struct mp_image_params *params) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + + if (!check_preemption(vo)) + { + /* + * When prempted, leave the reconfig() immediately + * without reconfiguring the vo_window and without + * initializing the vdpau objects. When recovered + * from preemption, if there is a difference between + * the VD thread parameters and the VO thread parameters + * the reconfig() is triggered again. + */ + return 0; + } + + VdpChromaType chroma_type = VDP_CHROMA_TYPE_420; + mp_vdpau_get_format(params->imgfmt, &chroma_type, NULL); + + VdpBool ok; + uint32_t max_w, max_h; + vdp_st = vdp->video_surface_query_capabilities(vc->vdp_device, chroma_type, + &ok, &max_w, &max_h); + CHECK_VDP_ERROR(vo, "Error when calling vdp_video_surface_query_capabilities"); + + if (!ok) + return -1; + if (params->w > max_w || params->h > max_h) { + if (ok) + MP_ERR(vo, "Video too large for vdpau.\n"); + return -1; + } + + vc->image_format = params->imgfmt; + vc->vid_width = params->w; + vc->vid_height = params->h; + + vc->rgb_mode = mp_vdpau_get_rgb_format(params->imgfmt, NULL); + + free_video_specific(vo); + + vo_x11_config_vo_window(vo); + + if (initialize_vdpau_objects(vo) < 0) + return -1; + + return 0; +} + +static void draw_osd_part(struct vo *vo, int index) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + struct osd_bitmap_surface *sfc = &vc->osd_surfaces[index]; + VdpOutputSurface output_surface = vc->output_surfaces[vc->surface_num]; + int i; + + VdpOutputSurfaceRenderBlendState blend_state = { + .struct_version = VDP_OUTPUT_SURFACE_RENDER_BLEND_STATE_VERSION, + .blend_factor_source_color = + VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_SRC_ALPHA, + .blend_factor_source_alpha = + VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ZERO, + .blend_factor_destination_color = + VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .blend_factor_destination_alpha = + VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ZERO, + .blend_equation_color = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD, + .blend_equation_alpha = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD, + }; + + VdpOutputSurfaceRenderBlendState blend_state_premultiplied = blend_state; + blend_state_premultiplied.blend_factor_source_color = + VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE; + + for (i = 0; i < sfc->render_count; i++) { + VdpOutputSurfaceRenderBlendState *blend = &blend_state; + if (sfc->format == VDP_RGBA_FORMAT_B8G8R8A8) + blend = &blend_state_premultiplied; + vdp_st = vdp-> + output_surface_render_bitmap_surface(output_surface, + &sfc->targets[i].dest, + sfc->surface, + &sfc->targets[i].source, + &sfc->targets[i].color, + blend, + VDP_OUTPUT_SURFACE_RENDER_ROTATE_0); + CHECK_VDP_WARNING(vo, "OSD: Error when rendering"); + } +} + +static int next_pow2(int v) +{ + for (int x = 0; x < 30; x++) { + if ((1 << x) >= v) + return 1 << x; + } + return INT_MAX; +} + +static void generate_osd_part(struct vo *vo, struct sub_bitmaps *imgs) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + struct osd_bitmap_surface *sfc = &vc->osd_surfaces[imgs->render_index]; + + if (imgs->change_id == sfc->change_id) + return; // Nothing changed and we still have the old data + + sfc->change_id = imgs->change_id; + sfc->render_count = 0; + + if (imgs->format == SUBBITMAP_EMPTY || imgs->num_parts == 0) + return; + + VdpRGBAFormat format; + switch (imgs->format) { + case SUBBITMAP_LIBASS: + format = VDP_RGBA_FORMAT_A8; + break; + case SUBBITMAP_BGRA: + format = VDP_RGBA_FORMAT_B8G8R8A8; + break; + default: + MP_ASSERT_UNREACHABLE(); + }; + + assert(imgs->packed); + + int r_w = next_pow2(imgs->packed_w); + int r_h = next_pow2(imgs->packed_h); + + if (sfc->format != format || sfc->surface == VDP_INVALID_HANDLE || + sfc->surface_w < r_w || sfc->surface_h < r_h) + { + MP_VERBOSE(vo, "Allocating a %dx%d surface for OSD bitmaps.\n", r_w, r_h); + + uint32_t m_w = 0, m_h = 0; + vdp_st = vdp->bitmap_surface_query_capabilities(vc->vdp_device, format, + &(VdpBool){0}, &m_w, &m_h); + CHECK_VDP_WARNING(vo, "Query to get max OSD surface size failed"); + + if (r_w > m_w || r_h > m_h) { + MP_ERR(vo, "OSD bitmaps do not fit on a surface with the maximum " + "supported size\n"); + return; + } + + if (sfc->surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->bitmap_surface_destroy(sfc->surface); + CHECK_VDP_WARNING(vo, "Error when calling vdp_bitmap_surface_destroy"); + } + + VdpBitmapSurface surface; + vdp_st = vdp->bitmap_surface_create(vc->vdp_device, format, + r_w, r_h, true, &surface); + CHECK_VDP_WARNING(vo, "OSD: error when creating surface"); + if (vdp_st != VDP_STATUS_OK) + return; + + sfc->surface = surface; + sfc->surface_w = r_w; + sfc->surface_h = r_h; + sfc->format = format; + } + + void *data = imgs->packed->planes[0]; + int stride = imgs->packed->stride[0]; + VdpRect rc = {0, 0, imgs->packed_w, imgs->packed_h}; + vdp_st = vdp->bitmap_surface_put_bits_native(sfc->surface, + &(const void *){data}, + &(uint32_t){stride}, + &rc); + CHECK_VDP_WARNING(vo, "OSD: putbits failed"); + + MP_TARRAY_GROW(vc, sfc->targets, imgs->num_parts); + sfc->render_count = imgs->num_parts; + + for (int i = 0; i < imgs->num_parts; i++) { + struct sub_bitmap *b = &imgs->parts[i]; + struct osd_target *target = &sfc->targets[i]; + target->source = (VdpRect){b->src_x, b->src_y, + b->src_x + b->w, b->src_y + b->h}; + target->dest = (VdpRect){b->x, b->y, b->x + b->dw, b->y + b->dh}; + target->color = (VdpColor){1, 1, 1, 1}; + if (imgs->format == SUBBITMAP_LIBASS) { + uint32_t color = b->libass.color; + target->color.alpha = 1.0 - ((color >> 0) & 0xff) / 255.0; + target->color.blue = ((color >> 8) & 0xff) / 255.0; + target->color.green = ((color >> 16) & 0xff) / 255.0; + target->color.red = ((color >> 24) & 0xff) / 255.0; + } + } +} + +static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs) +{ + struct vo *vo = ctx; + generate_osd_part(vo, imgs); + draw_osd_part(vo, imgs->render_index); +} + +static void draw_osd(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + if (!status_ok(vo)) + return; + + bool formats[SUBBITMAP_COUNT] = { + [SUBBITMAP_LIBASS] = vc->supports_a8, + [SUBBITMAP_BGRA] = true, + }; + + double pts = vc->current_image ? vc->current_image->pts : 0; + osd_draw(vo->osd, vc->osd_rect, pts, 0, formats, draw_osd_cb, vo); +} + +static int update_presentation_queue_status(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + + while (vc->query_surface_num != vc->surface_num) { + VdpTime vtime; + VdpPresentationQueueStatus status; + VdpOutputSurface surface = vc->output_surfaces[vc->query_surface_num]; + vdp_st = vdp->presentation_queue_query_surface_status(vc->flip_queue, + surface, + &status, &vtime); + CHECK_VDP_WARNING(vo, "Error calling " + "presentation_queue_query_surface_status"); + if (mp_msg_test(vo->log, MSGL_TRACE)) { + VdpTime current; + vdp_st = vdp->presentation_queue_get_time(vc->flip_queue, ¤t); + CHECK_VDP_WARNING(vo, "Error when calling " + "vdp_presentation_queue_get_time"); + MP_TRACE(vo, "Vdpau time: %"PRIu64"\n", (uint64_t)current); + MP_TRACE(vo, "Surface %d status: %d time: %"PRIu64"\n", + (int)surface, (int)status, (uint64_t)vtime); + } + if (status == VDP_PRESENTATION_QUEUE_STATUS_QUEUED) + break; + if (vc->vsync_interval > 1) { + uint64_t qtime = vc->queue_time[vc->query_surface_num]; + int diff = ((int64_t)vtime - (int64_t)qtime) / 1e6; + MP_TRACE(vo, "Queue time difference: %d ms\n", diff); + if (vtime < qtime + vc->vsync_interval / 2) + MP_VERBOSE(vo, "Frame shown too early (%d ms)\n", diff); + if (vtime > qtime + vc->vsync_interval) + MP_VERBOSE(vo, "Frame shown late (%d ms)\n", diff); + } + vc->query_surface_num = WRAP_ADD(vc->query_surface_num, 1, + vc->num_output_surfaces); + vc->recent_vsync_time = vtime; + } + int num_queued = WRAP_ADD(vc->surface_num, -vc->query_surface_num, + vc->num_output_surfaces); + MP_DBG(vo, "Queued surface count (before add): %d\n", num_queued); + return num_queued; +} + +// Return the timestamp of the vsync that must have happened before ts. +static inline uint64_t prev_vsync(struct vdpctx *vc, uint64_t ts) +{ + int64_t diff = (int64_t)(ts - vc->recent_vsync_time); + int64_t offset = diff % vc->vsync_interval; + if (offset < 0) + offset += vc->vsync_interval; + return ts - offset; +} + +static void flip_page(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + + int64_t pts_us = vc->current_pts; + int duration = vc->current_duration; + + vc->dropped_frame = true; // changed at end if false + + if (!check_preemption(vo)) + goto drop; + + vc->vsync_interval = 1; + if (vc->user_fps > 0) { + vc->vsync_interval = 1e9 / vc->user_fps; + } else if (vc->user_fps == 0) { + vc->vsync_interval = vo_get_vsync_interval(vo); + } + vc->vsync_interval = MPMAX(vc->vsync_interval, 1); + + if (duration > INT_MAX / 1000) + duration = -1; + else + duration *= 1000; + + if (vc->vsync_interval == 1) + duration = -1; // Make sure drop logic is disabled + + VdpTime vdp_time = 0; + vdp_st = vdp->presentation_queue_get_time(vc->flip_queue, &vdp_time); + CHECK_VDP_WARNING(vo, "Error when calling vdp_presentation_queue_get_time"); + + int64_t rel_pts_ns = (pts_us * 1000) - mp_time_ns(); + if (!pts_us || rel_pts_ns < 0) + rel_pts_ns = 0; + + uint64_t now = vdp_time; + uint64_t pts = now + rel_pts_ns; + uint64_t ideal_pts = pts; + uint64_t npts = duration >= 0 ? pts + duration : UINT64_MAX; + + /* This should normally never happen. + * - The last queued frame can't have a PTS that goes more than 50ms in the + * future. This is guaranteed by vo.c, which currently actually queues + * ahead by roughly the flip queue offset. Just to be sure + * give some additional room by doubling the time. + * - The last vsync can never be in the future. + */ + int64_t max_pts_ahead = vc->flip_offset_us * 1000 * 2; + if (vc->last_queue_time > now + max_pts_ahead || + vc->recent_vsync_time > now) + { + vc->last_queue_time = 0; + vc->recent_vsync_time = 0; + MP_WARN(vo, "Inconsistent timing detected.\n"); + } + +#define PREV_VSYNC(ts) prev_vsync(vc, ts) + + /* We hope to be here at least one vsync before the frame should be shown. + * If we are running late then don't drop the frame unless there is + * already one queued for the next vsync; even if we _hope_ to show the + * next frame soon enough to mean this one should be dropped we might + * not make the target time in reality. Without this check we could drop + * every frame, freezing the display completely if video lags behind. + */ + if (now > PREV_VSYNC(MPMAX(pts, vc->last_queue_time + vc->vsync_interval))) + npts = UINT64_MAX; + + /* Allow flipping a frame at a vsync if its presentation time is a + * bit after that vsync and the change makes the flip time delta + * from previous frame better match the target timestamp delta. + * This avoids instability with frame timestamps falling near vsyncs. + * For example if the frame timestamps were (with vsyncs at + * integer values) 0.01, 1.99, 4.01, 5.99, 8.01, ... then + * straightforward timing at next vsync would flip the frames at + * 1, 2, 5, 6, 9; this changes it to 1, 2, 4, 6, 8 and so on with + * regular 2-vsync intervals. + * + * Also allow moving the frame forward if it looks like we dropped + * the previous frame incorrectly (now that we know better after + * having final exact timestamp information for this frame) and + * there would unnecessarily be a vsync without a frame change. + */ + uint64_t vsync = PREV_VSYNC(pts); + if (pts < vsync + vc->vsync_interval / 4 + && (vsync - PREV_VSYNC(vc->last_queue_time) + > pts - vc->last_ideal_time + vc->vsync_interval / 2 + || (vc->dropped_frame && vsync > vc->dropped_time))) + pts -= vc->vsync_interval / 2; + + vc->dropped_time = ideal_pts; + + pts = MPMAX(pts, vc->last_queue_time + vc->vsync_interval); + pts = MPMAX(pts, now); + if (npts < PREV_VSYNC(pts) + vc->vsync_interval) + goto drop; + + int num_flips = update_presentation_queue_status(vo); + vsync = vc->recent_vsync_time + num_flips * vc->vsync_interval; + pts = MPMAX(pts, now); + pts = MPMAX(pts, vsync + (vc->vsync_interval >> 2)); + vsync = PREV_VSYNC(pts); + if (npts < vsync + vc->vsync_interval) + goto drop; + pts = vsync + (vc->vsync_interval >> 2); + VdpOutputSurface frame = vc->output_surfaces[vc->surface_num]; + vdp_st = vdp->presentation_queue_display(vc->flip_queue, frame, + vo->dwidth, vo->dheight, pts); + CHECK_VDP_WARNING(vo, "Error when calling vdp_presentation_queue_display"); + + MP_TRACE(vo, "Queue new surface %d: Vdpau time: %"PRIu64" " + "pts: %"PRIu64"\n", (int)frame, now, pts); + + vc->last_queue_time = pts; + vc->queue_time[vc->surface_num] = pts; + vc->last_ideal_time = ideal_pts; + vc->dropped_frame = false; + vc->surface_num = WRAP_ADD(vc->surface_num, 1, vc->num_output_surfaces); + return; + +drop: + vo_increment_drop_count(vo, 1); +} + +static void draw_frame(struct vo *vo, struct vo_frame *frame) +{ + struct vdpctx *vc = vo->priv; + + check_preemption(vo); + + if (frame->current && !frame->redraw) { + struct mp_image *vdp_mpi = + mp_vdpau_upload_video_surface(vc->mpvdp, frame->current); + if (!vdp_mpi) + MP_ERR(vo, "Could not upload image.\n"); + + talloc_free(vc->current_image); + vc->current_image = vdp_mpi; + } + + vc->current_pts = frame->pts; + vc->current_duration = frame->duration; + + if (status_ok(vo)) { + video_to_output_surface(vo, vc->current_image); + draw_osd(vo); + } +} + +// warning: the size and pixel format of surface must match that of the +// surfaces in vc->output_surfaces +static struct mp_image *read_output_surface(struct vo *vo, + VdpOutputSurface surface) +{ + struct vdpctx *vc = vo->priv; + VdpStatus vdp_st; + struct vdp_functions *vdp = vc->vdp; + if (!vo->params) + return NULL; + + VdpRGBAFormat fmt; + uint32_t w, h; + vdp_st = vdp->output_surface_get_parameters(surface, &fmt, &w, &h); + if (vdp_st != VDP_STATUS_OK) + return NULL; + + assert(fmt == OUTPUT_RGBA_FORMAT); + + struct mp_image *image = mp_image_alloc(IMGFMT_BGR0, w, h); + if (!image) + return NULL; + + void *dst_planes[] = { image->planes[0] }; + uint32_t dst_pitches[] = { image->stride[0] }; + vdp_st = vdp->output_surface_get_bits_native(surface, NULL, dst_planes, + dst_pitches); + CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_get_bits_native"); + + return image; +} + +static struct mp_image *get_window_screenshot(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + int last_surface = WRAP_ADD(vc->surface_num, -1, vc->num_output_surfaces); + VdpOutputSurface screen = vc->output_surfaces[last_surface]; + struct mp_image *image = read_output_surface(vo, screen); + if (image && image->w >= vo->dwidth && image->h >= vo->dheight) + mp_image_set_size(image, vo->dwidth, vo->dheight); + return image; +} + +static int query_format(struct vo *vo, int format) +{ + struct vdpctx *vc = vo->priv; + + if (mp_vdpau_get_format(format, NULL, NULL)) + return 1; + if (!vc->force_yuv && mp_vdpau_get_rgb_format(format, NULL)) + return 1; + return 0; +} + +static void destroy_vdpau_objects(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + + VdpStatus vdp_st; + + free_video_specific(vo); + + if (vc->flip_queue != VDP_INVALID_HANDLE) { + vdp_st = vdp->presentation_queue_destroy(vc->flip_queue); + CHECK_VDP_WARNING(vo, "Error when calling vdp_presentation_queue_destroy"); + } + + if (vc->flip_target != VDP_INVALID_HANDLE) { + vdp_st = vdp->presentation_queue_target_destroy(vc->flip_target); + CHECK_VDP_WARNING(vo, "Error when calling " + "vdp_presentation_queue_target_destroy"); + } + + for (int i = 0; i < vc->num_output_surfaces; i++) { + if (vc->output_surfaces[i] == VDP_INVALID_HANDLE) + continue; + vdp_st = vdp->output_surface_destroy(vc->output_surfaces[i]); + CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_destroy"); + } + if (vc->rotation_surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->output_surface_destroy(vc->rotation_surface); + CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_destroy"); + } + + for (int i = 0; i < MAX_OSD_PARTS; i++) { + struct osd_bitmap_surface *sfc = &vc->osd_surfaces[i]; + if (sfc->surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->bitmap_surface_destroy(sfc->surface); + CHECK_VDP_WARNING(vo, "Error when calling vdp_bitmap_surface_destroy"); + } + } + + mp_vdpau_destroy(vc->mpvdp); + vc->mpvdp = NULL; +} + +static void uninit(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + hwdec_devices_remove(vo->hwdec_devs, &vc->mpvdp->hwctx); + hwdec_devices_destroy(vo->hwdec_devs); + + /* Destroy all vdpau objects */ + mp_vdpau_mixer_destroy(vc->video_mixer); + destroy_vdpau_objects(vo); + + vo_x11_uninit(vo); +} + +static int preinit(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + if (!vo_x11_init(vo)) + return -1; + + if (!vo_x11_create_vo_window(vo, NULL, "vdpau")) { + vo_x11_uninit(vo); + return -1; + } + + vc->mpvdp = mp_vdpau_create_device_x11(vo->log, vo->x11->display, false); + if (!vc->mpvdp) { + vo_x11_uninit(vo); + return -1; + } + vc->mpvdp->hwctx.hw_imgfmt = IMGFMT_VDPAU; + + vo->hwdec_devs = hwdec_devices_create(); + hwdec_devices_add(vo->hwdec_devs, &vc->mpvdp->hwctx); + + vc->video_mixer = mp_vdpau_mixer_create(vc->mpvdp, vo->log); + vc->video_mixer->video_eq = mp_csp_equalizer_create(vo, vo->global); + + if (mp_vdpau_guess_if_emulated(vc->mpvdp)) { + MP_WARN(vo, "VDPAU is most likely emulated via VA-API.\n" + "This is inefficient. Use --vo=gpu instead.\n"); + } + + // Mark everything as invalid first so uninit() can tell what has been + // allocated + mark_vdpau_objects_uninitialized(vo); + + mp_vdpau_handle_preemption(vc->mpvdp, &vc->preemption_counter); + + vc->vdp_device = vc->mpvdp->vdp_device; + vc->vdp = &vc->mpvdp->vdp; + + vc->vdp->bitmap_surface_query_capabilities(vc->vdp_device, VDP_RGBA_FORMAT_A8, + &vc->supports_a8, &(uint32_t){0}, &(uint32_t){0}); + + MP_WARN(vo, "Warning: this compatibility VO is low quality and may " + "have issues with OSD, scaling, screenshots and more.\n" + "vo=gpu is the preferred choice in any case and " + "includes VDPAU support via hwdec=vdpau or vdpau-copy.\n"); + + return 0; +} + +static void checked_resize(struct vo *vo) +{ + if (!status_ok(vo)) + return; + resize(vo); +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + check_preemption(vo); + + switch (request) { + case VOCTRL_SET_PANSCAN: + checked_resize(vo); + return VO_TRUE; + case VOCTRL_SET_EQUALIZER: + vo->want_redraw = true; + return true; + case VOCTRL_RESET: + forget_frames(vo, true); + return true; + case VOCTRL_SCREENSHOT_WIN: + if (!status_ok(vo)) + return false; + *(struct mp_image **)data = get_window_screenshot(vo); + return true; + } + + int events = 0; + int r = vo_x11_control(vo, &events, request, data); + + if (events & VO_EVENT_RESIZE) { + checked_resize(vo); + } else if (events & VO_EVENT_EXPOSE) { + vo->want_redraw = true; + } + vo_event(vo, events); + + return r; +} + +#define OPT_BASE_STRUCT struct vdpctx + +const struct vo_driver video_out_vdpau = { + .description = "VDPAU with X11", + .name = "vdpau", + .caps = VO_CAP_FRAMEDROP | VO_CAP_ROTATE90, + .preinit = preinit, + .query_format = query_format, + .reconfig = reconfig, + .control = control, + .draw_frame = draw_frame, + .flip_page = flip_page, + .wakeup = vo_x11_wakeup, + .wait_events = vo_x11_wait_events, + .uninit = uninit, + .priv_size = sizeof(struct vdpctx), + .options = (const struct m_option []){ + {"chroma-deint", OPT_BOOL(chroma_deint), OPTDEF_INT(1)}, + {"pullup", OPT_BOOL(pullup)}, + {"denoise", OPT_FLOAT(denoise), M_RANGE(0, 1)}, + {"sharpen", OPT_FLOAT(sharpen), M_RANGE(-1, 1)}, + {"hqscaling", OPT_INT(hqscaling), M_RANGE(0, 9)}, + {"fps", OPT_FLOAT(user_fps)}, + {"composite-detect", OPT_BOOL(composite_detect), OPTDEF_INT(1)}, + {"queuetime-windowed", OPT_INT(flip_offset_window), OPTDEF_INT(50)}, + {"queuetime-fs", OPT_INT(flip_offset_fs), OPTDEF_INT(50)}, + {"output-surfaces", OPT_INT(num_output_surfaces), + M_RANGE(2, MAX_OUTPUT_SURFACES), OPTDEF_INT(3)}, + {"colorkey", OPT_COLOR(colorkey), + .defval = &(const struct m_color){.r = 2, .g = 5, .b = 7, .a = 255}}, + {"force-yuv", OPT_BOOL(force_yuv)}, + {NULL}, + }, + .options_prefix = "vo-vdpau", +}; |