Factorize the code to compute sampling points for bilinear sampling into a shared...
[movit] / effect_chain_test.cpp
1 // Unit tests for EffectChain.
2 //
3 // Note that this also contains the tests for some of the simpler effects.
4
5 #include "effect_chain.h"
6 #include "flat_input.h"
7 #include "gtest/gtest.h"
8 #include "mirror_effect.h"
9 #include "resize_effect.h"
10 #include "opengl.h"
11 #include "test_util.h"
12
13 TEST(EffectChainTest, EmptyChain) {
14         float data[] = {
15                 0.0f, 0.25f, 0.3f,
16                 0.75f, 1.0f, 1.0f,
17         };
18         float out_data[6];
19         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
20         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
21
22         expect_equal(data, out_data, 3, 2);
23 }
24
25 // An effect that does nothing.
26 class IdentityEffect : public Effect {
27 public:
28         IdentityEffect() {}
29         virtual std::string effect_type_id() const { return "IdentityEffect"; }
30         std::string output_fragment_shader() { return read_file("identity.frag"); }
31 };
32
33 TEST(EffectChainTest, Identity) {
34         float data[] = {
35                 0.0f, 0.25f, 0.3f,
36                 0.75f, 1.0f, 1.0f,
37         };
38         float out_data[6];
39         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
40         tester.get_chain()->add_effect(new IdentityEffect());
41         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
42
43         expect_equal(data, out_data, 3, 2);
44 }
45
46 // An effect that does nothing, but requests texture bounce.
47 class BouncingIdentityEffect : public Effect {
48 public:
49         BouncingIdentityEffect() {}
50         virtual std::string effect_type_id() const { return "IdentityEffect"; }
51         std::string output_fragment_shader() { return read_file("identity.frag"); }
52         bool needs_texture_bounce() const { return true; }
53 };
54
55 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
56         float data[] = {
57                 0.0f, 0.25f, 0.3f,
58                 0.75f, 1.0f, 1.0f,
59         };
60         float out_data[6];
61         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
62         tester.get_chain()->add_effect(new BouncingIdentityEffect());
63         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
64
65         expect_equal(data, out_data, 3, 2);
66 }
67
68 TEST(MirrorTest, BasicTest) {
69         float data[] = {
70                 0.0f, 0.25f, 0.3f,
71                 0.75f, 1.0f, 1.0f,
72         };
73         float expected_data[6] = {
74                 0.3f, 0.25f, 0.0f,
75                 1.0f, 1.0f, 0.75f,
76         };
77         float out_data[6];
78         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
79         tester.get_chain()->add_effect(new MirrorEffect());
80         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
81
82         expect_equal(expected_data, out_data, 3, 2);
83 }
84
85 // A dummy effect that inverts its input.
86 class InvertEffect : public Effect {
87 public:
88         InvertEffect() {}
89         virtual std::string effect_type_id() const { return "InvertEffect"; }
90         std::string output_fragment_shader() { return read_file("invert_effect.frag"); }
91 };
92
93 // Like IdentityEffect, but rewrites itself out of the loop,
94 // splicing in a InvertEffect instead. Also stores the new node,
95 // so we later can check that there are gamma conversion effects
96 // on both sides.
97 class RewritingToInvertEffect : public Effect {
98 public:
99         RewritingToInvertEffect() {}
100         virtual std::string effect_type_id() const { return "RewritingToInvertEffect"; }
101         std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
102         virtual void rewrite_graph(EffectChain *graph, Node *self) {
103                 Node *invert_node = graph->add_node(new InvertEffect());
104                 graph->replace_receiver(self, invert_node);
105                 graph->replace_sender(self, invert_node);
106
107                 self->disabled = true;
108                 this->invert_node = invert_node;
109         }
110
111         Node *invert_node;      
112 };
113
114 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
115         float data[] = {
116                 0.0f, 0.25f, 0.3f,
117                 0.75f, 1.0f, 1.0f,
118         };
119         float expected_data[6] = {
120                 1.0f, 0.9771f, 0.9673f,
121                 0.7192f, 0.0f, 0.0f,
122         };
123         float out_data[6];
124         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
125         RewritingToInvertEffect *effect = new RewritingToInvertEffect();
126         tester.get_chain()->add_effect(effect);
127         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
128
129         Node *node = effect->invert_node;
130         ASSERT_EQ(1, node->incoming_links.size());
131         ASSERT_EQ(1, node->outgoing_links.size());
132         EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
133         EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
134
135         expect_equal(expected_data, out_data, 3, 2);
136 }
137
138 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
139         unsigned char data[] = {
140                 0, 64,
141                 128, 255,
142         };
143         float expected_data[4] = {
144                 1.0f, 0.9771f,
145                 0.8983f, 0.0f,
146         };
147         float out_data[2];
148         EffectChainTester tester(NULL, 2, 2);
149         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
150         RewritingToInvertEffect *effect = new RewritingToInvertEffect();
151         tester.get_chain()->add_effect(effect);
152         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
153
154         Node *node = effect->invert_node;
155         ASSERT_EQ(1, node->incoming_links.size());
156         ASSERT_EQ(1, node->outgoing_links.size());
157         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
158         EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
159
160         expect_equal(expected_data, out_data, 2, 2);
161 }
162
163 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
164         float data[] = {
165                 0.0f, 0.25f, 0.3f,
166                 0.75f, 1.0f, 1.0f,
167         };
168         float expected_data[6] = {
169                 1.0f, 0.75f, 0.7f,
170                 0.25f, 0.0f, 0.0f,
171         };
172         float out_data[6];
173         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
174         RewritingToInvertEffect *effect = new RewritingToInvertEffect();
175         tester.get_chain()->add_effect(effect);
176         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
177
178         Node *node = effect->invert_node;
179         ASSERT_EQ(1, node->incoming_links.size());
180         ASSERT_EQ(1, node->outgoing_links.size());
181         EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
182         EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
183
184         expect_equal(expected_data, out_data, 3, 2);
185 }
186
187 // Like RewritingToInvertEffect, but splicing in a MirrorEffect instead,
188 // which does not need linear light or sRGB primaries.
189 class RewritingToMirrorEffect : public Effect {
190 public:
191         RewritingToMirrorEffect() {}
192         virtual std::string effect_type_id() const { return "RewritingToMirrorEffect"; }
193         std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
194         virtual void rewrite_graph(EffectChain *graph, Node *self) {
195                 Node *mirror_node = graph->add_node(new MirrorEffect());
196                 graph->replace_receiver(self, mirror_node);
197                 graph->replace_sender(self, mirror_node);
198
199                 self->disabled = true;
200                 this->mirror_node = mirror_node;
201         }
202
203         Node *mirror_node;
204 };
205
206 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
207         float data[] = {
208                 0.0f, 0.25f, 0.3f,
209                 0.75f, 1.0f, 1.0f,
210         };
211         float expected_data[6] = {
212                 0.3f, 0.25f, 0.0f,
213                 1.0f, 1.0f, 0.75f,
214         };
215         float out_data[6];
216         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
217         RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
218         tester.get_chain()->add_effect(effect);
219         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
220
221         Node *node = effect->mirror_node;
222         ASSERT_EQ(1, node->incoming_links.size());
223         EXPECT_EQ(0, node->outgoing_links.size());
224         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
225
226         expect_equal(expected_data, out_data, 3, 2);
227 }
228
229 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
230         float data[] = {
231                 0.0f, 0.25f, 0.3f,
232                 0.75f, 1.0f, 1.0f,
233         };
234         float expected_data[6] = {
235                 0.3f, 0.25f, 0.0f,
236                 1.0f, 1.0f, 0.75f,
237         };
238         float out_data[6];
239         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
240         RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
241         tester.get_chain()->add_effect(effect);
242         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
243
244         Node *node = effect->mirror_node;
245         ASSERT_EQ(1, node->incoming_links.size());
246         EXPECT_EQ(0, node->outgoing_links.size());
247         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
248
249         expect_equal(expected_data, out_data, 3, 2);
250 }
251
252 // The identity effect needs linear light, and thus will get conversions on both sides.
253 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
254 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
255         float data[256];
256         for (unsigned i = 0; i < 256; ++i) {
257                 data[i] = i / 255.0;
258         };
259         float out_data[256];
260         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
261         tester.get_chain()->add_effect(new IdentityEffect());
262         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
263
264         expect_equal(data, out_data, 256, 1);
265 }
266
267 // Same, but uses the forward sRGB table from the GPU.
268 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
269         unsigned char data[256];
270         float expected_data[256];
271         for (unsigned i = 0; i < 256; ++i) {
272                 data[i] = i;
273                 expected_data[i] = i / 255.0;
274         };
275         float out_data[256];
276         EffectChainTester tester(NULL, 256, 1);
277         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
278         tester.get_chain()->add_effect(new IdentityEffect());
279         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
280
281         expect_equal(expected_data, out_data, 256, 1);
282 }
283
284 // Same, for the Rec. 601/709 gamma curve.
285 TEST(EffectChainTest, IdentityThroughRec709) {
286         float data[256];
287         for (unsigned i = 0; i < 256; ++i) {
288                 data[i] = i / 255.0;
289         };
290         float out_data[256];
291         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
292         tester.get_chain()->add_effect(new IdentityEffect());
293         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
294
295         expect_equal(data, out_data, 256, 1);
296 }
297
298 // Effectively scales down its input linearly by 4x (and repeating it),
299 // which is not attainable without mipmaps.
300 class MipmapNeedingEffect : public Effect {
301 public:
302         MipmapNeedingEffect() {}
303         virtual bool needs_mipmaps() const { return true; }
304         virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
305         std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
306         void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
307         {
308                 glActiveTexture(GL_TEXTURE0);
309                 check_error();
310                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
311                 check_error();
312                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
313                 check_error();
314         }
315 };
316
317 TEST(EffectChainTest, MipmapGenerationWorks) {
318         float data[] = {  // In 4x4 blocks.
319                 1.0f, 0.0f, 0.0f, 0.0f,
320                 0.0f, 0.0f, 0.0f, 0.0f,
321                 0.0f, 0.0f, 0.0f, 0.0f,
322                 0.0f, 0.0f, 0.0f, 1.0f,
323
324                 0.0f, 0.0f, 0.0f, 0.0f,
325                 0.0f, 0.5f, 0.0f, 0.0f,
326                 0.0f, 0.0f, 1.0f, 0.0f,
327                 0.0f, 0.0f, 0.0f, 0.0f,
328
329                 1.0f, 1.0f, 1.0f, 1.0f,
330                 1.0f, 1.0f, 1.0f, 1.0f,
331                 1.0f, 1.0f, 1.0f, 1.0f,
332                 1.0f, 1.0f, 1.0f, 1.0f,
333
334                 0.0f, 0.0f, 0.0f, 0.0f,
335                 0.0f, 1.0f, 1.0f, 0.0f,
336                 0.0f, 1.0f, 1.0f, 0.0f,
337                 0.0f, 0.0f, 0.0f, 0.0f,
338         };
339         float expected_data[] = {  // Repeated four times each way.
340                 0.125f,   0.125f,   0.125f,   0.125f,
341                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
342                 1.0f,     1.0f,     1.0f,     1.0f,
343                 0.25f,    0.25f,    0.25f,    0.25f,
344
345                 0.125f,   0.125f,   0.125f,   0.125f,
346                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
347                 1.0f,     1.0f,     1.0f,     1.0f,
348                 0.25f,    0.25f,    0.25f,    0.25f,
349
350                 0.125f,   0.125f,   0.125f,   0.125f,
351                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
352                 1.0f,     1.0f,     1.0f,     1.0f,
353                 0.25f,    0.25f,    0.25f,    0.25f,
354
355                 0.125f,   0.125f,   0.125f,   0.125f,
356                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
357                 1.0f,     1.0f,     1.0f,     1.0f,
358                 0.25f,    0.25f,    0.25f,    0.25f,
359         };
360         float out_data[4 * 16];
361         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
362         tester.get_chain()->add_effect(new MipmapNeedingEffect());
363         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
364
365         expect_equal(expected_data, out_data, 4, 16);
366 }
367
368 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
369         float data[] = {  // In 4x4 blocks.
370                 1.0f, 0.0f, 0.0f, 0.0f,
371                 0.0f, 0.0f, 0.0f, 0.0f,
372                 0.0f, 0.0f, 0.0f, 0.0f,
373                 0.0f, 0.0f, 0.0f, 1.0f,
374
375                 0.0f, 0.0f, 0.0f, 0.0f,
376                 0.0f, 0.5f, 0.0f, 0.0f,
377                 0.0f, 0.0f, 1.0f, 0.0f,
378                 0.0f, 0.0f, 0.0f, 0.0f,
379
380                 1.0f, 1.0f, 1.0f, 1.0f,
381                 1.0f, 1.0f, 1.0f, 1.0f,
382                 1.0f, 1.0f, 1.0f, 1.0f,
383                 1.0f, 1.0f, 1.0f, 1.0f,
384
385                 0.0f, 0.0f, 0.0f, 0.0f,
386                 0.0f, 1.0f, 1.0f, 0.0f,
387                 0.0f, 1.0f, 1.0f, 0.0f,
388                 0.0f, 0.0f, 0.0f, 0.0f,
389         };
390         float expected_data[] = {  // Repeated four times horizontaly, interpolated vertically.
391                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
392                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
393                 0.1211f, 0.1211f, 0.1211f, 0.1211f,
394                 0.1133f, 0.1133f, 0.1133f, 0.1133f,
395                 0.1055f, 0.1055f, 0.1055f, 0.1055f,
396                 0.0977f, 0.0977f, 0.0977f, 0.0977f,
397                 0.2070f, 0.2070f, 0.2070f, 0.2070f,
398                 0.4336f, 0.4336f, 0.4336f, 0.4336f,
399                 0.6602f, 0.6602f, 0.6602f, 0.6602f,
400                 0.8867f, 0.8867f, 0.8867f, 0.8867f,
401                 0.9062f, 0.9062f, 0.9062f, 0.9062f,
402                 0.7188f, 0.7188f, 0.7188f, 0.7188f,
403                 0.5312f, 0.5312f, 0.5312f, 0.5312f,
404                 0.3438f, 0.3438f, 0.3438f, 0.3438f,
405                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
406                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
407         };
408         float out_data[4 * 16];
409
410         ResizeEffect *downscale = new ResizeEffect();
411         ASSERT_TRUE(downscale->set_int("width", 1));
412         ASSERT_TRUE(downscale->set_int("height", 4));
413
414         ResizeEffect *upscale = new ResizeEffect();
415         ASSERT_TRUE(upscale->set_int("width", 4));
416         ASSERT_TRUE(upscale->set_int("height", 16));
417
418         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
419         tester.get_chain()->add_effect(downscale);
420         tester.get_chain()->add_effect(upscale);
421         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
422
423         expect_equal(expected_data, out_data, 4, 16);
424 }