d1924754aceaa6556c6a790c550dd4f6463c8622
[vlc.git] / modules / hw / vdpau / chroma.c
1 /*****************************************************************************
2  * chroma.c: VLC picture import into VDPAU
3  *****************************************************************************
4  * Copyright (C) 2013 RĂ©mi Denis-Courmont
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include <stdlib.h>
26 #include <inttypes.h>
27 #include <assert.h>
28
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_filter.h>
32 #include "vlc_vdpau.h"
33
34 /* Picture history as recommended by VDPAU documentation */
35 #define MAX_PAST   2
36 #define MAX_FUTURE 1
37
38 struct filter_sys_t
39 {
40     vdp_t *vdp;
41     VdpDevice device;
42     VdpVideoMixer mixer;
43     VdpChromaType chroma;
44     VdpYCbCrFormat format;
45
46     struct
47     {
48         vlc_vdp_video_field_t *field;
49         mtime_t date;
50         bool force;
51     } history[MAX_PAST + 1 + MAX_FUTURE];
52
53     struct
54     {
55         float brightness;
56         float contrast;
57         float saturation;
58         float hue;
59     } procamp;
60 };
61
62 /** Initialize the colour space conversion matrix */
63 static VdpStatus MixerSetupColors(filter_t *filter, const VdpProcamp *procamp,
64                                   VdpCSCMatrix *restrict csc)
65 {
66     filter_sys_t *sys = filter->p_sys;
67     VdpStatus err;
68     VdpColorStandard std = (filter->fmt_in.video.i_height > 576)
69                          ? VDP_COLOR_STANDARD_ITUR_BT_709
70                          : VDP_COLOR_STANDARD_ITUR_BT_601;
71
72     err = vdp_generate_csc_matrix(sys->vdp, procamp, std, csc);
73     if (err != VDP_STATUS_OK)
74     {
75         msg_Err(filter, "video %s failure: %s", "color space matrix",
76                 vdp_get_error_string(sys->vdp, err));
77         return err;
78     }
79
80     if (procamp != NULL)
81     {
82         sys->procamp.brightness = procamp->brightness;
83         sys->procamp.contrast = procamp->contrast;
84         sys->procamp.saturation = procamp->saturation;
85         sys->procamp.hue = procamp->hue;
86     }
87     else
88     {
89         sys->procamp.brightness = 0.f;
90         sys->procamp.contrast = 1.f;
91         sys->procamp.saturation = 1.f;
92         sys->procamp.hue = 0.f;
93     }
94     return VDP_STATUS_OK;
95 }
96
97 /** Create VDPAU video mixer */
98 static VdpVideoMixer MixerCreate(filter_t *filter)
99 {
100     filter_sys_t *sys = filter->p_sys;
101     VdpVideoMixer mixer;
102     VdpStatus err;
103     VdpBool ok;
104
105     /* Check for potentially useful features */
106     VdpVideoMixerFeature featv[5];
107     unsigned featc = 0;
108
109     int algo = var_InheritInteger(filter, "vdpau-deinterlace");
110     bool ivtc = false;
111     if (algo == VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL)
112     {
113         err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
114                                                     algo, &ok);
115         if (err == VDP_STATUS_OK && ok == VDP_TRUE)
116             msg_Dbg(filter, "using video mixer %s feature",
117                     "temporal-spatial deinterlace");
118         else
119             algo = VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL; /* fallback */
120     }
121     if (algo == VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL)
122     {
123         err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
124                                                     algo, &ok);
125         if (err == VDP_STATUS_OK && ok == VDP_TRUE)
126             msg_Dbg(filter, "using video mixer %s feature",
127                     "temporal deinterlace");
128         else
129             algo = -1;
130     }
131     if (algo >= 0)
132     {
133         featv[featc++] = algo;
134         ivtc = var_InheritBool(filter, "vdpau-ivtc");
135         if (ivtc)
136         {
137             err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
138                                 VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE, &ok);
139             if (err == VDP_STATUS_OK && ok == VDP_TRUE)
140                 msg_Dbg(filter, "using video mixer %s feature",
141                         "inverse telecine");
142             featv[featc++] = VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE;
143         }
144     }
145
146     const float noise = var_InheritFloat(filter, "vdpau-noise-reduction");
147     if (noise > 0.f)
148     {
149         err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
150                                  VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION, &ok);
151         if (err == VDP_STATUS_OK && ok == VDP_TRUE)
152         {
153             msg_Dbg(filter, "using video mixer %s feature", "noise reduction");
154             featv[featc++] = VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION;
155         }
156     }
157
158     err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
159                                        VDP_VIDEO_MIXER_FEATURE_SHARPNESS, &ok);
160     if (err == VDP_STATUS_OK && ok == VDP_TRUE)
161     {
162         msg_Dbg(filter, "using video mixer %s feature", "sharpness");
163         featv[featc++] = VDP_VIDEO_MIXER_FEATURE_SHARPNESS;
164     }
165
166     const int offset = VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1 - 1;
167     unsigned level = var_InheritInteger(filter, "vdpau-scaling");
168     while (level > 0)
169     {
170
171         err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
172                                                     offset + level, &ok);
173         if (err == VDP_STATUS_OK && ok == VDP_TRUE)
174         {
175             msg_Dbg(filter, "using video mixer high quality scaling L%u",
176                     level);
177             featv[featc++] = offset + level;
178             break;
179         }
180         level--; /* fallback to lower quality */
181     }
182
183     /* Create the mixer */
184     VdpVideoMixerParameter parms[3] = {
185         VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH,
186         VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT,
187         VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE,
188     };
189     uint32_t width = filter->fmt_in.video.i_width;
190     uint32_t height = filter->fmt_in.video.i_height;
191     const void *values[3] = { &width, &height, &sys->chroma, };
192
193     err = vdp_video_mixer_create(sys->vdp, sys->device, featc, featv,
194                                  3, parms, values, &mixer);
195     if (err != VDP_STATUS_OK)
196     {
197         msg_Err(filter, "video %s %s failure: %s", "mixer", "creation",
198                 vdp_get_error_string(sys->vdp, err));
199         return VDP_INVALID_HANDLE;
200     }
201
202     msg_Dbg(filter, "using video mixer %"PRIu32, mixer);
203
204     /* Set initial features and attributes */
205     VdpVideoMixerAttribute attrv[3];
206     const void *valv[3];
207     unsigned attrc = 0;
208     VdpCSCMatrix csc;
209     uint8_t chroma_skip;
210
211     featc = 0;
212
213     if (MixerSetupColors(filter, NULL, &csc) == VDP_STATUS_OK)
214     {
215         attrv[attrc] = VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX;
216         valv[attrc] = &csc;
217         attrc++;
218     }
219
220     if (algo >= 0)
221     {
222         featv[featc++] = algo;
223         if (ivtc)
224             featv[featc++] = VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE;
225
226         chroma_skip = var_InheritBool(filter, "vdpau-chroma-skip");
227         attrv[attrc] = VDP_VIDEO_MIXER_ATTRIBUTE_SKIP_CHROMA_DEINTERLACE;
228         valv[attrc] = &chroma_skip;
229         attrc++;
230     }
231
232     if (noise > 0.f)
233     {
234         featv[featc++] = VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION;
235
236         attrv[attrc] = VDP_VIDEO_MIXER_ATTRIBUTE_NOISE_REDUCTION_LEVEL;
237         valv[attrc] = &noise;
238         attrc++;
239     }
240
241     if (level > 0)
242         featv[featc++] = offset + level;
243
244     if (featc > 0)
245     {
246         VdpBool enablev[featc];
247
248         for (unsigned i = 0; i < featc; i++)
249             enablev[i] = VDP_TRUE;
250
251         err = vdp_video_mixer_set_feature_enables(sys->vdp, mixer,
252                                                   featc, featv, enablev);
253         if (err != VDP_STATUS_OK)
254             msg_Err(filter, "video %s %s failure: %s", "mixer", "features",
255                     vdp_get_error_string(sys->vdp, err));
256     }
257
258     if (attrc > 0)
259     {
260         err = vdp_video_mixer_set_attribute_values(sys->vdp, mixer,
261                                                    attrc, attrv, valv);
262         if (err != VDP_STATUS_OK)
263             msg_Err(filter, "video %s %s failure: %s", "mixer", "attributes",
264                     vdp_get_error_string(sys->vdp, err));
265     }
266
267     return mixer;
268 }
269
270 static void Flush(filter_t *filter)
271 {
272     filter_sys_t *sys = filter->p_sys;
273
274     for (unsigned i = 0; i < MAX_PAST + MAX_FUTURE; i++)
275         if (sys->history[i].field != NULL)
276         {
277             sys->history[i].field->destroy(sys->history[i].field);
278             sys->history[i].field = NULL;
279         }
280 }
281
282 /** Get a VLC picture for a VDPAU output surface */
283 static picture_t *OutputAllocate(filter_t *filter)
284 {
285     filter_sys_t *sys = filter->p_sys;
286
287     picture_t *pic = filter_NewPicture(filter);
288     if (pic == NULL)
289         return NULL;
290
291     picture_sys_t *psys = pic->p_sys;
292     assert(psys->vdp != NULL);
293
294     if (likely(sys->mixer != VDP_INVALID_HANDLE))
295     {   /* Not the first output picture */
296         assert(psys->vdp == sys->vdp);
297         return pic;
298     }
299
300     /* First picture: get the context and allocate the mixer */
301     sys->vdp = vdp_hold_x11(psys->vdp, NULL);
302     sys->device = psys->device;
303     sys->mixer = MixerCreate(filter);
304     if (sys->mixer != VDP_INVALID_HANDLE)
305         return pic;
306
307     vdp_release_x11(psys->vdp);
308     psys->vdp = NULL;
309     picture_Release(pic);
310     return NULL;
311 }
312
313 /** Export a VDPAU video surface picture to a normal VLC picture */
314 static picture_t *VideoExport(filter_t *filter, picture_t *src, picture_t *dst)
315 {
316     filter_sys_t *sys = filter->p_sys;
317     vlc_vdp_video_field_t *field = src->context;
318     vlc_vdp_video_frame_t *psys = field->frame;
319     VdpStatus err;
320     VdpVideoSurface surface = psys->surface;
321     void *planes[3];
322     uint32_t pitches[3];
323
324     picture_CopyProperties(dst, src);
325
326     for (int i = 0; i < dst->i_planes; i++)
327     {
328         planes[i] = dst->p[i].p_pixels;
329         pitches[i] = dst->p[i].i_pitch;
330     }
331     if (dst->format.i_chroma == VLC_CODEC_I420
332      || dst->format.i_chroma == VLC_CODEC_I422
333      || dst->format.i_chroma == VLC_CODEC_I444)
334     {
335         planes[1] = dst->p[2].p_pixels;
336         planes[2] = dst->p[1].p_pixels;
337         pitches[1] = dst->p[2].i_pitch;
338         pitches[2] = dst->p[1].i_pitch;
339     }
340     err = vdp_video_surface_get_bits_y_cb_cr(psys->vdp, surface, sys->format,
341                                              planes, pitches);
342     if (err != VDP_STATUS_OK)
343     {
344         msg_Err(filter, "video %s %s failure: %s", "surface", "export",
345                 vdp_get_error_string(psys->vdp, err));
346         picture_Release(dst);
347         dst = NULL;
348     }
349     picture_Release(src);
350     return dst;
351 }
352
353 /** Import VLC picture into VDPAU video surface */
354 static picture_t *VideoImport(filter_t *filter, picture_t *src)
355 {
356     filter_sys_t *sys = filter->p_sys;
357     VdpVideoSurface surface;
358     VdpStatus err;
359
360     if (sys->vdp == NULL)
361         goto drop;
362
363     /* Create surface (TODO: reuse?) */
364     err = vdp_video_surface_create(sys->vdp, sys->device, sys->chroma,
365                                    filter->fmt_in.video.i_width,
366                                    filter->fmt_in.video.i_height, &surface);
367     if (err != VDP_STATUS_OK)
368     {
369         msg_Err(filter, "video %s %s failure: %s", "surface", "creation",
370                 vdp_get_error_string(sys->vdp, err));
371         goto drop;
372     }
373
374     /* Put bits */
375     const void *planes[3];
376     uint32_t pitches[3];
377     for (int i = 0; i < src->i_planes; i++)
378     {
379         planes[i] = src->p[i].p_pixels;
380         pitches[i] = src->p[i].i_pitch;
381     }
382     if (src->format.i_chroma == VLC_CODEC_I420
383      || src->format.i_chroma == VLC_CODEC_I422
384      || src->format.i_chroma == VLC_CODEC_I444)
385     {
386         planes[1] = src->p[2].p_pixels;
387         planes[2] = src->p[1].p_pixels;
388         pitches[1] = src->p[2].i_pitch;
389         pitches[2] = src->p[1].i_pitch;
390     }
391     err = vdp_video_surface_put_bits_y_cb_cr(sys->vdp, surface, sys->format,
392                                              planes, pitches);
393     if (err != VDP_STATUS_OK)
394     {
395         msg_Err(filter, "video %s %s failure: %s", "surface", "import",
396                 vdp_get_error_string(sys->vdp, err));
397         goto error;
398     }
399
400     /* Wrap surface into a picture */
401     video_format_t fmt = src->format;
402
403     switch (sys->chroma)
404     {
405         case VDP_CHROMA_TYPE_420:
406             fmt.i_chroma = VLC_CODEC_VDPAU_VIDEO_420;
407             break;
408         case VDP_CHROMA_TYPE_422:
409             fmt.i_chroma = VLC_CODEC_VDPAU_VIDEO_422;
410             break;
411         case VDP_CHROMA_TYPE_444:
412             fmt.i_chroma = VLC_CODEC_VDPAU_VIDEO_444;
413             break;
414         default:
415             assert(0);
416     }
417
418     picture_t *dst = picture_NewFromFormat(&fmt);
419     if (unlikely(dst == NULL))
420         goto error;
421     picture_CopyProperties(dst, src);
422     picture_Release(src);
423
424     err = vlc_vdp_video_attach(sys->vdp, surface, dst);
425     if (unlikely(err != VDP_STATUS_OK))
426     {
427         picture_Release(dst);
428         dst = NULL;
429     }
430     return dst;
431 error:
432     vdp_video_surface_destroy(sys->vdp, surface);
433 drop:
434     picture_Release(src);
435     return NULL;
436 }
437
438 static inline VdpVideoSurface picture_GetVideoSurface(const picture_t *pic)
439 {
440     vlc_vdp_video_field_t *field = pic->context;
441     return field->frame->surface;
442 }
443
444 static picture_t *VideoRender(filter_t *filter, picture_t *src)
445 {
446     filter_sys_t *sys = filter->p_sys;
447     VdpStatus err;
448
449     if (unlikely(src->context == NULL))
450     {
451         msg_Err(filter, "corrupt VDPAU video surface %p", src);
452         picture_Release(src);
453         return NULL;
454     }
455
456     picture_t *dst = OutputAllocate(filter);
457
458     /* Corner case: different VDPAU instances decoding and rendering */
459     vlc_vdp_video_field_t *field = src->context;
460     if (field->frame->vdp != sys->vdp)
461     {
462         video_format_t fmt = src->format;
463         switch (sys->chroma)
464         {
465              case VDP_CHROMA_TYPE_420: fmt.i_chroma = VLC_CODEC_NV12; break;
466              case VDP_CHROMA_TYPE_422: fmt.i_chroma = VLC_CODEC_UYVY; break;
467              case VDP_CHROMA_TYPE_444: fmt.i_chroma = VLC_CODEC_NV24; break;
468              default: assert(0);
469         }
470
471         picture_t *pic = picture_NewFromFormat(&fmt);
472         if (likely(pic != NULL))
473         {
474             pic = VideoExport(filter, src, pic);
475             if (pic != NULL)
476                 src = VideoImport(filter, pic);
477             else
478                 src = NULL;
479         }
480         else
481         {
482             picture_Release(src);
483             src = NULL;
484         }
485     }
486
487     /* Update history and take "present" picture field */
488     if (likely(src != NULL))
489     {
490         sys->history[MAX_PAST + MAX_FUTURE].field =
491                                               vlc_vdp_video_copy(src->context);
492         sys->history[MAX_PAST + MAX_FUTURE].date = src->date;
493         sys->history[MAX_PAST + MAX_FUTURE].force = src->b_force;
494         picture_Release(src);
495     }
496     else
497         sys->history[MAX_PAST + MAX_FUTURE].field = NULL;
498
499     if (dst == NULL)
500         goto skip;
501
502     vlc_vdp_video_field_t *f = sys->history[MAX_PAST].field;
503     if (f == NULL)
504         goto error;
505
506     dst->date = sys->history[MAX_PAST].date;
507     dst->b_force = sys->history[MAX_PAST].force;
508
509     /* Enable/Disable features */
510     const VdpVideoMixerFeature features[] = {
511         VDP_VIDEO_MIXER_FEATURE_SHARPNESS,
512     };
513     const VdpBool enables[] = {
514         f->sharpen != 0.f,
515     };
516
517     err = vdp_video_mixer_set_feature_enables(sys->vdp, sys->mixer,
518                   sizeof (features) / sizeof (features[0]), features, enables);
519     if (err != VDP_STATUS_OK)
520         msg_Err(filter, "video %s %s failure: %s", "mixer", "features",
521                 vdp_get_error_string(sys->vdp, err));
522
523     /* Configure mixer depending on upstream video filters */
524     VdpVideoMixerAttribute attrs[2] = {
525         VDP_VIDEO_MIXER_ATTRIBUTE_SHARPNESS_LEVEL,
526     };
527     const void *values[2] = {
528         &f->sharpen,
529     };
530     unsigned count = 1;
531     VdpCSCMatrix csc;
532
533     if ((sys->procamp.brightness != f->procamp.brightness
534       || sys->procamp.contrast != f->procamp.contrast
535       || sys->procamp.saturation != f->procamp.saturation
536       || sys->procamp.hue != f->procamp.hue)
537      && (MixerSetupColors(filter, &f->procamp, &csc) == VDP_STATUS_OK))
538     {
539         attrs[count] = VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX;
540         values[count] = &csc;
541         count++;
542     }
543
544     err = vdp_video_mixer_set_attribute_values(sys->vdp, sys->mixer,
545                                                count, attrs, values);
546     if (err != VDP_STATUS_OK)
547         msg_Err(filter, "video %s %s failure: %s", "mixer", "attributes",
548                 vdp_get_error_string(sys->vdp, err));
549
550     /* Check video orientation, allocate intermediate surface if needed */
551     bool swap = ORIENT_IS_SWAP(filter->fmt_in.video.orientation);
552     bool hflip = false, vflip = false;
553
554     switch (filter->fmt_in.video.orientation)
555     {
556         case ORIENT_TOP_LEFT:
557         case ORIENT_RIGHT_TOP:
558             break;
559         case ORIENT_TOP_RIGHT:
560         case ORIENT_RIGHT_BOTTOM:
561             hflip = true;
562             break;
563         case ORIENT_BOTTOM_LEFT:
564         case ORIENT_LEFT_TOP:
565             vflip = true;
566             break;
567         case ORIENT_BOTTOM_RIGHT:
568         case ORIENT_LEFT_BOTTOM:
569             vflip = hflip = true;
570             break;
571     }
572
573     VdpOutputSurface output = dst->p_sys->surface;
574
575     if (swap)
576     {
577         VdpRGBAFormat fmt;
578         uint32_t width, height;
579
580         err = vdp_output_surface_get_parameters(sys->vdp, output,
581                                                 &fmt, &width, &height);
582         if (err != VDP_STATUS_OK)
583         {
584             msg_Err(filter, "output %s %s failure: %s", "surface", "query",
585                     vdp_get_error_string(sys->vdp, err));
586             goto error;
587         }
588
589         err = vdp_output_surface_create(sys->vdp, sys->device,
590                                         fmt, height, width, &output);
591         if (err != VDP_STATUS_OK)
592         {
593             msg_Err(filter, "output %s %s failure: %s", "surface", "creation",
594                     vdp_get_error_string(sys->vdp, err));
595             goto error;
596         }
597     }
598
599     /* Render video into output */
600     VdpVideoMixerPictureStructure structure = f->structure;
601     VdpVideoSurface past[MAX_PAST];
602     VdpVideoSurface surface = f->frame->surface;
603     VdpVideoSurface future[MAX_FUTURE];
604     VdpRect src_rect = {
605         filter->fmt_in.video.i_x_offset, filter->fmt_in.video.i_y_offset,
606         filter->fmt_in.video.i_x_offset, filter->fmt_in.video.i_y_offset
607     };
608
609     if (hflip)
610         src_rect.x0 += filter->fmt_in.video.i_visible_width;
611     else
612         src_rect.x1 += filter->fmt_in.video.i_visible_width;
613     if (vflip)
614         src_rect.y0 += filter->fmt_in.video.i_visible_height;
615     else
616         src_rect.y1 += filter->fmt_in.video.i_visible_height;
617
618     VdpRect dst_rect = {
619         0, 0,
620         swap ? filter->fmt_out.video.i_visible_height
621              : filter->fmt_out.video.i_visible_width,
622         swap ? filter->fmt_out.video.i_visible_width
623              : filter->fmt_out.video.i_visible_height,
624     };
625
626     for (unsigned i = 0; i < MAX_PAST; i++)
627     {
628         f = sys->history[(MAX_PAST - 1) - i].field;
629         past[i] = (f != NULL) ? f->frame->surface : VDP_INVALID_HANDLE;
630     }
631     for (unsigned i = 0; i < MAX_FUTURE; i++)
632     {
633         f = sys->history[(MAX_PAST + 1) + i].field;
634         future[i] = (f != NULL) ? f->frame->surface : VDP_INVALID_HANDLE;
635     }
636
637     err = vdp_video_mixer_render(sys->vdp, sys->mixer, VDP_INVALID_HANDLE,
638                                  NULL, structure,
639                                  MAX_PAST, past, surface, MAX_FUTURE, future,
640                                  &src_rect, output, &dst_rect, NULL, 0, NULL);
641     if (err != VDP_STATUS_OK)
642     {
643         msg_Err(filter, "video %s %s failure: %s", "mixer", "rendering",
644                 vdp_get_error_string(sys->vdp, err));
645         goto error;
646     }
647
648     if (swap)
649     {
650         err = vdp_output_surface_render_output_surface(sys->vdp,
651             dst->p_sys->surface, NULL, output, NULL, NULL, NULL,
652             VDP_OUTPUT_SURFACE_RENDER_ROTATE_90);
653         vdp_output_surface_destroy(sys->vdp, output);
654         if (err != VDP_STATUS_OK)
655         {
656             msg_Err(filter, "output %s %s failure: %s", "surface", "render",
657                     vdp_get_error_string(sys->vdp, err));
658             goto error;
659         }
660     }
661
662 skip:
663     f = sys->history[0].field;
664     if (f != NULL)
665         f->destroy(f); /* Release oldest field */
666     memmove(sys->history, sys->history + 1, /* Advance history */
667             sizeof (sys->history[0]) * (MAX_PAST + MAX_FUTURE));
668
669     return dst;
670 error:
671     picture_Release(dst);
672     dst = NULL;
673     goto skip;
674 }
675
676 static picture_t *YCbCrRender(filter_t *filter, picture_t *src)
677 {
678     /* FIXME: Define a way to initialize the mixer in Open() instead. */
679     if (unlikely(filter->p_sys->vdp == NULL))
680     {
681         picture_t *dummy = OutputAllocate(filter);
682         if (dummy != NULL)
683             picture_Release(dummy);
684     }
685
686     src = VideoImport(filter, src);
687     return (src != NULL) ? VideoRender(filter, src) : NULL;
688 }
689
690 static int OutputOpen(vlc_object_t *obj)
691 {
692     filter_t *filter = (filter_t *)obj;
693     if (filter->fmt_out.video.i_chroma != VLC_CODEC_VDPAU_OUTPUT)
694         return VLC_EGENERIC;
695
696     assert(filter->fmt_out.video.orientation == ORIENT_TOP_LEFT);
697
698     filter_sys_t *sys = malloc(sizeof (*sys));
699     if (unlikely(sys == NULL))
700         return VLC_ENOMEM;
701
702     sys->vdp = NULL;
703     sys->mixer = VDP_INVALID_HANDLE;
704
705     if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_444)
706     {
707         sys->chroma = VDP_CHROMA_TYPE_444;
708         sys->format = VDP_YCBCR_FORMAT_NV12;
709         filter->pf_video_filter = VideoRender;
710     }
711     else
712     if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_422)
713     {
714         sys->chroma = VDP_CHROMA_TYPE_422;
715         /* TODO: check if the drivery supports NV12 or UYVY */
716         sys->format = VDP_YCBCR_FORMAT_UYVY;
717         filter->pf_video_filter = VideoRender;
718     }
719     else
720     if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_420)
721     {
722         sys->chroma = VDP_CHROMA_TYPE_420;
723         sys->format = VDP_YCBCR_FORMAT_NV12;
724         filter->pf_video_filter = VideoRender;
725     }
726     else
727     if (vlc_fourcc_to_vdp_ycc(filter->fmt_in.video.i_chroma,
728                               &sys->chroma, &sys->format))
729         filter->pf_video_filter = YCbCrRender;
730     else
731     {
732         free(sys);
733         return VLC_EGENERIC;
734     }
735
736     /* NOTE: The video mixer capabilities should be checked here, and the
737      * then video mixer set up. But:
738      * 1) The VDPAU back-end is accessible only once the first picture
739      *    gets filtered. Thus the video mixer is created later.
740      * 2) Bailing out due to insufficient capabilities would break the
741      *    video pipeline. Thus capabilities should be checked earlier. */
742
743     for (unsigned i = 0; i < MAX_PAST + MAX_FUTURE; i++)
744         sys->history[i].field = NULL;
745
746     sys->procamp.brightness = 0.f;
747     sys->procamp.contrast = 1.f;
748     sys->procamp.saturation = 1.f;
749     sys->procamp.hue = 0.f;
750
751     filter->pf_video_flush = Flush;
752     filter->p_sys = sys;
753     return VLC_SUCCESS;
754 }
755
756 static void OutputClose(vlc_object_t *obj)
757 {
758     filter_t *filter = (filter_t *)obj;
759     filter_sys_t *sys = filter->p_sys;
760
761     Flush(filter);
762     if (sys->mixer != VDP_INVALID_HANDLE)
763     {
764         vdp_video_mixer_destroy(sys->vdp, sys->mixer);
765         vdp_release_x11(sys->vdp);
766     }
767     free(sys);
768 }
769
770 static picture_t *VideoExport_Filter(filter_t *filter, picture_t *src)
771 {
772     if (unlikely(src->context == NULL))
773     {
774         msg_Err(filter, "corrupt VDPAU video surface %p", src);
775         picture_Release(src);
776         return NULL;
777     }
778
779     picture_t *dst = filter_NewPicture(filter);
780     if (dst == NULL)
781         return NULL;
782
783     return VideoExport(filter, src, dst);
784 }
785
786 static int YCbCrOpen(vlc_object_t *obj)
787 {
788     filter_t *filter = (filter_t *)obj;
789     if (filter->fmt_in.video.i_chroma != VLC_CODEC_VDPAU_VIDEO_420
790      && filter->fmt_in.video.i_chroma != VLC_CODEC_VDPAU_VIDEO_422
791      && filter->fmt_in.video.i_chroma != VLC_CODEC_VDPAU_VIDEO_444)
792         return VLC_EGENERIC;
793
794     if (filter->fmt_in.video.i_visible_width
795                                        != filter->fmt_out.video.i_visible_width
796      || filter->fmt_in.video.i_visible_height
797                                       != filter->fmt_out.video.i_visible_height
798      || filter->fmt_in.video.i_x_offset != filter->fmt_out.video.i_x_offset
799      || filter->fmt_in.video.i_y_offset != filter->fmt_out.video.i_y_offset
800      || (filter->fmt_in.video.i_sar_num * filter->fmt_out.video.i_sar_den
801           != filter->fmt_in.video.i_sar_den * filter->fmt_out.video.i_sar_num))
802         return VLC_EGENERIC;
803
804     filter_sys_t *sys = malloc(sizeof (*sys));
805     if (unlikely(sys == NULL))
806         return VLC_ENOMEM;
807
808     if (!vlc_fourcc_to_vdp_ycc(filter->fmt_out.video.i_chroma,
809                                &sys->chroma, &sys->format))
810     {
811         free(sys);
812         return VLC_EGENERIC;
813     }
814
815     filter->pf_video_filter = VideoExport_Filter;
816     filter->p_sys = sys;
817     return VLC_SUCCESS;
818 }
819
820 static void YCbCrClose(vlc_object_t *obj)
821 {
822     filter_t *filter = (filter_t *)obj;
823     filter_sys_t *sys = filter->p_sys;
824
825     free(sys);
826 }
827
828 static const int algo_values[] = {
829     -1,
830     VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL,
831     VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL,
832 };
833
834 static const char *const algo_names[] = {
835     N_("Bob"), N_("Temporal"), N_("Temporal-spatial"),
836 };
837
838 vlc_module_begin()
839     set_shortname(N_("VDPAU"))
840     set_description(N_("VDPAU surface conversions"))
841     set_capability("video filter2", 10)
842     set_category(CAT_VIDEO)
843     set_subcategory(SUBCAT_VIDEO_VFILTER)
844     set_callbacks(OutputOpen, OutputClose)
845
846     add_integer("vdpau-deinterlace",
847                 VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL,
848                 N_("Deinterlace"), N_("Deinterlacing algorithm"), true)
849         change_integer_list(algo_values, algo_names)
850     add_bool("vdpau-ivtc", false,
851              N_("Inverse telecine"), N_("Inverse telecine"), true)
852     add_bool("vdpau-chroma-skip", false,
853              N_("Deinterlace chroma skip"),
854              N_("Whether temporal deinterlacing applies to luma only"), true)
855     add_float_with_range("vdpau-noise-reduction", 0., 0., 1.,
856         N_("Noise reduction level"), N_("Noise reduction level"), true)
857     add_integer_with_range("vdpau-scaling", 0, 0, 9,
858        N_("Scaling quality"), N_("High quality scaling level"), true)
859
860     add_submodule()
861     set_callbacks(YCbCrOpen, YCbCrClose)
862 vlc_module_end()