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