Make sure we do not send infinities to benchmarking textures.
[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 class DeinterlaceTest : public testing::TestWithParam<string> {
23 protected:
24         DeinterlaceTest() : disabler(GetParam() == "fragment") {}
25         bool should_skip() { return disabler.should_skip(); }
26
27 private:
28         DisableComputeShadersTemporarily disabler;
29 };
30
31 TEST_P(DeinterlaceTest, ConstantColor) {
32         if (should_skip()) return;
33         float data[] = {
34                 0.3f, 0.3f,
35                 0.3f, 0.3f,
36                 0.3f, 0.3f,
37         };
38         float expected_data[] = {
39                 0.3f, 0.3f,
40                 0.3f, 0.3f,
41                 0.3f, 0.3f,
42                 0.3f, 0.3f,
43                 0.3f, 0.3f,
44                 0.3f, 0.3f,
45         };
46         float out_data[12];
47         EffectChainTester tester(nullptr, 2, 6);
48         Effect *input1 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 2, 3);
49         Effect *input2 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 2, 3);
50         Effect *input3 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 2, 3);
51         Effect *input4 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 2, 3);
52         Effect *input5 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 2, 3);
53         Effect *deinterlace_effect = tester.get_chain()->add_effect(new DeinterlaceEffect(), input1, input2, input3, input4, input5);
54
55         ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 0));
56         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
57         expect_equal(expected_data, out_data, 2, 6);
58
59         ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 1));
60         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
61         expect_equal(expected_data, out_data, 2, 6);
62 }
63
64 // Also tests that top/bottom change works like expected.
65 TEST_P(DeinterlaceTest, VerticalInterpolation) {
66         if (should_skip()) return;
67         const int width = 11;
68         const int height = 2;
69         float data[width * height] = {
70                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.2f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f, 
71                 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.
72         };
73         float expected_data_top[width * height * 2] = {
74                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.2f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Unchanged.
75                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.3f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,
76                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Unchanged.
77                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Repeated.
78         };
79         float expected_data_bottom[width * height * 2] = {
80                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.2f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Repeated
81                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.2f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Unchanged.
82                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.3f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,
83                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,   // Unchanged.
84         };
85         float neg_blowout_data[width * height];
86         float pos_blowout_data[width * height];
87         float out_data[width * height * 2];
88
89         // Set previous and next fields to something so big that all the temporal checks
90         // are effectively turned off.
91         fill(neg_blowout_data, neg_blowout_data + width * height, -100.0f);
92         fill(pos_blowout_data, pos_blowout_data + width * height,  100.0f);
93
94         EffectChainTester tester(nullptr, width, height * 2);
95         Effect *input1 = tester.add_input(neg_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
96         Effect *input2 = tester.add_input(neg_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
97         Effect *input3 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
98         Effect *input4 = tester.add_input(pos_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
99         Effect *input5 = tester.add_input(pos_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
100         Effect *deinterlace_effect = tester.get_chain()->add_effect(new DeinterlaceEffect(), input1, input2, input3, input4, input5);
101
102         ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 0));
103         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
104         expect_equal(expected_data_top, out_data, width, height * 2);
105
106         ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 1));
107         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
108         expect_equal(expected_data_bottom, out_data, width, height * 2);
109 }
110
111 TEST_P(DeinterlaceTest, DiagonalInterpolation) {
112         if (should_skip()) return;
113         const int width = 11;
114         const int height = 3;
115         float data[width * height] = {
116                 0.0f, 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.2f, 0.6f, 0.8f, 0.0f, 0.0f,
117                 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.
118                 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.
119         };
120
121         // Expected degrees are marked in comments. Mostly we want +45 for the second line
122         // and -63 for the fourth, but due to the score being over three neighboring pixels,
123         // sometimes it doesn't work ideally like that.
124         float expected_data_top[width * height * 2] = {
125                 0.0f, 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.2f, 0.6f, 0.8f, 0.0f, 0.0f,   // Unchanged.
126                 // |    /     /     /     /     /     /     /     /     /    |
127                 // 0  +45   +45   +45   +45   +45   +45   +45   +45   +45    0
128                 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.3f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f,
129                 // | /     /     /     /     /     /     /     /     /       |  
130                 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f, 0.0f, 0.0f, 0.0f, 0.0f,   // Unchanged.
131
132                 // 0  -45   -63   -63   -63   -63   -63   -63   +63!  +63!  +63!
133                 0.0f, 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.2f, 0.3f, 0.2f,
134
135                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f,   // Unchanged.
136                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.4f, 0.6f, 0.4f, 0.6f, 0.8f,   // Repeated.
137         };
138         float neg_blowout_data[width * height];
139         float pos_blowout_data[width * height];
140         float out_data[width * height * 2];
141
142         // Set previous and next fields to something so big that all the temporal checks
143         // are effectively turned off.
144         fill(neg_blowout_data, neg_blowout_data + width * height, -100.0f);
145         fill(pos_blowout_data, pos_blowout_data + width * height,  100.0f);
146
147         EffectChainTester tester(nullptr, width, height * 2);
148         Effect *input1 = tester.add_input(neg_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
149         Effect *input2 = tester.add_input(neg_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
150         Effect *input3 = tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
151         Effect *input4 = tester.add_input(pos_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
152         Effect *input5 = tester.add_input(pos_blowout_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
153         Effect *deinterlace_effect = tester.get_chain()->add_effect(new DeinterlaceEffect(), input1, input2, input3, input4, input5);
154
155         ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 0));
156         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
157         expect_equal(expected_data_top, out_data, width, height * 2);
158 }
159
160 TEST_P(DeinterlaceTest, FlickerBox) {
161         if (should_skip()) return;
162         const int width = 4;
163         const int height = 4;
164         float white_data[width * height] = {
165                 1.0f, 1.0f, 1.0f, 1.0f,
166                 1.0f, 1.0f, 1.0f, 1.0f,
167                 1.0f, 1.0f, 1.0f, 1.0f,
168                 1.0f, 1.0f, 1.0f, 1.0f,
169         };
170         float black_data[width * height] = {
171                 0.0f, 0.0f, 0.0f, 0.0f,
172                 0.0f, 0.0f, 0.0f, 0.0f,
173                 0.0f, 0.0f, 0.0f, 0.0f,
174                 0.0f, 0.0f, 0.0f, 0.0f,
175         };
176         float striped_data[width * height * 2] = {
177                 1.0f, 1.0f, 1.0f, 1.0f,
178                 0.0f, 0.0f, 0.0f, 0.0f,
179                 1.0f, 1.0f, 1.0f, 1.0f,
180                 0.0f, 0.0f, 0.0f, 0.0f,
181                 1.0f, 1.0f, 1.0f, 1.0f,
182                 0.0f, 0.0f, 0.0f, 0.0f,
183                 1.0f, 1.0f, 1.0f, 1.0f,
184                 0.0f, 0.0f, 0.0f, 0.0f,
185         };
186         float out_data[width * height * 2];
187
188         {
189                 EffectChainTester tester(nullptr, width, height * 2);
190                 Effect *white_input = tester.add_input(white_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
191                 Effect *black_input = tester.add_input(black_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
192                 Effect *deinterlace_effect = tester.get_chain()->add_effect(new DeinterlaceEffect(), white_input, black_input, white_input, black_input, white_input);
193
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(white_data, out_data, width, height);
197                 expect_equal(white_data, out_data + width * height, width, height);
198         }
199
200         {
201                 EffectChainTester tester(nullptr, width, height * 2);
202                 Effect *white_input = tester.add_input(white_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
203                 Effect *black_input = tester.add_input(black_data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, width, height);
204                 Effect *deinterlace_effect = tester.get_chain()->add_effect(new DeinterlaceEffect(), white_input, black_input, white_input, black_input, white_input);
205
206                 ASSERT_TRUE(deinterlace_effect->set_int("enable_spatial_interlacing_check", 0));
207                 ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 0));
208                 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
209                 expect_equal(striped_data, out_data, width, height * 2);
210         }
211 }
212
213 INSTANTIATE_TEST_CASE_P(DeinterlaceTest,
214                         DeinterlaceTest,
215                         testing::Values("fragment", "compute"));
216
217 #ifdef HAVE_BENCHMARK
218 namespace {
219
220 struct TestFormat {
221         MovitPixelFormat input_format;
222         GLenum output_format;
223         size_t bytes_per_pixel;
224 };
225 TestFormat gray_format = { FORMAT_GRAYSCALE, GL_RED, 1 };
226 TestFormat bgra_format = { FORMAT_BGRA_PREMULTIPLIED_ALPHA, GL_BGRA, 4 };
227
228 }  // namespace
229
230 void BM_DeinterlaceEffect(benchmark::State &state, TestFormat format, bool spatial_interlacing_check, const std::string &shader_type)
231 {
232         DisableComputeShadersTemporarily disabler(shader_type == "fragment");
233         if (disabler.should_skip(&state)) return;
234
235         unsigned width = state.range(0), height = state.range(1);
236         unsigned field_height = height / 2;
237
238         unique_ptr<float[]> field1(new float[width * field_height * format.bytes_per_pixel]);
239         unique_ptr<float[]> field2(new float[width * field_height * format.bytes_per_pixel]);
240         unique_ptr<float[]> field3(new float[width * field_height * format.bytes_per_pixel]);
241         unique_ptr<float[]> field4(new float[width * field_height * format.bytes_per_pixel]);
242         unique_ptr<float[]> field5(new float[width * field_height * format.bytes_per_pixel]);
243         unique_ptr<float[]> out_data(new float[width * height * format.bytes_per_pixel]);
244
245         for (unsigned i = 0; i < width * field_height * format.bytes_per_pixel; ++i) {
246                 field1[i] = rand() / (RAND_MAX + 1.0);
247                 field2[i] = rand() / (RAND_MAX + 1.0);
248                 field3[i] = rand() / (RAND_MAX + 1.0);
249                 field4[i] = rand() / (RAND_MAX + 1.0);
250                 field5[i] = rand() / (RAND_MAX + 1.0);
251         }
252
253         EffectChainTester tester(nullptr, width, height);
254         Effect *input1 = tester.add_input(field1.get(), format.input_format, COLORSPACE_sRGB, GAMMA_LINEAR, width, field_height);
255         Effect *input2 = tester.add_input(field2.get(), format.input_format, COLORSPACE_sRGB, GAMMA_LINEAR, width, field_height);
256         Effect *input3 = tester.add_input(field3.get(), format.input_format, COLORSPACE_sRGB, GAMMA_LINEAR, width, field_height);
257         Effect *input4 = tester.add_input(field4.get(), format.input_format, COLORSPACE_sRGB, GAMMA_LINEAR, width, field_height);
258         Effect *input5 = tester.add_input(field5.get(), format.input_format, COLORSPACE_sRGB, GAMMA_LINEAR, width, field_height);
259         Effect *deinterlace_effect = tester.get_chain()->add_effect(new DeinterlaceEffect(), input1, input2, input3, input4, input5);
260
261         ASSERT_TRUE(deinterlace_effect->set_int("current_field_position", 0));
262         ASSERT_TRUE(deinterlace_effect->set_int("enable_spatial_interlacing_check", spatial_interlacing_check));
263
264         tester.benchmark(state, out_data.get(), format.output_format, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
265 }
266 BENCHMARK_CAPTURE(BM_DeinterlaceEffect, Gray, gray_format, true, "fragment")->Args({720, 576})->Args({1280, 720})->Args({1920, 1080})->UseRealTime()->Unit(benchmark::kMicrosecond);
267 BENCHMARK_CAPTURE(BM_DeinterlaceEffect, BGRA, bgra_format, true, "fragment")->Args({720, 576})->Args({1280, 720})->Args({1920, 1080})->UseRealTime()->Unit(benchmark::kMicrosecond);
268 BENCHMARK_CAPTURE(BM_DeinterlaceEffect, BGRANoSpatialCheck, bgra_format, false, "fragment")->Args({720, 576})->Args({1280, 720})->Args({1920, 1080})->UseRealTime()->Unit(benchmark::kMicrosecond);
269 BENCHMARK_CAPTURE(BM_DeinterlaceEffect, GrayCompute, gray_format, true, "compute")->Args({720, 576})->Args({1280, 720})->Args({1920, 1080})->UseRealTime()->Unit(benchmark::kMicrosecond);
270 BENCHMARK_CAPTURE(BM_DeinterlaceEffect, BGRACompute, bgra_format, true, "compute")->Args({720, 576})->Args({1280, 720})->Args({1920, 1080})->UseRealTime()->Unit(benchmark::kMicrosecond);
271 BENCHMARK_CAPTURE(BM_DeinterlaceEffect, BGRANoSpatialCheckCompute, bgra_format, false, "compute")->Args({720, 576})->Args({1280, 720})->Args({1920, 1080})->UseRealTime()->Unit(benchmark::kMicrosecond);
272
273 #endif
274
275 }  // namespace movit