vdpau/chroma: reorient the video (fixes #11068)
[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     {
334         planes[1] = dst->p[2].p_pixels;
335         planes[2] = dst->p[1].p_pixels;
336         pitches[1] = dst->p[2].i_pitch;
337         pitches[2] = dst->p[1].i_pitch;
338     }
339     err = vdp_video_surface_get_bits_y_cb_cr(psys->vdp, surface, sys->format,
340                                              planes, pitches);
341     if (err != VDP_STATUS_OK)
342     {
343         msg_Err(filter, "video %s %s failure: %s", "surface", "export",
344                 vdp_get_error_string(psys->vdp, err));
345         picture_Release(dst);
346         dst = NULL;
347     }
348     picture_Release(src);
349     return dst;
350 }
351
352 /** Import VLC picture into VDPAU video surface */
353 static picture_t *VideoImport(filter_t *filter, picture_t *src)
354 {
355     filter_sys_t *sys = filter->p_sys;
356     VdpVideoSurface surface;
357     VdpStatus err;
358
359     if (sys->vdp == NULL)
360         goto drop;
361
362     /* Create surface (TODO: reuse?) */
363     err = vdp_video_surface_create(sys->vdp, sys->device, sys->chroma,
364                                    filter->fmt_in.video.i_width,
365                                    filter->fmt_in.video.i_height, &surface);
366     if (err != VDP_STATUS_OK)
367     {
368         msg_Err(filter, "video %s %s failure: %s", "surface", "creation",
369                 vdp_get_error_string(sys->vdp, err));
370         goto drop;
371     }
372
373     /* Put bits */
374     const void *planes[3];
375     uint32_t pitches[3];
376     for (int i = 0; i < src->i_planes; i++)
377     {
378         planes[i] = src->p[i].p_pixels;
379         pitches[i] = src->p[i].i_pitch;
380     }
381     if (src->format.i_chroma == VLC_CODEC_I420)
382     {
383         planes[1] = src->p[2].p_pixels;
384         planes[2] = src->p[1].p_pixels;
385         pitches[1] = src->p[2].i_pitch;
386         pitches[2] = src->p[1].i_pitch;
387     }
388     err = vdp_video_surface_put_bits_y_cb_cr(sys->vdp, surface, sys->format,
389                                              planes, pitches);
390     if (err != VDP_STATUS_OK)
391     {
392         msg_Err(filter, "video %s %s failure: %s", "surface", "import",
393                 vdp_get_error_string(sys->vdp, err));
394         goto error;
395     }
396
397     /* Wrap surface into a picture */
398     video_format_t fmt = src->format;
399     fmt.i_chroma = (sys->chroma == VDP_CHROMA_TYPE_420)
400         ? VLC_CODEC_VDPAU_VIDEO_420 : VLC_CODEC_VDPAU_VIDEO_422;
401
402     picture_t *dst = picture_NewFromFormat(&fmt);
403     if (unlikely(dst == NULL))
404         goto error;
405     picture_CopyProperties(dst, src);
406     picture_Release(src);
407
408     err = vlc_vdp_video_attach(sys->vdp, surface, dst);
409     if (unlikely(err != VDP_STATUS_OK))
410     {
411         picture_Release(dst);
412         dst = NULL;
413     }
414     return dst;
415 error:
416     vdp_video_surface_destroy(sys->vdp, surface);
417 drop:
418     picture_Release(src);
419     return NULL;
420 }
421
422 static inline VdpVideoSurface picture_GetVideoSurface(const picture_t *pic)
423 {
424     vlc_vdp_video_field_t *field = pic->context;
425     return field->frame->surface;
426 }
427
428 static picture_t *VideoRender(filter_t *filter, picture_t *src)
429 {
430     filter_sys_t *sys = filter->p_sys;
431     VdpStatus err;
432
433     if (unlikely(src->context == NULL))
434     {
435         msg_Err(filter, "corrupt VDPAU video surface");
436         picture_Release(src);
437         return NULL;
438     }
439
440     picture_t *dst = OutputAllocate(filter);
441
442     /* Corner case: different VDPAU instances decoding and rendering */
443     vlc_vdp_video_field_t *field = src->context;
444     if (field->frame->vdp != sys->vdp)
445     {
446         video_format_t fmt = src->format;
447         switch (sys->chroma)
448         {
449              case VDP_CHROMA_TYPE_420: fmt.i_chroma = VLC_CODEC_NV12; break;
450              case VDP_CHROMA_TYPE_422: fmt.i_chroma = VLC_CODEC_UYVY; break;
451              case VDP_CHROMA_TYPE_444: fmt.i_chroma = VLC_CODEC_NV24; break;
452              default: assert(0);
453         }
454
455         picture_t *pic = picture_NewFromFormat(&fmt);
456         if (likely(pic != NULL))
457         {
458             pic = VideoExport(filter, src, pic);
459             if (pic != NULL)
460                 src = VideoImport(filter, pic);
461             else
462                 src = NULL;
463         }
464         else
465         {
466             picture_Release(src);
467             src = NULL;
468         }
469     }
470
471     /* Update history and take "present" picture field */
472     if (likely(src != NULL))
473     {
474         sys->history[MAX_PAST + MAX_FUTURE].field =
475                                               vlc_vdp_video_copy(src->context);
476         sys->history[MAX_PAST + MAX_FUTURE].date = src->date;
477         sys->history[MAX_PAST + MAX_FUTURE].force = src->b_force;
478         picture_Release(src);
479     }
480     else
481         sys->history[MAX_PAST + MAX_FUTURE].field = NULL;
482
483     if (dst == NULL)
484         goto skip;
485
486     vlc_vdp_video_field_t *f = sys->history[MAX_PAST].field;
487     if (f == NULL)
488         goto error;
489
490     dst->date = sys->history[MAX_PAST].date;
491     dst->b_force = sys->history[MAX_PAST].force;
492
493     /* Enable/Disable features */
494     const VdpVideoMixerFeature features[] = {
495         VDP_VIDEO_MIXER_FEATURE_SHARPNESS,
496     };
497     const VdpBool enables[] = {
498         f->sharpen != 0.f,
499     };
500
501     err = vdp_video_mixer_set_feature_enables(sys->vdp, sys->mixer,
502                   sizeof (features) / sizeof (features[0]), features, enables);
503     if (err != VDP_STATUS_OK)
504         msg_Err(filter, "video %s %s failure: %s", "mixer", "features",
505                 vdp_get_error_string(sys->vdp, err));
506
507     /* Configure mixer depending on upstream video filters */
508     VdpVideoMixerAttribute attrs[2] = {
509         VDP_VIDEO_MIXER_ATTRIBUTE_SHARPNESS_LEVEL,
510     };
511     const void *values[2] = {
512         &f->sharpen,
513     };
514     unsigned count = 1;
515     VdpCSCMatrix csc;
516
517     if ((sys->procamp.brightness != f->procamp.brightness
518       || sys->procamp.contrast != f->procamp.contrast
519       || sys->procamp.saturation != f->procamp.saturation
520       || sys->procamp.hue != f->procamp.hue)
521      && (MixerSetupColors(filter, &f->procamp, &csc) == VDP_STATUS_OK))
522     {
523         attrs[count] = VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX;
524         values[count] = &csc;
525         count++;
526     }
527
528     err = vdp_video_mixer_set_attribute_values(sys->vdp, sys->mixer,
529                                                count, attrs, values);
530     if (err != VDP_STATUS_OK)
531         msg_Err(filter, "video %s %s failure: %s", "mixer", "attributes",
532                 vdp_get_error_string(sys->vdp, err));
533
534     /* Check video orientation, allocate intermediate surface if needed */
535     bool swap = ORIENT_IS_SWAP(filter->fmt_in.video.orientation);
536     bool hflip = false, vflip = false;
537
538     switch (filter->fmt_in.video.orientation)
539     {
540         case ORIENT_TOP_LEFT:
541         case ORIENT_RIGHT_TOP:
542             break;
543         case ORIENT_TOP_RIGHT:
544         case ORIENT_RIGHT_BOTTOM:
545             hflip = true;
546             break;
547         case ORIENT_BOTTOM_LEFT:
548         case ORIENT_LEFT_TOP:
549             vflip = true;
550             break;
551         case ORIENT_BOTTOM_RIGHT:
552         case ORIENT_LEFT_BOTTOM:
553             vflip = hflip = true;
554             break;
555     }
556
557     VdpOutputSurface output = dst->p_sys->surface;
558
559     if (swap)
560     {
561         VdpRGBAFormat fmt;
562         uint32_t width, height;
563
564         err = vdp_output_surface_get_parameters(sys->vdp, output,
565                                                 &fmt, &width, &height);
566         if (err != VDP_STATUS_OK)
567         {
568             msg_Err(filter, "output %s %s failure: %s", "surface", "query",
569                     vdp_get_error_string(sys->vdp, err));
570             goto error;
571         }
572
573         err = vdp_output_surface_create(sys->vdp, sys->device,
574                                         fmt, height, width, &output);
575         if (err != VDP_STATUS_OK)
576         {
577             msg_Err(filter, "output %s %s failure: %s", "surface", "creation",
578                     vdp_get_error_string(sys->vdp, err));
579             goto error;
580         }
581     }
582
583     /* Render video into output */
584     VdpVideoMixerPictureStructure structure = f->structure;
585     VdpVideoSurface past[MAX_PAST];
586     VdpVideoSurface surface = f->frame->surface;
587     VdpVideoSurface future[MAX_FUTURE];
588     VdpRect src_rect = {
589         filter->fmt_in.video.i_x_offset, filter->fmt_in.video.i_y_offset,
590         filter->fmt_in.video.i_x_offset, filter->fmt_in.video.i_y_offset
591     };
592
593     if (hflip)
594         src_rect.x0 += filter->fmt_in.video.i_visible_width;
595     else
596         src_rect.x1 += filter->fmt_in.video.i_visible_width;
597     if (vflip)
598         src_rect.y0 += filter->fmt_in.video.i_visible_height;
599     else
600         src_rect.y1 += filter->fmt_in.video.i_visible_height;
601
602     VdpRect dst_rect = {
603         0, 0,
604         swap ? filter->fmt_out.video.i_visible_height
605              : filter->fmt_out.video.i_visible_width,
606         swap ? filter->fmt_out.video.i_visible_width
607              : filter->fmt_out.video.i_visible_height,
608     };
609
610     for (unsigned i = 0; i < MAX_PAST; i++)
611     {
612         f = sys->history[(MAX_PAST - 1) - i].field;
613         past[i] = (f != NULL) ? f->frame->surface : VDP_INVALID_HANDLE;
614     }
615     for (unsigned i = 0; i < MAX_FUTURE; i++)
616     {
617         f = sys->history[(MAX_PAST + 1) + i].field;
618         future[i] = (f != NULL) ? f->frame->surface : VDP_INVALID_HANDLE;
619     }
620
621     err = vdp_video_mixer_render(sys->vdp, sys->mixer, VDP_INVALID_HANDLE,
622                                  NULL, structure,
623                                  MAX_PAST, past, surface, MAX_FUTURE, future,
624                                  &src_rect, output, &dst_rect, NULL, 0, NULL);
625     if (err != VDP_STATUS_OK)
626     {
627         msg_Err(filter, "video %s %s failure: %s", "mixer", "rendering",
628                 vdp_get_error_string(sys->vdp, err));
629         picture_Release(dst);
630         dst = NULL;
631     }
632
633     if (swap)
634     {
635         err = vdp_output_surface_render_output_surface(sys->vdp,
636             dst->p_sys->surface, NULL, output, NULL, NULL, NULL,
637             VDP_OUTPUT_SURFACE_RENDER_ROTATE_90);
638         vdp_output_surface_destroy(sys->vdp, output);
639         if (err != VDP_STATUS_OK)
640         {
641             msg_Err(filter, "output %s %s failure: %s", "surface", "render",
642                     vdp_get_error_string(sys->vdp, err));
643             goto error;
644         }
645     }
646
647 skip:
648     f = sys->history[0].field;
649     if (f != NULL)
650         f->destroy(f); /* Release oldest field */
651     memmove(sys->history, sys->history + 1, /* Advance history */
652             sizeof (sys->history[0]) * (MAX_PAST + MAX_FUTURE));
653
654     return dst;
655 error:
656     picture_Release(dst);
657     dst = NULL;
658     goto skip;
659 }
660
661 static picture_t *YCbCrRender(filter_t *filter, picture_t *src)
662 {
663     /* FIXME: Define a way to initialize the mixer in Open() instead. */
664     if (unlikely(filter->p_sys->vdp == NULL))
665     {
666         picture_t *dummy = OutputAllocate(filter);
667         if (dummy != NULL)
668             picture_Release(dummy);
669     }
670
671     src = VideoImport(filter, src);
672     return (src != NULL) ? VideoRender(filter, src) : NULL;
673 }
674
675 static int OutputOpen(vlc_object_t *obj)
676 {
677     filter_t *filter = (filter_t *)obj;
678     if (filter->fmt_out.video.i_chroma != VLC_CODEC_VDPAU_OUTPUT)
679         return VLC_EGENERIC;
680
681     assert(filter->fmt_out.video.orientation == ORIENT_TOP_LEFT);
682
683     filter_sys_t *sys = malloc(sizeof (*sys));
684     if (unlikely(sys == NULL))
685         return VLC_ENOMEM;
686
687     sys->vdp = NULL;
688     sys->mixer = VDP_INVALID_HANDLE;
689
690     if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_444)
691     {
692         sys->chroma = VDP_CHROMA_TYPE_444;
693         sys->format = VDP_YCBCR_FORMAT_NV12;
694         filter->pf_video_filter = VideoRender;
695     }
696     else
697     if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_422)
698     {
699         sys->chroma = VDP_CHROMA_TYPE_422;
700         /* TODO: check if the drivery supports NV12 or UYVY */
701         sys->format = VDP_YCBCR_FORMAT_UYVY;
702         filter->pf_video_filter = VideoRender;
703     }
704     else
705     if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_420)
706     {
707         sys->chroma = VDP_CHROMA_TYPE_420;
708         sys->format = VDP_YCBCR_FORMAT_NV12;
709         filter->pf_video_filter = VideoRender;
710     }
711     else
712     if (vlc_fourcc_to_vdp_ycc(filter->fmt_in.video.i_chroma,
713                               &sys->chroma, &sys->format))
714         filter->pf_video_filter = YCbCrRender;
715     else
716     {
717         free(sys);
718         return VLC_EGENERIC;
719     }
720
721     /* NOTE: The video mixer capabilities should be checked here, and the
722      * then video mixer set up. But:
723      * 1) The VDPAU back-end is accessible only once the first picture
724      *    gets filtered. Thus the video mixer is created later.
725      * 2) Bailing out due to insufficient capabilities would break the
726      *    video pipeline. Thus capabilities should be checked earlier. */
727
728     for (unsigned i = 0; i < MAX_PAST + MAX_FUTURE; i++)
729         sys->history[i].field = NULL;
730
731     sys->procamp.brightness = 0.f;
732     sys->procamp.contrast = 1.f;
733     sys->procamp.saturation = 1.f;
734     sys->procamp.hue = 0.f;
735
736     filter->pf_video_flush = Flush;
737     filter->p_sys = sys;
738     return VLC_SUCCESS;
739 }
740
741 static void OutputClose(vlc_object_t *obj)
742 {
743     filter_t *filter = (filter_t *)obj;
744     filter_sys_t *sys = filter->p_sys;
745
746     Flush(filter);
747     if (sys->mixer != VDP_INVALID_HANDLE)
748     {
749         vdp_video_mixer_destroy(sys->vdp, sys->mixer);
750         vdp_release_x11(sys->vdp);
751     }
752     free(sys);
753 }
754
755 static picture_t *VideoExport_Filter(filter_t *filter, picture_t *src)
756 {
757     if (unlikely(src->context == NULL))
758     {
759         msg_Err(filter, "corrupt VDPAU video surface %p", src);
760         picture_Release(src);
761         return NULL;
762     }
763
764     picture_t *dst = filter_NewPicture(filter);
765     if (dst == NULL)
766         return NULL;
767
768     return VideoExport(filter, src, dst);
769 }
770
771 static int YCbCrOpen(vlc_object_t *obj)
772 {
773     filter_t *filter = (filter_t *)obj;
774     if (filter->fmt_in.video.i_chroma != VLC_CODEC_VDPAU_VIDEO_420
775      && filter->fmt_in.video.i_chroma != VLC_CODEC_VDPAU_VIDEO_422
776      && filter->fmt_in.video.i_chroma != VLC_CODEC_VDPAU_VIDEO_444)
777         return VLC_EGENERIC;
778
779     if (filter->fmt_in.video.i_visible_width
780                                        != filter->fmt_out.video.i_visible_width
781      || filter->fmt_in.video.i_visible_height
782                                       != filter->fmt_out.video.i_visible_height
783      || filter->fmt_in.video.i_x_offset != filter->fmt_out.video.i_x_offset
784      || filter->fmt_in.video.i_y_offset != filter->fmt_out.video.i_y_offset
785      || (filter->fmt_in.video.i_sar_num * filter->fmt_out.video.i_sar_den
786           != filter->fmt_in.video.i_sar_den * filter->fmt_out.video.i_sar_num))
787         return VLC_EGENERIC;
788
789     filter_sys_t *sys = malloc(sizeof (*sys));
790     if (unlikely(sys == NULL))
791         return VLC_ENOMEM;
792
793     if (!vlc_fourcc_to_vdp_ycc(filter->fmt_out.video.i_chroma,
794                                &sys->chroma, &sys->format))
795     {
796         free(sys);
797         return VLC_EGENERIC;
798     }
799
800     filter->pf_video_filter = VideoExport_Filter;
801     filter->p_sys = sys;
802     return VLC_SUCCESS;
803 }
804
805 static void YCbCrClose(vlc_object_t *obj)
806 {
807     filter_t *filter = (filter_t *)obj;
808     filter_sys_t *sys = filter->p_sys;
809
810     free(sys);
811 }
812
813 static const int algo_values[] = {
814     -1,
815     VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL,
816     VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL,
817 };
818
819 static const char *const algo_names[] = {
820     N_("Bob"), N_("Temporal"), N_("Temporal-spatial"),
821 };
822
823 vlc_module_begin()
824     set_shortname(N_("VDPAU"))
825     set_description(N_("VDPAU surface conversions"))
826     set_capability("video filter2", 10)
827     set_category(CAT_VIDEO)
828     set_subcategory(SUBCAT_VIDEO_VFILTER)
829     set_callbacks(OutputOpen, OutputClose)
830
831     add_integer("vdpau-deinterlace",
832                 VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL,
833                 N_("Deinterlace"), N_("Deinterlacing algorithm"), true)
834         change_integer_list(algo_values, algo_names)
835     add_bool("vdpau-ivtc", false,
836              N_("Inverse telecine"), N_("Inverse telecine"), true)
837     add_bool("vdpau-chroma-skip", false,
838              N_("Deinterlace chroma skip"),
839              N_("Whether temporal deinterlacing applies to luma only"), true)
840     add_float_with_range("vdpau-noise-reduction", 0., 0., 1.,
841         N_("Noise reduction level"), N_("Noise reduction level"), true)
842     add_integer_with_range("vdpau-scaling", 0, 0, 9,
843        N_("Scaling quality"), N_("High quality scaling level"), true)
844
845     add_submodule()
846     set_callbacks(YCbCrOpen, YCbCrClose)
847 vlc_module_end()