Rename the OutputAlphaFormat enums; they had gotten inconsistent after the last alpha...
[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 <GL/glew.h>
6
7 #include "effect_chain.h"
8 #include "flat_input.h"
9 #include "gtest/gtest.h"
10 #include "mirror_effect.h"
11 #include "resize_effect.h"
12 #include "test_util.h"
13
14 TEST(EffectChainTest, EmptyChain) {
15         float data[] = {
16                 0.0f, 0.25f, 0.3f,
17                 0.75f, 1.0f, 1.0f,
18         };
19         float out_data[6];
20         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
21         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
22
23         expect_equal(data, out_data, 3, 2);
24 }
25
26 // An effect that does nothing.
27 class IdentityEffect : public Effect {
28 public:
29         IdentityEffect() {}
30         virtual std::string effect_type_id() const { return "IdentityEffect"; }
31         std::string output_fragment_shader() { return read_file("identity.frag"); }
32 };
33
34 TEST(EffectChainTest, Identity) {
35         float data[] = {
36                 0.0f, 0.25f, 0.3f,
37                 0.75f, 1.0f, 1.0f,
38         };
39         float out_data[6];
40         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
41         tester.get_chain()->add_effect(new IdentityEffect());
42         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
43
44         expect_equal(data, out_data, 3, 2);
45 }
46
47 // An effect that does nothing, but requests texture bounce.
48 class BouncingIdentityEffect : public Effect {
49 public:
50         BouncingIdentityEffect() {}
51         virtual std::string effect_type_id() const { return "IdentityEffect"; }
52         std::string output_fragment_shader() { return read_file("identity.frag"); }
53         bool needs_texture_bounce() const { return true; }
54         AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
55 };
56
57 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
58         float data[] = {
59                 0.0f, 0.25f, 0.3f,
60                 0.75f, 1.0f, 1.0f,
61         };
62         float out_data[6];
63         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
64         tester.get_chain()->add_effect(new BouncingIdentityEffect());
65         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
66
67         expect_equal(data, out_data, 3, 2);
68 }
69
70 TEST(MirrorTest, BasicTest) {
71         float data[] = {
72                 0.0f, 0.25f, 0.3f,
73                 0.75f, 1.0f, 1.0f,
74         };
75         float expected_data[6] = {
76                 0.3f, 0.25f, 0.0f,
77                 1.0f, 1.0f, 0.75f,
78         };
79         float out_data[6];
80         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
81         tester.get_chain()->add_effect(new MirrorEffect());
82         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
83
84         expect_equal(expected_data, out_data, 3, 2);
85 }
86
87 // A dummy effect that inverts its input.
88 class InvertEffect : public Effect {
89 public:
90         InvertEffect() {}
91         virtual std::string effect_type_id() const { return "InvertEffect"; }
92         std::string output_fragment_shader() { return read_file("invert_effect.frag"); }
93
94         // A real invert would actually care about its alpha,
95         // but in this unit test, it only complicates things.
96         virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
97 };
98
99 // Like IdentityEffect, but rewrites itself out of the loop,
100 // splicing in a different effect instead. Also stores the new node,
101 // so we later can check whatever properties we'd like about the graph.
102 template<class T>
103 class RewritingEffect : public Effect {
104 public:
105         RewritingEffect() : effect(new T()), replaced_node(NULL) {}
106         virtual std::string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
107         std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
108         virtual void rewrite_graph(EffectChain *graph, Node *self) {
109                 replaced_node = graph->add_node(effect);
110                 graph->replace_receiver(self, replaced_node);
111                 graph->replace_sender(self, replaced_node);
112                 self->disabled = true;
113         }
114
115         T *effect;
116         Node *replaced_node;
117 };
118
119 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
120         float data[] = {
121                 0.0f, 0.25f, 0.3f,
122                 0.75f, 1.0f, 1.0f,
123         };
124         float expected_data[6] = {
125                 1.0f, 0.9771f, 0.9673f,
126                 0.7192f, 0.0f, 0.0f,
127         };
128         float out_data[6];
129         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
130         RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
131         tester.get_chain()->add_effect(effect);
132         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
133
134         Node *node = effect->replaced_node;
135         ASSERT_EQ(1, node->incoming_links.size());
136         ASSERT_EQ(1, node->outgoing_links.size());
137         EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
138         EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
139
140         expect_equal(expected_data, out_data, 3, 2);
141 }
142
143 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
144         unsigned char data[] = {
145                 0, 64,
146                 128, 255,
147         };
148         float expected_data[4] = {
149                 1.0f, 0.9771f,
150                 0.8983f, 0.0f,
151         };
152         float out_data[4];
153         EffectChainTester tester(NULL, 2, 2);
154         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
155         RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
156         tester.get_chain()->add_effect(effect);
157         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
158
159         Node *node = effect->replaced_node;
160         ASSERT_EQ(1, node->incoming_links.size());
161         ASSERT_EQ(1, node->outgoing_links.size());
162         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
163         EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
164
165         expect_equal(expected_data, out_data, 2, 2);
166 }
167
168 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
169         float data[] = {
170                 0.0f, 0.25f, 0.3f,
171                 0.75f, 1.0f, 1.0f,
172         };
173         float expected_data[6] = {
174                 1.0f, 0.75f, 0.7f,
175                 0.25f, 0.0f, 0.0f,
176         };
177         float out_data[6];
178         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
179         RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
180         tester.get_chain()->add_effect(effect);
181         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
182
183         Node *node = effect->replaced_node;
184         ASSERT_EQ(1, node->incoming_links.size());
185         ASSERT_EQ(1, node->outgoing_links.size());
186         EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
187         EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
188
189         expect_equal(expected_data, out_data, 3, 2);
190 }
191
192 // A fake input that can change its output colorspace and gamma between instantiation
193 // and finalize.
194 class UnknownColorspaceInput : public FlatInput {
195 public:
196         UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
197             : FlatInput(format, pixel_format, type, width, height),
198               overridden_color_space(format.color_space),
199               overridden_gamma_curve(format.gamma_curve) {}
200         virtual std::string effect_type_id() const { return "UnknownColorspaceInput"; }
201
202         void set_color_space(Colorspace colorspace) {
203                 overridden_color_space = colorspace;
204         }
205         void set_gamma_curve(GammaCurve gamma_curve) {
206                 overridden_gamma_curve = gamma_curve;
207         }
208         Colorspace get_color_space() const { return overridden_color_space; }
209         GammaCurve get_gamma_curve() const { return overridden_gamma_curve; }
210
211 private:
212         Colorspace overridden_color_space;
213         GammaCurve overridden_gamma_curve;
214 };
215
216 TEST(EffectChainTester, HandlesInputChangingColorspace) {
217         const int size = 4;
218
219         float data[size] = {
220                 0.0,
221                 0.5,
222                 0.7,
223                 1.0,
224         };
225         float out_data[size];
226
227         EffectChainTester tester(NULL, 4, 1, FORMAT_GRAYSCALE);
228
229         // First say that we have sRGB, linear input.
230         ImageFormat format;
231         format.color_space = COLORSPACE_sRGB;
232         format.gamma_curve = GAMMA_LINEAR;
233
234         UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
235         input->set_pixel_data(data);
236         tester.get_chain()->add_input(input);
237
238         // Now we change to Rec. 601 input.
239         input->set_color_space(COLORSPACE_REC_601_625);
240         input->set_gamma_curve(GAMMA_REC_601);
241
242         // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
243         tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
244         expect_equal(data, out_data, 4, 1);
245 }
246
247 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
248         float data[] = {
249                 0.0f, 0.25f, 0.3f,
250                 0.75f, 1.0f, 1.0f,
251         };
252         float expected_data[6] = {
253                 0.3f, 0.25f, 0.0f,
254                 1.0f, 1.0f, 0.75f,
255         };
256         float out_data[6];
257         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
258         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
259         tester.get_chain()->add_effect(effect);
260         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
261
262         Node *node = effect->replaced_node;
263         ASSERT_EQ(1, node->incoming_links.size());
264         EXPECT_EQ(0, node->outgoing_links.size());
265         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
266
267         expect_equal(expected_data, out_data, 3, 2);
268 }
269
270 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
271         float data[] = {
272                 0.0f, 0.25f, 0.3f,
273                 0.75f, 1.0f, 1.0f,
274         };
275         float expected_data[6] = {
276                 0.3f, 0.25f, 0.0f,
277                 1.0f, 1.0f, 0.75f,
278         };
279         float out_data[6];
280         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
281         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
282         tester.get_chain()->add_effect(effect);
283         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
284
285         Node *node = effect->replaced_node;
286         ASSERT_EQ(1, node->incoming_links.size());
287         EXPECT_EQ(0, node->outgoing_links.size());
288         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
289
290         expect_equal(expected_data, out_data, 3, 2);
291 }
292
293 // The identity effect needs linear light, and thus will get conversions on both sides.
294 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
295 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
296         float data[256];
297         for (unsigned i = 0; i < 256; ++i) {
298                 data[i] = i / 255.0;
299         };
300         float out_data[256];
301         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
302         tester.get_chain()->add_effect(new IdentityEffect());
303         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
304
305         expect_equal(data, out_data, 256, 1);
306 }
307
308 // Same, but uses the forward sRGB table from the GPU.
309 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
310         unsigned char data[256];
311         float expected_data[256];
312         for (unsigned i = 0; i < 256; ++i) {
313                 data[i] = i;
314                 expected_data[i] = i / 255.0;
315         };
316         float out_data[256];
317         EffectChainTester tester(NULL, 256, 1);
318         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
319         tester.get_chain()->add_effect(new IdentityEffect());
320         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
321
322         expect_equal(expected_data, out_data, 256, 1);
323 }
324
325 // Same, for the Rec. 601/709 gamma curve.
326 TEST(EffectChainTest, IdentityThroughRec709) {
327         float data[256];
328         for (unsigned i = 0; i < 256; ++i) {
329                 data[i] = i / 255.0;
330         };
331         float out_data[256];
332         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
333         tester.get_chain()->add_effect(new IdentityEffect());
334         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
335
336         expect_equal(data, out_data, 256, 1);
337 }
338
339 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
340 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
341         const int size = 3;
342         float data[4 * size] = {
343                 0.8f, 0.0f, 0.0f, 0.5f,
344                 0.0f, 0.2f, 0.2f, 0.3f,
345                 0.1f, 0.0f, 1.0f, 1.0f,
346         };
347         float out_data[4 * size];
348         EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
349         tester.get_chain()->add_effect(new IdentityEffect());
350         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
351
352         expect_equal(data, out_data, 4, size);
353 }
354
355 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
356         const int size = 3;
357         float data[4 * size] = {
358                 0.8f, 0.0f, 0.0f, 0.5f,
359                 0.0f, 0.2f, 0.2f, 0.3f,
360                 0.1f, 0.0f, 1.0f, 1.0f,
361         };
362         float expected_data[4 * size] = {
363                 0.1f, 0.0f, 1.0f, 1.0f,
364                 0.0f, 0.2f, 0.2f, 0.3f,
365                 0.8f, 0.0f, 0.0f, 0.5f,
366         };
367         float out_data[4 * size];
368         EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
369         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
370         tester.get_chain()->add_effect(effect);
371         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
372
373         Node *node = effect->replaced_node;
374         ASSERT_EQ(1, node->incoming_links.size());
375         EXPECT_EQ(0, node->outgoing_links.size());
376         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
377
378         expect_equal(expected_data, out_data, 4, size);
379 }
380
381 // An input that outputs only blue, which has blank alpha.
382 class BlueInput : public Input {
383 public:
384         BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
385         virtual std::string effect_type_id() const { return "IdentityEffect"; }
386         std::string output_fragment_shader() { return read_file("blue.frag"); }
387         virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
388         virtual void finalize() {}
389         virtual bool can_output_linear_gamma() const { return true; }
390         virtual unsigned get_width() const { return 1; }
391         virtual unsigned get_height() const { return 1; }
392         virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
393         virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
394
395 private:
396         int needs_mipmaps;
397 };
398
399 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
400 // which outputs blank alpha.
401 class RewritingToBlueInput : public Input {
402 public:
403         RewritingToBlueInput() : blue_node(NULL) { register_int("needs_mipmaps", &needs_mipmaps); }
404         virtual std::string effect_type_id() const { return "RewritingToBlueInput"; }
405         std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
406         virtual void rewrite_graph(EffectChain *graph, Node *self) {
407                 Node *blue_node = graph->add_node(new BlueInput());
408                 graph->replace_receiver(self, blue_node);
409                 graph->replace_sender(self, blue_node);
410
411                 self->disabled = true;
412                 this->blue_node = blue_node;
413         }
414
415         // Dummy values that we need to implement because we inherit from Input.
416         // Same as BlueInput.
417         virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
418         virtual void finalize() {}
419         virtual bool can_output_linear_gamma() const { return true; }
420         virtual unsigned get_width() const { return 1; }
421         virtual unsigned get_height() const { return 1; }
422         virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
423         virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
424
425         Node *blue_node;
426
427 private:
428         int needs_mipmaps;
429 };
430
431 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
432         const int size = 3;
433         float data[4 * size] = {
434                 0.0f, 0.0f, 1.0f, 1.0f,
435                 0.0f, 0.0f, 1.0f, 1.0f,
436                 0.0f, 0.0f, 1.0f, 1.0f,
437         };
438         float out_data[4 * size];
439         EffectChainTester tester(NULL, size, 1);
440         RewritingToBlueInput *input = new RewritingToBlueInput();
441         tester.get_chain()->add_input(input);
442         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
443
444         Node *node = input->blue_node;
445         EXPECT_EQ(0, node->incoming_links.size());
446         EXPECT_EQ(0, node->outgoing_links.size());
447
448         expect_equal(data, out_data, 4, size);
449 }
450
451 // Effectively scales down its input linearly by 4x (and repeating it),
452 // which is not attainable without mipmaps.
453 class MipmapNeedingEffect : public Effect {
454 public:
455         MipmapNeedingEffect() {}
456         virtual bool needs_mipmaps() const { return true; }
457         virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
458         std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
459         void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
460         {
461                 glActiveTexture(GL_TEXTURE0);
462                 check_error();
463                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
464                 check_error();
465                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
466                 check_error();
467         }
468 };
469
470 TEST(EffectChainTest, MipmapGenerationWorks) {
471         float data[] = {  // In 4x4 blocks.
472                 1.0f, 0.0f, 0.0f, 0.0f,
473                 0.0f, 0.0f, 0.0f, 0.0f,
474                 0.0f, 0.0f, 0.0f, 0.0f,
475                 0.0f, 0.0f, 0.0f, 1.0f,
476
477                 0.0f, 0.0f, 0.0f, 0.0f,
478                 0.0f, 0.5f, 0.0f, 0.0f,
479                 0.0f, 0.0f, 1.0f, 0.0f,
480                 0.0f, 0.0f, 0.0f, 0.0f,
481
482                 1.0f, 1.0f, 1.0f, 1.0f,
483                 1.0f, 1.0f, 1.0f, 1.0f,
484                 1.0f, 1.0f, 1.0f, 1.0f,
485                 1.0f, 1.0f, 1.0f, 1.0f,
486
487                 0.0f, 0.0f, 0.0f, 0.0f,
488                 0.0f, 1.0f, 1.0f, 0.0f,
489                 0.0f, 1.0f, 1.0f, 0.0f,
490                 0.0f, 0.0f, 0.0f, 0.0f,
491         };
492         float expected_data[] = {  // Repeated four times each way.
493                 0.125f,   0.125f,   0.125f,   0.125f,
494                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
495                 1.0f,     1.0f,     1.0f,     1.0f,
496                 0.25f,    0.25f,    0.25f,    0.25f,
497
498                 0.125f,   0.125f,   0.125f,   0.125f,
499                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
500                 1.0f,     1.0f,     1.0f,     1.0f,
501                 0.25f,    0.25f,    0.25f,    0.25f,
502
503                 0.125f,   0.125f,   0.125f,   0.125f,
504                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
505                 1.0f,     1.0f,     1.0f,     1.0f,
506                 0.25f,    0.25f,    0.25f,    0.25f,
507
508                 0.125f,   0.125f,   0.125f,   0.125f,
509                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
510                 1.0f,     1.0f,     1.0f,     1.0f,
511                 0.25f,    0.25f,    0.25f,    0.25f,
512         };
513         float out_data[4 * 16];
514         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
515         tester.get_chain()->add_effect(new MipmapNeedingEffect());
516         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
517
518         expect_equal(expected_data, out_data, 4, 16);
519 }
520
521 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
522         float data[] = {  // In 4x4 blocks.
523                 1.0f, 0.0f, 0.0f, 0.0f,
524                 0.0f, 0.0f, 0.0f, 0.0f,
525                 0.0f, 0.0f, 0.0f, 0.0f,
526                 0.0f, 0.0f, 0.0f, 1.0f,
527
528                 0.0f, 0.0f, 0.0f, 0.0f,
529                 0.0f, 0.5f, 0.0f, 0.0f,
530                 0.0f, 0.0f, 1.0f, 0.0f,
531                 0.0f, 0.0f, 0.0f, 0.0f,
532
533                 1.0f, 1.0f, 1.0f, 1.0f,
534                 1.0f, 1.0f, 1.0f, 1.0f,
535                 1.0f, 1.0f, 1.0f, 1.0f,
536                 1.0f, 1.0f, 1.0f, 1.0f,
537
538                 0.0f, 0.0f, 0.0f, 0.0f,
539                 0.0f, 1.0f, 1.0f, 0.0f,
540                 0.0f, 1.0f, 1.0f, 0.0f,
541                 0.0f, 0.0f, 0.0f, 0.0f,
542         };
543         float expected_data[] = {  // Repeated four times horizontaly, interpolated vertically.
544                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
545                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
546                 0.1211f, 0.1211f, 0.1211f, 0.1211f,
547                 0.1133f, 0.1133f, 0.1133f, 0.1133f,
548                 0.1055f, 0.1055f, 0.1055f, 0.1055f,
549                 0.0977f, 0.0977f, 0.0977f, 0.0977f,
550                 0.2070f, 0.2070f, 0.2070f, 0.2070f,
551                 0.4336f, 0.4336f, 0.4336f, 0.4336f,
552                 0.6602f, 0.6602f, 0.6602f, 0.6602f,
553                 0.8867f, 0.8867f, 0.8867f, 0.8867f,
554                 0.9062f, 0.9062f, 0.9062f, 0.9062f,
555                 0.7188f, 0.7188f, 0.7188f, 0.7188f,
556                 0.5312f, 0.5312f, 0.5312f, 0.5312f,
557                 0.3438f, 0.3438f, 0.3438f, 0.3438f,
558                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
559                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
560         };
561         float out_data[4 * 16];
562
563         ResizeEffect *downscale = new ResizeEffect();
564         ASSERT_TRUE(downscale->set_int("width", 1));
565         ASSERT_TRUE(downscale->set_int("height", 4));
566
567         ResizeEffect *upscale = new ResizeEffect();
568         ASSERT_TRUE(upscale->set_int("width", 4));
569         ASSERT_TRUE(upscale->set_int("height", 16));
570
571         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
572         tester.get_chain()->add_effect(downscale);
573         tester.get_chain()->add_effect(upscale);
574         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
575
576         expect_equal(expected_data, out_data, 4, 16);
577 }
578
579 // An effect that multiplies with a constant. Used below.
580 class MultiplyEffect : public Effect {
581 public:
582         MultiplyEffect() { register_float("factor", &factor); }
583         virtual std::string effect_type_id() const { return "MultiplyEffect"; }
584         std::string output_fragment_shader() { return read_file("multiply.frag"); }
585         virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
586
587 private:
588         float factor;
589 };
590
591 // An effect that adds its two inputs together. Used below.
592 class AddEffect : public Effect {
593 public:
594         AddEffect() {}
595         virtual std::string effect_type_id() const { return "AddEffect"; }
596         std::string output_fragment_shader() { return read_file("add.frag"); }
597         virtual unsigned num_inputs() const { return 2; }
598         virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
599 };
600
601 // Constructs the graph
602 //
603 //             FlatInput               |
604 //            /         \              |
605 //  MultiplyEffect  MultiplyEffect     |
606 //            \         /              |
607 //             AddEffect               |
608 //
609 // and verifies that it gives the correct output.
610 TEST(EffectChainTest, DiamondGraph) {
611         float data[] = {
612                 1.0f, 1.0f,
613                 1.0f, 0.0f,
614         };
615         float expected_data[] = {
616                 2.5f, 2.5f,
617                 2.5f, 0.0f,
618         };
619         float out_data[2 * 2];
620
621         MultiplyEffect *mul_half = new MultiplyEffect();
622         ASSERT_TRUE(mul_half->set_float("factor", 0.5f));
623         
624         MultiplyEffect *mul_two = new MultiplyEffect();
625         ASSERT_TRUE(mul_two->set_float("factor", 2.0f));
626
627         EffectChainTester tester(NULL, 2, 2);
628
629         ImageFormat format;
630         format.color_space = COLORSPACE_sRGB;
631         format.gamma_curve = GAMMA_LINEAR;
632
633         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
634         input->set_pixel_data(data);
635
636         tester.get_chain()->add_input(input);
637         tester.get_chain()->add_effect(mul_half, input);
638         tester.get_chain()->add_effect(mul_two, input);
639         tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
640         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
641
642         expect_equal(expected_data, out_data, 2, 2);
643 }
644
645 // Constructs the graph
646 //
647 //             FlatInput                     |
648 //            /         \                    |
649 //  MultiplyEffect  MultiplyEffect           |
650 //         \             |                   |
651 //          \    BouncingIdentityEffect      |  
652 //            \         /                    |
653 //             AddEffect                     |
654 //
655 // and verifies that it gives the correct output.
656 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
657         float data[] = {
658                 1.0f, 1.0f,
659                 1.0f, 0.0f,
660         };
661         float expected_data[] = {
662                 2.5f, 2.5f,
663                 2.5f, 0.0f,
664         };
665         float out_data[2 * 2];
666
667         MultiplyEffect *mul_half = new MultiplyEffect();
668         ASSERT_TRUE(mul_half->set_float("factor", 0.5f));
669         
670         MultiplyEffect *mul_two = new MultiplyEffect();
671         ASSERT_TRUE(mul_two->set_float("factor", 2.0f));
672         
673         BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
674
675         EffectChainTester tester(NULL, 2, 2);
676
677         ImageFormat format;
678         format.color_space = COLORSPACE_sRGB;
679         format.gamma_curve = GAMMA_LINEAR;
680
681         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
682         input->set_pixel_data(data);
683
684         tester.get_chain()->add_input(input);
685         tester.get_chain()->add_effect(mul_half, input);
686         tester.get_chain()->add_effect(mul_two, input);
687         tester.get_chain()->add_effect(bounce, mul_two);
688         tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
689         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
690
691         expect_equal(expected_data, out_data, 2, 2);
692 }
693
694 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
695         float data[] = {
696                 0.735f, 0.0f,
697                 0.735f, 0.0f,
698         };
699         float expected_data[] = {
700                 0.0f, 0.5f,  // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
701                 0.0f, 0.5f,
702         };
703         float out_data[2 * 2];
704         
705         EffectChainTester tester(NULL, 2, 2);
706         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
707
708         // MirrorEffect does not get linear light, so the conversions will be
709         // inserted after it, not before.
710         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
711         tester.get_chain()->add_effect(effect);
712
713         Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
714         Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
715         tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
716         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
717
718         expect_equal(expected_data, out_data, 2, 2);
719
720         Node *node = effect->replaced_node;
721         ASSERT_EQ(1, node->incoming_links.size());
722         ASSERT_EQ(1, node->outgoing_links.size());
723         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
724         EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
725 }
726
727 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
728         float data[] = {
729                 0.5f, 0.0f,
730                 0.5f, 0.0f,
731         };
732         float expected_data[] = {
733                 0.0f, 0.5f,  // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
734                 0.0f, 0.5f,
735         };
736         float out_data[2 * 2];
737         
738         EffectChainTester tester(NULL, 2, 2);
739         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
740
741         // MirrorEffect does not get linear light, so the conversions will be
742         // inserted after it, not before.
743         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
744         tester.get_chain()->add_effect(effect);
745
746         Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
747         Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
748         tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
749         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
750
751         expect_equal(expected_data, out_data, 2, 2);
752
753         Node *node = effect->replaced_node;
754         ASSERT_EQ(1, node->incoming_links.size());
755         ASSERT_EQ(1, node->outgoing_links.size());
756         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
757         EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
758 }
759
760 // An effect that does nothing, but requests texture bounce and stores
761 // its input size.
762 class SizeStoringEffect : public BouncingIdentityEffect {
763 public:
764         SizeStoringEffect() : input_width(-1), input_height(-1) {}
765         virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) {
766                 assert(input_num == 0);
767                 input_width = width;
768                 input_height = height;
769         }
770         virtual std::string effect_type_id() const { return "SizeStoringEffect"; }
771
772         int input_width, input_height;
773 };
774
775 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
776         float data[2 * 2] = {
777                 0.0f, 0.0f,
778                 0.0f, 0.0f,
779         };
780         float out_data[2 * 2];
781         
782         EffectChainTester tester(NULL, 4, 3);  // Note non-square aspect.
783
784         ImageFormat format;
785         format.color_space = COLORSPACE_sRGB;
786         format.gamma_curve = GAMMA_LINEAR;
787
788         FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
789         input1->set_pixel_data(data);
790         
791         FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
792         input2->set_pixel_data(data);
793
794         SizeStoringEffect *input_store = new SizeStoringEffect();
795
796         tester.get_chain()->add_input(input1);
797         tester.get_chain()->add_input(input2);
798         tester.get_chain()->add_effect(new AddEffect(), input1, input2);
799         tester.get_chain()->add_effect(input_store);
800         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
801
802         EXPECT_EQ(2, input_store->input_width);
803         EXPECT_EQ(2, input_store->input_height);
804 }
805
806 TEST(EffectChainTest, AspectRatioConversion) {
807         float data1[4 * 3] = {
808                 0.0f, 0.0f, 0.0f, 0.0f,
809                 0.0f, 0.0f, 0.0f, 0.0f,
810                 0.0f, 0.0f, 0.0f, 0.0f,
811         };
812         float data2[7 * 7] = {
813                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
814                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
815                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
816                 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
817                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
818                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
819                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
820         };
821
822         // The right conversion here is that the 7x7 image decides the size,
823         // since it is the biggest, so everything is scaled up to 9x7
824         // (keep the height, round the width 9.333 to 9). 
825         float out_data[9 * 7];
826         
827         EffectChainTester tester(NULL, 4, 3);
828
829         ImageFormat format;
830         format.color_space = COLORSPACE_sRGB;
831         format.gamma_curve = GAMMA_LINEAR;
832
833         FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
834         input1->set_pixel_data(data1);
835         
836         FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
837         input2->set_pixel_data(data2);
838
839         SizeStoringEffect *input_store = new SizeStoringEffect();
840
841         tester.get_chain()->add_input(input1);
842         tester.get_chain()->add_input(input2);
843         tester.get_chain()->add_effect(new AddEffect(), input1, input2);
844         tester.get_chain()->add_effect(input_store);
845         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
846
847         EXPECT_EQ(9, input_store->input_width);
848         EXPECT_EQ(7, input_store->input_height);
849 }
850
851 // An effect that does nothing except changing its output sizes.
852 class VirtualResizeEffect : public Effect {
853 public:
854         VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
855                 : width(width),
856                   height(height),
857                   virtual_width(virtual_width),
858                   virtual_height(virtual_height) {}
859         virtual std::string effect_type_id() const { return "VirtualResizeEffect"; }
860         std::string output_fragment_shader() { return read_file("identity.frag"); }
861
862         virtual bool changes_output_size() const { return true; }
863
864         virtual void get_output_size(unsigned *width, unsigned *height,
865                                      unsigned *virtual_width, unsigned *virtual_height) const {
866                 *width = this->width;
867                 *height = this->height;
868                 *virtual_width = this->virtual_width;
869                 *virtual_height = this->virtual_height;
870         }
871
872 private:
873         int width, height, virtual_width, virtual_height;
874 };
875
876 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
877         const int size = 2, bigger_size = 3;
878         float data[size * size] = {
879                 1.0f, 0.0f,
880                 0.0f, 1.0f,
881         };
882         float out_data[size * size];
883         
884         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
885
886         SizeStoringEffect *size_store = new SizeStoringEffect();
887
888         tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
889         tester.get_chain()->add_effect(size_store);
890         tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
891         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
892
893         EXPECT_EQ(bigger_size, size_store->input_width);
894         EXPECT_EQ(bigger_size, size_store->input_height);
895
896         // If the resize is implemented as non-virtual, we'll fail here,
897         // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
898         expect_equal(data, out_data, size, size);
899 }