]> git.sesse.net Git - movit/blob - effect_chain_test.cpp
Add an effect for 4:2:2 interleaved YCbCr input (UYVY).
[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 <locale>
6 #include <sstream>
7 #include <string>
8
9 #include <epoxy/gl.h>
10 #include <assert.h>
11
12 #include "effect.h"
13 #include "effect_chain.h"
14 #include "flat_input.h"
15 #include "gtest/gtest.h"
16 #include "init.h"
17 #include "input.h"
18 #include "mirror_effect.h"
19 #include "multiply_effect.h"
20 #include "resize_effect.h"
21 #include "test_util.h"
22 #include "util.h"
23
24 using namespace std;
25
26 namespace movit {
27
28 TEST(EffectChainTest, EmptyChain) {
29         float data[] = {
30                 0.0f, 0.25f, 0.3f,
31                 0.75f, 1.0f, 1.0f,
32         };
33         float out_data[6];
34         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
35         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
36
37         expect_equal(data, out_data, 3, 2);
38 }
39
40 // An effect that does nothing.
41 class IdentityEffect : public Effect {
42 public:
43         IdentityEffect() {}
44         virtual string effect_type_id() const { return "IdentityEffect"; }
45         string output_fragment_shader() { return read_file("identity.frag"); }
46 };
47
48 TEST(EffectChainTest, Identity) {
49         float data[] = {
50                 0.0f, 0.25f, 0.3f,
51                 0.75f, 1.0f, 1.0f,
52         };
53         float out_data[6];
54         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
55         tester.get_chain()->add_effect(new IdentityEffect());
56         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
57
58         expect_equal(data, out_data, 3, 2);
59 }
60
61 // An effect that does nothing, but requests texture bounce.
62 class BouncingIdentityEffect : public Effect {
63 public:
64         BouncingIdentityEffect() {}
65         virtual string effect_type_id() const { return "IdentityEffect"; }
66         string output_fragment_shader() { return read_file("identity.frag"); }
67         bool needs_texture_bounce() const { return true; }
68         AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
69 };
70
71 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
72         float data[] = {
73                 0.0f, 0.25f, 0.3f,
74                 0.75f, 1.0f, 1.0f,
75         };
76         float out_data[6];
77         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
78         tester.get_chain()->add_effect(new BouncingIdentityEffect());
79         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
80
81         expect_equal(data, out_data, 3, 2);
82 }
83
84 TEST(MirrorTest, BasicTest) {
85         float data[] = {
86                 0.0f, 0.25f, 0.3f,
87                 0.75f, 1.0f, 1.0f,
88         };
89         float expected_data[6] = {
90                 0.3f, 0.25f, 0.0f,
91                 1.0f, 1.0f, 0.75f,
92         };
93         float out_data[6];
94         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
95         tester.get_chain()->add_effect(new MirrorEffect());
96         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
97
98         expect_equal(expected_data, out_data, 3, 2);
99 }
100
101 // A dummy effect that inverts its input.
102 class InvertEffect : public Effect {
103 public:
104         InvertEffect() {}
105         virtual string effect_type_id() const { return "InvertEffect"; }
106         string output_fragment_shader() { return read_file("invert_effect.frag"); }
107
108         // A real invert would actually care about its alpha,
109         // but in this unit test, it only complicates things.
110         virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
111 };
112
113 // Like IdentityEffect, but rewrites itself out of the loop,
114 // splicing in a different effect instead. Also stores the new node,
115 // so we later can check whatever properties we'd like about the graph.
116 template<class T>
117 class RewritingEffect : public Effect {
118 public:
119         RewritingEffect() : effect(new T()), replaced_node(NULL) {}
120         virtual string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
121         string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
122         virtual void rewrite_graph(EffectChain *graph, Node *self) {
123                 replaced_node = graph->add_node(effect);
124                 graph->replace_receiver(self, replaced_node);
125                 graph->replace_sender(self, replaced_node);
126                 self->disabled = true;
127         }
128
129         T *effect;
130         Node *replaced_node;
131 };
132
133 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
134         float data[] = {
135                 0.0f, 0.25f, 0.3f,
136                 0.75f, 1.0f, 1.0f,
137         };
138         float expected_data[6] = {
139                 1.0f, 0.9771f, 0.9673f,
140                 0.7192f, 0.0f, 0.0f,
141         };
142         float out_data[6];
143         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
144         RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
145         tester.get_chain()->add_effect(effect);
146         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
147
148         Node *node = effect->replaced_node;
149         ASSERT_EQ(1, node->incoming_links.size());
150         ASSERT_EQ(1, node->outgoing_links.size());
151         EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
152         EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
153
154         expect_equal(expected_data, out_data, 3, 2);
155 }
156
157 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
158         unsigned char data[] = {
159                   0,   0,   0, 255,
160                  64,  64,  64, 255,
161                 128, 128, 128, 255,
162                 255, 255, 255, 255,
163         };
164         float expected_data[] = {
165                 1.0000f, 1.0000f, 1.0000f, 1.0000f,
166                 0.9771f, 0.9771f, 0.9771f, 1.0000f,
167                 0.8983f, 0.8983f, 0.8983f, 1.0000f,
168                 0.0000f, 0.0000f, 0.0000f, 1.0000f
169         };
170         float out_data[4 * 4];
171         EffectChainTester tester(NULL, 1, 4);
172         tester.add_input(data, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_sRGB);
173         RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
174         tester.get_chain()->add_effect(effect);
175         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
176
177         Node *node = effect->replaced_node;
178         ASSERT_EQ(1, node->incoming_links.size());
179         ASSERT_EQ(1, node->outgoing_links.size());
180         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
181         EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
182
183         expect_equal(expected_data, out_data, 4, 4);
184 }
185
186 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
187         float data[] = {
188                 0.0f, 0.25f, 0.3f,
189                 0.75f, 1.0f, 1.0f,
190         };
191         float expected_data[6] = {
192                 1.0f, 0.75f, 0.7f,
193                 0.25f, 0.0f, 0.0f,
194         };
195         float out_data[6];
196         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
197         RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
198         tester.get_chain()->add_effect(effect);
199         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
200
201         Node *node = effect->replaced_node;
202         ASSERT_EQ(1, node->incoming_links.size());
203         ASSERT_EQ(1, node->outgoing_links.size());
204         EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
205         EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
206
207         expect_equal(expected_data, out_data, 3, 2);
208 }
209
210 // A fake input that can change its output colorspace and gamma between instantiation
211 // and finalize.
212 class UnknownColorspaceInput : public FlatInput {
213 public:
214         UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
215             : FlatInput(format, pixel_format, type, width, height),
216               overridden_color_space(format.color_space),
217               overridden_gamma_curve(format.gamma_curve) {}
218         virtual string effect_type_id() const { return "UnknownColorspaceInput"; }
219
220         void set_color_space(Colorspace colorspace) {
221                 overridden_color_space = colorspace;
222         }
223         void set_gamma_curve(GammaCurve gamma_curve) {
224                 overridden_gamma_curve = gamma_curve;
225         }
226         Colorspace get_color_space() const { return overridden_color_space; }
227         GammaCurve get_gamma_curve() const { return overridden_gamma_curve; }
228
229 private:
230         Colorspace overridden_color_space;
231         GammaCurve overridden_gamma_curve;
232 };
233
234 TEST(EffectChainTest, HandlesInputChangingColorspace) {
235         const int size = 4;
236
237         float data[size] = {
238                 0.0,
239                 0.5,
240                 0.7,
241                 1.0,
242         };
243         float out_data[size];
244
245         EffectChainTester tester(NULL, 4, 1, FORMAT_GRAYSCALE);
246
247         // First say that we have sRGB, linear input.
248         ImageFormat format;
249         format.color_space = COLORSPACE_sRGB;
250         format.gamma_curve = GAMMA_LINEAR;
251
252         UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
253         input->set_pixel_data(data);
254         tester.get_chain()->add_input(input);
255
256         // Now we change to Rec. 601 input.
257         input->set_color_space(COLORSPACE_REC_601_625);
258         input->set_gamma_curve(GAMMA_REC_601);
259
260         // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
261         tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
262         expect_equal(data, out_data, 4, 1);
263 }
264
265 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
266         float data[] = {
267                 0.0f, 0.25f, 0.3f,
268                 0.75f, 1.0f, 1.0f,
269         };
270         float expected_data[6] = {
271                 0.3f, 0.25f, 0.0f,
272                 1.0f, 1.0f, 0.75f,
273         };
274         float out_data[6];
275         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
276         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
277         tester.get_chain()->add_effect(effect);
278         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
279
280         Node *node = effect->replaced_node;
281         ASSERT_EQ(1, node->incoming_links.size());
282         EXPECT_EQ(0, node->outgoing_links.size());
283         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
284
285         expect_equal(expected_data, out_data, 3, 2);
286 }
287
288 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
289         float data[] = {
290                 0.0f, 0.25f, 0.3f,
291                 0.75f, 1.0f, 1.0f,
292         };
293         float expected_data[6] = {
294                 0.3f, 0.25f, 0.0f,
295                 1.0f, 1.0f, 0.75f,
296         };
297         float out_data[6];
298         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
299         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
300         tester.get_chain()->add_effect(effect);
301         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
302
303         Node *node = effect->replaced_node;
304         ASSERT_EQ(1, node->incoming_links.size());
305         EXPECT_EQ(0, node->outgoing_links.size());
306         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
307
308         expect_equal(expected_data, out_data, 3, 2);
309 }
310
311 // The identity effect needs linear light, and thus will get conversions on both sides.
312 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
313 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
314         float data[256];
315         for (unsigned i = 0; i < 256; ++i) {
316                 data[i] = i / 255.0;
317         };
318         float out_data[256];
319         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
320         tester.get_chain()->add_effect(new IdentityEffect());
321         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
322
323         expect_equal(data, out_data, 256, 1);
324 }
325
326 // Same, but uses the forward sRGB table from the GPU.
327 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
328         unsigned char data[256];
329         float expected_data[256];
330         for (unsigned i = 0; i < 256; ++i) {
331                 data[i] = i;
332                 expected_data[i] = i / 255.0;
333         };
334         float out_data[256];
335         EffectChainTester tester(NULL, 256, 1);
336         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
337         tester.get_chain()->add_effect(new IdentityEffect());
338         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
339
340         expect_equal(expected_data, out_data, 256, 1);
341 }
342
343 // Same, for the Rec. 601/709 gamma curve.
344 TEST(EffectChainTest, IdentityThroughRec709) {
345         float data[256];
346         for (unsigned i = 0; i < 256; ++i) {
347                 data[i] = i / 255.0;
348         };
349         float out_data[256];
350         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
351         tester.get_chain()->add_effect(new IdentityEffect());
352         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
353
354         expect_equal(data, out_data, 256, 1);
355 }
356
357 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
358 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
359         const int size = 3;
360         float data[4 * size] = {
361                 0.8f, 0.0f, 0.0f, 0.5f,
362                 0.0f, 0.2f, 0.2f, 0.3f,
363                 0.1f, 0.0f, 1.0f, 1.0f,
364         };
365         float out_data[4 * size];
366         EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
367         tester.get_chain()->add_effect(new IdentityEffect());
368         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
369
370         expect_equal(data, out_data, 4, size);
371 }
372
373 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
374         const int size = 3;
375         float data[4 * size] = {
376                 0.8f, 0.0f, 0.0f, 0.5f,
377                 0.0f, 0.2f, 0.2f, 0.3f,
378                 0.1f, 0.0f, 1.0f, 1.0f,
379         };
380         float expected_data[4 * size] = {
381                 0.1f, 0.0f, 1.0f, 1.0f,
382                 0.0f, 0.2f, 0.2f, 0.3f,
383                 0.8f, 0.0f, 0.0f, 0.5f,
384         };
385         float out_data[4 * size];
386         EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
387         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
388         tester.get_chain()->add_effect(effect);
389         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
390
391         Node *node = effect->replaced_node;
392         ASSERT_EQ(1, node->incoming_links.size());
393         EXPECT_EQ(0, node->outgoing_links.size());
394         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
395
396         expect_equal(expected_data, out_data, 4, size);
397 }
398
399 // An input that outputs only blue, which has blank alpha.
400 class BlueInput : public Input {
401 public:
402         BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
403         virtual string effect_type_id() const { return "IdentityEffect"; }
404         string output_fragment_shader() { return read_file("blue.frag"); }
405         virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
406         virtual void finalize() {}
407         virtual bool can_output_linear_gamma() const { return true; }
408         virtual unsigned get_width() const { return 1; }
409         virtual unsigned get_height() const { return 1; }
410         virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
411         virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
412
413 private:
414         int needs_mipmaps;
415 };
416
417 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
418 // which outputs blank alpha.
419 class RewritingToBlueInput : public Input {
420 public:
421         RewritingToBlueInput() : blue_node(NULL) { register_int("needs_mipmaps", &needs_mipmaps); }
422         virtual string effect_type_id() const { return "RewritingToBlueInput"; }
423         string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
424         virtual void rewrite_graph(EffectChain *graph, Node *self) {
425                 Node *blue_node = graph->add_node(new BlueInput());
426                 graph->replace_receiver(self, blue_node);
427                 graph->replace_sender(self, blue_node);
428
429                 self->disabled = true;
430                 this->blue_node = blue_node;
431         }
432
433         // Dummy values that we need to implement because we inherit from Input.
434         // Same as BlueInput.
435         virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
436         virtual void finalize() {}
437         virtual bool can_output_linear_gamma() const { return true; }
438         virtual unsigned get_width() const { return 1; }
439         virtual unsigned get_height() const { return 1; }
440         virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
441         virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
442
443         Node *blue_node;
444
445 private:
446         int needs_mipmaps;
447 };
448
449 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
450         const int size = 3;
451         float data[4 * size] = {
452                 0.0f, 0.0f, 1.0f, 1.0f,
453                 0.0f, 0.0f, 1.0f, 1.0f,
454                 0.0f, 0.0f, 1.0f, 1.0f,
455         };
456         float out_data[4 * size];
457         EffectChainTester tester(NULL, size, 1);
458         RewritingToBlueInput *input = new RewritingToBlueInput();
459         tester.get_chain()->add_input(input);
460         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
461
462         Node *node = input->blue_node;
463         EXPECT_EQ(0, node->incoming_links.size());
464         EXPECT_EQ(0, node->outgoing_links.size());
465
466         expect_equal(data, out_data, 4, size);
467 }
468
469 // An effect that does nothing, and specifies that it preserves blank alpha.
470 class BlankAlphaPreservingEffect : public Effect {
471 public:
472         BlankAlphaPreservingEffect() {}
473         virtual string effect_type_id() const { return "BlankAlphaPreservingEffect"; }
474         string output_fragment_shader() { return read_file("identity.frag"); }
475         virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
476 };
477
478 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
479         const int size = 3;
480         float data[4 * size] = {
481                 0.0f, 0.0f, 1.0f, 1.0f,
482                 0.0f, 0.0f, 1.0f, 1.0f,
483                 0.0f, 0.0f, 1.0f, 1.0f,
484         };
485         float out_data[4 * size];
486         EffectChainTester tester(NULL, size, 1);
487         tester.get_chain()->add_input(new BlueInput());
488         tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
489         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
490         tester.get_chain()->add_effect(effect);
491         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
492
493         Node *node = effect->replaced_node;
494         EXPECT_EQ(1, node->incoming_links.size());
495         EXPECT_EQ(0, node->outgoing_links.size());
496
497         expect_equal(data, out_data, 4, size);
498 }
499
500 // This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
501 // just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
502 // an alpha conversion _should_ be inserted at the very end. (There is some overlap
503 // with other tests.)
504 TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
505         const int size = 3;
506         float data[4 * size] = {
507                 0.0f, 0.0f, 1.0f, 1.0f,
508                 0.0f, 0.0f, 1.0f, 1.0f,
509                 0.0f, 0.0f, 1.0f, 1.0f,
510         };
511         float out_data[4 * size];
512         EffectChainTester tester(NULL, size, 1);
513         tester.get_chain()->add_input(new BlueInput());
514         tester.get_chain()->add_effect(new IdentityEffect());  // Not BlankAlphaPreservingEffect.
515         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
516         tester.get_chain()->add_effect(effect);
517         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
518
519         Node *node = effect->replaced_node;
520         EXPECT_EQ(1, node->incoming_links.size());
521         EXPECT_EQ(1, node->outgoing_links.size());
522         EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
523
524         expect_equal(data, out_data, 4, size);
525 }
526
527 // Effectively scales down its input linearly by 4x (and repeating it),
528 // which is not attainable without mipmaps.
529 class MipmapNeedingEffect : public Effect {
530 public:
531         MipmapNeedingEffect() {}
532         virtual bool needs_mipmaps() const { return true; }
533
534         // To be allowed to mess with the sampler state.
535         virtual bool needs_texture_bounce() const { return true; }
536
537         virtual string effect_type_id() const { return "MipmapNeedingEffect"; }
538         string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
539         virtual void inform_added(EffectChain *chain) { this->chain = chain; }
540
541         void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
542         {
543                 Node *self = chain->find_node_for_effect(this);
544                 glActiveTexture(chain->get_input_sampler(self, 0));
545                 check_error();
546                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
547                 check_error();
548                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
549                 check_error();
550         }
551
552 private:
553         EffectChain *chain;
554 };
555
556 TEST(EffectChainTest, MipmapGenerationWorks) {
557         float data[] = {  // In 4x4 blocks.
558                 1.0f, 0.0f, 0.0f, 0.0f,
559                 0.0f, 0.0f, 0.0f, 0.0f,
560                 0.0f, 0.0f, 0.0f, 0.0f,
561                 0.0f, 0.0f, 0.0f, 1.0f,
562
563                 0.0f, 0.0f, 0.0f, 0.0f,
564                 0.0f, 0.5f, 0.0f, 0.0f,
565                 0.0f, 0.0f, 1.0f, 0.0f,
566                 0.0f, 0.0f, 0.0f, 0.0f,
567
568                 1.0f, 1.0f, 1.0f, 1.0f,
569                 1.0f, 1.0f, 1.0f, 1.0f,
570                 1.0f, 1.0f, 1.0f, 1.0f,
571                 1.0f, 1.0f, 1.0f, 1.0f,
572
573                 0.0f, 0.0f, 0.0f, 0.0f,
574                 0.0f, 1.0f, 1.0f, 0.0f,
575                 0.0f, 1.0f, 1.0f, 0.0f,
576                 0.0f, 0.0f, 0.0f, 0.0f,
577         };
578         float expected_data[] = {  // Repeated four times each way.
579                 0.125f,   0.125f,   0.125f,   0.125f,
580                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
581                 1.0f,     1.0f,     1.0f,     1.0f,
582                 0.25f,    0.25f,    0.25f,    0.25f,
583
584                 0.125f,   0.125f,   0.125f,   0.125f,
585                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
586                 1.0f,     1.0f,     1.0f,     1.0f,
587                 0.25f,    0.25f,    0.25f,    0.25f,
588
589                 0.125f,   0.125f,   0.125f,   0.125f,
590                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
591                 1.0f,     1.0f,     1.0f,     1.0f,
592                 0.25f,    0.25f,    0.25f,    0.25f,
593
594                 0.125f,   0.125f,   0.125f,   0.125f,
595                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
596                 1.0f,     1.0f,     1.0f,     1.0f,
597                 0.25f,    0.25f,    0.25f,    0.25f,
598         };
599         float out_data[4 * 16];
600         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
601         tester.get_chain()->add_effect(new MipmapNeedingEffect());
602         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
603
604         expect_equal(expected_data, out_data, 4, 16);
605 }
606
607 class NonMipmapCapableInput : public FlatInput {
608 public:
609         NonMipmapCapableInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
610                 : FlatInput(format, pixel_format, type, width, height) {}
611
612         virtual bool can_supply_mipmaps() const { return false; }
613         bool set_int(const std::string& key, int value) {
614                 if (key == "needs_mipmaps") {
615                         assert(value == 0);
616                 }
617                 return FlatInput::set_int(key, value);
618         }
619 };
620
621 // The same test as MipmapGenerationWorks, but with an input that refuses
622 // to supply mipmaps.
623 TEST(EffectChainTest, MipmapsWithNonMipmapCapableInput) {
624         float data[] = {  // In 4x4 blocks.
625                 1.0f, 0.0f, 0.0f, 0.0f,
626                 0.0f, 0.0f, 0.0f, 0.0f,
627                 0.0f, 0.0f, 0.0f, 0.0f,
628                 0.0f, 0.0f, 0.0f, 1.0f,
629
630                 0.0f, 0.0f, 0.0f, 0.0f,
631                 0.0f, 0.5f, 0.0f, 0.0f,
632                 0.0f, 0.0f, 1.0f, 0.0f,
633                 0.0f, 0.0f, 0.0f, 0.0f,
634
635                 1.0f, 1.0f, 1.0f, 1.0f,
636                 1.0f, 1.0f, 1.0f, 1.0f,
637                 1.0f, 1.0f, 1.0f, 1.0f,
638                 1.0f, 1.0f, 1.0f, 1.0f,
639
640                 0.0f, 0.0f, 0.0f, 0.0f,
641                 0.0f, 1.0f, 1.0f, 0.0f,
642                 0.0f, 1.0f, 1.0f, 0.0f,
643                 0.0f, 0.0f, 0.0f, 0.0f,
644         };
645         float expected_data[] = {  // Repeated four times each way.
646                 0.125f,   0.125f,   0.125f,   0.125f,
647                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
648                 1.0f,     1.0f,     1.0f,     1.0f,
649                 0.25f,    0.25f,    0.25f,    0.25f,
650
651                 0.125f,   0.125f,   0.125f,   0.125f,
652                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
653                 1.0f,     1.0f,     1.0f,     1.0f,
654                 0.25f,    0.25f,    0.25f,    0.25f,
655
656                 0.125f,   0.125f,   0.125f,   0.125f,
657                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
658                 1.0f,     1.0f,     1.0f,     1.0f,
659                 0.25f,    0.25f,    0.25f,    0.25f,
660
661                 0.125f,   0.125f,   0.125f,   0.125f,
662                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
663                 1.0f,     1.0f,     1.0f,     1.0f,
664                 0.25f,    0.25f,    0.25f,    0.25f,
665         };
666         float out_data[4 * 16];
667         EffectChainTester tester(NULL, 4, 16, FORMAT_GRAYSCALE);
668
669         ImageFormat format;
670         format.color_space = COLORSPACE_sRGB;
671         format.gamma_curve = GAMMA_LINEAR;
672
673         NonMipmapCapableInput *input = new NonMipmapCapableInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 16);
674         input->set_pixel_data(data);
675         tester.get_chain()->add_input(input);
676         tester.get_chain()->add_effect(new MipmapNeedingEffect());
677         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
678
679         expect_equal(expected_data, out_data, 4, 16);
680 }
681
682 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
683         float data[] = {  // In 4x4 blocks.
684                 1.0f, 0.0f, 0.0f, 0.0f,
685                 0.0f, 0.0f, 0.0f, 0.0f,
686                 0.0f, 0.0f, 0.0f, 0.0f,
687                 0.0f, 0.0f, 0.0f, 1.0f,
688
689                 0.0f, 0.0f, 0.0f, 0.0f,
690                 0.0f, 0.5f, 0.0f, 0.0f,
691                 0.0f, 0.0f, 1.0f, 0.0f,
692                 0.0f, 0.0f, 0.0f, 0.0f,
693
694                 1.0f, 1.0f, 1.0f, 1.0f,
695                 1.0f, 1.0f, 1.0f, 1.0f,
696                 1.0f, 1.0f, 1.0f, 1.0f,
697                 1.0f, 1.0f, 1.0f, 1.0f,
698
699                 0.0f, 0.0f, 0.0f, 0.0f,
700                 0.0f, 1.0f, 1.0f, 0.0f,
701                 0.0f, 1.0f, 1.0f, 0.0f,
702                 0.0f, 0.0f, 0.0f, 0.0f,
703         };
704         float expected_data[] = {  // Repeated four times horizontaly, interpolated vertically.
705                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
706                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
707                 0.1211f, 0.1211f, 0.1211f, 0.1211f,
708                 0.1133f, 0.1133f, 0.1133f, 0.1133f,
709                 0.1055f, 0.1055f, 0.1055f, 0.1055f,
710                 0.0977f, 0.0977f, 0.0977f, 0.0977f,
711                 0.2070f, 0.2070f, 0.2070f, 0.2070f,
712                 0.4336f, 0.4336f, 0.4336f, 0.4336f,
713                 0.6602f, 0.6602f, 0.6602f, 0.6602f,
714                 0.8867f, 0.8867f, 0.8867f, 0.8867f,
715                 0.9062f, 0.9062f, 0.9062f, 0.9062f,
716                 0.7188f, 0.7188f, 0.7188f, 0.7188f,
717                 0.5312f, 0.5312f, 0.5312f, 0.5312f,
718                 0.3438f, 0.3438f, 0.3438f, 0.3438f,
719                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
720                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
721         };
722         float out_data[4 * 16];
723
724         ResizeEffect *downscale = new ResizeEffect();
725         ASSERT_TRUE(downscale->set_int("width", 1));
726         ASSERT_TRUE(downscale->set_int("height", 4));
727
728         ResizeEffect *upscale = new ResizeEffect();
729         ASSERT_TRUE(upscale->set_int("width", 4));
730         ASSERT_TRUE(upscale->set_int("height", 16));
731
732         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
733         tester.get_chain()->add_effect(downscale);
734         tester.get_chain()->add_effect(upscale);
735         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
736
737         expect_equal(expected_data, out_data, 4, 16);
738 }
739
740 // An effect that adds its two inputs together. Used below.
741 class AddEffect : public Effect {
742 public:
743         AddEffect() {}
744         virtual string effect_type_id() const { return "AddEffect"; }
745         string output_fragment_shader() { return read_file("add.frag"); }
746         virtual unsigned num_inputs() const { return 2; }
747         virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
748 };
749
750 // Constructs the graph
751 //
752 //             FlatInput               |
753 //            /         \              |
754 //  MultiplyEffect  MultiplyEffect     |
755 //            \         /              |
756 //             AddEffect               |
757 //
758 // and verifies that it gives the correct output.
759 TEST(EffectChainTest, DiamondGraph) {
760         float data[] = {
761                 1.0f, 1.0f,
762                 1.0f, 0.0f,
763         };
764         float expected_data[] = {
765                 2.5f, 2.5f,
766                 2.5f, 0.0f,
767         };
768         float out_data[2 * 2];
769
770         const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
771         const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
772
773         MultiplyEffect *mul_half = new MultiplyEffect();
774         ASSERT_TRUE(mul_half->set_vec4("factor", half));
775         
776         MultiplyEffect *mul_two = new MultiplyEffect();
777         ASSERT_TRUE(mul_two->set_vec4("factor", two));
778
779         EffectChainTester tester(NULL, 2, 2);
780
781         ImageFormat format;
782         format.color_space = COLORSPACE_sRGB;
783         format.gamma_curve = GAMMA_LINEAR;
784
785         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
786         input->set_pixel_data(data);
787
788         tester.get_chain()->add_input(input);
789         tester.get_chain()->add_effect(mul_half, input);
790         tester.get_chain()->add_effect(mul_two, input);
791         tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
792         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
793
794         expect_equal(expected_data, out_data, 2, 2);
795 }
796
797 // Constructs the graph
798 //
799 //             FlatInput                     |
800 //            /         \                    |
801 //  MultiplyEffect  MultiplyEffect           |
802 //         \             |                   |
803 //          \    BouncingIdentityEffect      |  
804 //            \         /                    |
805 //             AddEffect                     |
806 //
807 // and verifies that it gives the correct output.
808 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
809         float data[] = {
810                 1.0f, 1.0f,
811                 1.0f, 0.0f,
812         };
813         float expected_data[] = {
814                 2.5f, 2.5f,
815                 2.5f, 0.0f,
816         };
817         float out_data[2 * 2];
818
819         const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
820         const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
821
822         MultiplyEffect *mul_half = new MultiplyEffect();
823         ASSERT_TRUE(mul_half->set_vec4("factor", half));
824         
825         MultiplyEffect *mul_two = new MultiplyEffect();
826         ASSERT_TRUE(mul_two->set_vec4("factor", two));
827         
828         BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
829
830         EffectChainTester tester(NULL, 2, 2);
831
832         ImageFormat format;
833         format.color_space = COLORSPACE_sRGB;
834         format.gamma_curve = GAMMA_LINEAR;
835
836         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
837         input->set_pixel_data(data);
838
839         tester.get_chain()->add_input(input);
840         tester.get_chain()->add_effect(mul_half, input);
841         tester.get_chain()->add_effect(mul_two, input);
842         tester.get_chain()->add_effect(bounce, mul_two);
843         tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
844         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
845
846         expect_equal(expected_data, out_data, 2, 2);
847 }
848
849 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
850         float data[] = {
851                 0.735f, 0.0f,
852                 0.735f, 0.0f,
853         };
854         float expected_data[] = {
855                 0.0f, 0.5f,  // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
856                 0.0f, 0.5f,
857         };
858         float out_data[2 * 2];
859         
860         EffectChainTester tester(NULL, 2, 2);
861         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
862
863         // MirrorEffect does not get linear light, so the conversions will be
864         // inserted after it, not before.
865         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
866         tester.get_chain()->add_effect(effect);
867
868         Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
869         Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
870         tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
871         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
872
873         expect_equal(expected_data, out_data, 2, 2);
874
875         Node *node = effect->replaced_node;
876         ASSERT_EQ(1, node->incoming_links.size());
877         ASSERT_EQ(1, node->outgoing_links.size());
878         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
879         EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
880 }
881
882 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
883         float data[] = {
884                 0.5f, 0.0f,
885                 0.5f, 0.0f,
886         };
887         float expected_data[] = {
888                 0.0f, 0.5f,  // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
889                 0.0f, 0.5f,
890         };
891         float out_data[2 * 2];
892         
893         EffectChainTester tester(NULL, 2, 2);
894         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
895
896         // MirrorEffect does not get linear light, so the conversions will be
897         // inserted after it, not before.
898         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
899         tester.get_chain()->add_effect(effect);
900
901         Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
902         Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
903         tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
904         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
905
906         expect_equal(expected_data, out_data, 2, 2);
907
908         Node *node = effect->replaced_node;
909         ASSERT_EQ(1, node->incoming_links.size());
910         ASSERT_EQ(1, node->outgoing_links.size());
911         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
912         EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
913 }
914
915 // An effect that does nothing, but requests texture bounce and stores
916 // its input size.
917 class SizeStoringEffect : public BouncingIdentityEffect {
918 public:
919         SizeStoringEffect() : input_width(-1), input_height(-1) {}
920         virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) {
921                 assert(input_num == 0);
922                 input_width = width;
923                 input_height = height;
924         }
925         virtual string effect_type_id() const { return "SizeStoringEffect"; }
926
927         int input_width, input_height;
928 };
929
930 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
931         float data[2 * 2] = {
932                 0.0f, 0.0f,
933                 0.0f, 0.0f,
934         };
935         float out_data[2 * 2];
936         
937         EffectChainTester tester(NULL, 4, 3);  // Note non-square aspect.
938
939         ImageFormat format;
940         format.color_space = COLORSPACE_sRGB;
941         format.gamma_curve = GAMMA_LINEAR;
942
943         FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
944         input1->set_pixel_data(data);
945         
946         FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
947         input2->set_pixel_data(data);
948
949         SizeStoringEffect *input_store = new SizeStoringEffect();
950
951         tester.get_chain()->add_input(input1);
952         tester.get_chain()->add_input(input2);
953         tester.get_chain()->add_effect(new AddEffect(), input1, input2);
954         tester.get_chain()->add_effect(input_store);
955         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
956
957         EXPECT_EQ(2, input_store->input_width);
958         EXPECT_EQ(2, input_store->input_height);
959 }
960
961 TEST(EffectChainTest, AspectRatioConversion) {
962         float data1[4 * 3] = {
963                 0.0f, 0.0f, 0.0f, 0.0f,
964                 0.0f, 0.0f, 0.0f, 0.0f,
965                 0.0f, 0.0f, 0.0f, 0.0f,
966         };
967         float data2[7 * 7] = {
968                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
969                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
970                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
971                 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
972                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
973                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
974                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
975         };
976
977         // The right conversion here is that the 7x7 image decides the size,
978         // since it is the biggest, so everything is scaled up to 9x7
979         // (keep the height, round the width 9.333 to 9). 
980         float out_data[9 * 7];
981         
982         EffectChainTester tester(NULL, 4, 3);
983
984         ImageFormat format;
985         format.color_space = COLORSPACE_sRGB;
986         format.gamma_curve = GAMMA_LINEAR;
987
988         FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
989         input1->set_pixel_data(data1);
990         
991         FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
992         input2->set_pixel_data(data2);
993
994         SizeStoringEffect *input_store = new SizeStoringEffect();
995
996         tester.get_chain()->add_input(input1);
997         tester.get_chain()->add_input(input2);
998         tester.get_chain()->add_effect(new AddEffect(), input1, input2);
999         tester.get_chain()->add_effect(input_store);
1000         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1001
1002         EXPECT_EQ(9, input_store->input_width);
1003         EXPECT_EQ(7, input_store->input_height);
1004 }
1005
1006 // An effect that does nothing except changing its output sizes.
1007 class VirtualResizeEffect : public Effect {
1008 public:
1009         VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
1010                 : width(width),
1011                   height(height),
1012                   virtual_width(virtual_width),
1013                   virtual_height(virtual_height) {}
1014         virtual string effect_type_id() const { return "VirtualResizeEffect"; }
1015         string output_fragment_shader() { return read_file("identity.frag"); }
1016
1017         virtual bool changes_output_size() const { return true; }
1018
1019         virtual void get_output_size(unsigned *width, unsigned *height,
1020                                      unsigned *virtual_width, unsigned *virtual_height) const {
1021                 *width = this->width;
1022                 *height = this->height;
1023                 *virtual_width = this->virtual_width;
1024                 *virtual_height = this->virtual_height;
1025         }
1026
1027 private:
1028         int width, height, virtual_width, virtual_height;
1029 };
1030
1031 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
1032         const int size = 2, bigger_size = 3;
1033         float data[size * size] = {
1034                 1.0f, 0.0f,
1035                 0.0f, 1.0f,
1036         };
1037         float out_data[size * size];
1038         
1039         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1040
1041         SizeStoringEffect *size_store = new SizeStoringEffect();
1042
1043         tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
1044         tester.get_chain()->add_effect(size_store);
1045         tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
1046         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1047
1048         EXPECT_EQ(bigger_size, size_store->input_width);
1049         EXPECT_EQ(bigger_size, size_store->input_height);
1050
1051         // If the resize is implemented as non-virtual, we'll fail here,
1052         // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
1053         expect_equal(data, out_data, size, size);
1054 }
1055
1056 // Does not use EffectChainTest, so that it can construct an EffectChain without
1057 // a shared ResourcePool (which is also properly destroyed afterwards).
1058 // Also turns on debugging to test that code path.
1059 TEST(EffectChainTest, IdentityWithOwnPool) {
1060         const int width = 3, height = 2;
1061         float data[] = {
1062                 0.0f, 0.25f, 0.3f,
1063                 0.75f, 1.0f, 1.0f,
1064         };
1065         const float expected_data[] = {
1066                 0.75f, 1.0f, 1.0f,
1067                 0.0f, 0.25f, 0.3f,
1068         };
1069         float out_data[6], temp[6 * 4];
1070
1071         EffectChain chain(width, height);
1072         movit_debug_level = MOVIT_DEBUG_ON;
1073
1074         ImageFormat format;
1075         format.color_space = COLORSPACE_sRGB;
1076         format.gamma_curve = GAMMA_LINEAR;
1077
1078         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
1079         input->set_pixel_data(data);
1080         chain.add_input(input);
1081         chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1082
1083         GLuint texnum, fbo;
1084         glGenTextures(1, &texnum);
1085         check_error();
1086         glBindTexture(GL_TEXTURE_2D, texnum);
1087         check_error();
1088         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
1089         check_error();
1090
1091         glGenFramebuffers(1, &fbo);
1092         check_error();
1093         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1094         check_error();
1095         glFramebufferTexture2D(
1096                 GL_FRAMEBUFFER,
1097                 GL_COLOR_ATTACHMENT0,
1098                 GL_TEXTURE_2D,
1099                 texnum,
1100                 0);
1101         check_error();
1102         glBindFramebuffer(GL_FRAMEBUFFER, 0);
1103         check_error();
1104
1105         chain.finalize();
1106
1107         chain.render_to_fbo(fbo, width, height);
1108
1109         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1110         check_error();
1111         glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp);
1112         check_error();
1113         for (unsigned i = 0; i < 6; ++i) {
1114                 out_data[i] = temp[i * 4];
1115         }
1116
1117         expect_equal(expected_data, out_data, width, height);
1118
1119         // Reset the debug status again.
1120         movit_debug_level = MOVIT_DEBUG_OFF;
1121 }
1122
1123 // A dummy effect whose only purpose is to test sprintf decimal behavior.
1124 class PrintfingBlueEffect : public Effect {
1125 public:
1126         PrintfingBlueEffect() {}
1127         virtual string effect_type_id() const { return "PrintfingBlueEffect"; }
1128         string output_fragment_shader() {
1129                 stringstream ss;
1130                 ss.imbue(locale("C"));
1131                 ss.precision(8);
1132                 ss << "vec4 FUNCNAME(vec2 tc) { return vec4("
1133                    << 0.0f << ", " << 0.0f << ", "
1134                    << 0.5f << ", " << 1.0f << "); }\n";
1135                 return ss.str();
1136         }
1137 };
1138
1139 TEST(EffectChainTest, StringStreamLocalesWork) {
1140         // An example of a locale with comma instead of period as decimal separator.
1141         // Obviously, if you run on a machine without this locale available,
1142         // the test will always succeed. Note that the OpenGL driver might call
1143         // setlocale() behind-the-scenes, and that might corrupt the returned
1144         // pointer, so we need to take our own copy of it here.
1145         char *saved_locale = strdup(setlocale(LC_ALL, "nb_NO.UTF_8"));
1146         float data[] = {
1147                 0.0f, 0.0f, 0.0f, 0.0f,
1148         };
1149         float expected_data[] = {
1150                 0.0f, 0.0f, 0.5f, 1.0f,
1151         };
1152         float out_data[4];
1153         EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1154         tester.get_chain()->add_effect(new PrintfingBlueEffect());
1155         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1156
1157         expect_equal(expected_data, out_data, 4, 1);
1158
1159         setlocale(LC_ALL, saved_locale);
1160         free(saved_locale);
1161 }
1162
1163
1164 }  // namespace movit