1 // Unit tests for ResampleEffect.
4 #include <gtest/gtest.h>
9 #include "effect_chain.h"
10 #include "flat_input.h"
12 #include "image_format.h"
14 #include "resample_effect.h"
15 #include "test_util.h"
25 return sin(M_PI * x) / (M_PI * x);
28 float lanczos(float x, float a)
33 return sinc(x) * sinc(x / a);
39 TEST(ResampleEffectTest, IdentityTransformDoesNothing) {
42 float data[size * size] = {
48 float out_data[size * size];
50 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
51 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
52 ASSERT_TRUE(resample_effect->set_int("width", 4));
53 ASSERT_TRUE(resample_effect->set_int("height", 4));
54 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
56 expect_equal(data, out_data, size, size);
59 TEST(ResampleEffectTest, UpscaleByTwoGetsCorrectPixelCenters) {
62 float data[size * size] = {
63 0.0, 0.0, 0.0, 0.0, 0.0,
64 0.0, 0.0, 0.0, 0.0, 0.0,
65 0.0, 0.0, 1.0, 0.0, 0.0,
66 0.0, 0.0, 0.0, 0.0, 0.0,
67 0.0, 0.0, 0.0, 0.0, 0.0,
69 float expected_data[size * size * 4], out_data[size * size * 4];
71 for (int y = 0; y < size * 2; ++y) {
72 for (int x = 0; x < size * 2; ++x) {
73 float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
74 weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
75 expected_data[y * (size * 2) + x] = weight;
79 EffectChainTester tester(nullptr, size * 2, size * 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
82 format.color_space = COLORSPACE_sRGB;
83 format.gamma_curve = GAMMA_LINEAR;
85 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
86 input->set_pixel_data(data);
87 tester.get_chain()->add_input(input);
89 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
90 ASSERT_TRUE(resample_effect->set_int("width", size * 2));
91 ASSERT_TRUE(resample_effect->set_int("height", size * 2));
92 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
94 expect_equal(expected_data, out_data, size * 2, size * 2);
97 TEST(ResampleEffectTest, DownscaleByTwoGetsCorrectPixelCenters) {
100 // This isn't a perfect dot, since the Lanczos filter has a slight
101 // sharpening effect; the most important thing is that we have kept
102 // the texel center right (everything is nicely symmetric).
103 // The approximate magnitudes have been checked against ImageMagick.
104 float expected_data[size * size] = {
105 0.0045, -0.0067, -0.0599, -0.0067, 0.0045,
106 -0.0067, 0.0100, 0.0892, 0.0100, -0.0067,
107 -0.0599, 0.0890, 0.7925, 0.0892, -0.0599,
108 -0.0067, 0.0100, 0.0890, 0.0100, -0.0067,
109 0.0045, -0.0067, -0.0599, -0.0067, 0.0045,
111 float data[size * size * 4], out_data[size * size];
113 for (int y = 0; y < size * 2; ++y) {
114 for (int x = 0; x < size * 2; ++x) {
115 float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
116 weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
117 data[y * (size * 2) + x] = weight;
121 EffectChainTester tester(nullptr, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
124 format.color_space = COLORSPACE_sRGB;
125 format.gamma_curve = GAMMA_LINEAR;
127 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size * 2, size * 2);
128 input->set_pixel_data(data);
129 tester.get_chain()->add_input(input);
131 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
132 ASSERT_TRUE(resample_effect->set_int("width", size));
133 ASSERT_TRUE(resample_effect->set_int("height", size));
134 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
136 expect_equal(expected_data, out_data, size, size);
139 TEST(ResampleEffectTest, UpscaleByThreeGetsCorrectPixelCenters) {
142 float data[size * size] = {
143 0.0, 0.0, 0.0, 0.0, 0.0,
144 0.0, 0.0, 0.0, 0.0, 0.0,
145 0.0, 0.0, 1.0, 0.0, 0.0,
146 0.0, 0.0, 0.0, 0.0, 0.0,
147 0.0, 0.0, 0.0, 0.0, 0.0,
149 float out_data[size * size * 9];
151 EffectChainTester tester(nullptr, size * 3, size * 3, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
154 format.color_space = COLORSPACE_sRGB;
155 format.gamma_curve = GAMMA_LINEAR;
157 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
158 input->set_pixel_data(data);
159 tester.get_chain()->add_input(input);
161 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
162 ASSERT_TRUE(resample_effect->set_int("width", size * 3));
163 ASSERT_TRUE(resample_effect->set_int("height", size * 3));
164 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
166 // We only bother checking that the middle pixel is still correct,
167 // and that symmetry holds.
168 EXPECT_FLOAT_EQ(1.0, out_data[7 * (size * 3) + 7]);
169 for (unsigned y = 0; y < size * 3; ++y) {
170 for (unsigned x = 0; x < size * 3; ++x) {
171 EXPECT_NEAR(out_data[y * (size * 3) + x], out_data[(size * 3 - y - 1) * (size * 3) + x], 1e-6);
172 EXPECT_NEAR(out_data[y * (size * 3) + x], out_data[y * (size * 3) + (size * 3 - x - 1)], 1e-6);
177 TEST(ResampleEffectTest, HeavyResampleGetsSumRight) {
178 // Do only one resample pass, more specifically the last one, which goes to
179 // our fp32 output. This allows us to analyze the precision without intermediate
181 const int swidth = 1, sheight = 1280;
182 const int dwidth = 1, dheight = 64;
184 float data[swidth * sheight], out_data[dwidth * dheight], expected_data[dwidth * dheight];
185 for (int y = 0; y < sheight; ++y) {
186 for (int x = 0; x < swidth; ++x) {
187 data[y * swidth + x] = 1.0f;
190 for (int y = 0; y < dheight; ++y) {
191 for (int x = 0; x < dwidth; ++x) {
192 expected_data[y * dwidth + x] = 1.0f;
196 EffectChainTester tester(nullptr, dwidth, dheight, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
199 format.color_space = COLORSPACE_sRGB;
200 format.gamma_curve = GAMMA_LINEAR;
202 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, swidth, sheight);
203 input->set_pixel_data(data);
205 tester.get_chain()->add_input(input);
206 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
207 ASSERT_TRUE(resample_effect->set_int("width", dwidth));
208 ASSERT_TRUE(resample_effect->set_int("height", dheight));
209 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
211 // Require that we are within 10-bit accuracy. Note that this limit is for
212 // one pass only, but the limit is tight enough that it should be good enough
213 // for 10-bit accuracy even after two passes.
214 expect_equal(expected_data, out_data, dwidth, dheight, 0.12 / 1023.0);
217 TEST(ResampleEffectTest, ReadWholePixelFromLeft) {
220 float data[size * size] = {
221 0.0, 0.0, 0.0, 0.0, 0.0,
222 0.0, 0.0, 0.0, 0.0, 0.0,
223 0.0, 0.0, 1.0, 0.0, 0.0,
224 0.0, 0.0, 0.0, 0.0, 0.0,
225 0.0, 0.0, 0.0, 0.0, 0.0,
227 float expected_data[size * size] = {
228 0.0, 0.0, 0.0, 0.0, 0.0,
229 0.0, 0.0, 0.0, 0.0, 0.0,
230 0.0, 1.0, 0.0, 0.0, 0.0,
231 0.0, 0.0, 0.0, 0.0, 0.0,
232 0.0, 0.0, 0.0, 0.0, 0.0,
234 float out_data[size * size];
236 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
237 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
238 ASSERT_TRUE(resample_effect->set_int("width", size));
239 ASSERT_TRUE(resample_effect->set_int("height", size));
240 ASSERT_TRUE(resample_effect->set_float("left", 1.0f));
241 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
243 expect_equal(expected_data, out_data, size, size);
246 TEST(ResampleEffectTest, ReadQuarterPixelFromLeft) {
249 float data[size * size] = {
250 0.0, 0.0, 0.0, 0.0, 0.0,
251 0.0, 0.0, 0.0, 0.0, 0.0,
252 0.0, 0.0, 1.0, 0.0, 0.0,
253 0.0, 0.0, 0.0, 0.0, 0.0,
254 0.0, 0.0, 0.0, 0.0, 0.0,
257 float expected_data[size * size] = {
258 0.0, 0.0, 0.0, 0.0, 0.0,
259 0.0, 0.0, 0.0, 0.0, 0.0,
261 // sin(x*pi)/(x*pi) * sin(x*pi/3)/(x*pi/3) for
262 // x = -1.75, -0.75, 0.25, 1.25, 2.25.
263 // Note that the weight is mostly on the left side.
264 -0.06779, 0.27019, 0.89007, -0.13287, 0.03002,
266 0.0, 0.0, 0.0, 0.0, 0.0,
267 0.0, 0.0, 0.0, 0.0, 0.0,
269 float out_data[size * size];
271 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
272 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
273 ASSERT_TRUE(resample_effect->set_int("width", size));
274 ASSERT_TRUE(resample_effect->set_int("height", size));
275 ASSERT_TRUE(resample_effect->set_float("left", 0.25f));
276 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
278 expect_equal(expected_data, out_data, size, size);
281 TEST(ResampleEffectTest, ReadQuarterPixelFromTop) {
283 const int height = 5;
285 float data[width * height] = {
293 // See ReadQuarterPixelFromLeft for explanation of the data.
294 float expected_data[width * height] = {
301 float out_data[width * height];
303 EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
304 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
305 ASSERT_TRUE(resample_effect->set_int("width", width));
306 ASSERT_TRUE(resample_effect->set_int("height", height));
307 ASSERT_TRUE(resample_effect->set_float("top", 0.25f));
308 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
310 expect_equal(expected_data, out_data, width, height);
313 TEST(ResampleEffectTest, ReadHalfPixelFromLeftAndScale) {
314 const int src_width = 4;
315 const int dst_width = 8;
317 float data[src_width * 1] = {
320 float expected_data[dst_width * 1] = {
321 // Empirical; the real test is that we are the same for 0.499 and 0.501.
322 1.1553, 1.7158, 2.2500, 2.7461, 3.2812, 3.8418, 4.0703, 4.0508
324 float out_data[dst_width * 1];
326 EffectChainTester tester(nullptr, dst_width, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
329 format.color_space = COLORSPACE_sRGB;
330 format.gamma_curve = GAMMA_LINEAR;
332 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, src_width, 1);
333 input->set_pixel_data(data);
334 tester.get_chain()->add_input(input);
336 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
337 ASSERT_TRUE(resample_effect->set_int("width", dst_width));
338 ASSERT_TRUE(resample_effect->set_int("height", 1));
340 // Check that we are (almost) the same no matter the rounding.
341 ASSERT_TRUE(resample_effect->set_float("left", 0.499f));
342 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
343 expect_equal(expected_data, out_data, dst_width, 1, 1.5f / 255.0f, 0.4f / 255.0f);
345 ASSERT_TRUE(resample_effect->set_float("left", 0.501f));
346 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
347 expect_equal(expected_data, out_data, dst_width, 1, 1.5f / 255.0f, 0.4f / 255.0f);
350 TEST(ResampleEffectTest, Zoom) {
352 const int height = 3;
354 float data[width * height] = {
355 0.0, 0.0, 0.0, 0.0, 0.0,
356 0.2, 0.4, 0.6, 0.4, 0.2,
357 0.0, 0.0, 0.0, 0.0, 0.0,
359 float expected_data[width * height] = {
360 0.0, 0.0, 0.0, 0.0, 0.0,
361 0.4, 0.5396, 0.6, 0.5396, 0.4,
362 0.0, 0.0, 0.0, 0.0, 0.0,
364 float out_data[width * height];
366 EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
367 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
368 ASSERT_TRUE(resample_effect->set_int("width", width));
369 ASSERT_TRUE(resample_effect->set_int("height", height));
370 ASSERT_TRUE(resample_effect->set_float("zoom_x", 2.0f));
371 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
373 expect_equal(expected_data, out_data, width, height);
376 TEST(ResampleEffectTest, VerticalZoomFromTop) {
378 const int height = 5;
380 float data[width * height] = {
381 0.2, 0.4, 0.6, 0.4, 0.2,
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,
385 0.0, 0.0, 0.0, 0.0, 0.0,
388 // Largely empirical data; the main point is that the top line
389 // is unchanged, since that's our zooming point.
390 float expected_data[width * height] = {
391 0.2000, 0.4000, 0.6000, 0.4000, 0.2000,
392 0.1389, 0.2778, 0.4167, 0.2778, 0.1389,
393 0.0600, 0.1199, 0.1798, 0.1199, 0.0600,
394 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
395 -0.0229, -0.0459, -0.0688, -0.0459, -0.0229,
397 float out_data[width * height];
399 EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
400 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
401 ASSERT_TRUE(resample_effect->set_int("width", width));
402 ASSERT_TRUE(resample_effect->set_int("height", height));
403 ASSERT_TRUE(resample_effect->set_float("zoom_y", 3.0f));
404 ASSERT_TRUE(resample_effect->set_float("zoom_center_y", 0.5f / height));
405 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
407 expect_equal(expected_data, out_data, width, height);
410 TEST(ResampleEffectTest, Precision) {
411 const int size = 1920; // Difficult non-power-of-two size.
412 const int offset = 5;
414 // Deliberately put the data of interest very close to the right,
415 // where texture coordinates are farther from 0 and thus less precise.
416 float data[size * 2] = {0};
417 data[size - offset] = 1.0f;
418 float expected_data[size * 2] = {0};
419 for (int x = 0; x < size * 2; ++x) {
420 expected_data[x] = lanczos((x - (size - 2 * offset + 1) + 0.5f) * 0.5f, 3.0f);
422 float out_data[size * 2];
424 EffectChainTester tester(data, size * 2, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
425 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
426 ASSERT_TRUE(resample_effect->set_int("width", size * 2));
427 ASSERT_TRUE(resample_effect->set_int("height", 1));
428 ASSERT_TRUE(resample_effect->set_float("zoom_x", 2.0f));
429 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
431 expect_equal(expected_data, out_data, size, 1);
434 #ifdef HAVE_BENCHMARK
435 template<> inline uint8_t from_fp32<uint8_t>(float x) { return x; }
438 void BM_ResampleEffect(benchmark::State &state, GammaCurve gamma_curve, GLenum output_format, const std::string &shader_type)
440 DisableComputeShadersTemporarily disabler(shader_type == "fragment");
441 if (disabler.should_skip(&state)) return;
443 unsigned in_width = state.range(0), in_height = state.range(1);
444 unsigned out_width = state.range(2), out_height = state.range(3);
446 unique_ptr<T[]> data(new T[in_width * in_height * 4]);
447 unique_ptr<T[]> out_data(new T[out_width * out_height * 4]);
449 for (unsigned i = 0; i < in_width * in_height * 4; ++i) {
450 data[i] = from_fp32<T>(float(rand()));
453 EffectChainTester tester(nullptr, out_width, out_height, FORMAT_BGRA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, gamma_curve, output_format);
454 tester.add_input(data.get(), FORMAT_BGRA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, gamma_curve, in_width, in_height);
455 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
457 ASSERT_TRUE(resample_effect->set_int("width", out_width));
458 ASSERT_TRUE(resample_effect->set_int("height", out_height));
460 tester.benchmark(state, out_data.get(), GL_BGRA, COLORSPACE_sRGB, gamma_curve, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
463 void BM_ResampleEffectHalf(benchmark::State &state, GammaCurve gamma_curve, const std::string &shader_type)
465 BM_ResampleEffect<fp16_int_t>(state, gamma_curve, GL_RGBA16F, shader_type);
468 void BM_ResampleEffectInt8(benchmark::State &state, GammaCurve gamma_curve, const std::string &shader_type)
470 BM_ResampleEffect<uint8_t>(state, gamma_curve, GL_RGBA8, shader_type);
473 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);
474 BENCHMARK_CAPTURE(BM_ResampleEffectHalf, Float16Upscale, GAMMA_LINEAR, "fragment")->Args({640, 360, 1280, 720})->Args({320, 180, 1280, 720})->Args({321, 181, 1280, 720})->UseRealTime()->Unit(benchmark::kMicrosecond);
475 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);
476 BENCHMARK_CAPTURE(BM_ResampleEffectHalf, Float16Downscale, GAMMA_LINEAR, "fragment")->Args({1280, 720, 640, 360})->Args({1280, 720, 320, 180})->Args({1280, 720, 321, 181})->UseRealTime()->Unit(benchmark::kMicrosecond);
478 void BM_ComputeScalingWeights(benchmark::State &state)
480 constexpr unsigned src_size = 1280;
481 constexpr unsigned dst_size = 35;
482 int old_precision = movit_texel_subpixel_precision;
483 movit_texel_subpixel_precision = 64; // To get consistent results across GPUs; this is a CPU test.
485 // One iteration warmup to make sure the Lanczos table is computed.
486 calculate_scaling_weights(src_size, dst_size, 0.999f, 0.0f);
488 for (auto _ : state) {
489 ScalingWeights weights = calculate_scaling_weights(src_size, dst_size, 0.999f, 0.0f);
492 movit_texel_subpixel_precision = old_precision;
494 BENCHMARK(BM_ComputeScalingWeights)->Unit(benchmark::kMicrosecond);