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"
12 #include "resample_effect.h"
13 #include "test_util.h"
23 return sin(M_PI * x) / (M_PI * x);
26 float lanczos(float x, float a)
31 return sinc(x) * sinc(x / a);
37 TEST(ResampleEffectTest, IdentityTransformDoesNothing) {
40 float data[size * size] = {
46 float out_data[size * size];
48 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
49 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
50 ASSERT_TRUE(resample_effect->set_int("width", 4));
51 ASSERT_TRUE(resample_effect->set_int("height", 4));
52 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
54 expect_equal(data, out_data, size, size);
57 TEST(ResampleEffectTest, UpscaleByTwoGetsCorrectPixelCenters) {
60 float data[size * size] = {
61 0.0, 0.0, 0.0, 0.0, 0.0,
62 0.0, 0.0, 0.0, 0.0, 0.0,
63 0.0, 0.0, 1.0, 0.0, 0.0,
64 0.0, 0.0, 0.0, 0.0, 0.0,
65 0.0, 0.0, 0.0, 0.0, 0.0,
67 float expected_data[size * size * 4], out_data[size * size * 4];
69 for (int y = 0; y < size * 2; ++y) {
70 for (int x = 0; x < size * 2; ++x) {
71 float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
72 weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
73 expected_data[y * (size * 2) + x] = weight;
77 EffectChainTester tester(nullptr, size * 2, size * 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
80 format.color_space = COLORSPACE_sRGB;
81 format.gamma_curve = GAMMA_LINEAR;
83 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
84 input->set_pixel_data(data);
85 tester.get_chain()->add_input(input);
87 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
88 ASSERT_TRUE(resample_effect->set_int("width", size * 2));
89 ASSERT_TRUE(resample_effect->set_int("height", size * 2));
90 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
92 expect_equal(expected_data, out_data, size * 2, size * 2);
95 TEST(ResampleEffectTest, DownscaleByTwoGetsCorrectPixelCenters) {
98 // This isn't a perfect dot, since the Lanczos filter has a slight
99 // sharpening effect; the most important thing is that we have kept
100 // the texel center right (everything is nicely symmetric).
101 // The approximate magnitudes have been checked against ImageMagick.
102 float expected_data[size * size] = {
103 0.0045, -0.0067, -0.0599, -0.0067, 0.0045,
104 -0.0067, 0.0100, 0.0892, 0.0100, -0.0067,
105 -0.0599, 0.0890, 0.7925, 0.0892, -0.0599,
106 -0.0067, 0.0100, 0.0890, 0.0100, -0.0067,
107 0.0045, -0.0067, -0.0599, -0.0067, 0.0045,
109 float data[size * size * 4], out_data[size * size];
111 for (int y = 0; y < size * 2; ++y) {
112 for (int x = 0; x < size * 2; ++x) {
113 float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
114 weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
115 data[y * (size * 2) + x] = weight;
119 EffectChainTester tester(nullptr, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
122 format.color_space = COLORSPACE_sRGB;
123 format.gamma_curve = GAMMA_LINEAR;
125 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size * 2, size * 2);
126 input->set_pixel_data(data);
127 tester.get_chain()->add_input(input);
129 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
130 ASSERT_TRUE(resample_effect->set_int("width", size));
131 ASSERT_TRUE(resample_effect->set_int("height", size));
132 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
134 expect_equal(expected_data, out_data, size, size);
137 TEST(ResampleEffectTest, UpscaleByThreeGetsCorrectPixelCenters) {
140 float data[size * size] = {
141 0.0, 0.0, 0.0, 0.0, 0.0,
142 0.0, 0.0, 0.0, 0.0, 0.0,
143 0.0, 0.0, 1.0, 0.0, 0.0,
144 0.0, 0.0, 0.0, 0.0, 0.0,
145 0.0, 0.0, 0.0, 0.0, 0.0,
147 float out_data[size * size * 9];
149 EffectChainTester tester(nullptr, size * 3, size * 3, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
152 format.color_space = COLORSPACE_sRGB;
153 format.gamma_curve = GAMMA_LINEAR;
155 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
156 input->set_pixel_data(data);
157 tester.get_chain()->add_input(input);
159 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
160 ASSERT_TRUE(resample_effect->set_int("width", size * 3));
161 ASSERT_TRUE(resample_effect->set_int("height", size * 3));
162 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
164 // We only bother checking that the middle pixel is still correct,
165 // and that symmetry holds.
166 EXPECT_FLOAT_EQ(1.0, out_data[7 * (size * 3) + 7]);
167 for (unsigned y = 0; y < size * 3; ++y) {
168 for (unsigned x = 0; x < size * 3; ++x) {
169 EXPECT_NEAR(out_data[y * (size * 3) + x], out_data[(size * 3 - y - 1) * (size * 3) + x], 1e-6);
170 EXPECT_NEAR(out_data[y * (size * 3) + x], out_data[y * (size * 3) + (size * 3 - x - 1)], 1e-6);
175 TEST(ResampleEffectTest, HeavyResampleGetsSumRight) {
176 // Do only one resample pass, more specifically the last one, which goes to
177 // our fp32 output. This allows us to analyze the precision without intermediate
179 const int swidth = 1, sheight = 1280;
180 const int dwidth = 1, dheight = 64;
182 float data[swidth * sheight], out_data[dwidth * dheight], expected_data[dwidth * dheight];
183 for (int y = 0; y < sheight; ++y) {
184 for (int x = 0; x < swidth; ++x) {
185 data[y * swidth + x] = 1.0f;
188 for (int y = 0; y < dheight; ++y) {
189 for (int x = 0; x < dwidth; ++x) {
190 expected_data[y * dwidth + x] = 1.0f;
194 EffectChainTester tester(nullptr, dwidth, dheight, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
197 format.color_space = COLORSPACE_sRGB;
198 format.gamma_curve = GAMMA_LINEAR;
200 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, swidth, sheight);
201 input->set_pixel_data(data);
203 tester.get_chain()->add_input(input);
204 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
205 ASSERT_TRUE(resample_effect->set_int("width", dwidth));
206 ASSERT_TRUE(resample_effect->set_int("height", dheight));
207 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
209 // Require that we are within 10-bit accuracy. Note that this limit is for
210 // one pass only, but the limit is tight enough that it should be good enough
211 // for 10-bit accuracy even after two passes.
212 expect_equal(expected_data, out_data, dwidth, dheight, 0.12 / 1023.0);
215 TEST(ResampleEffectTest, ReadWholePixelFromLeft) {
218 float data[size * size] = {
219 0.0, 0.0, 0.0, 0.0, 0.0,
220 0.0, 0.0, 0.0, 0.0, 0.0,
221 0.0, 0.0, 1.0, 0.0, 0.0,
222 0.0, 0.0, 0.0, 0.0, 0.0,
223 0.0, 0.0, 0.0, 0.0, 0.0,
225 float expected_data[size * size] = {
226 0.0, 0.0, 0.0, 0.0, 0.0,
227 0.0, 0.0, 0.0, 0.0, 0.0,
228 0.0, 1.0, 0.0, 0.0, 0.0,
229 0.0, 0.0, 0.0, 0.0, 0.0,
230 0.0, 0.0, 0.0, 0.0, 0.0,
232 float out_data[size * size];
234 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
235 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
236 ASSERT_TRUE(resample_effect->set_int("width", size));
237 ASSERT_TRUE(resample_effect->set_int("height", size));
238 ASSERT_TRUE(resample_effect->set_float("left", 1.0f));
239 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
241 expect_equal(expected_data, out_data, size, size);
244 TEST(ResampleEffectTest, ReadQuarterPixelFromLeft) {
247 float data[size * size] = {
248 0.0, 0.0, 0.0, 0.0, 0.0,
249 0.0, 0.0, 0.0, 0.0, 0.0,
250 0.0, 0.0, 1.0, 0.0, 0.0,
251 0.0, 0.0, 0.0, 0.0, 0.0,
252 0.0, 0.0, 0.0, 0.0, 0.0,
255 float expected_data[size * size] = {
256 0.0, 0.0, 0.0, 0.0, 0.0,
257 0.0, 0.0, 0.0, 0.0, 0.0,
259 // sin(x*pi)/(x*pi) * sin(x*pi/3)/(x*pi/3) for
260 // x = -1.75, -0.75, 0.25, 1.25, 2.25.
261 // Note that the weight is mostly on the left side.
262 -0.06779, 0.27019, 0.89007, -0.13287, 0.03002,
264 0.0, 0.0, 0.0, 0.0, 0.0,
265 0.0, 0.0, 0.0, 0.0, 0.0,
267 float out_data[size * size];
269 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
270 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
271 ASSERT_TRUE(resample_effect->set_int("width", size));
272 ASSERT_TRUE(resample_effect->set_int("height", size));
273 ASSERT_TRUE(resample_effect->set_float("left", 0.25f));
274 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
276 expect_equal(expected_data, out_data, size, size);
279 TEST(ResampleEffectTest, ReadQuarterPixelFromTop) {
281 const int height = 5;
283 float data[width * height] = {
291 // See ReadQuarterPixelFromLeft for explanation of the data.
292 float expected_data[width * height] = {
299 float out_data[width * height];
301 EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
302 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
303 ASSERT_TRUE(resample_effect->set_int("width", width));
304 ASSERT_TRUE(resample_effect->set_int("height", height));
305 ASSERT_TRUE(resample_effect->set_float("top", 0.25f));
306 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
308 expect_equal(expected_data, out_data, width, height);
311 TEST(ResampleEffectTest, ReadHalfPixelFromLeftAndScale) {
312 const int src_width = 4;
313 const int dst_width = 8;
315 float data[src_width * 1] = {
318 float expected_data[dst_width * 1] = {
319 // Empirical; the real test is that we are the same for 0.499 and 0.501.
320 1.1553, 1.7158, 2.2500, 2.7461, 3.2812, 3.8418, 4.0703, 4.0508
322 float out_data[dst_width * 1];
324 EffectChainTester tester(nullptr, dst_width, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
327 format.color_space = COLORSPACE_sRGB;
328 format.gamma_curve = GAMMA_LINEAR;
330 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, src_width, 1);
331 input->set_pixel_data(data);
332 tester.get_chain()->add_input(input);
334 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
335 ASSERT_TRUE(resample_effect->set_int("width", dst_width));
336 ASSERT_TRUE(resample_effect->set_int("height", 1));
338 // Check that we are (almost) the same no matter the rounding.
339 ASSERT_TRUE(resample_effect->set_float("left", 0.499f));
340 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
341 expect_equal(expected_data, out_data, dst_width, 1, 1.5f / 255.0f, 0.4f / 255.0f);
343 ASSERT_TRUE(resample_effect->set_float("left", 0.501f));
344 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
345 expect_equal(expected_data, out_data, dst_width, 1, 1.5f / 255.0f, 0.4f / 255.0f);
348 TEST(ResampleEffectTest, Zoom) {
350 const int height = 3;
352 float data[width * height] = {
353 0.0, 0.0, 0.0, 0.0, 0.0,
354 0.2, 0.4, 0.6, 0.4, 0.2,
355 0.0, 0.0, 0.0, 0.0, 0.0,
357 float expected_data[width * height] = {
358 0.0, 0.0, 0.0, 0.0, 0.0,
359 0.4, 0.5396, 0.6, 0.5396, 0.4,
360 0.0, 0.0, 0.0, 0.0, 0.0,
362 float out_data[width * height];
364 EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
365 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
366 ASSERT_TRUE(resample_effect->set_int("width", width));
367 ASSERT_TRUE(resample_effect->set_int("height", height));
368 ASSERT_TRUE(resample_effect->set_float("zoom_x", 2.0f));
369 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
371 expect_equal(expected_data, out_data, width, height);
374 TEST(ResampleEffectTest, VerticalZoomFromTop) {
376 const int height = 5;
378 float data[width * height] = {
379 0.2, 0.4, 0.6, 0.4, 0.2,
380 0.0, 0.0, 0.0, 0.0, 0.0,
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,
386 // Largely empirical data; the main point is that the top line
387 // is unchanged, since that's our zooming point.
388 float expected_data[width * height] = {
389 0.2000, 0.4000, 0.6000, 0.4000, 0.2000,
390 0.1389, 0.2778, 0.4167, 0.2778, 0.1389,
391 0.0600, 0.1199, 0.1798, 0.1199, 0.0600,
392 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
393 -0.0229, -0.0459, -0.0688, -0.0459, -0.0229,
395 float out_data[width * height];
397 EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
398 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
399 ASSERT_TRUE(resample_effect->set_int("width", width));
400 ASSERT_TRUE(resample_effect->set_int("height", height));
401 ASSERT_TRUE(resample_effect->set_float("zoom_y", 3.0f));
402 ASSERT_TRUE(resample_effect->set_float("zoom_center_y", 0.5f / height));
403 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
405 expect_equal(expected_data, out_data, width, height);
408 TEST(ResampleEffectTest, Precision) {
409 const int size = 1920; // Difficult non-power-of-two size.
410 const int offset = 5;
412 // Deliberately put the data of interest very close to the right,
413 // where texture coordinates are farther from 0 and thus less precise.
414 float data[size * 2] = {0};
415 data[size - offset] = 1.0f;
416 float expected_data[size * 2] = {0};
417 for (int x = 0; x < size * 2; ++x) {
418 expected_data[x] = lanczos((x - (size - 2 * offset + 1) + 0.5f) * 0.5f, 3.0f);
420 float out_data[size * 2];
422 EffectChainTester tester(data, size * 2, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
423 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
424 ASSERT_TRUE(resample_effect->set_int("width", size * 2));
425 ASSERT_TRUE(resample_effect->set_int("height", 1));
426 ASSERT_TRUE(resample_effect->set_float("zoom_x", 2.0f));
427 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
429 expect_equal(expected_data, out_data, size, 1);
432 #ifdef HAVE_BENCHMARK
434 void BM_ResampleEffect(benchmark::State &state, GammaCurve gamma_curve, GLenum output_format, const std::string &shader_type)
436 DisableComputeShadersTemporarily disabler(shader_type == "fragment");
437 if (disabler.should_skip(&state)) return;
439 unsigned in_width = state.range(0), in_height = state.range(1);
440 unsigned out_width = state.range(2), out_height = state.range(3);
442 unique_ptr<T[]> data(new T[in_width * in_height * 4]);
443 unique_ptr<T[]> out_data(new T[out_width * out_height * 4]);
445 for (unsigned i = 0; i < in_width * in_height * 4; ++i) {
449 EffectChainTester tester(nullptr, out_width, out_height, FORMAT_BGRA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, gamma_curve, output_format);
450 tester.add_input(data.get(), FORMAT_BGRA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, gamma_curve, in_width, in_height);
451 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
453 ASSERT_TRUE(resample_effect->set_int("width", out_width));
454 ASSERT_TRUE(resample_effect->set_int("height", out_height));
456 tester.benchmark(state, out_data.get(), GL_BGRA, COLORSPACE_sRGB, gamma_curve, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
459 void BM_ResampleEffectFloat(benchmark::State &state, GammaCurve gamma_curve, const std::string &shader_type)
461 BM_ResampleEffect<float>(state, gamma_curve, GL_RGBA16F, shader_type);
464 void BM_ResampleEffectInt8(benchmark::State &state, GammaCurve gamma_curve, const std::string &shader_type)
466 BM_ResampleEffect<uint8_t>(state, gamma_curve, GL_RGBA8, shader_type);
469 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);
470 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);
471 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);
472 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);