2009-02-16 20:58:13 +00:00
|
|
|
/*
|
|
|
|
* VDPAU video output driver
|
|
|
|
*
|
2013-10-23 17:06:14 +00:00
|
|
|
* Copyright (C) 2008 NVIDIA (Rajib Mahapatra <rmahapatra@nvidia.com>)
|
2009-11-15 02:39:22 +00:00
|
|
|
* Copyright (C) 2009 Uoti Urpala
|
2009-02-16 20:58:13 +00:00
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* This file is part of mpv.
|
2009-02-16 20:58:13 +00:00
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* mpv is free software; you can redistribute it and/or modify
|
2009-02-16 20:58:13 +00:00
|
|
|
* 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.
|
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* mpv is distributed in the hope that it will be useful,
|
2009-02-16 20:58:13 +00:00
|
|
|
* 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
|
2015-04-13 07:36:54 +00:00
|
|
|
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
2009-02-16 20:58:13 +00:00
|
|
|
*/
|
|
|
|
|
2009-05-09 15:08:30 +00:00
|
|
|
/*
|
vdpau: split off decoder parts, use "new" libavcodec vdpau hwaccel API
Move the decoder parts from vo_vdpau.c to a new file vdpau_old.c. This
file is named so because because it's written against the "old"
libavcodec vdpau pseudo-decoder (e.g. "h264_vdpau").
Add support for the "new" libavcodec vdpau support. This was recently
added and replaces the "old" vdpau parts. (In fact, Libav is about to
deprecate and remove the "old" API without deprecation grace period,
so we have to support it now. Moreover, there will probably be no Libav
release which supports both, so the transition is even less smooth than
we could hope, and we have to support both the old and new API.)
Whether the old or new API is used is checked by a configure test: if
the new API is found, it is used, otherwise the old API is assumed.
Some details might be handled differently. Especially display preemption
is a bit problematic with the "new" libavcodec vdpau support: it wants
to keep a pointer to a specific vdpau API function (which can be driver
specific, because preemption might switch drivers). Also, surface IDs
are now directly stored in AVFrames (and mp_images), so they can't be
forced to VDP_INVALID_HANDLE on preemption. (This changes even with
older libavcodec versions, because mp_image always uses the newer
representation to make vo_vdpau.c simpler.)
Decoder initialization in the new code tries to deal with codec
profiles, while the old code always uses the highest profile per codec.
Surface allocation changes. Since the decoder won't call config() in
vo_vdpau.c on video size change anymore, we allow allocating surfaces
of arbitrary size instead of locking it to what the VO was configured.
The non-hwdec code also has slightly different allocation behavior now.
Enabling the old vdpau special decoders via e.g. --vd=lavc:h264_vdpau
doesn't work anymore (a warning suggesting the --hwdec option is
printed instead).
2013-07-27 23:49:45 +00:00
|
|
|
* Actual decoding is done in video/decode/vdpau.c
|
2009-02-16 20:58:13 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
2009-09-18 13:27:55 +00:00
|
|
|
#include <stdlib.h>
|
2009-05-06 20:49:51 +00:00
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdbool.h>
|
2009-11-15 02:39:22 +00:00
|
|
|
#include <limits.h>
|
2012-08-26 16:00:26 +00:00
|
|
|
#include <assert.h>
|
|
|
|
|
vdpau: split off decoder parts, use "new" libavcodec vdpau hwaccel API
Move the decoder parts from vo_vdpau.c to a new file vdpau_old.c. This
file is named so because because it's written against the "old"
libavcodec vdpau pseudo-decoder (e.g. "h264_vdpau").
Add support for the "new" libavcodec vdpau support. This was recently
added and replaces the "old" vdpau parts. (In fact, Libav is about to
deprecate and remove the "old" API without deprecation grace period,
so we have to support it now. Moreover, there will probably be no Libav
release which supports both, so the transition is even less smooth than
we could hope, and we have to support both the old and new API.)
Whether the old or new API is used is checked by a configure test: if
the new API is found, it is used, otherwise the old API is assumed.
Some details might be handled differently. Especially display preemption
is a bit problematic with the "new" libavcodec vdpau support: it wants
to keep a pointer to a specific vdpau API function (which can be driver
specific, because preemption might switch drivers). Also, surface IDs
are now directly stored in AVFrames (and mp_images), so they can't be
forced to VDP_INVALID_HANDLE on preemption. (This changes even with
older libavcodec versions, because mp_image always uses the newer
representation to make vo_vdpau.c simpler.)
Decoder initialization in the new code tries to deal with codec
profiles, while the old code always uses the highest profile per codec.
Surface allocation changes. Since the decoder won't call config() in
vo_vdpau.c on video size change anymore, we allow allocating surfaces
of arbitrary size instead of locking it to what the VO was configured.
The non-hwdec code also has slightly different allocation behavior now.
Enabling the old vdpau special decoders via e.g. --vd=lavc:h264_vdpau
doesn't work anymore (a warning suggesting the --hwdec option is
printed instead).
2013-07-27 23:49:45 +00:00
|
|
|
#include "video/vdpau.h"
|
2014-04-29 13:11:44 +00:00
|
|
|
#include "video/vdpau_mixer.h"
|
2013-11-23 20:26:31 +00:00
|
|
|
#include "video/hwdec.h"
|
2013-12-17 01:39:45 +00:00
|
|
|
#include "common/msg.h"
|
2013-12-17 01:02:25 +00:00
|
|
|
#include "options/options.h"
|
2016-01-11 18:03:40 +00:00
|
|
|
#include "mpv_talloc.h"
|
2012-11-09 00:06:43 +00:00
|
|
|
#include "vo.h"
|
2009-02-16 20:58:13 +00:00
|
|
|
#include "x11_common.h"
|
2012-11-09 00:06:43 +00:00
|
|
|
#include "video/csputils.h"
|
2013-11-24 11:58:06 +00:00
|
|
|
#include "sub/osd.h"
|
2013-12-17 01:02:25 +00:00
|
|
|
#include "options/m_option.h"
|
2012-11-09 00:06:43 +00:00
|
|
|
#include "video/mp_image.h"
|
2009-09-06 23:02:24 +00:00
|
|
|
#include "osdep/timer.h"
|
2009-02-23 09:21:57 +00:00
|
|
|
|
2014-04-08 17:13:15 +00:00
|
|
|
// Returns x + a, but wrapped around to the range [0, m)
|
|
|
|
// a must be within [-m, m], x within [0, m)
|
2010-05-06 01:46:10 +00:00
|
|
|
#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)))
|
|
|
|
|
2009-02-16 20:58:13 +00:00
|
|
|
|
|
|
|
/* number of video and output surfaces */
|
2010-05-14 02:18:38 +00:00
|
|
|
#define MAX_OUTPUT_SURFACES 15
|
2009-02-16 20:58:13 +00:00
|
|
|
|
2011-10-06 18:46:01 +00:00
|
|
|
/* Pixelformat used for output surfaces */
|
|
|
|
#define OUTPUT_RGBA_FORMAT VDP_RGBA_FORMAT_B8G8R8A8
|
|
|
|
|
2009-02-16 20:58:13 +00:00
|
|
|
/*
|
|
|
|
* Global variable declaration - VDPAU specific
|
|
|
|
*/
|
|
|
|
|
2009-05-06 18:04:37 +00:00
|
|
|
struct vdpctx {
|
2013-11-05 21:06:32 +00:00
|
|
|
struct mp_vdpau_ctx *mpvdp;
|
vdpau: split off decoder parts, use "new" libavcodec vdpau hwaccel API
Move the decoder parts from vo_vdpau.c to a new file vdpau_old.c. This
file is named so because because it's written against the "old"
libavcodec vdpau pseudo-decoder (e.g. "h264_vdpau").
Add support for the "new" libavcodec vdpau support. This was recently
added and replaces the "old" vdpau parts. (In fact, Libav is about to
deprecate and remove the "old" API without deprecation grace period,
so we have to support it now. Moreover, there will probably be no Libav
release which supports both, so the transition is even less smooth than
we could hope, and we have to support both the old and new API.)
Whether the old or new API is used is checked by a configure test: if
the new API is found, it is used, otherwise the old API is assumed.
Some details might be handled differently. Especially display preemption
is a bit problematic with the "new" libavcodec vdpau support: it wants
to keep a pointer to a specific vdpau API function (which can be driver
specific, because preemption might switch drivers). Also, surface IDs
are now directly stored in AVFrames (and mp_images), so they can't be
forced to VDP_INVALID_HANDLE on preemption. (This changes even with
older libavcodec versions, because mp_image always uses the newer
representation to make vo_vdpau.c simpler.)
Decoder initialization in the new code tries to deal with codec
profiles, while the old code always uses the highest profile per codec.
Surface allocation changes. Since the decoder won't call config() in
vo_vdpau.c on video size change anymore, we allow allocating surfaces
of arbitrary size instead of locking it to what the VO was configured.
The non-hwdec code also has slightly different allocation behavior now.
Enabling the old vdpau special decoders via e.g. --vd=lavc:h264_vdpau
doesn't work anymore (a warning suggesting the --hwdec option is
printed instead).
2013-07-27 23:49:45 +00:00
|
|
|
struct vdp_functions *vdp;
|
2009-05-06 20:42:24 +00:00
|
|
|
VdpDevice vdp_device;
|
2013-11-05 21:06:32 +00:00
|
|
|
uint64_t preemption_counter;
|
vdpau: split off decoder parts, use "new" libavcodec vdpau hwaccel API
Move the decoder parts from vo_vdpau.c to a new file vdpau_old.c. This
file is named so because because it's written against the "old"
libavcodec vdpau pseudo-decoder (e.g. "h264_vdpau").
Add support for the "new" libavcodec vdpau support. This was recently
added and replaces the "old" vdpau parts. (In fact, Libav is about to
deprecate and remove the "old" API without deprecation grace period,
so we have to support it now. Moreover, there will probably be no Libav
release which supports both, so the transition is even less smooth than
we could hope, and we have to support both the old and new API.)
Whether the old or new API is used is checked by a configure test: if
the new API is found, it is used, otherwise the old API is assumed.
Some details might be handled differently. Especially display preemption
is a bit problematic with the "new" libavcodec vdpau support: it wants
to keep a pointer to a specific vdpau API function (which can be driver
specific, because preemption might switch drivers). Also, surface IDs
are now directly stored in AVFrames (and mp_images), so they can't be
forced to VDP_INVALID_HANDLE on preemption. (This changes even with
older libavcodec versions, because mp_image always uses the newer
representation to make vo_vdpau.c simpler.)
Decoder initialization in the new code tries to deal with codec
profiles, while the old code always uses the highest profile per codec.
Surface allocation changes. Since the decoder won't call config() in
vo_vdpau.c on video size change anymore, we allow allocating surfaces
of arbitrary size instead of locking it to what the VO was configured.
The non-hwdec code also has slightly different allocation behavior now.
Enabling the old vdpau special decoders via e.g. --vd=lavc:h264_vdpau
doesn't work anymore (a warning suggesting the --hwdec option is
printed instead).
2013-07-27 23:49:45 +00:00
|
|
|
|
2013-08-17 17:57:18 +00:00
|
|
|
struct m_color colorkey;
|
|
|
|
|
2009-05-06 20:42:24 +00:00
|
|
|
VdpPresentationQueueTarget flip_target;
|
|
|
|
VdpPresentationQueue flip_queue;
|
|
|
|
|
2012-08-26 16:00:26 +00:00
|
|
|
VdpOutputSurface output_surfaces[MAX_OUTPUT_SURFACES];
|
2010-05-14 02:18:38 +00:00
|
|
|
int num_output_surfaces;
|
2013-08-18 03:12:21 +00:00
|
|
|
VdpOutputSurface black_pixel;
|
2015-07-11 03:42:22 +00:00
|
|
|
VdpOutputSurface rotation_surface;
|
2013-10-01 21:45:30 +00:00
|
|
|
|
2014-04-29 13:19:03 +00:00
|
|
|
struct mp_image *current_image;
|
2015-07-01 17:24:28 +00:00
|
|
|
int64_t current_pts;
|
2024-02-04 15:47:04 +00:00
|
|
|
int64_t current_duration;
|
2013-10-01 21:45:30 +00:00
|
|
|
|
2015-06-05 17:18:12 +00:00
|
|
|
int output_surface_w, output_surface_h;
|
2018-03-07 23:23:12 +00:00
|
|
|
int rotation;
|
2009-05-06 20:42:24 +00:00
|
|
|
|
2023-02-20 03:32:50 +00:00
|
|
|
bool force_yuv;
|
2014-04-29 13:11:44 +00:00
|
|
|
struct mp_vdpau_mixer *video_mixer;
|
2023-02-20 03:32:50 +00:00
|
|
|
bool pullup;
|
2009-05-06 20:42:24 +00:00
|
|
|
float denoise;
|
|
|
|
float sharpen;
|
2009-11-15 16:39:48 +00:00
|
|
|
int hqscaling;
|
2023-02-20 03:32:50 +00:00
|
|
|
bool chroma_deint;
|
2010-02-05 18:13:33 +00:00
|
|
|
int flip_offset_window;
|
|
|
|
int flip_offset_fs;
|
2014-12-09 16:46:35 +00:00
|
|
|
int64_t flip_offset_us;
|
2009-05-06 20:42:24 +00:00
|
|
|
|
|
|
|
VdpRect src_rect_vid;
|
|
|
|
VdpRect out_rect_vid;
|
2012-10-27 20:10:32 +00:00
|
|
|
struct mp_osd_res osd_rect;
|
2015-11-04 20:47:20 +00:00
|
|
|
VdpBool supports_a8;
|
2009-05-06 20:42:24 +00:00
|
|
|
|
video/filter: change filter API, use refcounting, remove filter DR
Change the entire filter API to use reference counted images instead
of vf_get_image().
Remove filter "direct rendering". This was useful for vf_expand and (in
rare cases) vf_sub: DR allowed these filters to pass a cropped image to
the filters before them. Then, on filtering, the image was "uncropped",
so that black bars could be added around the image without copying. This
means that in some cases, vf_expand will be slower (-vf gradfun,expand
for example).
Note that another form of DR used for in-place filters has been replaced
by simpler logic. Instead of trying to do DR, filters can check if the
image is writeable (with mp_image_is_writeable()), and do true in-place
if that's the case. This affects filters like vf_gradfun and vf_sub.
Everything has to support strides now. If something doesn't, making a
copy of the image data is required.
2012-11-05 13:25:04 +00:00
|
|
|
int surface_num; // indexes output_surfaces
|
2010-05-14 02:18:38 +00:00
|
|
|
int query_surface_num;
|
2009-11-15 02:39:22 +00:00
|
|
|
VdpTime recent_vsync_time;
|
2024-02-04 18:03:18 +00:00
|
|
|
double user_fps;
|
2023-02-20 03:32:50 +00:00
|
|
|
bool composite_detect;
|
2024-02-04 18:03:18 +00:00
|
|
|
int64_t vsync_interval;
|
2009-11-15 02:39:22 +00:00
|
|
|
uint64_t last_queue_time;
|
2010-05-14 02:18:38 +00:00
|
|
|
uint64_t queue_time[MAX_OUTPUT_SURFACES];
|
2009-11-15 02:39:22 +00:00
|
|
|
uint64_t last_ideal_time;
|
|
|
|
bool dropped_frame;
|
|
|
|
uint64_t dropped_time;
|
2009-05-06 20:42:24 +00:00
|
|
|
uint32_t vid_width, vid_height;
|
|
|
|
uint32_t image_format;
|
|
|
|
VdpYCbCrFormat vdp_pixel_format;
|
2013-08-18 03:12:21 +00:00
|
|
|
bool rgb_mode;
|
2009-05-06 20:42:24 +00:00
|
|
|
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
// OSD
|
|
|
|
struct osd_bitmap_surface {
|
2012-08-31 12:42:30 +00:00
|
|
|
VdpRGBAFormat format;
|
2012-08-26 16:00:26 +00:00
|
|
|
VdpBitmapSurface surface;
|
2016-07-01 18:05:38 +00:00
|
|
|
uint32_t surface_w, surface_h;
|
2012-09-28 19:49:32 +00:00
|
|
|
// List of surfaces to be rendered
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
struct osd_target {
|
2012-09-28 19:49:32 +00:00
|
|
|
VdpRect source;
|
|
|
|
VdpRect dest;
|
|
|
|
VdpColor color;
|
|
|
|
} *targets;
|
2012-09-28 19:49:09 +00:00
|
|
|
int targets_size;
|
|
|
|
int render_count;
|
2015-03-18 11:33:14 +00:00
|
|
|
int change_id;
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
} osd_surfaces[MAX_OSD_PARTS];
|
2009-05-06 20:49:51 +00:00
|
|
|
};
|
2009-02-16 20:58:13 +00:00
|
|
|
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
static bool status_ok(struct vo *vo);
|
|
|
|
|
2016-07-07 14:10:53 +00:00
|
|
|
static int video_to_output_surface(struct vo *vo, struct mp_image *mpi)
|
2009-02-16 20:58:13 +00:00
|
|
|
{
|
2009-05-06 18:04:37 +00:00
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
struct vdp_functions *vdp = vc->vdp;
|
2009-02-16 20:58:13 +00:00
|
|
|
VdpTime dummy;
|
|
|
|
VdpStatus vdp_st;
|
2016-07-07 14:10:53 +00:00
|
|
|
|
|
|
|
VdpOutputSurface output_surface = vc->output_surfaces[vc->surface_num];
|
|
|
|
VdpRect *output_rect = &vc->out_rect_vid;
|
|
|
|
VdpRect *video_rect = &vc->src_rect_vid;
|
2013-10-01 21:45:30 +00:00
|
|
|
|
2015-01-22 16:57:59 +00:00
|
|
|
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");
|
|
|
|
|
2015-03-23 17:15:40 +00:00
|
|
|
// 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.
|
2015-07-11 03:42:22 +00:00
|
|
|
if (vc->rgb_mode || !mpi || mpi->params.rotate != 0) {
|
2013-08-18 03:12:21 +00:00
|
|
|
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);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error clearing screen");
|
2013-08-18 03:12:21 +00:00
|
|
|
}
|
|
|
|
|
2015-03-23 17:15:40 +00:00
|
|
|
if (!mpi)
|
|
|
|
return -1;
|
|
|
|
|
2014-04-29 13:14:19 +00:00
|
|
|
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;
|
|
|
|
|
2015-07-11 03:42:22 +00:00
|
|
|
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);
|
|
|
|
}
|
2009-09-18 21:00:42 +00:00
|
|
|
return 0;
|
2009-09-18 13:27:55 +00:00
|
|
|
}
|
2009-03-25 23:32:27 +00:00
|
|
|
|
2013-10-01 21:45:30 +00:00
|
|
|
static void forget_frames(struct vo *vo, bool seek_reset)
|
2009-09-18 13:27:55 +00:00
|
|
|
{
|
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
|
2014-04-29 13:19:03 +00:00
|
|
|
if (!seek_reset)
|
|
|
|
mp_image_unrefp(&vc->current_image);
|
2013-10-01 21:45:30 +00:00
|
|
|
|
2009-11-15 02:39:22 +00:00
|
|
|
vc->dropped_frame = false;
|
2009-02-16 20:58:13 +00:00
|
|
|
}
|
|
|
|
|
2015-06-05 17:33:09 +00:00
|
|
|
static int s_size(int max, int s, int disp)
|
2014-10-01 15:12:46 +00:00
|
|
|
{
|
|
|
|
disp = MPMAX(1, disp);
|
2018-03-07 23:23:12 +00:00
|
|
|
return MPMIN(max, MPMAX(s, disp));
|
2014-10-01 15:12:46 +00:00
|
|
|
}
|
|
|
|
|
2009-05-04 00:09:50 +00:00
|
|
|
static void resize(struct vo *vo)
|
2009-02-16 20:58:13 +00:00
|
|
|
{
|
2009-05-06 18:04:37 +00:00
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
struct vdp_functions *vdp = vc->vdp;
|
2009-02-16 20:58:13 +00:00
|
|
|
VdpStatus vdp_st;
|
2012-10-27 20:10:32 +00:00
|
|
|
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;
|
2015-07-11 03:42:22 +00:00
|
|
|
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;
|
|
|
|
}
|
2012-10-04 15:16:28 +00:00
|
|
|
|
2015-06-05 17:33:09 +00:00
|
|
|
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;
|
|
|
|
|
2014-12-09 16:46:35 +00:00
|
|
|
vc->flip_offset_us = vo->opts->fullscreen ?
|
|
|
|
1000LL * vc->flip_offset_fs :
|
|
|
|
1000LL * vc->flip_offset_window;
|
2023-09-18 01:44:05 +00:00
|
|
|
vo_set_queue_params(vo, vc->flip_offset_us * 1000, 1);
|
2009-02-16 20:58:13 +00:00
|
|
|
|
2018-03-07 23:23:12 +00:00
|
|
|
if (vc->output_surface_w < vo->dwidth || vc->output_surface_h < vo->dheight ||
|
|
|
|
vc->rotation != vo->params->rotate)
|
|
|
|
{
|
2015-06-05 17:33:09 +00:00
|
|
|
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);
|
2009-02-16 20:58:13 +00:00
|
|
|
// Creation of output_surfaces
|
2012-08-26 16:00:26 +00:00
|
|
|
for (int i = 0; i < vc->num_output_surfaces; i++)
|
2010-05-26 05:54:09 +00:00
|
|
|
if (vc->output_surfaces[i] != VDP_INVALID_HANDLE) {
|
|
|
|
vdp_st = vdp->output_surface_destroy(vc->output_surfaces[i]);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error when calling "
|
|
|
|
"vdp_output_surface_destroy");
|
2010-05-26 05:54:09 +00:00
|
|
|
}
|
2012-08-26 16:00:26 +00:00
|
|
|
for (int i = 0; i < vc->num_output_surfaces; i++) {
|
2009-05-09 15:08:30 +00:00
|
|
|
vdp_st = vdp->output_surface_create(vc->vdp_device,
|
2011-10-06 18:46:01 +00:00
|
|
|
OUTPUT_RGBA_FORMAT,
|
2015-06-05 17:18:12 +00:00
|
|
|
vc->output_surface_w,
|
|
|
|
vc->output_surface_h,
|
2009-05-09 15:08:30 +00:00
|
|
|
&vc->output_surfaces[i]);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_create");
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_DBG(vo, "vdpau out create: %u\n",
|
2009-05-09 15:08:30 +00:00
|
|
|
vc->output_surfaces[i]);
|
2009-02-16 20:58:13 +00:00
|
|
|
}
|
2015-07-11 03:42:22 +00:00
|
|
|
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");
|
2018-03-07 23:23:12 +00:00
|
|
|
vc->rotation_surface = VDP_INVALID_HANDLE;
|
2015-07-11 03:42:22 +00:00
|
|
|
}
|
|
|
|
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);
|
2009-02-16 20:58:13 +00:00
|
|
|
}
|
2018-03-07 23:23:12 +00:00
|
|
|
vc->rotation = vo->params->rotate;
|
2011-12-05 04:36:20 +00:00
|
|
|
vo->want_redraw = true;
|
2009-02-16 20:58:13 +00:00
|
|
|
}
|
|
|
|
|
2009-05-04 00:09:50 +00:00
|
|
|
static int win_x11_init_vdpau_flip_queue(struct vo *vo)
|
2009-02-16 20:58:13 +00:00
|
|
|
{
|
2009-05-06 18:04:37 +00:00
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
struct vdp_functions *vdp = vc->vdp;
|
2009-05-04 00:09:50 +00:00
|
|
|
struct vo_x11_state *x11 = vo->x11;
|
2009-02-16 20:58:13 +00:00
|
|
|
VdpStatus vdp_st;
|
|
|
|
|
2009-09-06 23:02:24 +00:00
|
|
|
if (vc->flip_target == VDP_INVALID_HANDLE) {
|
|
|
|
vdp_st = vdp->presentation_queue_target_create_x11(vc->vdp_device,
|
|
|
|
x11->window,
|
|
|
|
&vc->flip_target);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_ERROR(vo, "Error when calling "
|
|
|
|
"vdp_presentation_queue_target_create_x11");
|
2009-09-06 23:02:24 +00:00
|
|
|
}
|
2009-02-16 20:58:13 +00:00
|
|
|
|
2022-04-25 11:27:18 +00:00
|
|
|
/* Empirically this seems to be the first call which fails when we
|
2009-09-06 23:02:24 +00:00
|
|
|
* 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
|
2014-05-09 19:49:32 +00:00
|
|
|
* probably not guaranteed behavior.
|
2009-09-06 23:02:24 +00:00
|
|
|
*/
|
|
|
|
if (vc->flip_queue == VDP_INVALID_HANDLE) {
|
|
|
|
vdp_st = vdp->presentation_queue_create(vc->vdp_device, vc->flip_target,
|
|
|
|
&vc->flip_queue);
|
2014-05-09 19:49:32 +00:00
|
|
|
CHECK_VDP_ERROR(vo, "Error when calling vdp_presentation_queue_create");
|
2009-09-06 23:02:24 +00:00
|
|
|
}
|
2009-02-16 20:58:13 +00:00
|
|
|
|
2013-08-17 17:57:18 +00:00
|
|
|
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);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error setting colorkey");
|
2013-08-17 17:57:18 +00:00
|
|
|
}
|
|
|
|
|
2012-07-27 00:40:38 +00:00
|
|
|
if (vc->composite_detect && vo_x11_screen_is_composited(vo)) {
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_INFO(vo, "Compositing window manager detected. Assuming timing info "
|
|
|
|
"is inaccurate.\n");
|
2014-08-17 00:50:59 +00:00
|
|
|
vc->user_fps = -1;
|
|
|
|
}
|
2009-11-15 02:39:22 +00:00
|
|
|
|
2009-02-16 20:58:13 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Free everything specific to a certain video file
|
2009-05-06 18:04:37 +00:00
|
|
|
static void free_video_specific(struct vo *vo)
|
|
|
|
{
|
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
struct vdp_functions *vdp = vc->vdp;
|
2009-02-16 20:58:13 +00:00
|
|
|
VdpStatus vdp_st;
|
|
|
|
|
2013-10-01 21:45:30 +00:00
|
|
|
forget_frames(vo, false);
|
2009-03-18 17:02:29 +00:00
|
|
|
|
2013-08-18 03:12:21 +00:00
|
|
|
if (vc->black_pixel != VDP_INVALID_HANDLE) {
|
|
|
|
vdp_st = vdp->output_surface_destroy(vc->black_pixel);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_destroy");
|
2013-08-18 03:12:21 +00:00
|
|
|
}
|
|
|
|
vc->black_pixel = VDP_INVALID_HANDLE;
|
|
|
|
}
|
|
|
|
|
2009-10-22 01:21:14 +00:00
|
|
|
static int initialize_vdpau_objects(struct vo *vo)
|
2009-09-06 23:02:24 +00:00
|
|
|
{
|
|
|
|
struct vdpctx *vc = vo->priv;
|
2013-08-18 03:12:21 +00:00
|
|
|
struct vdp_functions *vdp = vc->vdp;
|
|
|
|
VdpStatus vdp_st;
|
2009-09-06 23:02:24 +00:00
|
|
|
|
2015-05-28 19:54:02 +00:00
|
|
|
mp_vdpau_get_format(vc->image_format, NULL, &vc->vdp_pixel_format);
|
vdpau: split off decoder parts, use "new" libavcodec vdpau hwaccel API
Move the decoder parts from vo_vdpau.c to a new file vdpau_old.c. This
file is named so because because it's written against the "old"
libavcodec vdpau pseudo-decoder (e.g. "h264_vdpau").
Add support for the "new" libavcodec vdpau support. This was recently
added and replaces the "old" vdpau parts. (In fact, Libav is about to
deprecate and remove the "old" API without deprecation grace period,
so we have to support it now. Moreover, there will probably be no Libav
release which supports both, so the transition is even less smooth than
we could hope, and we have to support both the old and new API.)
Whether the old or new API is used is checked by a configure test: if
the new API is found, it is used, otherwise the old API is assumed.
Some details might be handled differently. Especially display preemption
is a bit problematic with the "new" libavcodec vdpau support: it wants
to keep a pointer to a specific vdpau API function (which can be driver
specific, because preemption might switch drivers). Also, surface IDs
are now directly stored in AVFrames (and mp_images), so they can't be
forced to VDP_INVALID_HANDLE on preemption. (This changes even with
older libavcodec versions, because mp_image always uses the newer
representation to make vo_vdpau.c simpler.)
Decoder initialization in the new code tries to deal with codec
profiles, while the old code always uses the highest profile per codec.
Surface allocation changes. Since the decoder won't call config() in
vo_vdpau.c on video size change anymore, we allow allocating surfaces
of arbitrary size instead of locking it to what the VO was configured.
The non-hwdec code also has slightly different allocation behavior now.
Enabling the old vdpau special decoders via e.g. --vd=lavc:h264_vdpau
doesn't work anymore (a warning suggesting the --hwdec option is
printed instead).
2013-07-27 23:49:45 +00:00
|
|
|
|
2014-04-29 13:11:44 +00:00
|
|
|
vc->video_mixer->initialized = false;
|
|
|
|
|
2009-09-06 23:02:24 +00:00
|
|
|
if (win_x11_init_vdpau_flip_queue(vo) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2014-05-22 18:55:05 +00:00
|
|
|
if (vc->black_pixel == VDP_INVALID_HANDLE) {
|
2013-08-18 03:12:21 +00:00
|
|
|
vdp_st = vdp->output_surface_create(vc->vdp_device, OUTPUT_RGBA_FORMAT,
|
|
|
|
1, 1, &vc->black_pixel);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_ERROR(vo, "Allocating clearing surface");
|
2013-08-18 03:12:21 +00:00
|
|
|
const char data[4] = {0};
|
|
|
|
vdp_st = vdp->output_surface_put_bits_native(vc->black_pixel,
|
|
|
|
(const void*[]){data},
|
|
|
|
(uint32_t[]){4}, NULL);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_ERROR(vo, "Initializing clearing surface");
|
2013-08-18 03:12:21 +00:00
|
|
|
}
|
2009-09-06 23:02:24 +00:00
|
|
|
|
2013-10-01 21:45:30 +00:00
|
|
|
forget_frames(vo, false);
|
2009-09-06 23:02:24 +00:00
|
|
|
resize(vo);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mark_vdpau_objects_uninitialized(struct vo *vo)
|
|
|
|
{
|
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
|
2013-10-01 21:45:30 +00:00
|
|
|
forget_frames(vo, false);
|
2013-08-18 03:12:21 +00:00
|
|
|
vc->black_pixel = VDP_INVALID_HANDLE;
|
2009-09-06 23:02:24 +00:00
|
|
|
vc->flip_queue = VDP_INVALID_HANDLE;
|
|
|
|
vc->flip_target = VDP_INVALID_HANDLE;
|
2012-08-26 16:00:26 +00:00
|
|
|
for (int i = 0; i < MAX_OUTPUT_SURFACES; i++)
|
2009-09-06 23:02:24 +00:00
|
|
|
vc->output_surfaces[i] = VDP_INVALID_HANDLE;
|
2015-07-11 03:42:22 +00:00
|
|
|
vc->rotation_surface = VDP_INVALID_HANDLE;
|
2009-09-06 23:02:24 +00:00
|
|
|
vc->vdp_device = VDP_INVALID_HANDLE;
|
2012-09-28 19:49:09 +00:00
|
|
|
for (int i = 0; i < MAX_OSD_PARTS; i++) {
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
struct osd_bitmap_surface *sfc = &vc->osd_surfaces[i];
|
2015-03-18 11:33:14 +00:00
|
|
|
sfc->change_id = 0;
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
*sfc = (struct osd_bitmap_surface){
|
2012-09-28 19:49:09 +00:00
|
|
|
.surface = VDP_INVALID_HANDLE,
|
|
|
|
};
|
|
|
|
}
|
2015-06-05 17:18:12 +00:00
|
|
|
vc->output_surface_w = vc->output_surface_h = -1;
|
2009-09-06 23:02:24 +00:00
|
|
|
}
|
|
|
|
|
2014-05-09 19:49:32 +00:00
|
|
|
static bool check_preemption(struct vo *vo)
|
2009-09-06 23:02:24 +00:00
|
|
|
{
|
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
|
2014-05-09 19:49:32 +00:00
|
|
|
int r = mp_vdpau_handle_preemption(vc->mpvdp, &vc->preemption_counter);
|
|
|
|
if (r < 1) {
|
2009-09-06 23:02:24 +00:00
|
|
|
mark_vdpau_objects_uninitialized(vo);
|
2014-05-09 19:49:32 +00:00
|
|
|
if (r < 0)
|
|
|
|
return false;
|
|
|
|
vc->vdp_device = vc->mpvdp->vdp_device;
|
|
|
|
if (initialize_vdpau_objects(vo) < 0)
|
|
|
|
return false;
|
2009-09-06 23:02:24 +00:00
|
|
|
}
|
2014-05-09 19:49:32 +00:00
|
|
|
return true;
|
2009-09-06 23:02:24 +00:00
|
|
|
}
|
|
|
|
|
vdpau: split off decoder parts, use "new" libavcodec vdpau hwaccel API
Move the decoder parts from vo_vdpau.c to a new file vdpau_old.c. This
file is named so because because it's written against the "old"
libavcodec vdpau pseudo-decoder (e.g. "h264_vdpau").
Add support for the "new" libavcodec vdpau support. This was recently
added and replaces the "old" vdpau parts. (In fact, Libav is about to
deprecate and remove the "old" API without deprecation grace period,
so we have to support it now. Moreover, there will probably be no Libav
release which supports both, so the transition is even less smooth than
we could hope, and we have to support both the old and new API.)
Whether the old or new API is used is checked by a configure test: if
the new API is found, it is used, otherwise the old API is assumed.
Some details might be handled differently. Especially display preemption
is a bit problematic with the "new" libavcodec vdpau support: it wants
to keep a pointer to a specific vdpau API function (which can be driver
specific, because preemption might switch drivers). Also, surface IDs
are now directly stored in AVFrames (and mp_images), so they can't be
forced to VDP_INVALID_HANDLE on preemption. (This changes even with
older libavcodec versions, because mp_image always uses the newer
representation to make vo_vdpau.c simpler.)
Decoder initialization in the new code tries to deal with codec
profiles, while the old code always uses the highest profile per codec.
Surface allocation changes. Since the decoder won't call config() in
vo_vdpau.c on video size change anymore, we allow allocating surfaces
of arbitrary size instead of locking it to what the VO was configured.
The non-hwdec code also has slightly different allocation behavior now.
Enabling the old vdpau special decoders via e.g. --vd=lavc:h264_vdpau
doesn't work anymore (a warning suggesting the --hwdec option is
printed instead).
2013-07-27 23:49:45 +00:00
|
|
|
static bool status_ok(struct vo *vo)
|
|
|
|
{
|
2014-05-09 19:49:32 +00:00
|
|
|
return vo->config_ok && check_preemption(vo);
|
vdpau: split off decoder parts, use "new" libavcodec vdpau hwaccel API
Move the decoder parts from vo_vdpau.c to a new file vdpau_old.c. This
file is named so because because it's written against the "old"
libavcodec vdpau pseudo-decoder (e.g. "h264_vdpau").
Add support for the "new" libavcodec vdpau support. This was recently
added and replaces the "old" vdpau parts. (In fact, Libav is about to
deprecate and remove the "old" API without deprecation grace period,
so we have to support it now. Moreover, there will probably be no Libav
release which supports both, so the transition is even less smooth than
we could hope, and we have to support both the old and new API.)
Whether the old or new API is used is checked by a configure test: if
the new API is found, it is used, otherwise the old API is assumed.
Some details might be handled differently. Especially display preemption
is a bit problematic with the "new" libavcodec vdpau support: it wants
to keep a pointer to a specific vdpau API function (which can be driver
specific, because preemption might switch drivers). Also, surface IDs
are now directly stored in AVFrames (and mp_images), so they can't be
forced to VDP_INVALID_HANDLE on preemption. (This changes even with
older libavcodec versions, because mp_image always uses the newer
representation to make vo_vdpau.c simpler.)
Decoder initialization in the new code tries to deal with codec
profiles, while the old code always uses the highest profile per codec.
Surface allocation changes. Since the decoder won't call config() in
vo_vdpau.c on video size change anymore, we allow allocating surfaces
of arbitrary size instead of locking it to what the VO was configured.
The non-hwdec code also has slightly different allocation behavior now.
Enabling the old vdpau special decoders via e.g. --vd=lavc:h264_vdpau
doesn't work anymore (a warning suggesting the --hwdec option is
printed instead).
2013-07-27 23:49:45 +00:00
|
|
|
}
|
|
|
|
|
2009-02-16 20:58:13 +00:00
|
|
|
/*
|
|
|
|
* connect to X server, create and map window, initialize all
|
|
|
|
* VDPAU objects, create different surfaces etc.
|
|
|
|
*/
|
2015-10-03 16:20:16 +00:00
|
|
|
static int reconfig(struct vo *vo, struct mp_image_params *params)
|
2009-02-16 20:58:13 +00:00
|
|
|
{
|
2009-05-06 20:42:24 +00:00
|
|
|
struct vdpctx *vc = vo->priv;
|
2015-06-04 20:29:21 +00:00
|
|
|
struct vdp_functions *vdp = vc->vdp;
|
|
|
|
VdpStatus vdp_st;
|
2009-02-16 20:58:13 +00:00
|
|
|
|
2014-05-09 19:49:32 +00:00
|
|
|
if (!check_preemption(vo))
|
2021-08-09 06:31:43 +00:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
2009-11-15 13:50:28 +00:00
|
|
|
|
2015-06-04 20:29:21 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-01-24 20:22:25 +00:00
|
|
|
vc->image_format = params->imgfmt;
|
|
|
|
vc->vid_width = params->w;
|
|
|
|
vc->vid_height = params->h;
|
2011-10-06 18:46:01 +00:00
|
|
|
|
2014-05-22 18:55:17 +00:00
|
|
|
vc->rgb_mode = mp_vdpau_get_rgb_format(params->imgfmt, NULL);
|
2013-08-18 03:12:21 +00:00
|
|
|
|
2009-05-06 18:04:37 +00:00
|
|
|
free_video_specific(vo);
|
2009-02-16 20:58:13 +00:00
|
|
|
|
2015-09-30 21:31:34 +00:00
|
|
|
vo_x11_config_vo_window(vo);
|
2009-02-16 20:58:13 +00:00
|
|
|
|
2009-09-06 23:02:24 +00:00
|
|
|
if (initialize_vdpau_objects(vo) < 0)
|
2009-02-16 20:58:13 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
static void draw_osd_part(struct vo *vo, int index)
|
2009-05-06 18:04:37 +00:00
|
|
|
{
|
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
struct vdp_functions *vdp = vc->vdp;
|
2009-02-23 09:21:57 +00:00
|
|
|
VdpStatus vdp_st;
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
struct osd_bitmap_surface *sfc = &vc->osd_surfaces[index];
|
2009-05-06 20:42:24 +00:00
|
|
|
VdpOutputSurface output_surface = vc->output_surfaces[vc->surface_num];
|
2009-02-23 09:21:57 +00:00
|
|
|
int i;
|
|
|
|
|
2009-05-09 15:08:30 +00:00
|
|
|
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 =
|
2013-11-18 13:02:14 +00:00
|
|
|
VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ZERO,
|
2009-05-09 15:08:30 +00:00
|
|
|
.blend_factor_destination_color =
|
|
|
|
VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
|
|
|
|
.blend_factor_destination_alpha =
|
2013-11-18 13:02:14 +00:00
|
|
|
VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ZERO,
|
2009-05-09 15:08:30 +00:00
|
|
|
.blend_equation_color = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD,
|
|
|
|
.blend_equation_alpha = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD,
|
|
|
|
};
|
|
|
|
|
2012-10-05 18:37:16 +00:00
|
|
|
VdpOutputSurfaceRenderBlendState blend_state_premultiplied = blend_state;
|
|
|
|
blend_state_premultiplied.blend_factor_source_color =
|
|
|
|
VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE;
|
|
|
|
|
2012-09-28 19:49:09 +00:00
|
|
|
for (i = 0; i < sfc->render_count; i++) {
|
2012-10-05 18:37:16 +00:00
|
|
|
VdpOutputSurfaceRenderBlendState *blend = &blend_state;
|
|
|
|
if (sfc->format == VDP_RGBA_FORMAT_B8G8R8A8)
|
|
|
|
blend = &blend_state_premultiplied;
|
2009-05-09 15:08:30 +00:00
|
|
|
vdp_st = vdp->
|
|
|
|
output_surface_render_bitmap_surface(output_surface,
|
2012-09-28 19:49:09 +00:00
|
|
|
&sfc->targets[i].dest,
|
|
|
|
sfc->surface,
|
|
|
|
&sfc->targets[i].source,
|
|
|
|
&sfc->targets[i].color,
|
2012-10-05 18:37:16 +00:00
|
|
|
blend,
|
2009-05-09 15:08:30 +00:00
|
|
|
VDP_OUTPUT_SURFACE_RENDER_ROTATE_0);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "OSD: Error when rendering");
|
2009-02-23 09:21:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-01 18:05:38 +00:00
|
|
|
static int next_pow2(int v)
|
|
|
|
{
|
|
|
|
for (int x = 0; x < 30; x++) {
|
|
|
|
if ((1 << x) >= v)
|
|
|
|
return 1 << x;
|
|
|
|
}
|
|
|
|
return INT_MAX;
|
|
|
|
}
|
|
|
|
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
static void generate_osd_part(struct vo *vo, struct sub_bitmaps *imgs)
|
2009-05-06 18:04:37 +00:00
|
|
|
{
|
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
struct vdp_functions *vdp = vc->vdp;
|
2009-02-23 09:21:57 +00:00
|
|
|
VdpStatus vdp_st;
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
struct osd_bitmap_surface *sfc = &vc->osd_surfaces[imgs->render_index];
|
2011-10-27 10:07:10 +00:00
|
|
|
|
2015-03-18 11:33:14 +00:00
|
|
|
if (imgs->change_id == sfc->change_id)
|
2012-08-26 16:00:26 +00:00
|
|
|
return; // Nothing changed and we still have the old data
|
2011-10-27 10:07:10 +00:00
|
|
|
|
2016-07-01 18:05:38 +00:00
|
|
|
sfc->change_id = imgs->change_id;
|
2012-09-28 19:49:09 +00:00
|
|
|
sfc->render_count = 0;
|
2012-08-26 16:00:26 +00:00
|
|
|
|
2012-10-01 22:25:06 +00:00
|
|
|
if (imgs->format == SUBBITMAP_EMPTY || imgs->num_parts == 0)
|
2012-08-31 12:42:30 +00:00
|
|
|
return;
|
2012-08-26 16:00:26 +00:00
|
|
|
|
2012-08-31 12:42:30 +00:00
|
|
|
VdpRGBAFormat format;
|
2012-09-28 19:19:36 +00:00
|
|
|
switch (imgs->format) {
|
2012-08-31 12:42:30 +00:00
|
|
|
case SUBBITMAP_LIBASS:
|
|
|
|
format = VDP_RGBA_FORMAT_A8;
|
|
|
|
break;
|
2022-01-11 20:03:27 +00:00
|
|
|
case SUBBITMAP_BGRA:
|
2012-08-31 12:42:30 +00:00
|
|
|
format = VDP_RGBA_FORMAT_B8G8R8A8;
|
|
|
|
break;
|
|
|
|
default:
|
2023-01-10 18:26:51 +00:00
|
|
|
MP_ASSERT_UNREACHABLE();
|
2012-08-31 12:42:30 +00:00
|
|
|
};
|
2016-07-01 18:05:38 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-08-26 16:00:26 +00:00
|
|
|
if (sfc->surface != VDP_INVALID_HANDLE) {
|
|
|
|
vdp_st = vdp->bitmap_surface_destroy(sfc->surface);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error when calling vdp_bitmap_surface_destroy");
|
2010-05-26 05:54:09 +00:00
|
|
|
}
|
2016-07-01 18:05:38 +00:00
|
|
|
|
|
|
|
VdpBitmapSurface surface;
|
2012-08-31 12:42:30 +00:00
|
|
|
vdp_st = vdp->bitmap_surface_create(vc->vdp_device, format,
|
2016-07-01 18:05:38 +00:00
|
|
|
r_w, r_h, true, &surface);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "OSD: error when creating surface");
|
2016-07-01 18:05:38 +00:00
|
|
|
if (vdp_st != VDP_STATUS_OK)
|
2015-11-06 20:12:20 +00:00
|
|
|
return;
|
2009-02-23 09:21:57 +00:00
|
|
|
|
2016-07-01 18:05:38 +00:00
|
|
|
sfc->surface = surface;
|
|
|
|
sfc->surface_w = r_w;
|
|
|
|
sfc->surface_h = r_h;
|
|
|
|
sfc->format = format;
|
2012-09-28 19:19:36 +00:00
|
|
|
}
|
2012-08-26 16:00:26 +00:00
|
|
|
|
2016-07-01 18:05:38 +00:00
|
|
|
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++) {
|
2012-09-28 19:19:36 +00:00
|
|
|
struct sub_bitmap *b = &imgs->parts[i];
|
2016-07-01 18:05:38 +00:00
|
|
|
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};
|
2012-09-28 19:19:36 +00:00
|
|
|
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;
|
2012-08-31 12:42:30 +00:00
|
|
|
}
|
2012-08-26 16:00:26 +00:00
|
|
|
}
|
|
|
|
}
|
2011-10-27 10:07:10 +00:00
|
|
|
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs)
|
2012-08-26 16:00:26 +00:00
|
|
|
{
|
|
|
|
struct vo *vo = ctx;
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
generate_osd_part(vo, imgs);
|
|
|
|
draw_osd_part(vo, imgs->render_index);
|
2012-08-26 16:00:26 +00:00
|
|
|
}
|
|
|
|
|
2014-06-15 18:46:57 +00:00
|
|
|
static void draw_osd(struct vo *vo)
|
2012-08-26 16:00:26 +00:00
|
|
|
{
|
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
if (!status_ok(vo))
|
2012-08-26 16:00:26 +00:00
|
|
|
return;
|
|
|
|
|
2015-11-04 20:47:20 +00:00
|
|
|
bool formats[SUBBITMAP_COUNT] = {
|
|
|
|
[SUBBITMAP_LIBASS] = vc->supports_a8,
|
2022-01-11 20:03:27 +00:00
|
|
|
[SUBBITMAP_BGRA] = true,
|
2012-08-26 16:00:26 +00:00
|
|
|
};
|
|
|
|
|
2014-06-15 18:46:57 +00:00
|
|
|
double pts = vc->current_image ? vc->current_image->pts : 0;
|
|
|
|
osd_draw(vo->osd, vc->osd_rect, pts, 0, formats, draw_osd_cb, vo);
|
2009-02-16 20:58:13 +00:00
|
|
|
}
|
|
|
|
|
2010-05-14 02:18:38 +00:00
|
|
|
static int update_presentation_queue_status(struct vo *vo)
|
2009-02-16 20:58:13 +00:00
|
|
|
{
|
2009-05-06 18:04:37 +00:00
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
struct vdp_functions *vdp = vc->vdp;
|
2009-02-16 20:58:13 +00:00
|
|
|
VdpStatus vdp_st;
|
2009-05-09 15:08:30 +00:00
|
|
|
|
2010-05-14 02:18:38 +00:00
|
|
|
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);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error calling "
|
2010-05-14 02:18:38 +00:00
|
|
|
"presentation_queue_query_surface_status");
|
2014-04-03 17:41:09 +00:00
|
|
|
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);
|
|
|
|
}
|
2010-05-14 02:18:38 +00:00
|
|
|
if (status == VDP_PRESENTATION_QUEUE_STATUS_QUEUED)
|
|
|
|
break;
|
|
|
|
if (vc->vsync_interval > 1) {
|
|
|
|
uint64_t qtime = vc->queue_time[vc->query_surface_num];
|
2024-02-04 18:03:18 +00:00
|
|
|
double diff = MP_TIME_NS_TO_MS((int64_t)vtime - (int64_t)qtime);
|
|
|
|
MP_TRACE(vo, "Queue time difference: %.4f ms\n", diff);
|
2010-05-14 02:18:38 +00:00
|
|
|
if (vtime < qtime + vc->vsync_interval / 2)
|
2024-02-04 18:03:18 +00:00
|
|
|
MP_VERBOSE(vo, "Frame shown too early (%.4f ms)\n", diff);
|
2010-05-14 02:18:38 +00:00
|
|
|
if (vtime > qtime + vc->vsync_interval)
|
2024-02-04 18:03:18 +00:00
|
|
|
MP_VERBOSE(vo, "Frame shown late (%.4f ms)\n", diff);
|
2010-05-14 02:18:38 +00:00
|
|
|
}
|
|
|
|
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);
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_DBG(vo, "Queued surface count (before add): %d\n", num_queued);
|
2010-05-14 02:18:38 +00:00
|
|
|
return num_queued;
|
2009-11-15 02:39:22 +00:00
|
|
|
}
|
|
|
|
|
2014-04-08 17:02:57 +00:00
|
|
|
// Return the timestamp of the vsync that must have happened before ts.
|
|
|
|
static inline uint64_t prev_vsync(struct vdpctx *vc, uint64_t ts)
|
2009-11-15 02:39:22 +00:00
|
|
|
{
|
2014-04-08 17:02:57 +00:00
|
|
|
int64_t diff = (int64_t)(ts - vc->recent_vsync_time);
|
|
|
|
int64_t offset = diff % vc->vsync_interval;
|
|
|
|
if (offset < 0)
|
|
|
|
offset += vc->vsync_interval;
|
2009-11-15 02:39:22 +00:00
|
|
|
return ts - offset;
|
|
|
|
}
|
|
|
|
|
2015-07-01 17:24:28 +00:00
|
|
|
static void flip_page(struct vo *vo)
|
2009-11-15 02:39:22 +00:00
|
|
|
{
|
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
struct vdp_functions *vdp = vc->vdp;
|
|
|
|
VdpStatus vdp_st;
|
|
|
|
|
2024-02-04 15:47:04 +00:00
|
|
|
int64_t pts_ns = vc->current_pts;
|
|
|
|
int64_t duration = vc->current_duration;
|
2015-07-01 17:24:28 +00:00
|
|
|
|
2014-09-20 13:14:43 +00:00
|
|
|
vc->dropped_frame = true; // changed at end if false
|
|
|
|
|
2014-05-09 19:49:32 +00:00
|
|
|
if (!check_preemption(vo))
|
2015-07-01 17:24:28 +00:00
|
|
|
goto drop;
|
2009-09-06 23:02:24 +00:00
|
|
|
|
2014-08-17 00:50:59 +00:00
|
|
|
vc->vsync_interval = 1;
|
|
|
|
if (vc->user_fps > 0) {
|
|
|
|
vc->vsync_interval = 1e9 / vc->user_fps;
|
|
|
|
} else if (vc->user_fps == 0) {
|
2023-09-16 02:45:24 +00:00
|
|
|
vc->vsync_interval = vo_get_vsync_interval(vo);
|
2014-08-17 00:50:59 +00:00
|
|
|
}
|
2015-07-01 17:25:13 +00:00
|
|
|
vc->vsync_interval = MPMAX(vc->vsync_interval, 1);
|
2014-08-17 00:50:59 +00:00
|
|
|
|
2012-07-27 00:40:38 +00:00
|
|
|
if (vc->vsync_interval == 1)
|
2009-11-15 02:39:22 +00:00
|
|
|
duration = -1; // Make sure drop logic is disabled
|
|
|
|
|
vo_vdpau: simplify time management and make it more robust
vo_vdpau used a somewhat complicated and fragile mechanism to convert
the vdpau time to internal mpv time. This was fragile as in it couldn't
deal well with Mesa's (apparently) random timestamps, which can change
the base offset in multiple situations. It can happen when moving the
mpv window to a different screen, and somehow it also happens when
pausing the player.
It seems this mechanism to synchronize the vdpau time is not actually
needed. There are only 2 places where sync_vdptime() is used (i.e.
returning the current vdpau time interpolated by system time).
The first call is for determining the PTS used to queue a frame. This
also uses convert_to_vdptime(). It's easily replaced by querying the
time directly, and adding the wait time to it (rel_pts_ns in the patch).
The second call is pretty odd: it updates the vdpau time a second time
in the same function. From what I can see, this can matter only if
update_presentation_queue_status() is very slow. I'm not sure what to
make out of this, because the call merely queries the presentation
queue. Just assume it isn't slow, and that we don't have to update the
time.
Another potential issue with this is that we call VdpPresentationQueueGetTime()
every frame now, instead of every 5 seconds and interpolating the other
calls via system time. More over, this is per video frame (which can be
portantially dropped, and not per actually displayed frame. Assume this
doesn't matter.
This simplifies the code, and should make it more robust on Mesa. But
note that what Mesa does is obviously insane - this is one situation
where you really need a stable time source. There are still plenty of
race condition windows where things can go wrong, although this commit
should drastically reduce the possibility of this.
In my tests, everything worked well. But I have no access to a Mesa
system with vdpau, so it needs testing by others.
See github issues #520, #694, #695.
2014-04-07 16:33:25 +00:00
|
|
|
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");
|
|
|
|
|
2024-02-04 15:47:04 +00:00
|
|
|
int64_t rel_pts_ns = pts_ns - mp_time_ns();
|
|
|
|
if (!pts_ns || rel_pts_ns < 0)
|
vo_vdpau: simplify time management and make it more robust
vo_vdpau used a somewhat complicated and fragile mechanism to convert
the vdpau time to internal mpv time. This was fragile as in it couldn't
deal well with Mesa's (apparently) random timestamps, which can change
the base offset in multiple situations. It can happen when moving the
mpv window to a different screen, and somehow it also happens when
pausing the player.
It seems this mechanism to synchronize the vdpau time is not actually
needed. There are only 2 places where sync_vdptime() is used (i.e.
returning the current vdpau time interpolated by system time).
The first call is for determining the PTS used to queue a frame. This
also uses convert_to_vdptime(). It's easily replaced by querying the
time directly, and adding the wait time to it (rel_pts_ns in the patch).
The second call is pretty odd: it updates the vdpau time a second time
in the same function. From what I can see, this can matter only if
update_presentation_queue_status() is very slow. I'm not sure what to
make out of this, because the call merely queries the presentation
queue. Just assume it isn't slow, and that we don't have to update the
time.
Another potential issue with this is that we call VdpPresentationQueueGetTime()
every frame now, instead of every 5 seconds and interpolating the other
calls via system time. More over, this is per video frame (which can be
portantially dropped, and not per actually displayed frame. Assume this
doesn't matter.
This simplifies the code, and should make it more robust on Mesa. But
note that what Mesa does is obviously insane - this is one situation
where you really need a stable time source. There are still plenty of
race condition windows where things can go wrong, although this commit
should drastically reduce the possibility of this.
In my tests, everything worked well. But I have no access to a Mesa
system with vdpau, so it needs testing by others.
See github issues #520, #694, #695.
2014-04-07 16:33:25 +00:00
|
|
|
rel_pts_ns = 0;
|
2014-02-02 11:07:08 +00:00
|
|
|
|
vo_vdpau: simplify time management and make it more robust
vo_vdpau used a somewhat complicated and fragile mechanism to convert
the vdpau time to internal mpv time. This was fragile as in it couldn't
deal well with Mesa's (apparently) random timestamps, which can change
the base offset in multiple situations. It can happen when moving the
mpv window to a different screen, and somehow it also happens when
pausing the player.
It seems this mechanism to synchronize the vdpau time is not actually
needed. There are only 2 places where sync_vdptime() is used (i.e.
returning the current vdpau time interpolated by system time).
The first call is for determining the PTS used to queue a frame. This
also uses convert_to_vdptime(). It's easily replaced by querying the
time directly, and adding the wait time to it (rel_pts_ns in the patch).
The second call is pretty odd: it updates the vdpau time a second time
in the same function. From what I can see, this can matter only if
update_presentation_queue_status() is very slow. I'm not sure what to
make out of this, because the call merely queries the presentation
queue. Just assume it isn't slow, and that we don't have to update the
time.
Another potential issue with this is that we call VdpPresentationQueueGetTime()
every frame now, instead of every 5 seconds and interpolating the other
calls via system time. More over, this is per video frame (which can be
portantially dropped, and not per actually displayed frame. Assume this
doesn't matter.
This simplifies the code, and should make it more robust on Mesa. But
note that what Mesa does is obviously insane - this is one situation
where you really need a stable time source. There are still plenty of
race condition windows where things can go wrong, although this commit
should drastically reduce the possibility of this.
In my tests, everything worked well. But I have no access to a Mesa
system with vdpau, so it needs testing by others.
See github issues #520, #694, #695.
2014-04-07 16:33:25 +00:00
|
|
|
uint64_t now = vdp_time;
|
|
|
|
uint64_t pts = now + rel_pts_ns;
|
2009-11-15 02:39:22 +00:00
|
|
|
uint64_t ideal_pts = pts;
|
|
|
|
uint64_t npts = duration >= 0 ? pts + duration : UINT64_MAX;
|
|
|
|
|
2014-04-08 18:16:53 +00:00
|
|
|
/* This should normally never happen.
|
|
|
|
* - The last queued frame can't have a PTS that goes more than 50ms in the
|
video: move display and timing to a separate thread
The VO is run inside its own thread. It also does most of video timing.
The playloop hands the image data and a realtime timestamp to the VO,
and the VO does the rest.
In particular, this allows the playloop to do other things, instead of
blocking for video redraw. But if anything accesses the VO during video
timing, it will block.
This also fixes vo_sdl.c event handling; but that is only a side-effect,
since reimplementing the broken way would require more effort.
Also drop --softsleep. In theory, this option helps if the kernel's
sleeping mechanism is too inaccurate for video timing. In practice, I
haven't ever encountered a situation where it helps, and it just burns
CPU cycles. On the other hand it's probably actively harmful, because
it prevents the libavcodec decoder threads from doing real work.
Side note:
Originally, I intended that multiple frames can be queued to the VO. But
this is not done, due to problems with OSD and other certain features.
OSD in particular is simply designed in a way that it can be neither
timed nor copied, so you do have to render it into the video frame
before you can draw the next frame. (Subtitles have no such restriction.
sd_lavc was even updated to fix this.) It seems the right solution to
queuing multiple VO frames is rendering on VO-backed framebuffers, like
vo_vdpau.c does. This requires VO driver support, and is out of scope
of this commit.
As consequence, the VO has a queue size of 1. The existing video queue
is just needed to compute frame duration, and will be moved out in the
next commit.
2014-08-12 21:02:08 +00:00
|
|
|
* future. This is guaranteed by vo.c, which currently actually queues
|
2014-12-09 16:46:35 +00:00
|
|
|
* ahead by roughly the flip queue offset. Just to be sure
|
2014-04-08 18:16:53 +00:00
|
|
|
* give some additional room by doubling the time.
|
|
|
|
* - The last vsync can never be in the future.
|
|
|
|
*/
|
2014-12-09 16:46:35 +00:00
|
|
|
int64_t max_pts_ahead = vc->flip_offset_us * 1000 * 2;
|
2014-04-08 18:16:53 +00:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2014-04-08 17:02:57 +00:00
|
|
|
#define PREV_VSYNC(ts) prev_vsync(vc, ts)
|
2009-11-15 02:39:22 +00:00
|
|
|
|
|
|
|
/* 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.
|
|
|
|
*/
|
2019-10-31 10:24:20 +00:00
|
|
|
if (now > PREV_VSYNC(MPMAX(pts, vc->last_queue_time + vc->vsync_interval)))
|
2009-11-15 02:39:22 +00:00
|
|
|
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);
|
2014-09-20 12:54:19 +00:00
|
|
|
if (pts < vsync + vc->vsync_interval / 4
|
2014-04-08 17:02:57 +00:00
|
|
|
&& (vsync - PREV_VSYNC(vc->last_queue_time)
|
2014-09-20 12:54:19 +00:00
|
|
|
> pts - vc->last_ideal_time + vc->vsync_interval / 2
|
2015-03-02 18:09:25 +00:00
|
|
|
|| (vc->dropped_frame && vsync > vc->dropped_time)))
|
2014-09-20 12:54:19 +00:00
|
|
|
pts -= vc->vsync_interval / 2;
|
2009-11-15 02:39:22 +00:00
|
|
|
|
|
|
|
vc->dropped_time = ideal_pts;
|
|
|
|
|
2019-10-31 10:24:20 +00:00
|
|
|
pts = MPMAX(pts, vc->last_queue_time + vc->vsync_interval);
|
|
|
|
pts = MPMAX(pts, now);
|
2014-09-20 12:54:19 +00:00
|
|
|
if (npts < PREV_VSYNC(pts) + vc->vsync_interval)
|
2015-07-01 17:24:28 +00:00
|
|
|
goto drop;
|
2009-11-15 02:39:22 +00:00
|
|
|
|
2010-05-14 02:18:38 +00:00
|
|
|
int num_flips = update_presentation_queue_status(vo);
|
|
|
|
vsync = vc->recent_vsync_time + num_flips * vc->vsync_interval;
|
2019-10-31 10:24:20 +00:00
|
|
|
pts = MPMAX(pts, now);
|
|
|
|
pts = MPMAX(pts, vsync + (vc->vsync_interval >> 2));
|
2009-11-15 02:39:22 +00:00
|
|
|
vsync = PREV_VSYNC(pts);
|
2014-09-20 12:54:19 +00:00
|
|
|
if (npts < vsync + vc->vsync_interval)
|
2015-07-01 17:24:28 +00:00
|
|
|
goto drop;
|
2014-09-20 12:54:19 +00:00
|
|
|
pts = vsync + (vc->vsync_interval >> 2);
|
2014-04-03 17:41:09 +00:00
|
|
|
VdpOutputSurface frame = vc->output_surfaces[vc->surface_num];
|
|
|
|
vdp_st = vdp->presentation_queue_display(vc->flip_queue, frame,
|
|
|
|
vo->dwidth, vo->dheight, pts);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error when calling vdp_presentation_queue_display");
|
2009-02-16 20:58:13 +00:00
|
|
|
|
vo_vdpau: simplify time management and make it more robust
vo_vdpau used a somewhat complicated and fragile mechanism to convert
the vdpau time to internal mpv time. This was fragile as in it couldn't
deal well with Mesa's (apparently) random timestamps, which can change
the base offset in multiple situations. It can happen when moving the
mpv window to a different screen, and somehow it also happens when
pausing the player.
It seems this mechanism to synchronize the vdpau time is not actually
needed. There are only 2 places where sync_vdptime() is used (i.e.
returning the current vdpau time interpolated by system time).
The first call is for determining the PTS used to queue a frame. This
also uses convert_to_vdptime(). It's easily replaced by querying the
time directly, and adding the wait time to it (rel_pts_ns in the patch).
The second call is pretty odd: it updates the vdpau time a second time
in the same function. From what I can see, this can matter only if
update_presentation_queue_status() is very slow. I'm not sure what to
make out of this, because the call merely queries the presentation
queue. Just assume it isn't slow, and that we don't have to update the
time.
Another potential issue with this is that we call VdpPresentationQueueGetTime()
every frame now, instead of every 5 seconds and interpolating the other
calls via system time. More over, this is per video frame (which can be
portantially dropped, and not per actually displayed frame. Assume this
doesn't matter.
This simplifies the code, and should make it more robust on Mesa. But
note that what Mesa does is obviously insane - this is one situation
where you really need a stable time source. There are still plenty of
race condition windows where things can go wrong, although this commit
should drastically reduce the possibility of this.
In my tests, everything worked well. But I have no access to a Mesa
system with vdpau, so it needs testing by others.
See github issues #520, #694, #695.
2014-04-07 16:33:25 +00:00
|
|
|
MP_TRACE(vo, "Queue new surface %d: Vdpau time: %"PRIu64" "
|
|
|
|
"pts: %"PRIu64"\n", (int)frame, now, pts);
|
2014-04-03 17:41:09 +00:00
|
|
|
|
2009-11-15 02:39:22 +00:00
|
|
|
vc->last_queue_time = pts;
|
2010-05-14 02:18:38 +00:00
|
|
|
vc->queue_time[vc->surface_num] = pts;
|
2009-11-15 02:39:22 +00:00
|
|
|
vc->last_ideal_time = ideal_pts;
|
|
|
|
vc->dropped_frame = false;
|
2010-05-14 02:18:38 +00:00
|
|
|
vc->surface_num = WRAP_ADD(vc->surface_num, 1, vc->num_output_surfaces);
|
2015-07-01 17:24:28 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
drop:
|
|
|
|
vo_increment_drop_count(vo, 1);
|
2009-02-16 20:58:13 +00:00
|
|
|
}
|
|
|
|
|
2015-07-01 17:24:28 +00:00
|
|
|
static void draw_frame(struct vo *vo, struct vo_frame *frame)
|
2014-04-29 13:19:03 +00:00
|
|
|
{
|
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
|
2014-05-09 19:49:32 +00:00
|
|
|
check_preemption(vo);
|
2014-05-04 08:50:32 +00:00
|
|
|
|
2015-07-01 17:24:28 +00:00
|
|
|
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");
|
2014-05-04 08:50:32 +00:00
|
|
|
|
2015-07-01 17:24:28 +00:00
|
|
|
talloc_free(vc->current_image);
|
|
|
|
vc->current_image = vdp_mpi;
|
|
|
|
}
|
|
|
|
|
|
|
|
vc->current_pts = frame->pts;
|
|
|
|
vc->current_duration = frame->duration;
|
2009-09-18 13:27:55 +00:00
|
|
|
|
2016-07-07 14:10:53 +00:00
|
|
|
if (status_ok(vo)) {
|
|
|
|
video_to_output_surface(vo, vc->current_image);
|
|
|
|
draw_osd(vo);
|
|
|
|
}
|
2009-09-18 13:27:55 +00:00
|
|
|
}
|
|
|
|
|
2011-10-06 18:46:01 +00:00
|
|
|
// warning: the size and pixel format of surface must match that of the
|
|
|
|
// surfaces in vc->output_surfaces
|
2013-12-21 17:05:23 +00:00
|
|
|
static struct mp_image *read_output_surface(struct vo *vo,
|
2015-06-05 17:25:25 +00:00
|
|
|
VdpOutputSurface surface)
|
2011-10-06 18:46:01 +00:00
|
|
|
{
|
2013-12-21 17:05:23 +00:00
|
|
|
struct vdpctx *vc = vo->priv;
|
2011-10-06 18:46:01 +00:00
|
|
|
VdpStatus vdp_st;
|
|
|
|
struct vdp_functions *vdp = vc->vdp;
|
2014-03-28 23:25:08 +00:00
|
|
|
if (!vo->params)
|
|
|
|
return NULL;
|
|
|
|
|
2015-06-05 17:25:25 +00:00
|
|
|
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);
|
video: introduce failure path for image allocations
Until now, failure to allocate image data resulted in a crash (i.e.
abort() was called). This was intentional, because it's pretty silly to
degrade playback, and in almost all situations, the OOM will probably
kill you anyway. (And then there's the standard Linux overcommit
behavior, which also will kill you at some point.)
But I changed my opinion, so here we go. This change does not affect
_all_ memory allocations, just image data. Now in most failure cases,
the output will just be skipped. For video filters, this coincidentally
means that failure is treated as EOF (because the playback core assumes
EOF if nothing comes out of the video filter chain). In other
situations, output might be in some way degraded, like skipping frames,
not scaling OSD, and such.
Functions whose return values changed semantics:
mp_image_alloc
mp_image_new_copy
mp_image_new_ref
mp_image_make_writeable
mp_image_setrefp
mp_image_to_av_frame_and_unref
mp_image_from_av_frame
mp_image_new_external_ref
mp_image_new_custom_ref
mp_image_pool_make_writeable
mp_image_pool_get
mp_image_pool_new_copy
mp_vdpau_mixed_frame_create
vf_alloc_out_image
vf_make_out_image_writeable
glGetWindowScreenshot
2014-06-17 20:43:43 +00:00
|
|
|
if (!image)
|
|
|
|
return NULL;
|
|
|
|
|
2011-10-06 18:46:01 +00:00
|
|
|
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);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_get_bits_native");
|
2011-10-06 18:46:01 +00:00
|
|
|
|
|
|
|
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];
|
2015-06-05 17:25:25 +00:00
|
|
|
struct mp_image *image = read_output_surface(vo, screen);
|
2015-06-06 19:06:54 +00:00
|
|
|
if (image && image->w >= vo->dwidth && image->h >= vo->dheight)
|
|
|
|
mp_image_set_size(image, vo->dwidth, vo->dheight);
|
2011-10-06 18:46:01 +00:00
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
2015-01-21 21:08:24 +00:00
|
|
|
static int query_format(struct vo *vo, int format)
|
2009-02-16 20:58:13 +00:00
|
|
|
{
|
2013-08-18 03:12:21 +00:00
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
|
|
|
|
if (mp_vdpau_get_format(format, NULL, NULL))
|
2015-01-21 21:08:24 +00:00
|
|
|
return 1;
|
2014-05-22 18:55:17 +00:00
|
|
|
if (!vc->force_yuv && mp_vdpau_get_rgb_format(format, NULL))
|
2015-01-21 21:08:24 +00:00
|
|
|
return 1;
|
2013-08-18 03:12:21 +00:00
|
|
|
return 0;
|
2009-02-16 20:58:13 +00:00
|
|
|
}
|
|
|
|
|
2009-05-06 18:04:37 +00:00
|
|
|
static void destroy_vdpau_objects(struct vo *vo)
|
2009-02-16 20:58:13 +00:00
|
|
|
{
|
2009-05-06 18:04:37 +00:00
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
struct vdp_functions *vdp = vc->vdp;
|
|
|
|
|
2009-02-16 20:58:13 +00:00
|
|
|
VdpStatus vdp_st;
|
|
|
|
|
2009-05-06 18:04:37 +00:00
|
|
|
free_video_specific(vo);
|
2009-02-16 20:58:13 +00:00
|
|
|
|
2009-05-08 17:57:22 +00:00
|
|
|
if (vc->flip_queue != VDP_INVALID_HANDLE) {
|
|
|
|
vdp_st = vdp->presentation_queue_destroy(vc->flip_queue);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error when calling vdp_presentation_queue_destroy");
|
2009-05-08 17:57:22 +00:00
|
|
|
}
|
2009-02-16 20:58:13 +00:00
|
|
|
|
2009-05-08 17:57:22 +00:00
|
|
|
if (vc->flip_target != VDP_INVALID_HANDLE) {
|
|
|
|
vdp_st = vdp->presentation_queue_target_destroy(vc->flip_target);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error when calling "
|
2009-05-09 15:08:30 +00:00
|
|
|
"vdp_presentation_queue_target_destroy");
|
2009-05-08 17:57:22 +00:00
|
|
|
}
|
2009-02-16 20:58:13 +00:00
|
|
|
|
2013-07-22 22:45:23 +00:00
|
|
|
for (int i = 0; i < vc->num_output_surfaces; i++) {
|
2009-05-08 17:57:22 +00:00
|
|
|
if (vc->output_surfaces[i] == VDP_INVALID_HANDLE)
|
|
|
|
continue;
|
2009-05-06 20:42:24 +00:00
|
|
|
vdp_st = vdp->output_surface_destroy(vc->output_surfaces[i]);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_destroy");
|
2009-02-16 20:58:13 +00:00
|
|
|
}
|
2015-07-11 03:42:22 +00:00
|
|
|
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");
|
|
|
|
}
|
2009-02-16 20:58:13 +00:00
|
|
|
|
2012-09-28 19:49:09 +00:00
|
|
|
for (int i = 0; i < MAX_OSD_PARTS; i++) {
|
VO, sub: refactor
Remove VFCTRL_DRAW_OSD, VFCAP_EOSD_FILTER, VFCAP_EOSD_RGBA, VFCAP_EOSD,
VOCTRL_DRAW_EOSD, VOCTRL_GET_EOSD_RES, VOCTRL_QUERY_EOSD_FORMAT.
Remove draw_osd_with_eosd(), which rendered the OSD by calling
VOCTRL_DRAW_EOSD. Change VOs to call osd_draw() directly, which takes
a callback as argument. (This basically works like the old OSD API,
except multiple OSD bitmap formats are supported and caching is
possible.)
Remove all mentions of "eosd". It's simply "osd" now.
Make OSD size per-OSD-object, as they can be different when using
vf_sub. Include display_par/video_par in resolution change detection.
Fix the issue with margin borders in vo_corevideo.
2012-10-19 17:25:18 +00:00
|
|
|
struct osd_bitmap_surface *sfc = &vc->osd_surfaces[i];
|
2012-09-28 19:49:09 +00:00
|
|
|
if (sfc->surface != VDP_INVALID_HANDLE) {
|
|
|
|
vdp_st = vdp->bitmap_surface_destroy(sfc->surface);
|
2013-12-21 17:05:23 +00:00
|
|
|
CHECK_VDP_WARNING(vo, "Error when calling vdp_bitmap_surface_destroy");
|
2012-09-28 19:49:09 +00:00
|
|
|
}
|
2009-02-23 09:21:57 +00:00
|
|
|
}
|
|
|
|
|
2013-11-05 21:06:32 +00:00
|
|
|
mp_vdpau_destroy(vc->mpvdp);
|
|
|
|
vc->mpvdp = NULL;
|
2009-02-16 20:58:13 +00:00
|
|
|
}
|
|
|
|
|
2009-05-04 00:09:50 +00:00
|
|
|
static void uninit(struct vo *vo)
|
2009-02-16 20:58:13 +00:00
|
|
|
{
|
2014-04-29 13:11:44 +00:00
|
|
|
struct vdpctx *vc = vo->priv;
|
|
|
|
|
2016-05-09 17:42:03 +00:00
|
|
|
hwdec_devices_remove(vo->hwdec_devs, &vc->mpvdp->hwctx);
|
|
|
|
hwdec_devices_destroy(vo->hwdec_devs);
|
|
|
|
|
2009-02-16 20:58:13 +00:00
|
|
|
/* Destroy all vdpau objects */
|
2014-04-29 13:11:44 +00:00
|
|
|
mp_vdpau_mixer_destroy(vc->video_mixer);
|
2009-05-06 18:04:37 +00:00
|
|
|
destroy_vdpau_objects(vo);
|
2009-02-23 09:21:57 +00:00
|
|
|
|
2009-05-04 00:09:50 +00:00
|
|
|
vo_x11_uninit(vo);
|
2009-02-16 20:58:13 +00:00
|
|
|
}
|
|
|
|
|
2013-07-22 20:52:42 +00:00
|
|
|
static int preinit(struct vo *vo)
|
2009-02-16 20:58:13 +00:00
|
|
|
{
|
2012-06-25 20:12:03 +00:00
|
|
|
struct vdpctx *vc = vo->priv;
|
2009-02-16 20:58:13 +00:00
|
|
|
|
2013-11-05 21:06:32 +00:00
|
|
|
if (!vo_x11_init(vo))
|
|
|
|
return -1;
|
|
|
|
|
2015-09-30 21:31:34 +00:00
|
|
|
if (!vo_x11_create_vo_window(vo, NULL, "vdpau")) {
|
|
|
|
vo_x11_uninit(vo);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-06-20 20:26:57 +00:00
|
|
|
vc->mpvdp = mp_vdpau_create_device_x11(vo->log, vo->x11->display, false);
|
2013-11-05 21:06:32 +00:00
|
|
|
if (!vc->mpvdp) {
|
|
|
|
vo_x11_uninit(vo);
|
|
|
|
return -1;
|
|
|
|
}
|
2023-08-02 18:26:00 +00:00
|
|
|
vc->mpvdp->hwctx.hw_imgfmt = IMGFMT_VDPAU;
|
vdpau: split off decoder parts, use "new" libavcodec vdpau hwaccel API
Move the decoder parts from vo_vdpau.c to a new file vdpau_old.c. This
file is named so because because it's written against the "old"
libavcodec vdpau pseudo-decoder (e.g. "h264_vdpau").
Add support for the "new" libavcodec vdpau support. This was recently
added and replaces the "old" vdpau parts. (In fact, Libav is about to
deprecate and remove the "old" API without deprecation grace period,
so we have to support it now. Moreover, there will probably be no Libav
release which supports both, so the transition is even less smooth than
we could hope, and we have to support both the old and new API.)
Whether the old or new API is used is checked by a configure test: if
the new API is found, it is used, otherwise the old API is assumed.
Some details might be handled differently. Especially display preemption
is a bit problematic with the "new" libavcodec vdpau support: it wants
to keep a pointer to a specific vdpau API function (which can be driver
specific, because preemption might switch drivers). Also, surface IDs
are now directly stored in AVFrames (and mp_images), so they can't be
forced to VDP_INVALID_HANDLE on preemption. (This changes even with
older libavcodec versions, because mp_image always uses the newer
representation to make vo_vdpau.c simpler.)
Decoder initialization in the new code tries to deal with codec
profiles, while the old code always uses the highest profile per codec.
Surface allocation changes. Since the decoder won't call config() in
vo_vdpau.c on video size change anymore, we allow allocating surfaces
of arbitrary size instead of locking it to what the VO was configured.
The non-hwdec code also has slightly different allocation behavior now.
Enabling the old vdpau special decoders via e.g. --vd=lavc:h264_vdpau
doesn't work anymore (a warning suggesting the --hwdec option is
printed instead).
2013-07-27 23:49:45 +00:00
|
|
|
|
2016-05-09 17:42:03 +00:00
|
|
|
vo->hwdec_devs = hwdec_devices_create();
|
|
|
|
hwdec_devices_add(vo->hwdec_devs, &vc->mpvdp->hwctx);
|
2014-08-11 21:08:35 +00:00
|
|
|
|
2014-04-29 13:11:44 +00:00
|
|
|
vc->video_mixer = mp_vdpau_mixer_create(vc->mpvdp, vo->log);
|
video: redo video equalizer option handling
I really wouldn't care much about this, but some parts of the core code
are under HAVE_GPL, so there's some need to get rid of it. Simply turn
the video equalizer from its current fine-grained handling with vf/vo
fallbacks into global options. This makes updating them much simpler.
This removes any possibility of applying video equalizers in filters,
which affects vf_scale, and the previously removed vf_eq. Not a big
loss, since the preferred VOs have this builtin.
Remove video equalizer handling from vo_direct3d, vo_sdl, vo_vaapi, and
vo_xv. I'm not going to waste my time on these legacy VOs.
vo.eq_opts_cache exists _only_ to send a VOCTRL_SET_EQUALIZER, which
exists _only_ to trigger a redraw. This seems silly, but for now I feel
like this is less of a pain. The rest of the equalizer using code is
self-updating.
See commit 96b906a51d5 for how some video equalizer code was GPL only.
Some command line option names and ranges can probably be traced back to
a GPL only committer, but we don't consider these copyrightable.
2017-08-22 15:01:35 +00:00
|
|
|
vc->video_mixer->video_eq = mp_csp_equalizer_create(vo, vo->global);
|
2014-04-29 13:11:44 +00:00
|
|
|
|
2014-05-27 23:37:53 +00:00
|
|
|
if (mp_vdpau_guess_if_emulated(vc->mpvdp)) {
|
|
|
|
MP_WARN(vo, "VDPAU is most likely emulated via VA-API.\n"
|
2018-01-20 15:10:42 +00:00
|
|
|
"This is inefficient. Use --vo=gpu instead.\n");
|
2014-05-27 23:37:53 +00:00
|
|
|
}
|
|
|
|
|
2009-05-08 18:48:01 +00:00
|
|
|
// Mark everything as invalid first so uninit() can tell what has been
|
|
|
|
// allocated
|
2009-09-06 23:02:24 +00:00
|
|
|
mark_vdpau_objects_uninitialized(vo);
|
2009-05-08 18:48:01 +00:00
|
|
|
|
2014-05-09 19:49:32 +00:00
|
|
|
mp_vdpau_handle_preemption(vc->mpvdp, &vc->preemption_counter);
|
|
|
|
|
2013-11-05 21:06:32 +00:00
|
|
|
vc->vdp_device = vc->mpvdp->vdp_device;
|
2014-03-19 18:57:08 +00:00
|
|
|
vc->vdp = &vc->mpvdp->vdp;
|
2013-11-05 21:06:32 +00:00
|
|
|
|
2015-11-04 20:47:20 +00:00
|
|
|
vc->vdp->bitmap_surface_query_capabilities(vc->vdp_device, VDP_RGBA_FORMAT_A8,
|
|
|
|
&vc->supports_a8, &(uint32_t){0}, &(uint32_t){0});
|
|
|
|
|
2019-08-29 19:52:50 +00:00
|
|
|
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");
|
|
|
|
|
2009-02-16 20:58:13 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-08-11 18:13:34 +00:00
|
|
|
static void checked_resize(struct vo *vo)
|
|
|
|
{
|
|
|
|
if (!status_ok(vo))
|
|
|
|
return;
|
|
|
|
resize(vo);
|
|
|
|
}
|
|
|
|
|
2009-05-04 00:09:50 +00:00
|
|
|
static int control(struct vo *vo, uint32_t request, void *data)
|
2009-02-16 20:58:13 +00:00
|
|
|
{
|
2014-05-09 19:49:32 +00:00
|
|
|
check_preemption(vo);
|
2009-09-06 23:02:24 +00:00
|
|
|
|
2009-02-16 20:58:13 +00:00
|
|
|
switch (request) {
|
2009-09-05 03:20:03 +00:00
|
|
|
case VOCTRL_SET_PANSCAN:
|
2011-08-11 18:13:34 +00:00
|
|
|
checked_resize(vo);
|
2009-09-05 03:20:03 +00:00
|
|
|
return VO_TRUE;
|
2009-09-18 13:27:55 +00:00
|
|
|
case VOCTRL_RESET:
|
2013-10-01 21:45:30 +00:00
|
|
|
forget_frames(vo, true);
|
2009-09-18 13:27:55 +00:00
|
|
|
return true;
|
2015-01-23 21:06:12 +00:00
|
|
|
case VOCTRL_SCREENSHOT_WIN:
|
2011-12-20 01:47:16 +00:00
|
|
|
if (!status_ok(vo))
|
|
|
|
return false;
|
2015-01-24 21:56:02 +00:00
|
|
|
*(struct mp_image **)data = get_window_screenshot(vo);
|
2011-10-06 18:46:01 +00:00
|
|
|
return true;
|
2009-02-16 20:58:13 +00:00
|
|
|
}
|
2013-05-16 12:02:28 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2014-11-02 19:26:51 +00:00
|
|
|
vo_event(vo, events);
|
2013-05-16 12:02:28 +00:00
|
|
|
|
|
|
|
return r;
|
2009-02-16 20:58:13 +00:00
|
|
|
}
|
|
|
|
|
2012-06-25 20:12:03 +00:00
|
|
|
#define OPT_BASE_STRUCT struct vdpctx
|
|
|
|
|
2009-05-04 00:09:50 +00:00
|
|
|
const struct vo_driver video_out_vdpau = {
|
2013-10-23 17:06:14 +00:00
|
|
|
.description = "VDPAU with X11",
|
|
|
|
.name = "vdpau",
|
2015-07-11 03:42:22 +00:00
|
|
|
.caps = VO_CAP_FRAMEDROP | VO_CAP_ROTATE90,
|
2009-05-04 00:09:50 +00:00
|
|
|
.preinit = preinit,
|
2012-11-04 15:24:18 +00:00
|
|
|
.query_format = query_format,
|
2014-01-24 20:22:25 +00:00
|
|
|
.reconfig = reconfig,
|
2009-05-04 00:09:50 +00:00
|
|
|
.control = control,
|
2015-07-01 17:24:28 +00:00
|
|
|
.draw_frame = draw_frame,
|
|
|
|
.flip_page = flip_page,
|
2016-07-20 18:52:08 +00:00
|
|
|
.wakeup = vo_x11_wakeup,
|
|
|
|
.wait_events = vo_x11_wait_events,
|
2009-05-04 00:09:50 +00:00
|
|
|
.uninit = uninit,
|
2012-08-06 15:51:53 +00:00
|
|
|
.priv_size = sizeof(struct vdpctx),
|
2012-06-25 20:12:03 +00:00
|
|
|
.options = (const struct m_option []){
|
2023-02-20 03:32:50 +00:00
|
|
|
{"chroma-deint", OPT_BOOL(chroma_deint), OPTDEF_INT(1)},
|
|
|
|
{"pullup", OPT_BOOL(pullup)},
|
options: change option macros and all option declarations
Change all OPT_* macros such that they don't define the entire m_option
initializer, and instead expand only to a part of it, which sets certain
fields. This requires changing almost every option declaration, because
they all use these macros. A declaration now always starts with
{"name", ...
followed by designated initializers only (possibly wrapped in macros).
The OPT_* macros now initialize the .offset and .type fields only,
sometimes also .priv and others.
I think this change makes the option macros less tricky. The old code
had to stuff everything into macro arguments (and attempted to allow
setting arbitrary fields by letting the user pass designated
initializers in the vararg parts). Some of this was made messy due to
C99 and C11 not allowing 0-sized varargs with ',' removal. It's also
possible that this change is pointless, other than cosmetic preferences.
Not too happy about some things. For example, the OPT_CHOICE()
indentation I applied looks a bit ugly.
Much of this change was done with regex search&replace, but some places
required manual editing. In particular, code in "obscure" areas (which I
didn't include in compilation) might be broken now.
In wayland_common.c the author of some option declarations confused the
flags parameter with the default value (though the default value was
also properly set below). I fixed this with this change.
2020-03-14 20:28:01 +00:00
|
|
|
{"denoise", OPT_FLOAT(denoise), M_RANGE(0, 1)},
|
|
|
|
{"sharpen", OPT_FLOAT(sharpen), M_RANGE(-1, 1)},
|
|
|
|
{"hqscaling", OPT_INT(hqscaling), M_RANGE(0, 9)},
|
2024-02-04 18:03:18 +00:00
|
|
|
{"fps", OPT_DOUBLE(user_fps)},
|
2023-02-20 03:32:50 +00:00
|
|
|
{"composite-detect", OPT_BOOL(composite_detect), OPTDEF_INT(1)},
|
options: change option macros and all option declarations
Change all OPT_* macros such that they don't define the entire m_option
initializer, and instead expand only to a part of it, which sets certain
fields. This requires changing almost every option declaration, because
they all use these macros. A declaration now always starts with
{"name", ...
followed by designated initializers only (possibly wrapped in macros).
The OPT_* macros now initialize the .offset and .type fields only,
sometimes also .priv and others.
I think this change makes the option macros less tricky. The old code
had to stuff everything into macro arguments (and attempted to allow
setting arbitrary fields by letting the user pass designated
initializers in the vararg parts). Some of this was made messy due to
C99 and C11 not allowing 0-sized varargs with ',' removal. It's also
possible that this change is pointless, other than cosmetic preferences.
Not too happy about some things. For example, the OPT_CHOICE()
indentation I applied looks a bit ugly.
Much of this change was done with regex search&replace, but some places
required manual editing. In particular, code in "obscure" areas (which I
didn't include in compilation) might be broken now.
In wayland_common.c the author of some option declarations confused the
flags parameter with the default value (though the default value was
also properly set below). I fixed this with this change.
2020-03-14 20:28:01 +00:00
|
|
|
{"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}},
|
2023-02-20 03:32:50 +00:00
|
|
|
{"force-yuv", OPT_BOOL(force_yuv)},
|
2012-06-25 20:12:03 +00:00
|
|
|
{NULL},
|
2016-09-05 19:07:03 +00:00
|
|
|
},
|
2016-11-25 20:00:39 +00:00
|
|
|
.options_prefix = "vo-vdpau",
|
2009-05-04 00:09:50 +00:00
|
|
|
};
|