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