1 // Unit tests for ResampleEffect.
4 #include <gtest/gtest.h>
9 #include "effect_chain.h"
10 #include "flat_input.h"
11 #include "image_format.h"
13 #include "resample_effect.h"
14 #include "test_util.h"
24 return sin(M_PI * x) / (M_PI * x);
27 float lanczos(float x, float a)
32 return sinc(x) * sinc(x / a);
38 TEST(ResampleEffectTest, IdentityTransformDoesNothing) {
41 float data[size * size] = {
47 float out_data[size * size];
49 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
50 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
51 ASSERT_TRUE(resample_effect->set_int("width", 4));
52 ASSERT_TRUE(resample_effect->set_int("height", 4));
53 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
55 expect_equal(data, out_data, size, size);
58 TEST(ResampleEffectTest, UpscaleByTwoGetsCorrectPixelCenters) {
61 float data[size * size] = {
62 0.0, 0.0, 0.0, 0.0, 0.0,
63 0.0, 0.0, 0.0, 0.0, 0.0,
64 0.0, 0.0, 1.0, 0.0, 0.0,
65 0.0, 0.0, 0.0, 0.0, 0.0,
66 0.0, 0.0, 0.0, 0.0, 0.0,
68 float expected_data[size * size * 4], out_data[size * size * 4];
70 for (int y = 0; y < size * 2; ++y) {
71 for (int x = 0; x < size * 2; ++x) {
72 float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
73 weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
74 expected_data[y * (size * 2) + x] = weight;
78 EffectChainTester tester(nullptr, size * 2, size * 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
81 format.color_space = COLORSPACE_sRGB;
82 format.gamma_curve = GAMMA_LINEAR;
84 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
85 input->set_pixel_data(data);
86 tester.get_chain()->add_input(input);
88 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
89 ASSERT_TRUE(resample_effect->set_int("width", size * 2));
90 ASSERT_TRUE(resample_effect->set_int("height", size * 2));
91 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
93 expect_equal(expected_data, out_data, size * 2, size * 2);
96 TEST(ResampleEffectTest, DownscaleByTwoGetsCorrectPixelCenters) {
99 // This isn't a perfect dot, since the Lanczos filter has a slight
100 // sharpening effect; the most important thing is that we have kept
101 // the texel center right (everything is nicely symmetric).
102 // The approximate magnitudes have been checked against ImageMagick.
103 float expected_data[size * size] = {
104 0.0045, -0.0067, -0.0599, -0.0067, 0.0045,
105 -0.0067, 0.0100, 0.0892, 0.0100, -0.0067,
106 -0.0599, 0.0890, 0.7925, 0.0892, -0.0599,
107 -0.0067, 0.0100, 0.0890, 0.0100, -0.0067,
108 0.0045, -0.0067, -0.0599, -0.0067, 0.0045,
110 float data[size * size * 4], out_data[size * size];
112 for (int y = 0; y < size * 2; ++y) {
113 for (int x = 0; x < size * 2; ++x) {
114 float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
115 weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
116 data[y * (size * 2) + x] = weight;
120 EffectChainTester tester(nullptr, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
123 format.color_space = COLORSPACE_sRGB;
124 format.gamma_curve = GAMMA_LINEAR;
126 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size * 2, size * 2);
127 input->set_pixel_data(data);
128 tester.get_chain()->add_input(input);
130 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
131 ASSERT_TRUE(resample_effect->set_int("width", size));
132 ASSERT_TRUE(resample_effect->set_int("height", size));
133 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
135 expect_equal(expected_data, out_data, size, size);
138 TEST(ResampleEffectTest, UpscaleByThreeGetsCorrectPixelCenters) {
141 float data[size * size] = {
142 0.0, 0.0, 0.0, 0.0, 0.0,
143 0.0, 0.0, 0.0, 0.0, 0.0,
144 0.0, 0.0, 1.0, 0.0, 0.0,
145 0.0, 0.0, 0.0, 0.0, 0.0,
146 0.0, 0.0, 0.0, 0.0, 0.0,
148 float out_data[size * size * 9];
150 EffectChainTester tester(nullptr, size * 3, size * 3, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
153 format.color_space = COLORSPACE_sRGB;
154 format.gamma_curve = GAMMA_LINEAR;
156 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
157 input->set_pixel_data(data);
158 tester.get_chain()->add_input(input);
160 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
161 ASSERT_TRUE(resample_effect->set_int("width", size * 3));
162 ASSERT_TRUE(resample_effect->set_int("height", size * 3));
163 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
165 // We only bother checking that the middle pixel is still correct,
166 // and that symmetry holds.
167 EXPECT_FLOAT_EQ(1.0, out_data[7 * (size * 3) + 7]);
168 for (unsigned y = 0; y < size * 3; ++y) {
169 for (unsigned x = 0; x < size * 3; ++x) {
170 EXPECT_NEAR(out_data[y * (size * 3) + x], out_data[(size * 3 - y - 1) * (size * 3) + x], 1e-6);
171 EXPECT_NEAR(out_data[y * (size * 3) + x], out_data[y * (size * 3) + (size * 3 - x - 1)], 1e-6);
176 TEST(ResampleEffectTest, HeavyResampleGetsSumRight) {
177 // Do only one resample pass, more specifically the last one, which goes to
178 // our fp32 output. This allows us to analyze the precision without intermediate
180 const int swidth = 1, sheight = 1280;
181 const int dwidth = 1, dheight = 64;
183 float data[swidth * sheight], out_data[dwidth * dheight], expected_data[dwidth * dheight];
184 for (int y = 0; y < sheight; ++y) {
185 for (int x = 0; x < swidth; ++x) {
186 data[y * swidth + x] = 1.0f;
189 for (int y = 0; y < dheight; ++y) {
190 for (int x = 0; x < dwidth; ++x) {
191 expected_data[y * dwidth + x] = 1.0f;
195 EffectChainTester tester(nullptr, dwidth, dheight, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
198 format.color_space = COLORSPACE_sRGB;
199 format.gamma_curve = GAMMA_LINEAR;
201 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, swidth, sheight);
202 input->set_pixel_data(data);
204 tester.get_chain()->add_input(input);
205 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
206 ASSERT_TRUE(resample_effect->set_int("width", dwidth));
207 ASSERT_TRUE(resample_effect->set_int("height", dheight));
208 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
210 // Require that we are within 10-bit accuracy. Note that this limit is for
211 // one pass only, but the limit is tight enough that it should be good enough
212 // for 10-bit accuracy even after two passes.
213 expect_equal(expected_data, out_data, dwidth, dheight, 0.12 / 1023.0);
216 TEST(ResampleEffectTest, ReadWholePixelFromLeft) {
219 float data[size * size] = {
220 0.0, 0.0, 0.0, 0.0, 0.0,
221 0.0, 0.0, 0.0, 0.0, 0.0,
222 0.0, 0.0, 1.0, 0.0, 0.0,
223 0.0, 0.0, 0.0, 0.0, 0.0,
224 0.0, 0.0, 0.0, 0.0, 0.0,
226 float expected_data[size * size] = {
227 0.0, 0.0, 0.0, 0.0, 0.0,
228 0.0, 0.0, 0.0, 0.0, 0.0,
229 0.0, 1.0, 0.0, 0.0, 0.0,
230 0.0, 0.0, 0.0, 0.0, 0.0,
231 0.0, 0.0, 0.0, 0.0, 0.0,
233 float out_data[size * size];
235 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
236 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
237 ASSERT_TRUE(resample_effect->set_int("width", size));
238 ASSERT_TRUE(resample_effect->set_int("height", size));
239 ASSERT_TRUE(resample_effect->set_float("left", 1.0f));
240 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
242 expect_equal(expected_data, out_data, size, size);
245 TEST(ResampleEffectTest, ReadQuarterPixelFromLeft) {
248 float data[size * size] = {
249 0.0, 0.0, 0.0, 0.0, 0.0,
250 0.0, 0.0, 0.0, 0.0, 0.0,
251 0.0, 0.0, 1.0, 0.0, 0.0,
252 0.0, 0.0, 0.0, 0.0, 0.0,
253 0.0, 0.0, 0.0, 0.0, 0.0,
256 float expected_data[size * size] = {
257 0.0, 0.0, 0.0, 0.0, 0.0,
258 0.0, 0.0, 0.0, 0.0, 0.0,
260 // sin(x*pi)/(x*pi) * sin(x*pi/3)/(x*pi/3) for
261 // x = -1.75, -0.75, 0.25, 1.25, 2.25.
262 // Note that the weight is mostly on the left side.
263 -0.06779, 0.27019, 0.89007, -0.13287, 0.03002,
265 0.0, 0.0, 0.0, 0.0, 0.0,
266 0.0, 0.0, 0.0, 0.0, 0.0,
268 float out_data[size * size];
270 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
271 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
272 ASSERT_TRUE(resample_effect->set_int("width", size));
273 ASSERT_TRUE(resample_effect->set_int("height", size));
274 ASSERT_TRUE(resample_effect->set_float("left", 0.25f));
275 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
277 expect_equal(expected_data, out_data, size, size);
280 TEST(ResampleEffectTest, ReadQuarterPixelFromTop) {
282 const int height = 5;
284 float data[width * height] = {
292 // See ReadQuarterPixelFromLeft for explanation of the data.
293 float expected_data[width * height] = {
300 float out_data[width * height];
302 EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
303 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
304 ASSERT_TRUE(resample_effect->set_int("width", width));
305 ASSERT_TRUE(resample_effect->set_int("height", height));
306 ASSERT_TRUE(resample_effect->set_float("top", 0.25f));
307 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
309 expect_equal(expected_data, out_data, width, height);
312 TEST(ResampleEffectTest, ReadHalfPixelFromLeftAndScale) {
313 const int src_width = 4;
314 const int dst_width = 8;
316 float data[src_width * 1] = {
319 float expected_data[dst_width * 1] = {
320 // Empirical; the real test is that we are the same for 0.499 and 0.501.
321 1.1553, 1.7158, 2.2500, 2.7461, 3.2812, 3.8418, 4.0703, 4.0508
323 float out_data[dst_width * 1];
325 EffectChainTester tester(nullptr, dst_width, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
328 format.color_space = COLORSPACE_sRGB;
329 format.gamma_curve = GAMMA_LINEAR;
331 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, src_width, 1);
332 input->set_pixel_data(data);
333 tester.get_chain()->add_input(input);
335 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
336 ASSERT_TRUE(resample_effect->set_int("width", dst_width));
337 ASSERT_TRUE(resample_effect->set_int("height", 1));
339 // Check that we are (almost) the same no matter the rounding.
340 ASSERT_TRUE(resample_effect->set_float("left", 0.499f));
341 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
342 expect_equal(expected_data, out_data, dst_width, 1, 1.5f / 255.0f, 0.4f / 255.0f);
344 ASSERT_TRUE(resample_effect->set_float("left", 0.501f));
345 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
346 expect_equal(expected_data, out_data, dst_width, 1, 1.5f / 255.0f, 0.4f / 255.0f);
349 TEST(ResampleEffectTest, Zoom) {
351 const int height = 3;
353 float data[width * height] = {
354 0.0, 0.0, 0.0, 0.0, 0.0,
355 0.2, 0.4, 0.6, 0.4, 0.2,
356 0.0, 0.0, 0.0, 0.0, 0.0,
358 float expected_data[width * height] = {
359 0.0, 0.0, 0.0, 0.0, 0.0,
360 0.4, 0.5396, 0.6, 0.5396, 0.4,
361 0.0, 0.0, 0.0, 0.0, 0.0,
363 float out_data[width * height];
365 EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
366 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
367 ASSERT_TRUE(resample_effect->set_int("width", width));
368 ASSERT_TRUE(resample_effect->set_int("height", height));
369 ASSERT_TRUE(resample_effect->set_float("zoom_x", 2.0f));
370 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
372 expect_equal(expected_data, out_data, width, height);
375 TEST(ResampleEffectTest, VerticalZoomFromTop) {
377 const int height = 5;
379 float data[width * height] = {
380 0.2, 0.4, 0.6, 0.4, 0.2,
381 0.0, 0.0, 0.0, 0.0, 0.0,
382 0.0, 0.0, 0.0, 0.0, 0.0,
383 0.0, 0.0, 0.0, 0.0, 0.0,
384 0.0, 0.0, 0.0, 0.0, 0.0,
387 // Largely empirical data; the main point is that the top line
388 // is unchanged, since that's our zooming point.
389 float expected_data[width * height] = {
390 0.2000, 0.4000, 0.6000, 0.4000, 0.2000,
391 0.1389, 0.2778, 0.4167, 0.2778, 0.1389,
392 0.0600, 0.1199, 0.1798, 0.1199, 0.0600,
393 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
394 -0.0229, -0.0459, -0.0688, -0.0459, -0.0229,
396 float out_data[width * height];
398 EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
399 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
400 ASSERT_TRUE(resample_effect->set_int("width", width));
401 ASSERT_TRUE(resample_effect->set_int("height", height));
402 ASSERT_TRUE(resample_effect->set_float("zoom_y", 3.0f));
403 ASSERT_TRUE(resample_effect->set_float("zoom_center_y", 0.5f / height));
404 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
406 expect_equal(expected_data, out_data, width, height);
409 TEST(ResampleEffectTest, Precision) {
410 const int size = 1920; // Difficult non-power-of-two size.
411 const int offset = 5;
413 // Deliberately put the data of interest very close to the right,
414 // where texture coordinates are farther from 0 and thus less precise.
415 float data[size * 2] = {0};
416 data[size - offset] = 1.0f;
417 float expected_data[size * 2] = {0};
418 for (int x = 0; x < size * 2; ++x) {
419 expected_data[x] = lanczos((x - (size - 2 * offset + 1) + 0.5f) * 0.5f, 3.0f);
421 float out_data[size * 2];
423 EffectChainTester tester(data, size * 2, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
424 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
425 ASSERT_TRUE(resample_effect->set_int("width", size * 2));
426 ASSERT_TRUE(resample_effect->set_int("height", 1));
427 ASSERT_TRUE(resample_effect->set_float("zoom_x", 2.0f));
428 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
430 expect_equal(expected_data, out_data, size, 1);
433 #ifdef HAVE_BENCHMARK
435 void BM_ResampleEffect(benchmark::State &state, GammaCurve gamma_curve, GLenum output_format, const std::string &shader_type)
437 DisableComputeShadersTemporarily disabler(shader_type == "fragment");
438 if (disabler.should_skip(&state)) return;
440 unsigned in_width = state.range(0), in_height = state.range(1);
441 unsigned out_width = state.range(2), out_height = state.range(3);
443 unique_ptr<T[]> data(new T[in_width * in_height * 4]);
444 unique_ptr<T[]> out_data(new T[out_width * out_height * 4]);
446 for (unsigned i = 0; i < in_width * in_height * 4; ++i) {
450 EffectChainTester tester(nullptr, out_width, out_height, FORMAT_BGRA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, gamma_curve, output_format);
451 tester.add_input(data.get(), FORMAT_BGRA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, gamma_curve, in_width, in_height);
452 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
454 ASSERT_TRUE(resample_effect->set_int("width", out_width));
455 ASSERT_TRUE(resample_effect->set_int("height", out_height));
457 tester.benchmark(state, out_data.get(), GL_BGRA, COLORSPACE_sRGB, gamma_curve, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
460 void BM_ResampleEffectFloat(benchmark::State &state, GammaCurve gamma_curve, const std::string &shader_type)
462 BM_ResampleEffect<float>(state, gamma_curve, GL_RGBA16F, shader_type);
465 void BM_ResampleEffectInt8(benchmark::State &state, GammaCurve gamma_curve, const std::string &shader_type)
467 BM_ResampleEffect<uint8_t>(state, gamma_curve, GL_RGBA8, shader_type);
470 BENCHMARK_CAPTURE(BM_ResampleEffectInt8, Int8Upscale, GAMMA_REC_709, "fragment")->Args({640, 360, 1280, 720})->Args({320, 180, 1280, 720})->Args({321, 181, 1280, 720})->UseRealTime()->Unit(benchmark::kMicrosecond);
471 BENCHMARK_CAPTURE(BM_ResampleEffectFloat, Float32Upscale, GAMMA_LINEAR, "fragment")->Args({640, 360, 1280, 720})->Args({320, 180, 1280, 720})->Args({321, 181, 1280, 720})->UseRealTime()->Unit(benchmark::kMicrosecond);
472 BENCHMARK_CAPTURE(BM_ResampleEffectInt8, Int8Downscale, GAMMA_REC_709, "fragment")->Args({1280, 720, 640, 360})->Args({1280, 720, 320, 180})->Args({1280, 720, 321, 181})->UseRealTime()->Unit(benchmark::kMicrosecond);
473 BENCHMARK_CAPTURE(BM_ResampleEffectFloat, Float32Downscale, GAMMA_LINEAR, "fragment")->Args({1280, 720, 640, 360})->Args({1280, 720, 320, 180})->Args({1280, 720, 321, 181})->UseRealTime()->Unit(benchmark::kMicrosecond);
475 void BM_ComputeScalingWeights(benchmark::State &state)
477 constexpr unsigned src_size = 1280;
478 constexpr unsigned dst_size = 35;
479 int old_precision = movit_texel_subpixel_precision;
480 movit_texel_subpixel_precision = 64; // To get consistent results across GPUs; this is a CPU test.
482 // One iteration warmup to make sure the Lanczos table is computed.
483 calculate_scaling_weights(src_size, dst_size, 0.999f, 0.0f);
485 for (auto _ : state) {
486 ScalingWeights weights = calculate_scaling_weights(src_size, dst_size, 0.999f, 0.0f);
489 movit_texel_subpixel_precision = old_precision;
491 BENCHMARK(BM_ComputeScalingWeights)->Unit(benchmark::kMicrosecond);