]> git.sesse.net Git - movit/blob - deinterlace_effect.h
Release Movit 1.6.2.
[movit] / deinterlace_effect.h
1 #ifndef _MOVIT_DEINTERLACE_EFFECT_H
2 #define _MOVIT_DEINTERLACE_EFFECT_H 1
3
4 // YADIF deinterlacing filter (original by Michael Niedermayer, in MPlayer).
5 //
6 // Good deinterlacing is very hard. YADIF, despite its innocious-sounding
7 // name (Yet Another DeInterlacing Filter) is probably the most commonly
8 // used (non-trivial) deinterlacing filter in the open-source world.
9 // It works by trying to fill in the missing lines from neighboring ones
10 // (spatial interpolation), and then constrains that estimate within an
11 // interval found from previous and next frames (temporal interpolation).
12 // It's not very fast, even in GPU implementation, but 1080i60 -> 1080p60
13 // realtime conversion is well within range for a mid-range GPU.
14 //
15 // The inner workings of YADIF are poorly documented; implementation details
16 // are generally explained the .frag file. However, a few things should be
17 // mentioned here: YADIF has two modes, with and without a “spatial interlacing
18 // check” which basically allows more temporal change in areas of high detail.
19 // (The variant with the check corresponds to the original's modes 0 and 1, and
20 // the variant without to modes 2 and 3. The remaining difference is whether it
21 // is frame-doubling or not, which in Movit is up to the driver, not the
22 // filter.)
23 //
24 // Neither mode is perfect by any means. If the spatial check is off, the
25 // filter possesses the potentially nice quality that a static picture
26 // deinterlaces exactly to itself. (If it's on, there's some flickering
27 // on very fine vertical detail. The picture is nice and stable if no such
28 // detail is present, though.) But then, certain patterns, like horizontally
29 // scrolling text, leaves residues. Both have issues with diagonal lines at
30 // certain angles leaving stray pixels, although in practical applications,
31 // YADIF is pretty good.
32 //
33 // In general, having the spatial check on (the default) is the safe choice.
34 // However, if you are reasonably certain that the image comes from a video source
35 // (ie., no graphical overlays), or if the case of still images is particularly
36 // important for you (e.g., slides from a laptop), you could turn it off.
37 // It is slightly faster, although in practice, it does not mean all that much.
38 // You need to decide before finalize(), as the choice gets compiled into the shader.
39 //
40 // YADIF needs five fields as input; the previous two, the current one, and
41 // then the two next ones. (By convention, they come in that order, although if
42 // you reverse them, it doesn't matter, as the filter is symmetric. It _does_
43 // matter if you change the ordering in any other way, though.) They need to be
44 // of the same resolution, or the effect will assert-fail. If you cannot supply
45 // this, you could simply reuse the current field for previous/next as
46 // required; it won't be optimal in any way, but it also won't blow up on you.
47 //
48 // This requirement to “see the future” will mean you have an extra full frame
49 // of delay (33.3 ms at 60i, 40 ms at 50i). You will also need to tell the
50 // filter for each and every invocation if the current field (ie., the one in
51 // the middle input) is a top or bottom field (neighboring fields have opposite
52 // parity, so all the others are implicit).
53
54 #include <epoxy/gl.h>
55 #include <memory>
56 #include <string>
57
58 #include "effect.h"
59
60 namespace movit {
61
62 class DeinterlaceComputeEffect;
63
64 class DeinterlaceEffect : public Effect {
65 public:
66         DeinterlaceEffect();
67         std::string effect_type_id() const override { return "DeinterlaceEffect"; }
68         std::string output_fragment_shader() override;
69
70         // Replaces itself with DeinterlaceComputeEffect if compute shaders are supported.
71         // Otherwise, does nothing.
72         void rewrite_graph(EffectChain *graph, Node *self) override;
73         bool set_int(const std::string &key, int value) override;
74
75         void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num) override;
76
77         // First = before previous, second = previous, third = current,
78         // fourth = next, fifth = after next. These are treated symmetrically,
79         // though.
80         //
81         // Note that if you have interlaced _frames_ and not _fields_, you will
82         // need to pull them apart first, for instance with SliceEffect.
83         unsigned num_inputs() const override { return 5; }
84         bool needs_texture_bounce() const override { return true; }
85         bool changes_output_size() const override { return true; }
86
87         AlphaHandling alpha_handling() const override { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
88
89         void inform_input_size(unsigned input_num, unsigned width, unsigned height) override;
90         void get_output_size(unsigned *width, unsigned *height,
91                              unsigned *virtual_width, unsigned *virtual_height) const override;
92
93         enum FieldPosition { TOP = 0, BOTTOM = 1 };
94
95 private:
96         // If compute shaders are supported, contains the actual effect.
97         // If not, nullptr.
98         std::unique_ptr<DeinterlaceComputeEffect> compute_effect_owner;
99         DeinterlaceComputeEffect *compute_effect = nullptr;
100
101         unsigned widths[5], heights[5];
102
103         // See file-level comment for explanation of this option.
104         bool enable_spatial_interlacing_check;
105
106         // Which field the current input (the middle one) is.
107         FieldPosition current_field_position;
108
109         // Offset for one pixel in the horizontal direction (1/width).
110         float inv_width;
111
112         // Vertical resolution of the output.
113         float num_lines;
114
115         // All of these offsets are vertical texel offsets; they are needed to adjust
116         // for the changed texel center as the number of lines double, and depend on
117         // <current_field_position>.
118
119         // For sampling unchanged lines from the current field.
120         float self_offset;
121
122         // For evaluating the low-pass filter (in the current field). Four taps.
123         float current_offset[2];
124
125         // For evaluating the high-pass filter (in the previous and next fields).
126         // Five taps, but evaluated twice since there are two fields.
127         float other_offset[3];
128 };
129
130 // A compute shader implementation of DeinterlaceEffect. It saves a bunch of loads
131 // since it can share them between neighboring pixels (and also does not need
132 // texture bounce), so it has the potential to be faster, although exactly how
133 // much depends on your chain and other factors. DeinterlaceEffect will
134 // automatically become a proxy to DeinterlaceComputeEffect if your system
135 // supports compute shaders.
136 class DeinterlaceComputeEffect : public Effect {
137 public:
138         DeinterlaceComputeEffect();
139         std::string effect_type_id() const override { return "DeinterlaceComputeEffect"; }
140         std::string output_fragment_shader() override;
141
142         void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num) override;
143
144         unsigned num_inputs() const override { return 5; }
145         bool changes_output_size() const override { return true; }
146         bool is_compute_shader() const override { return true; }
147         void get_compute_dimensions(unsigned output_width, unsigned output_height,
148                                     unsigned *x, unsigned *y, unsigned *z) const override;
149
150         AlphaHandling alpha_handling() const override { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
151
152         void inform_input_size(unsigned input_num, unsigned width, unsigned height) override;
153         void get_output_size(unsigned *width, unsigned *height,
154                              unsigned *virtual_width, unsigned *virtual_height) const override;
155
156         enum FieldPosition { TOP = 0, BOTTOM = 1 };
157
158 private:
159         unsigned widths[5], heights[5];
160
161         // See file-level comment for explanation of this option.
162         bool enable_spatial_interlacing_check;
163
164         // Which field the current input (the middle one) is.
165         FieldPosition current_field_position;
166
167         // Offset for one pixel in the horizontal and verticla direction (1/width, 1/height).
168         float inv_width, inv_height;
169
170         // For evaluating the low-pass filter (in the current field). Four taps.
171         float current_field_vertical_offset;
172 };
173
174 }  // namespace movit
175
176 #endif // !defined(_MOVIT_DEINTERLACE_EFFECT_H)