Parametrize BM_DeinterlaceEffect so that we have both grayscale and BGRA.
[movit] / deinterlace_effect_test.cpp
1 // Unit tests for DeinterlaceEffect.
2
3 #ifdef HAVE_BENCHMARK
4 #include <benchmark/benchmark.h>
5 #endif
6 #include <epoxy/gl.h>
7
8 #include <algorithm>
9 #include <memory>
10
11 #include "effect_chain.h"
12 #include "gtest/gtest.h"
13 #include "image_format.h"
14 #include "input.h"
15 #include "deinterlace_effect.h"
16 #include "test_util.h"
17
18 using namespace std;
19
20 namespace movit {
21
22 TEST(DeinterlaceTest, ConstantColor) {
23         float data[] = {
24                 0.3f, 0.3f,
25                 0.3f, 0.3f,
26                 0.3f, 0.3f,
27         };
28         float expected_data[] = {
29                 0.3f, 0.3f,
30                 0.3f, 0.3f,
31                 0.3f, 0.3f,
32                 0.3f, 0.3f,
33                 0.3f, 0.3f,
34                 0.3f, 0.3f,
35         };
36         float out_data[12];
37         EffectChainTester tester(nullptr, 2, 6);
38         Effect *input1 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 2, 3);
39         Effect *input2 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 2, 3);
40         Effect *input3 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 2, 3);
41         Effect *input4 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 2, 3);
42         Effect *input5 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 2, 3);
43         Effect *deinterlace_effect = tester.get_chain()->add_effect(new DeinterlaceEffect(), input1, input2, input3, input4, input5);
44
45         ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 0));
46         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
47         expect_equal(expected_data, out_data, 2, 6);
48
49         ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 1));
50         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
51         expect_equal(expected_data, out_data, 2, 6);
52 }
53
54 // Also tests that top/bottom change works like expected.
55 TEST(DeinterlaceTest, VerticalInterpolation) {
56         const int width = 11;
57         const int height = 2;
58         float data[width * height] = {
59                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.2f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f, 
60                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Differs from previous.
61         };
62         float expected_data_top[width * height * 2] = {
63                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.2f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Unchanged.
64                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.3f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,
65                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Unchanged.
66                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Repeated.
67         };
68         float expected_data_bottom[width * height * 2] = {
69                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.2f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Repeated
70                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.2f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Unchanged.
71                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.3f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,
72                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Unchanged.
73         };
74         float neg_blowout_data[width * height];
75         float pos_blowout_data[width * height];
76         float out_data[width * height * 2];
77
78         // Set previous and next fields to something so big that all the temporal checks
79         // are effectively turned off.
80         fill(neg_blowout_data, neg_blowout_data + width * height, -100.0f);
81         fill(pos_blowout_data, pos_blowout_data + width * height,  100.0f);
82
83         EffectChainTester tester(nullptr, width, height * 2);
84         Effect *input1 = tester.add_input(neg_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
85         Effect *input2 = tester.add_input(neg_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
86         Effect *input3 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
87         Effect *input4 = tester.add_input(pos_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
88         Effect *input5 = tester.add_input(pos_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
89         Effect *deinterlace_effect = tester.get_chain()->add_effect(new DeinterlaceEffect(), input1, input2, input3, input4, input5);
90
91         ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 0));
92         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
93         expect_equal(expected_data_top, out_data, width, height * 2);
94
95         ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 1));
96         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
97         expect_equal(expected_data_bottom, out_data, width, height * 2);
98 }
99
100 TEST(DeinterlaceTest, DiagonalInterpolation) {
101         const int width = 11;
102         const int height = 3;
103         float data[width * height] = {
104                 0.0f, 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.2f, 0.6f, 0.8f, 0.0f, 0.0f,
105                 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f, 0.0f,   // Offset two pixels, one value modified.
106                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f,   // Offset four the other way.
107         };
108
109         // Expected degrees are marked in comments. Mostly we want +45 for the second line
110         // and -63 for the fourth, but due to the score being over three neighboring pixels,
111         // sometimes it doesn't work ideally like that.
112         float expected_data_top[width * height * 2] = {
113                 0.0f, 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.2f, 0.6f, 0.8f, 0.0f, 0.0f,   // Unchanged.
114                 // |    /     /     /     /     /     /     /     /     /    |
115                 // 0  +45   +45   +45   +45   +45   +45   +45   +45   +45    0
116                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.3f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,
117                 // | /     /     /     /     /     /     /     /     /       |  
118                 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f, 0.0f,   // Unchanged.
119
120                 // 0  -45   -63   -63   -63   -63   -63   -63   +63!  +63!  +63!
121                 0.0f, 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.2f, 0.3f, 0.2f,
122
123                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f,   // Unchanged.
124                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f,   // Repeated.
125         };
126         float neg_blowout_data[width * height];
127         float pos_blowout_data[width * height];
128         float out_data[width * height * 2];
129
130         // Set previous and next fields to something so big that all the temporal checks
131         // are effectively turned off.
132         fill(neg_blowout_data, neg_blowout_data + width * height, -100.0f);
133         fill(pos_blowout_data, pos_blowout_data + width * height,  100.0f);
134
135         EffectChainTester tester(nullptr, width, height * 2);
136         Effect *input1 = tester.add_input(neg_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
137         Effect *input2 = tester.add_input(neg_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
138         Effect *input3 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
139         Effect *input4 = tester.add_input(pos_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
140         Effect *input5 = tester.add_input(pos_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
141         Effect *deinterlace_effect = tester.get_chain()->add_effect(new DeinterlaceEffect(), input1, input2, input3, input4, input5);
142
143         ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 0));
144         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
145         expect_equal(expected_data_top, out_data, width, height * 2);
146 }
147
148 TEST(DeinterlaceTest, FlickerBox) {
149         const int width = 4;
150         const int height = 4;
151         float white_data[width * height] = {
152                 1.0f, 1.0f, 1.0f, 1.0f,
153                 1.0f, 1.0f, 1.0f, 1.0f,
154                 1.0f, 1.0f, 1.0f, 1.0f,
155                 1.0f, 1.0f, 1.0f, 1.0f,
156         };
157         float black_data[width * height] = {
158                 0.0f, 0.0f, 0.0f, 0.0f,
159                 0.0f, 0.0f, 0.0f, 0.0f,
160                 0.0f, 0.0f, 0.0f, 0.0f,
161                 0.0f, 0.0f, 0.0f, 0.0f,
162         };
163         float striped_data[width * height * 2] = {
164                 1.0f, 1.0f, 1.0f, 1.0f,
165                 0.0f, 0.0f, 0.0f, 0.0f,
166                 1.0f, 1.0f, 1.0f, 1.0f,
167                 0.0f, 0.0f, 0.0f, 0.0f,
168                 1.0f, 1.0f, 1.0f, 1.0f,
169                 0.0f, 0.0f, 0.0f, 0.0f,
170                 1.0f, 1.0f, 1.0f, 1.0f,
171                 0.0f, 0.0f, 0.0f, 0.0f,
172         };
173         float out_data[width * height * 2];
174
175         {
176                 EffectChainTester tester(nullptr, width, height * 2);
177                 Effect *white_input = tester.add_input(white_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
178                 Effect *black_input = tester.add_input(black_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
179                 Effect *deinterlace_effect = tester.get_chain()->add_effect(new DeinterlaceEffect(), white_input, black_input, white_input, black_input, white_input);
180
181                 ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 0));
182                 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
183                 expect_equal(white_data, out_data, width, height);
184                 expect_equal(white_data, out_data + width * height, width, height);
185         }
186
187         {
188                 EffectChainTester tester(nullptr, width, height * 2);
189                 Effect *white_input = tester.add_input(white_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
190                 Effect *black_input = tester.add_input(black_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
191                 Effect *deinterlace_effect = tester.get_chain()->add_effect(new DeinterlaceEffect(), white_input, black_input, white_input, black_input, white_input);
192
193                 ASSERT_TRUE(deinterlace_effect->set_int("enable_spatial_interlacing_check", 0));
194                 ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 0));
195                 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
196                 expect_equal(striped_data, out_data, width, height * 2);
197         }
198 }
199
200 #ifdef HAVE_BENCHMARK
201 void BM_DeinterlaceEffect(benchmark::State &state, size_t bytes_per_pixel, MovitPixelFormat input_format, GLenum output_format)
202 {
203         unsigned width = state.range(0), height = state.range(1);
204         unsigned field_height = height / 2;
205
206         unique_ptr<float[]> field1(new float[width * field_height * bytes_per_pixel]);
207         unique_ptr<float[]> field2(new float[width * field_height * bytes_per_pixel]);
208         unique_ptr<float[]> field3(new float[width * field_height * bytes_per_pixel]);
209         unique_ptr<float[]> field4(new float[width * field_height * bytes_per_pixel]);
210         unique_ptr<float[]> field5(new float[width * field_height * bytes_per_pixel]);
211         unique_ptr<float[]> out_data(new float[width * height * bytes_per_pixel]);
212
213         for (unsigned i = 0; i < width * field_height * bytes_per_pixel; ++i) {
214                 field1[i] = rand();
215                 field2[i] = rand();
216                 field3[i] = rand();
217                 field4[i] = rand();
218                 field5[i] = rand();
219         }
220
221         EffectChainTester tester(nullptr, width, height);
222         Effect *input1 = tester.add_input(field1.get(), input_format, COLORSPACE_sRGB, GAMMA_LINEAR, width, field_height);
223         Effect *input2 = tester.add_input(field2.get(), input_format, COLORSPACE_sRGB, GAMMA_LINEAR, width, field_height);
224         Effect *input3 = tester.add_input(field3.get(), input_format, COLORSPACE_sRGB, GAMMA_LINEAR, width, field_height);
225         Effect *input4 = tester.add_input(field4.get(), input_format, COLORSPACE_sRGB, GAMMA_LINEAR, width, field_height);
226         Effect *input5 = tester.add_input(field5.get(), input_format, COLORSPACE_sRGB, GAMMA_LINEAR, width, field_height);
227         Effect *deinterlace_effect = tester.get_chain()->add_effect(new DeinterlaceEffect(), input1, input2, input3, input4, input5);
228
229         ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 0));
230
231         tester.benchmark(state, out_data.get(), output_format, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
232 }
233 BENCHMARK_CAPTURE(BM_DeinterlaceEffect, Gray, 1, FORMAT_GRAYSCALE, GL_RED)->Args({720, 576})->Args({1280, 720})->Args({1920, 1080})->UseRealTime()->Unit(benchmark::kMicrosecond);
234 BENCHMARK_CAPTURE(BM_DeinterlaceEffect, BGRA, 4, FORMAT_BGRA_POSTMULTIPLIED_ALPHA, GL_BGRA)->Args({720, 576})->Args({1280, 720})->Args({1920, 1080})->UseRealTime()->Unit(benchmark::kMicrosecond);
235
236 #endif
237
238 }  // namespace movit