]> git.sesse.net Git - movit/blob - effect_chain_test.cpp
Fix two issues related to non-treelike (diamond) effect graphs.
[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 InvertEffect instead. Also stores the new node,
100 // so we later can check that there are gamma conversion effects
101 // on both sides.
102 class RewritingToInvertEffect : public Effect {
103 public:
104         RewritingToInvertEffect() {}
105         virtual std::string effect_type_id() const { return "RewritingToInvertEffect"; }
106         std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
107         virtual void rewrite_graph(EffectChain *graph, Node *self) {
108                 Node *invert_node = graph->add_node(new InvertEffect());
109                 graph->replace_receiver(self, invert_node);
110                 graph->replace_sender(self, invert_node);
111
112                 self->disabled = true;
113                 this->invert_node = invert_node;
114         }
115
116         Node *invert_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         RewritingToInvertEffect *effect = new RewritingToInvertEffect();
131         tester.get_chain()->add_effect(effect);
132         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
133
134         Node *node = effect->invert_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[2];
153         EffectChainTester tester(NULL, 2, 2);
154         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
155         RewritingToInvertEffect *effect = new RewritingToInvertEffect();
156         tester.get_chain()->add_effect(effect);
157         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
158
159         Node *node = effect->invert_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         RewritingToInvertEffect *effect = new RewritingToInvertEffect();
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->invert_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 // Like RewritingToInvertEffect, but splicing in a MirrorEffect instead,
248 // which does not need linear light or sRGB primaries.
249 class RewritingToMirrorEffect : public Effect {
250 public:
251         RewritingToMirrorEffect() {}
252         virtual std::string effect_type_id() const { return "RewritingToMirrorEffect"; }
253         std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
254         virtual void rewrite_graph(EffectChain *graph, Node *self) {
255                 Node *mirror_node = graph->add_node(new MirrorEffect());
256                 graph->replace_receiver(self, mirror_node);
257                 graph->replace_sender(self, mirror_node);
258
259                 self->disabled = true;
260                 this->mirror_node = mirror_node;
261         }
262
263         Node *mirror_node;
264 };
265
266 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
267         float data[] = {
268                 0.0f, 0.25f, 0.3f,
269                 0.75f, 1.0f, 1.0f,
270         };
271         float expected_data[6] = {
272                 0.3f, 0.25f, 0.0f,
273                 1.0f, 1.0f, 0.75f,
274         };
275         float out_data[6];
276         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
277         RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
278         tester.get_chain()->add_effect(effect);
279         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
280
281         Node *node = effect->mirror_node;
282         ASSERT_EQ(1, node->incoming_links.size());
283         EXPECT_EQ(0, node->outgoing_links.size());
284         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
285
286         expect_equal(expected_data, out_data, 3, 2);
287 }
288
289 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
290         float data[] = {
291                 0.0f, 0.25f, 0.3f,
292                 0.75f, 1.0f, 1.0f,
293         };
294         float expected_data[6] = {
295                 0.3f, 0.25f, 0.0f,
296                 1.0f, 1.0f, 0.75f,
297         };
298         float out_data[6];
299         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
300         RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
301         tester.get_chain()->add_effect(effect);
302         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
303
304         Node *node = effect->mirror_node;
305         ASSERT_EQ(1, node->incoming_links.size());
306         EXPECT_EQ(0, node->outgoing_links.size());
307         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
308
309         expect_equal(expected_data, out_data, 3, 2);
310 }
311
312 // The identity effect needs linear light, and thus will get conversions on both sides.
313 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
314 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
315         float data[256];
316         for (unsigned i = 0; i < 256; ++i) {
317                 data[i] = i / 255.0;
318         };
319         float out_data[256];
320         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
321         tester.get_chain()->add_effect(new IdentityEffect());
322         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
323
324         expect_equal(data, out_data, 256, 1);
325 }
326
327 // Same, but uses the forward sRGB table from the GPU.
328 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
329         unsigned char data[256];
330         float expected_data[256];
331         for (unsigned i = 0; i < 256; ++i) {
332                 data[i] = i;
333                 expected_data[i] = i / 255.0;
334         };
335         float out_data[256];
336         EffectChainTester tester(NULL, 256, 1);
337         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
338         tester.get_chain()->add_effect(new IdentityEffect());
339         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
340
341         expect_equal(expected_data, out_data, 256, 1);
342 }
343
344 // Same, for the Rec. 601/709 gamma curve.
345 TEST(EffectChainTest, IdentityThroughRec709) {
346         float data[256];
347         for (unsigned i = 0; i < 256; ++i) {
348                 data[i] = i / 255.0;
349         };
350         float out_data[256];
351         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
352         tester.get_chain()->add_effect(new IdentityEffect());
353         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
354
355         expect_equal(data, out_data, 256, 1);
356 }
357
358 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
359 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
360         const int size = 3;
361         float data[4 * size] = {
362                 0.8f, 0.0f, 0.0f, 0.5f,
363                 0.0f, 0.2f, 0.2f, 0.3f,
364                 0.1f, 0.0f, 1.0f, 1.0f,
365         };
366         float out_data[6];
367         EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
368         tester.get_chain()->add_effect(new IdentityEffect());
369         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
370
371         expect_equal(data, out_data, 4, size);
372 }
373
374 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
375         const int size = 3;
376         float data[4 * size] = {
377                 0.8f, 0.0f, 0.0f, 0.5f,
378                 0.0f, 0.2f, 0.2f, 0.3f,
379                 0.1f, 0.0f, 1.0f, 1.0f,
380         };
381         float expected_data[4 * size] = {
382                 0.1f, 0.0f, 1.0f, 1.0f,
383                 0.0f, 0.2f, 0.2f, 0.3f,
384                 0.8f, 0.0f, 0.0f, 0.5f,
385         };
386         float out_data[4 * size];
387         EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
388         RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
389         tester.get_chain()->add_effect(effect);
390         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
391
392         Node *node = effect->mirror_node;
393         ASSERT_EQ(1, node->incoming_links.size());
394         EXPECT_EQ(0, node->outgoing_links.size());
395         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
396
397         expect_equal(expected_data, out_data, 4, size);
398 }
399
400 // An input that outputs only blue, which has blank alpha.
401 class BlueInput : public Input {
402 public:
403         BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
404         virtual std::string effect_type_id() const { return "IdentityEffect"; }
405         std::string output_fragment_shader() { return read_file("blue.frag"); }
406         virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
407         virtual void finalize() {}
408         virtual bool can_output_linear_gamma() const { return true; }
409         virtual unsigned get_width() const { return 1; }
410         virtual unsigned get_height() const { return 1; }
411         virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
412         virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
413
414 private:
415         int needs_mipmaps;
416 };
417
418 // Like RewritingToInvertEffect, but splicing in a BlueInput instead,
419 // which outputs blank alpha.
420 class RewritingToBlueInput : public Input {
421 public:
422         RewritingToBlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
423         virtual std::string effect_type_id() const { return "RewritingToBlueInput"; }
424         std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
425         virtual void rewrite_graph(EffectChain *graph, Node *self) {
426                 Node *blue_node = graph->add_node(new BlueInput());
427                 graph->replace_receiver(self, blue_node);
428                 graph->replace_sender(self, blue_node);
429
430                 self->disabled = true;
431                 this->blue_node = blue_node;
432         }
433
434         // Dummy values that we need to implement because we inherit from Input.
435         // Same as BlueInput.
436         virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
437         virtual void finalize() {}
438         virtual bool can_output_linear_gamma() const { return true; }
439         virtual unsigned get_width() const { return 1; }
440         virtual unsigned get_height() const { return 1; }
441         virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
442         virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
443
444         Node *blue_node;
445
446 private:
447         int needs_mipmaps;
448 };
449
450 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
451         const int size = 3;
452         float data[4 * size] = {
453                 0.0f, 0.0f, 1.0f, 1.0f,
454                 0.0f, 0.0f, 1.0f, 1.0f,
455                 0.0f, 0.0f, 1.0f, 1.0f,
456         };
457         float out_data[4 * size];
458         EffectChainTester tester(NULL, size, 1);
459         RewritingToBlueInput *input = new RewritingToBlueInput();
460         tester.get_chain()->add_input(input);
461         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_PREMULTIPLIED);
462
463         Node *node = input->blue_node;
464         EXPECT_EQ(0, node->incoming_links.size());
465         EXPECT_EQ(0, node->outgoing_links.size());
466
467         expect_equal(data, out_data, 4, size);
468 }
469
470 // Effectively scales down its input linearly by 4x (and repeating it),
471 // which is not attainable without mipmaps.
472 class MipmapNeedingEffect : public Effect {
473 public:
474         MipmapNeedingEffect() {}
475         virtual bool needs_mipmaps() const { return true; }
476         virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
477         std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
478         void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
479         {
480                 glActiveTexture(GL_TEXTURE0);
481                 check_error();
482                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
483                 check_error();
484                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
485                 check_error();
486         }
487 };
488
489 TEST(EffectChainTest, MipmapGenerationWorks) {
490         float data[] = {  // In 4x4 blocks.
491                 1.0f, 0.0f, 0.0f, 0.0f,
492                 0.0f, 0.0f, 0.0f, 0.0f,
493                 0.0f, 0.0f, 0.0f, 0.0f,
494                 0.0f, 0.0f, 0.0f, 1.0f,
495
496                 0.0f, 0.0f, 0.0f, 0.0f,
497                 0.0f, 0.5f, 0.0f, 0.0f,
498                 0.0f, 0.0f, 1.0f, 0.0f,
499                 0.0f, 0.0f, 0.0f, 0.0f,
500
501                 1.0f, 1.0f, 1.0f, 1.0f,
502                 1.0f, 1.0f, 1.0f, 1.0f,
503                 1.0f, 1.0f, 1.0f, 1.0f,
504                 1.0f, 1.0f, 1.0f, 1.0f,
505
506                 0.0f, 0.0f, 0.0f, 0.0f,
507                 0.0f, 1.0f, 1.0f, 0.0f,
508                 0.0f, 1.0f, 1.0f, 0.0f,
509                 0.0f, 0.0f, 0.0f, 0.0f,
510         };
511         float expected_data[] = {  // Repeated four times each way.
512                 0.125f,   0.125f,   0.125f,   0.125f,
513                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
514                 1.0f,     1.0f,     1.0f,     1.0f,
515                 0.25f,    0.25f,    0.25f,    0.25f,
516
517                 0.125f,   0.125f,   0.125f,   0.125f,
518                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
519                 1.0f,     1.0f,     1.0f,     1.0f,
520                 0.25f,    0.25f,    0.25f,    0.25f,
521
522                 0.125f,   0.125f,   0.125f,   0.125f,
523                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
524                 1.0f,     1.0f,     1.0f,     1.0f,
525                 0.25f,    0.25f,    0.25f,    0.25f,
526
527                 0.125f,   0.125f,   0.125f,   0.125f,
528                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
529                 1.0f,     1.0f,     1.0f,     1.0f,
530                 0.25f,    0.25f,    0.25f,    0.25f,
531         };
532         float out_data[4 * 16];
533         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
534         tester.get_chain()->add_effect(new MipmapNeedingEffect());
535         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
536
537         expect_equal(expected_data, out_data, 4, 16);
538 }
539
540 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
541         float data[] = {  // In 4x4 blocks.
542                 1.0f, 0.0f, 0.0f, 0.0f,
543                 0.0f, 0.0f, 0.0f, 0.0f,
544                 0.0f, 0.0f, 0.0f, 0.0f,
545                 0.0f, 0.0f, 0.0f, 1.0f,
546
547                 0.0f, 0.0f, 0.0f, 0.0f,
548                 0.0f, 0.5f, 0.0f, 0.0f,
549                 0.0f, 0.0f, 1.0f, 0.0f,
550                 0.0f, 0.0f, 0.0f, 0.0f,
551
552                 1.0f, 1.0f, 1.0f, 1.0f,
553                 1.0f, 1.0f, 1.0f, 1.0f,
554                 1.0f, 1.0f, 1.0f, 1.0f,
555                 1.0f, 1.0f, 1.0f, 1.0f,
556
557                 0.0f, 0.0f, 0.0f, 0.0f,
558                 0.0f, 1.0f, 1.0f, 0.0f,
559                 0.0f, 1.0f, 1.0f, 0.0f,
560                 0.0f, 0.0f, 0.0f, 0.0f,
561         };
562         float expected_data[] = {  // Repeated four times horizontaly, interpolated vertically.
563                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
564                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
565                 0.1211f, 0.1211f, 0.1211f, 0.1211f,
566                 0.1133f, 0.1133f, 0.1133f, 0.1133f,
567                 0.1055f, 0.1055f, 0.1055f, 0.1055f,
568                 0.0977f, 0.0977f, 0.0977f, 0.0977f,
569                 0.2070f, 0.2070f, 0.2070f, 0.2070f,
570                 0.4336f, 0.4336f, 0.4336f, 0.4336f,
571                 0.6602f, 0.6602f, 0.6602f, 0.6602f,
572                 0.8867f, 0.8867f, 0.8867f, 0.8867f,
573                 0.9062f, 0.9062f, 0.9062f, 0.9062f,
574                 0.7188f, 0.7188f, 0.7188f, 0.7188f,
575                 0.5312f, 0.5312f, 0.5312f, 0.5312f,
576                 0.3438f, 0.3438f, 0.3438f, 0.3438f,
577                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
578                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
579         };
580         float out_data[4 * 16];
581
582         ResizeEffect *downscale = new ResizeEffect();
583         ASSERT_TRUE(downscale->set_int("width", 1));
584         ASSERT_TRUE(downscale->set_int("height", 4));
585
586         ResizeEffect *upscale = new ResizeEffect();
587         ASSERT_TRUE(upscale->set_int("width", 4));
588         ASSERT_TRUE(upscale->set_int("height", 16));
589
590         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
591         tester.get_chain()->add_effect(downscale);
592         tester.get_chain()->add_effect(upscale);
593         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
594
595         expect_equal(expected_data, out_data, 4, 16);
596 }
597
598 // An effect that multiplies with a constant. Used below.
599 class MultiplyEffect : public Effect {
600 public:
601         MultiplyEffect() { register_float("factor", &factor); }
602         virtual std::string effect_type_id() const { return "MultiplyEffect"; }
603         std::string output_fragment_shader() { return read_file("multiply.frag"); }
604         virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
605
606 private:
607         float factor;
608 };
609
610 // An effect that adds its two inputs together. Used below.
611 class AddEffect : public Effect {
612 public:
613         AddEffect() {}
614         virtual std::string effect_type_id() const { return "AddEffect"; }
615         std::string output_fragment_shader() { return read_file("add.frag"); }
616         virtual unsigned num_inputs() const { return 2; }
617         virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
618 };
619
620 // Constructs the graph
621 //
622 //             FlatInput               |
623 //            /         \              |
624 //  MultiplyEffect  MultiplyEffect     |
625 //            \         /              |
626 //             AddEffect               |
627 //
628 // and verifies that it gives the correct output.
629 TEST(EffectChainTest, DiamondGraph) {
630         float data[] = {
631                 1.0f, 1.0f,
632                 1.0f, 0.0f,
633         };
634         float expected_data[] = {
635                 2.5f, 2.5f,
636                 2.5f, 0.0f,
637         };
638         float out_data[2 * 2];
639
640         MultiplyEffect *mul_half = new MultiplyEffect();
641         ASSERT_TRUE(mul_half->set_float("factor", 0.5f));
642         
643         MultiplyEffect *mul_two = new MultiplyEffect();
644         ASSERT_TRUE(mul_two->set_float("factor", 2.0f));
645
646         EffectChainTester tester(NULL, 2, 2);
647
648         ImageFormat format;
649         format.color_space = COLORSPACE_sRGB;
650         format.gamma_curve = GAMMA_LINEAR;
651
652         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
653         input->set_pixel_data(data);
654
655         tester.get_chain()->add_input(input);
656         tester.get_chain()->add_effect(mul_half, input);
657         tester.get_chain()->add_effect(mul_two, input);
658         tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
659         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
660
661         expect_equal(expected_data, out_data, 2, 2);
662 }