Refactor RewritingTo{MirrorEffect,InvertEffect} into a reusable template; it started...
[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 };
55
56 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
57         float data[] = {
58                 0.0f, 0.25f, 0.3f,
59                 0.75f, 1.0f, 1.0f,
60         };
61         float out_data[6];
62         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
63         tester.get_chain()->add_effect(new BouncingIdentityEffect());
64         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
65
66         expect_equal(data, out_data, 3, 2);
67 }
68
69 TEST(MirrorTest, BasicTest) {
70         float data[] = {
71                 0.0f, 0.25f, 0.3f,
72                 0.75f, 1.0f, 1.0f,
73         };
74         float expected_data[6] = {
75                 0.3f, 0.25f, 0.0f,
76                 1.0f, 1.0f, 0.75f,
77         };
78         float out_data[6];
79         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
80         tester.get_chain()->add_effect(new MirrorEffect());
81         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
82
83         expect_equal(expected_data, out_data, 3, 2);
84 }
85
86 // A dummy effect that inverts its input.
87 class InvertEffect : public Effect {
88 public:
89         InvertEffect() {}
90         virtual std::string effect_type_id() const { return "InvertEffect"; }
91         std::string output_fragment_shader() { return read_file("invert_effect.frag"); }
92
93         // A real invert would actually care about its alpha,
94         // but in this unit test, it only complicates things.
95         virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
96 };
97
98 // Like IdentityEffect, but rewrites itself out of the loop,
99 // splicing in a different effect instead. Also stores the new node,
100 // so we later can check whatever properties we'd like about the graph.
101 template<class T>
102 class RewritingEffect : public Effect {
103 public:
104         RewritingEffect() : effect(new T()) {}
105         virtual std::string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
106         std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
107         virtual void rewrite_graph(EffectChain *graph, Node *self) {
108                 replaced_node = graph->add_node(effect);
109                 graph->replace_receiver(self, replaced_node);
110                 graph->replace_sender(self, replaced_node);
111                 self->disabled = true;
112         }
113
114         T *effect;
115         Node *replaced_node;
116 };
117
118 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
119         float data[] = {
120                 0.0f, 0.25f, 0.3f,
121                 0.75f, 1.0f, 1.0f,
122         };
123         float expected_data[6] = {
124                 1.0f, 0.9771f, 0.9673f,
125                 0.7192f, 0.0f, 0.0f,
126         };
127         float out_data[6];
128         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
129         RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
130         tester.get_chain()->add_effect(effect);
131         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
132
133         Node *node = effect->replaced_node;
134         ASSERT_EQ(1, node->incoming_links.size());
135         ASSERT_EQ(1, node->outgoing_links.size());
136         EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
137         EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
138
139         expect_equal(expected_data, out_data, 3, 2);
140 }
141
142 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
143         unsigned char data[] = {
144                 0, 64,
145                 128, 255,
146         };
147         float expected_data[4] = {
148                 1.0f, 0.9771f,
149                 0.8983f, 0.0f,
150         };
151         float out_data[2];
152         EffectChainTester tester(NULL, 2, 2);
153         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
154         RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
155         tester.get_chain()->add_effect(effect);
156         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
157
158         Node *node = effect->replaced_node;
159         ASSERT_EQ(1, node->incoming_links.size());
160         ASSERT_EQ(1, node->outgoing_links.size());
161         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
162         EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
163
164         expect_equal(expected_data, out_data, 2, 2);
165 }
166
167 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
168         float data[] = {
169                 0.0f, 0.25f, 0.3f,
170                 0.75f, 1.0f, 1.0f,
171         };
172         float expected_data[6] = {
173                 1.0f, 0.75f, 0.7f,
174                 0.25f, 0.0f, 0.0f,
175         };
176         float out_data[6];
177         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
178         RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
179         tester.get_chain()->add_effect(effect);
180         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
181
182         Node *node = effect->replaced_node;
183         ASSERT_EQ(1, node->incoming_links.size());
184         ASSERT_EQ(1, node->outgoing_links.size());
185         EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
186         EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
187
188         expect_equal(expected_data, out_data, 3, 2);
189 }
190
191 // A fake input that can change its output colorspace and gamma between instantiation
192 // and finalize.
193 class UnknownColorspaceInput : public FlatInput {
194 public:
195         UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
196             : FlatInput(format, pixel_format, type, width, height),
197               overridden_color_space(format.color_space),
198               overridden_gamma_curve(format.gamma_curve) {}
199         virtual std::string effect_type_id() const { return "UnknownColorspaceInput"; }
200
201         void set_color_space(Colorspace colorspace) {
202                 overridden_color_space = colorspace;
203         }
204         void set_gamma_curve(GammaCurve gamma_curve) {
205                 overridden_gamma_curve = gamma_curve;
206         }
207         Colorspace get_color_space() const { return overridden_color_space; }
208         GammaCurve get_gamma_curve() const { return overridden_gamma_curve; }
209
210 private:
211         Colorspace overridden_color_space;
212         GammaCurve overridden_gamma_curve;
213 };
214
215 TEST(EffectChainTester, HandlesInputChangingColorspace) {
216         const int size = 4;
217
218         float data[size] = {
219                 0.0,
220                 0.5,
221                 0.7,
222                 1.0,
223         };
224         float out_data[size];
225
226         EffectChainTester tester(NULL, 4, 1, FORMAT_GRAYSCALE);
227
228         // First say that we have sRGB, linear input.
229         ImageFormat format;
230         format.color_space = COLORSPACE_sRGB;
231         format.gamma_curve = GAMMA_LINEAR;
232
233         UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
234         input->set_pixel_data(data);
235         tester.get_chain()->add_input(input);
236
237         // Now we change to Rec. 601 input.
238         input->set_color_space(COLORSPACE_REC_601_625);
239         input->set_gamma_curve(GAMMA_REC_601);
240
241         // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
242         tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
243         expect_equal(data, out_data, 4, 1);
244 }
245
246 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
247         float data[] = {
248                 0.0f, 0.25f, 0.3f,
249                 0.75f, 1.0f, 1.0f,
250         };
251         float expected_data[6] = {
252                 0.3f, 0.25f, 0.0f,
253                 1.0f, 1.0f, 0.75f,
254         };
255         float out_data[6];
256         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
257         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
258         tester.get_chain()->add_effect(effect);
259         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
260
261         Node *node = effect->replaced_node;
262         ASSERT_EQ(1, node->incoming_links.size());
263         EXPECT_EQ(0, node->outgoing_links.size());
264         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
265
266         expect_equal(expected_data, out_data, 3, 2);
267 }
268
269 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
270         float data[] = {
271                 0.0f, 0.25f, 0.3f,
272                 0.75f, 1.0f, 1.0f,
273         };
274         float expected_data[6] = {
275                 0.3f, 0.25f, 0.0f,
276                 1.0f, 1.0f, 0.75f,
277         };
278         float out_data[6];
279         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
280         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
281         tester.get_chain()->add_effect(effect);
282         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
283
284         Node *node = effect->replaced_node;
285         ASSERT_EQ(1, node->incoming_links.size());
286         EXPECT_EQ(0, node->outgoing_links.size());
287         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
288
289         expect_equal(expected_data, out_data, 3, 2);
290 }
291
292 // The identity effect needs linear light, and thus will get conversions on both sides.
293 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
294 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
295         float data[256];
296         for (unsigned i = 0; i < 256; ++i) {
297                 data[i] = i / 255.0;
298         };
299         float out_data[256];
300         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
301         tester.get_chain()->add_effect(new IdentityEffect());
302         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
303
304         expect_equal(data, out_data, 256, 1);
305 }
306
307 // Same, but uses the forward sRGB table from the GPU.
308 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
309         unsigned char data[256];
310         float expected_data[256];
311         for (unsigned i = 0; i < 256; ++i) {
312                 data[i] = i;
313                 expected_data[i] = i / 255.0;
314         };
315         float out_data[256];
316         EffectChainTester tester(NULL, 256, 1);
317         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
318         tester.get_chain()->add_effect(new IdentityEffect());
319         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
320
321         expect_equal(expected_data, out_data, 256, 1);
322 }
323
324 // Same, for the Rec. 601/709 gamma curve.
325 TEST(EffectChainTest, IdentityThroughRec709) {
326         float data[256];
327         for (unsigned i = 0; i < 256; ++i) {
328                 data[i] = i / 255.0;
329         };
330         float out_data[256];
331         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
332         tester.get_chain()->add_effect(new IdentityEffect());
333         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
334
335         expect_equal(data, out_data, 256, 1);
336 }
337
338 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
339 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
340         const int size = 3;
341         float data[4 * size] = {
342                 0.8f, 0.0f, 0.0f, 0.5f,
343                 0.0f, 0.2f, 0.2f, 0.3f,
344                 0.1f, 0.0f, 1.0f, 1.0f,
345         };
346         float out_data[6];
347         EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
348         tester.get_chain()->add_effect(new IdentityEffect());
349         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
350
351         expect_equal(data, out_data, 4, size);
352 }
353
354 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
355         const int size = 3;
356         float data[4 * size] = {
357                 0.8f, 0.0f, 0.0f, 0.5f,
358                 0.0f, 0.2f, 0.2f, 0.3f,
359                 0.1f, 0.0f, 1.0f, 1.0f,
360         };
361         float expected_data[4 * size] = {
362                 0.1f, 0.0f, 1.0f, 1.0f,
363                 0.0f, 0.2f, 0.2f, 0.3f,
364                 0.8f, 0.0f, 0.0f, 0.5f,
365         };
366         float out_data[4 * size];
367         EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
368         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
369         tester.get_chain()->add_effect(effect);
370         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
371
372         Node *node = effect->replaced_node;
373         ASSERT_EQ(1, node->incoming_links.size());
374         EXPECT_EQ(0, node->outgoing_links.size());
375         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
376
377         expect_equal(expected_data, out_data, 4, size);
378 }
379
380 // An input that outputs only blue, which has blank alpha.
381 class BlueInput : public Input {
382 public:
383         BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
384         virtual std::string effect_type_id() const { return "IdentityEffect"; }
385         std::string output_fragment_shader() { return read_file("blue.frag"); }
386         virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
387         virtual void finalize() {}
388         virtual bool can_output_linear_gamma() const { return true; }
389         virtual unsigned get_width() const { return 1; }
390         virtual unsigned get_height() const { return 1; }
391         virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
392         virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
393
394 private:
395         int needs_mipmaps;
396 };
397
398 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
399 // which outputs blank alpha.
400 class RewritingToBlueInput : public Input {
401 public:
402         RewritingToBlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
403         virtual std::string effect_type_id() const { return "RewritingToBlueInput"; }
404         std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
405         virtual void rewrite_graph(EffectChain *graph, Node *self) {
406                 Node *blue_node = graph->add_node(new BlueInput());
407                 graph->replace_receiver(self, blue_node);
408                 graph->replace_sender(self, blue_node);
409
410                 self->disabled = true;
411                 this->blue_node = blue_node;
412         }
413
414         // Dummy values that we need to implement because we inherit from Input.
415         // Same as BlueInput.
416         virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
417         virtual void finalize() {}
418         virtual bool can_output_linear_gamma() const { return true; }
419         virtual unsigned get_width() const { return 1; }
420         virtual unsigned get_height() const { return 1; }
421         virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
422         virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
423
424         Node *blue_node;
425
426 private:
427         int needs_mipmaps;
428 };
429
430 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
431         const int size = 3;
432         float data[4 * size] = {
433                 0.0f, 0.0f, 1.0f, 1.0f,
434                 0.0f, 0.0f, 1.0f, 1.0f,
435                 0.0f, 0.0f, 1.0f, 1.0f,
436         };
437         float out_data[4 * size];
438         EffectChainTester tester(NULL, size, 1);
439         RewritingToBlueInput *input = new RewritingToBlueInput();
440         tester.get_chain()->add_input(input);
441         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_PREMULTIPLIED);
442
443         Node *node = input->blue_node;
444         EXPECT_EQ(0, node->incoming_links.size());
445         EXPECT_EQ(0, node->outgoing_links.size());
446
447         expect_equal(data, out_data, 4, size);
448 }
449
450 // Effectively scales down its input linearly by 4x (and repeating it),
451 // which is not attainable without mipmaps.
452 class MipmapNeedingEffect : public Effect {
453 public:
454         MipmapNeedingEffect() {}
455         virtual bool needs_mipmaps() const { return true; }
456         virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
457         std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
458         void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
459         {
460                 glActiveTexture(GL_TEXTURE0);
461                 check_error();
462                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
463                 check_error();
464                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
465                 check_error();
466         }
467 };
468
469 TEST(EffectChainTest, MipmapGenerationWorks) {
470         float data[] = {  // In 4x4 blocks.
471                 1.0f, 0.0f, 0.0f, 0.0f,
472                 0.0f, 0.0f, 0.0f, 0.0f,
473                 0.0f, 0.0f, 0.0f, 0.0f,
474                 0.0f, 0.0f, 0.0f, 1.0f,
475
476                 0.0f, 0.0f, 0.0f, 0.0f,
477                 0.0f, 0.5f, 0.0f, 0.0f,
478                 0.0f, 0.0f, 1.0f, 0.0f,
479                 0.0f, 0.0f, 0.0f, 0.0f,
480
481                 1.0f, 1.0f, 1.0f, 1.0f,
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
486                 0.0f, 0.0f, 0.0f, 0.0f,
487                 0.0f, 1.0f, 1.0f, 0.0f,
488                 0.0f, 1.0f, 1.0f, 0.0f,
489                 0.0f, 0.0f, 0.0f, 0.0f,
490         };
491         float expected_data[] = {  // Repeated four times each way.
492                 0.125f,   0.125f,   0.125f,   0.125f,
493                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
494                 1.0f,     1.0f,     1.0f,     1.0f,
495                 0.25f,    0.25f,    0.25f,    0.25f,
496
497                 0.125f,   0.125f,   0.125f,   0.125f,
498                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
499                 1.0f,     1.0f,     1.0f,     1.0f,
500                 0.25f,    0.25f,    0.25f,    0.25f,
501
502                 0.125f,   0.125f,   0.125f,   0.125f,
503                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
504                 1.0f,     1.0f,     1.0f,     1.0f,
505                 0.25f,    0.25f,    0.25f,    0.25f,
506
507                 0.125f,   0.125f,   0.125f,   0.125f,
508                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
509                 1.0f,     1.0f,     1.0f,     1.0f,
510                 0.25f,    0.25f,    0.25f,    0.25f,
511         };
512         float out_data[4 * 16];
513         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
514         tester.get_chain()->add_effect(new MipmapNeedingEffect());
515         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
516
517         expect_equal(expected_data, out_data, 4, 16);
518 }
519
520 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
521         float data[] = {  // In 4x4 blocks.
522                 1.0f, 0.0f, 0.0f, 0.0f,
523                 0.0f, 0.0f, 0.0f, 0.0f,
524                 0.0f, 0.0f, 0.0f, 0.0f,
525                 0.0f, 0.0f, 0.0f, 1.0f,
526
527                 0.0f, 0.0f, 0.0f, 0.0f,
528                 0.0f, 0.5f, 0.0f, 0.0f,
529                 0.0f, 0.0f, 1.0f, 0.0f,
530                 0.0f, 0.0f, 0.0f, 0.0f,
531
532                 1.0f, 1.0f, 1.0f, 1.0f,
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
537                 0.0f, 0.0f, 0.0f, 0.0f,
538                 0.0f, 1.0f, 1.0f, 0.0f,
539                 0.0f, 1.0f, 1.0f, 0.0f,
540                 0.0f, 0.0f, 0.0f, 0.0f,
541         };
542         float expected_data[] = {  // Repeated four times horizontaly, interpolated vertically.
543                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
544                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
545                 0.1211f, 0.1211f, 0.1211f, 0.1211f,
546                 0.1133f, 0.1133f, 0.1133f, 0.1133f,
547                 0.1055f, 0.1055f, 0.1055f, 0.1055f,
548                 0.0977f, 0.0977f, 0.0977f, 0.0977f,
549                 0.2070f, 0.2070f, 0.2070f, 0.2070f,
550                 0.4336f, 0.4336f, 0.4336f, 0.4336f,
551                 0.6602f, 0.6602f, 0.6602f, 0.6602f,
552                 0.8867f, 0.8867f, 0.8867f, 0.8867f,
553                 0.9062f, 0.9062f, 0.9062f, 0.9062f,
554                 0.7188f, 0.7188f, 0.7188f, 0.7188f,
555                 0.5312f, 0.5312f, 0.5312f, 0.5312f,
556                 0.3438f, 0.3438f, 0.3438f, 0.3438f,
557                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
558                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
559         };
560         float out_data[4 * 16];
561
562         ResizeEffect *downscale = new ResizeEffect();
563         ASSERT_TRUE(downscale->set_int("width", 1));
564         ASSERT_TRUE(downscale->set_int("height", 4));
565
566         ResizeEffect *upscale = new ResizeEffect();
567         ASSERT_TRUE(upscale->set_int("width", 4));
568         ASSERT_TRUE(upscale->set_int("height", 16));
569
570         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
571         tester.get_chain()->add_effect(downscale);
572         tester.get_chain()->add_effect(upscale);
573         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
574
575         expect_equal(expected_data, out_data, 4, 16);
576 }
577
578 // An effect that multiplies with a constant. Used below.
579 class MultiplyEffect : public Effect {
580 public:
581         MultiplyEffect() { register_float("factor", &factor); }
582         virtual std::string effect_type_id() const { return "MultiplyEffect"; }
583         std::string output_fragment_shader() { return read_file("multiply.frag"); }
584         virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
585
586 private:
587         float factor;
588 };
589
590 // An effect that adds its two inputs together. Used below.
591 class AddEffect : public Effect {
592 public:
593         AddEffect() {}
594         virtual std::string effect_type_id() const { return "AddEffect"; }
595         std::string output_fragment_shader() { return read_file("add.frag"); }
596         virtual unsigned num_inputs() const { return 2; }
597         virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
598 };
599
600 // Constructs the graph
601 //
602 //             FlatInput               |
603 //            /         \              |
604 //  MultiplyEffect  MultiplyEffect     |
605 //            \         /              |
606 //             AddEffect               |
607 //
608 // and verifies that it gives the correct output.
609 TEST(EffectChainTest, DiamondGraph) {
610         float data[] = {
611                 1.0f, 1.0f,
612                 1.0f, 0.0f,
613         };
614         float expected_data[] = {
615                 2.5f, 2.5f,
616                 2.5f, 0.0f,
617         };
618         float out_data[2 * 2];
619
620         MultiplyEffect *mul_half = new MultiplyEffect();
621         ASSERT_TRUE(mul_half->set_float("factor", 0.5f));
622         
623         MultiplyEffect *mul_two = new MultiplyEffect();
624         ASSERT_TRUE(mul_two->set_float("factor", 2.0f));
625
626         EffectChainTester tester(NULL, 2, 2);
627
628         ImageFormat format;
629         format.color_space = COLORSPACE_sRGB;
630         format.gamma_curve = GAMMA_LINEAR;
631
632         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
633         input->set_pixel_data(data);
634
635         tester.get_chain()->add_input(input);
636         tester.get_chain()->add_effect(mul_half, input);
637         tester.get_chain()->add_effect(mul_two, input);
638         tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
639         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
640
641         expect_equal(expected_data, out_data, 2, 2);
642 }