1 /*
2 * This file is part of mpv.
3 *
4 * mpv is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * mpv is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with mpv. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <assert.h>
19
20 #include <libavutil/hwcontext.h>
21 #include <libavutil/hwcontext_vdpau.h>
22
23 #include "vdpau.h"
24
25 #include "osdep/threads.h"
26 #include "osdep/timer.h"
27
28 #include "video/out/x11_common.h"
29 #include "img_format.h"
30 #include "mp_image.h"
31 #include "mp_image_pool.h"
32 #include "vdpau_mixer.h"
33
mark_vdpau_objects_uninitialized(struct mp_vdpau_ctx * ctx)34 static void mark_vdpau_objects_uninitialized(struct mp_vdpau_ctx *ctx)
35 {
36 for (int i = 0; i < MAX_VIDEO_SURFACES; i++) {
37 ctx->video_surfaces[i].surface = VDP_INVALID_HANDLE;
38 ctx->video_surfaces[i].osurface = VDP_INVALID_HANDLE;
39 ctx->video_surfaces[i].allocated = false;
40 }
41 ctx->vdp_device = VDP_INVALID_HANDLE;
42 ctx->preemption_obj = VDP_INVALID_HANDLE;
43 }
44
preemption_callback(VdpDevice device,void * context)45 static void preemption_callback(VdpDevice device, void *context)
46 {
47 struct mp_vdpau_ctx *ctx = context;
48
49 pthread_mutex_lock(&ctx->preempt_lock);
50 ctx->is_preempted = true;
51 pthread_mutex_unlock(&ctx->preempt_lock);
52 }
53
win_x11_init_vdpau_procs(struct mp_vdpau_ctx * ctx,bool probing)54 static int win_x11_init_vdpau_procs(struct mp_vdpau_ctx *ctx, bool probing)
55 {
56 Display *x11 = ctx->x11;
57 VdpStatus vdp_st;
58
59 // Don't operate on ctx->vdp directly, so that even if init fails, ctx->vdp
60 // will have the function pointers from the previous successful init, and
61 // won't randomly make other code crash on calling NULL pointers.
62 struct vdp_functions vdp = {0};
63
64 if (!x11)
65 return -1;
66
67 struct vdp_function {
68 const int id;
69 int offset;
70 };
71
72 static const struct vdp_function vdp_func[] = {
73 #define VDP_FUNCTION(_, macro_name, mp_name) {macro_name, offsetof(struct vdp_functions, mp_name)},
74 #include "video/vdpau_functions.inc"
75 #undef VDP_FUNCTION
76 {0, -1}
77 };
78
79 VdpGetProcAddress *get_proc_address;
80 vdp_st = vdp_device_create_x11(x11, DefaultScreen(x11), &ctx->vdp_device,
81 &get_proc_address);
82 if (vdp_st != VDP_STATUS_OK) {
83 if (ctx->is_preempted) {
84 MP_DBG(ctx, "Error calling vdp_device_create_x11 while preempted: %d\n",
85 vdp_st);
86 } else {
87 int lev = probing ? MSGL_V : MSGL_ERR;
88 mp_msg(ctx->log, lev, "Error when calling vdp_device_create_x11: %d\n",
89 vdp_st);
90 }
91 return -1;
92 }
93
94 for (const struct vdp_function *dsc = vdp_func; dsc->offset >= 0; dsc++) {
95 vdp_st = get_proc_address(ctx->vdp_device, dsc->id,
96 (void **)((char *)&vdp + dsc->offset));
97 if (vdp_st != VDP_STATUS_OK) {
98 MP_ERR(ctx, "Error when calling vdp_get_proc_address(function "
99 "id %d): %s\n", dsc->id,
100 vdp.get_error_string ? vdp.get_error_string(vdp_st) : "?");
101 return -1;
102 }
103 }
104
105 ctx->vdp = vdp;
106 ctx->get_proc_address = get_proc_address;
107
108 if (ctx->av_device_ref) {
109 AVHWDeviceContext *hwctx = (void *)ctx->av_device_ref->data;
110 AVVDPAUDeviceContext *vdctx = hwctx->hwctx;
111
112 vdctx->device = ctx->vdp_device;
113 vdctx->get_proc_address = ctx->get_proc_address;
114 }
115
116 vdp_st = vdp.output_surface_create(ctx->vdp_device, VDP_RGBA_FORMAT_B8G8R8A8,
117 1, 1, &ctx->preemption_obj);
118 if (vdp_st != VDP_STATUS_OK) {
119 MP_ERR(ctx, "Could not create dummy object: %s",
120 vdp.get_error_string(vdp_st));
121 return -1;
122 }
123
124 vdp.preemption_callback_register(ctx->vdp_device, preemption_callback, ctx);
125 return 0;
126 }
127
handle_preemption(struct mp_vdpau_ctx * ctx)128 static int handle_preemption(struct mp_vdpau_ctx *ctx)
129 {
130 if (!ctx->is_preempted)
131 return 0;
132 mark_vdpau_objects_uninitialized(ctx);
133 if (!ctx->preemption_user_notified) {
134 MP_ERR(ctx, "Got display preemption notice! Will attempt to recover.\n");
135 ctx->preemption_user_notified = true;
136 }
137 /* Trying to initialize seems to be quite slow, so only try once a
138 * second to avoid using 100% CPU. */
139 if (ctx->last_preemption_retry_fail &&
140 mp_time_sec() - ctx->last_preemption_retry_fail < 1.0)
141 return -1;
142 if (win_x11_init_vdpau_procs(ctx, false) < 0) {
143 ctx->last_preemption_retry_fail = mp_time_sec();
144 return -1;
145 }
146 ctx->preemption_user_notified = false;
147 ctx->last_preemption_retry_fail = 0;
148 ctx->is_preempted = false;
149 ctx->preemption_counter++;
150 MP_INFO(ctx, "Recovered from display preemption.\n");
151 return 1;
152 }
153
154 // Check whether vdpau display preemption happened. The caller provides a
155 // preemption counter, which contains the logical timestamp of the last
156 // preemption handled by the caller. The counter can be 0 for init.
157 // If counter is NULL, only ever return -1 or 1.
158 // Return values:
159 // -1: the display is currently preempted, and vdpau can't be used
160 // 0: a preemption event happened, and the caller must recover
161 // (*counter is updated, and a second call will report status ok)
162 // 1: everything is fine, no preemption happened
mp_vdpau_handle_preemption(struct mp_vdpau_ctx * ctx,uint64_t * counter)163 int mp_vdpau_handle_preemption(struct mp_vdpau_ctx *ctx, uint64_t *counter)
164 {
165 int r = 1;
166 pthread_mutex_lock(&ctx->preempt_lock);
167
168 const void *p[4] = {&(uint32_t){0}};
169 uint32_t stride[4] = {4};
170 VdpRect rc = {0};
171 ctx->vdp.output_surface_put_bits_native(ctx->preemption_obj, p, stride, &rc);
172
173 // First time init
174 if (counter && !*counter)
175 *counter = ctx->preemption_counter;
176
177 if (handle_preemption(ctx) < 0)
178 r = -1;
179
180 if (counter && r > 0 && *counter < ctx->preemption_counter) {
181 *counter = ctx->preemption_counter;
182 r = 0; // signal recovery after preemption
183 }
184
185 pthread_mutex_unlock(&ctx->preempt_lock);
186 return r;
187 }
188
189 struct surface_ref {
190 struct mp_vdpau_ctx *ctx;
191 int index;
192 };
193
release_decoder_surface(void * ptr)194 static void release_decoder_surface(void *ptr)
195 {
196 struct surface_ref *r = ptr;
197 struct mp_vdpau_ctx *ctx = r->ctx;
198
199 pthread_mutex_lock(&ctx->pool_lock);
200 assert(ctx->video_surfaces[r->index].in_use);
201 ctx->video_surfaces[r->index].in_use = false;
202 pthread_mutex_unlock(&ctx->pool_lock);
203
204 talloc_free(r);
205 }
206
create_ref(struct mp_vdpau_ctx * ctx,int index)207 static struct mp_image *create_ref(struct mp_vdpau_ctx *ctx, int index)
208 {
209 struct surface_entry *e = &ctx->video_surfaces[index];
210 assert(!e->in_use);
211 e->in_use = true;
212 e->age = ctx->age_counter++;
213 struct surface_ref *ref = talloc_ptrtype(NULL, ref);
214 *ref = (struct surface_ref){ctx, index};
215 struct mp_image *res =
216 mp_image_new_custom_ref(NULL, ref, release_decoder_surface);
217 if (res) {
218 mp_image_setfmt(res, e->rgb ? IMGFMT_VDPAU_OUTPUT : IMGFMT_VDPAU);
219 mp_image_set_size(res, e->w, e->h);
220 res->planes[0] = (void *)"dummy"; // must be non-NULL, otherwise arbitrary
221 res->planes[3] = (void *)(intptr_t)(e->rgb ? e->osurface : e->surface);
222 }
223 return res;
224 }
225
mp_vdpau_get_surface(struct mp_vdpau_ctx * ctx,VdpChromaType chroma,VdpRGBAFormat rgb_format,bool rgb,int w,int h)226 static struct mp_image *mp_vdpau_get_surface(struct mp_vdpau_ctx *ctx,
227 VdpChromaType chroma,
228 VdpRGBAFormat rgb_format,
229 bool rgb, int w, int h)
230 {
231 struct vdp_functions *vdp = &ctx->vdp;
232 int surface_index = -1;
233 VdpStatus vdp_st;
234
235 if (rgb) {
236 chroma = (VdpChromaType)-1;
237 } else {
238 rgb_format = (VdpChromaType)-1;
239 }
240
241 pthread_mutex_lock(&ctx->pool_lock);
242
243 // Destroy all unused surfaces that don't have matching parameters
244 for (int n = 0; n < MAX_VIDEO_SURFACES; n++) {
245 struct surface_entry *e = &ctx->video_surfaces[n];
246 if (!e->in_use && e->allocated) {
247 if (e->w != w || e->h != h || e->rgb != rgb ||
248 e->chroma != chroma || e->rgb_format != rgb_format)
249 {
250 if (e->rgb) {
251 vdp_st = vdp->output_surface_destroy(e->osurface);
252 } else {
253 vdp_st = vdp->video_surface_destroy(e->surface);
254 }
255 CHECK_VDP_WARNING(ctx, "Error when destroying surface");
256 e->surface = e->osurface = VDP_INVALID_HANDLE;
257 e->allocated = false;
258 }
259 }
260 }
261
262 // Try to find an existing unused surface
263 for (int n = 0; n < MAX_VIDEO_SURFACES; n++) {
264 struct surface_entry *e = &ctx->video_surfaces[n];
265 if (!e->in_use && e->allocated) {
266 assert(e->w == w && e->h == h);
267 assert(e->chroma == chroma);
268 assert(e->rgb_format == rgb_format);
269 assert(e->rgb == rgb);
270 if (surface_index >= 0) {
271 struct surface_entry *other = &ctx->video_surfaces[surface_index];
272 if (other->age < e->age)
273 continue;
274 }
275 surface_index = n;
276 }
277 }
278
279 if (surface_index >= 0)
280 goto done;
281
282 // Allocate new surface
283 for (int n = 0; n < MAX_VIDEO_SURFACES; n++) {
284 struct surface_entry *e = &ctx->video_surfaces[n];
285 if (!e->in_use) {
286 assert(e->surface == VDP_INVALID_HANDLE);
287 assert(e->osurface == VDP_INVALID_HANDLE);
288 assert(!e->allocated);
289 e->chroma = chroma;
290 e->rgb_format = rgb_format;
291 e->rgb = rgb;
292 e->w = w;
293 e->h = h;
294 if (mp_vdpau_handle_preemption(ctx, NULL) >= 0) {
295 if (rgb) {
296 vdp_st = vdp->output_surface_create(ctx->vdp_device, rgb_format,
297 w, h, &e->osurface);
298 e->allocated = e->osurface != VDP_INVALID_HANDLE;
299 } else {
300 vdp_st = vdp->video_surface_create(ctx->vdp_device, chroma,
301 w, h, &e->surface);
302 e->allocated = e->surface != VDP_INVALID_HANDLE;
303 }
304 CHECK_VDP_WARNING(ctx, "Error when allocating surface");
305 } else {
306 e->allocated = false;
307 e->osurface = VDP_INVALID_HANDLE;
308 e->surface = VDP_INVALID_HANDLE;
309 }
310 surface_index = n;
311 goto done;
312 }
313 }
314
315 done: ;
316 struct mp_image *mpi = NULL;
317 if (surface_index >= 0)
318 mpi = create_ref(ctx, surface_index);
319
320 pthread_mutex_unlock(&ctx->pool_lock);
321
322 if (!mpi)
323 MP_ERR(ctx, "no surfaces available in mp_vdpau_get_video_surface\n");
324 return mpi;
325 }
326
mp_vdpau_get_video_surface(struct mp_vdpau_ctx * ctx,VdpChromaType chroma,int w,int h)327 struct mp_image *mp_vdpau_get_video_surface(struct mp_vdpau_ctx *ctx,
328 VdpChromaType chroma, int w, int h)
329 {
330 return mp_vdpau_get_surface(ctx, chroma, 0, false, w, h);
331 }
332
free_device_ref(struct AVHWDeviceContext * hwctx)333 static void free_device_ref(struct AVHWDeviceContext *hwctx)
334 {
335 struct mp_vdpau_ctx *ctx = hwctx->user_opaque;
336
337 struct vdp_functions *vdp = &ctx->vdp;
338 VdpStatus vdp_st;
339
340 for (int i = 0; i < MAX_VIDEO_SURFACES; i++) {
341 // can't hold references past context lifetime
342 assert(!ctx->video_surfaces[i].in_use);
343 if (ctx->video_surfaces[i].surface != VDP_INVALID_HANDLE) {
344 vdp_st = vdp->video_surface_destroy(ctx->video_surfaces[i].surface);
345 CHECK_VDP_WARNING(ctx, "Error when calling vdp_video_surface_destroy");
346 }
347 if (ctx->video_surfaces[i].osurface != VDP_INVALID_HANDLE) {
348 vdp_st = vdp->output_surface_destroy(ctx->video_surfaces[i].osurface);
349 CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_destroy");
350 }
351 }
352
353 if (ctx->preemption_obj != VDP_INVALID_HANDLE) {
354 vdp_st = vdp->output_surface_destroy(ctx->preemption_obj);
355 CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_destroy");
356 }
357
358 if (vdp->device_destroy && ctx->vdp_device != VDP_INVALID_HANDLE) {
359 vdp_st = vdp->device_destroy(ctx->vdp_device);
360 CHECK_VDP_WARNING(ctx, "Error when calling vdp_device_destroy");
361 }
362
363 if (ctx->close_display)
364 XCloseDisplay(ctx->x11);
365
366 pthread_mutex_destroy(&ctx->pool_lock);
367 pthread_mutex_destroy(&ctx->preempt_lock);
368 talloc_free(ctx);
369 }
370
mp_vdpau_create_device_x11(struct mp_log * log,Display * x11,bool probing)371 struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11,
372 bool probing)
373 {
374 AVBufferRef *avref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VDPAU);
375 if (!avref)
376 return NULL;
377
378 AVHWDeviceContext *hwctx = (void *)avref->data;
379 AVVDPAUDeviceContext *vdctx = hwctx->hwctx;
380
381 struct mp_vdpau_ctx *ctx = talloc_ptrtype(NULL, ctx);
382 *ctx = (struct mp_vdpau_ctx) {
383 .log = log,
384 .x11 = x11,
385 .preemption_counter = 1,
386 .av_device_ref = avref,
387 .hwctx = {
388 .av_device_ref = avref,
389 },
390 };
391 mpthread_mutex_init_recursive(&ctx->preempt_lock);
392 pthread_mutex_init(&ctx->pool_lock, NULL);
393
394 hwctx->free = free_device_ref;
395 hwctx->user_opaque = ctx;
396
397 mark_vdpau_objects_uninitialized(ctx);
398
399 if (win_x11_init_vdpau_procs(ctx, probing) < 0) {
400 mp_vdpau_destroy(ctx);
401 return NULL;
402 }
403
404 vdctx->device = ctx->vdp_device;
405 vdctx->get_proc_address = ctx->get_proc_address;
406
407 if (av_hwdevice_ctx_init(ctx->av_device_ref) < 0) {
408 mp_vdpau_destroy(ctx);
409 return NULL;
410 }
411
412 return ctx;
413 }
414
mp_vdpau_destroy(struct mp_vdpau_ctx * ctx)415 void mp_vdpau_destroy(struct mp_vdpau_ctx *ctx)
416 {
417 if (!ctx)
418 return;
419
420 AVBufferRef *ref = ctx->av_device_ref;
421 av_buffer_unref(&ref); // frees ctx as well
422 }
423
mp_vdpau_get_format(int imgfmt,VdpChromaType * out_chroma_type,VdpYCbCrFormat * out_pixel_format)424 bool mp_vdpau_get_format(int imgfmt, VdpChromaType *out_chroma_type,
425 VdpYCbCrFormat *out_pixel_format)
426 {
427 VdpChromaType chroma = VDP_CHROMA_TYPE_420;
428 VdpYCbCrFormat ycbcr = (VdpYCbCrFormat)-1;
429
430 switch (imgfmt) {
431 case IMGFMT_420P:
432 ycbcr = VDP_YCBCR_FORMAT_YV12;
433 break;
434 case IMGFMT_NV12:
435 ycbcr = VDP_YCBCR_FORMAT_NV12;
436 break;
437 case IMGFMT_UYVY:
438 ycbcr = VDP_YCBCR_FORMAT_UYVY;
439 chroma = VDP_CHROMA_TYPE_422;
440 break;
441 case IMGFMT_VDPAU:
442 break;
443 default:
444 return false;
445 }
446
447 if (out_chroma_type)
448 *out_chroma_type = chroma;
449 if (out_pixel_format)
450 *out_pixel_format = ycbcr;
451 return true;
452 }
453
mp_vdpau_get_rgb_format(int imgfmt,VdpRGBAFormat * out_rgba_format)454 bool mp_vdpau_get_rgb_format(int imgfmt, VdpRGBAFormat *out_rgba_format)
455 {
456 VdpRGBAFormat format = (VdpRGBAFormat)-1;
457
458 switch (imgfmt) {
459 case IMGFMT_BGRA:
460 format = VDP_RGBA_FORMAT_B8G8R8A8; break;
461 default:
462 return false;
463 }
464
465 if (out_rgba_format)
466 *out_rgba_format = format;
467 return true;
468 }
469
470 // Use mp_vdpau_get_video_surface, and upload mpi to it. Return NULL on failure.
471 // If the image is already a vdpau video surface, just return a reference.
mp_vdpau_upload_video_surface(struct mp_vdpau_ctx * ctx,struct mp_image * mpi)472 struct mp_image *mp_vdpau_upload_video_surface(struct mp_vdpau_ctx *ctx,
473 struct mp_image *mpi)
474 {
475 struct vdp_functions *vdp = &ctx->vdp;
476 VdpStatus vdp_st;
477
478 if (mpi->imgfmt == IMGFMT_VDPAU || mpi->imgfmt == IMGFMT_VDPAU_OUTPUT)
479 return mp_image_new_ref(mpi);
480
481 VdpChromaType chroma = (VdpChromaType)-1;
482 VdpYCbCrFormat ycbcr = (VdpYCbCrFormat)-1;
483 VdpRGBAFormat rgbafmt = (VdpRGBAFormat)-1;
484 bool rgb = !mp_vdpau_get_format(mpi->imgfmt, &chroma, &ycbcr);
485 if (rgb && !mp_vdpau_get_rgb_format(mpi->imgfmt, &rgbafmt))
486 return NULL;
487
488 struct mp_image *hwmpi =
489 mp_vdpau_get_surface(ctx, chroma, rgbafmt, rgb, mpi->w, mpi->h);
490 if (!hwmpi)
491 return NULL;
492
493 struct mp_image *src = mpi;
494 if (mpi->stride[0] < 0)
495 src = mp_image_new_copy(mpi); // unflips it when copying
496
497 if (hwmpi->imgfmt == IMGFMT_VDPAU) {
498 VdpVideoSurface surface = (intptr_t)hwmpi->planes[3];
499 const void *destdata[3] = {src->planes[0], src->planes[2], src->planes[1]};
500 if (src->imgfmt == IMGFMT_NV12)
501 destdata[1] = destdata[2];
502 vdp_st = vdp->video_surface_put_bits_y_cb_cr(surface,
503 ycbcr, destdata, src->stride);
504 } else {
505 VdpOutputSurface rgb_surface = (intptr_t)hwmpi->planes[3];
506 vdp_st = vdp->output_surface_put_bits_native(rgb_surface,
507 &(const void *){src->planes[0]},
508 &(uint32_t){src->stride[0]},
509 NULL);
510 }
511 CHECK_VDP_WARNING(ctx, "Error when uploading surface");
512
513 if (src != mpi)
514 talloc_free(src);
515
516 mp_image_copy_attributes(hwmpi, mpi);
517 return hwmpi;
518 }
519
mp_vdpau_guess_if_emulated(struct mp_vdpau_ctx * ctx)520 bool mp_vdpau_guess_if_emulated(struct mp_vdpau_ctx *ctx)
521 {
522 struct vdp_functions *vdp = &ctx->vdp;
523 VdpStatus vdp_st;
524 char const* info = NULL;
525 vdp_st = vdp->get_information_string(&info);
526 CHECK_VDP_WARNING(ctx, "Error when calling vdp_get_information_string");
527 return vdp_st == VDP_STATUS_OK && info && strstr(info, "VAAPI");
528 }
529
530 // (This clearly works only for contexts wrapped by our code.)
mp_vdpau_get_ctx_from_av(AVBufferRef * hw_device_ctx)531 struct mp_vdpau_ctx *mp_vdpau_get_ctx_from_av(AVBufferRef *hw_device_ctx)
532 {
533 AVHWDeviceContext *hwctx = (void *)hw_device_ctx->data;
534
535 if (hwctx->free != free_device_ref)
536 return NULL; // not ours
537
538 return hwctx->user_opaque;
539 }
540
is_emulated(struct AVBufferRef * hw_device_ctx)541 static bool is_emulated(struct AVBufferRef *hw_device_ctx)
542 {
543 struct mp_vdpau_ctx *ctx = mp_vdpau_get_ctx_from_av(hw_device_ctx);
544 if (!ctx)
545 return false;
546
547 return mp_vdpau_guess_if_emulated(ctx);
548 }
549
vdpau_create_standalone(struct mpv_global * global,struct mp_log * log,struct hwcontext_create_dev_params * params)550 static struct AVBufferRef *vdpau_create_standalone(struct mpv_global *global,
551 struct mp_log *log, struct hwcontext_create_dev_params *params)
552 {
553 XInitThreads();
554
555 Display *display = XOpenDisplay(NULL);
556 if (!display)
557 return NULL;
558
559 struct mp_vdpau_ctx *vdp =
560 mp_vdpau_create_device_x11(log, display, params->probing);
561 if (!vdp) {
562 XCloseDisplay(display);
563 return NULL;
564 }
565
566 vdp->close_display = true;
567 return vdp->hwctx.av_device_ref;
568 }
569
570 const struct hwcontext_fns hwcontext_fns_vdpau = {
571 .av_hwdevice_type = AV_HWDEVICE_TYPE_VDPAU,
572 .create_dev = vdpau_create_standalone,
573 .is_emulated = is_emulated,
574 };
575