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