]> git.sesse.net Git - movit/blob - effect_chain_test.cpp
92fe52b1ecfcd0974ecf5e252f0de6b538b5beaf
[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 "resource_pool.h"
22 #include "test_util.h"
23 #include "util.h"
24
25 using namespace std;
26
27 namespace movit {
28
29 TEST(EffectChainTest, EmptyChain) {
30         float data[] = {
31                 0.0f, 0.25f, 0.3f,
32                 0.75f, 1.0f, 1.0f,
33         };
34         float out_data[6];
35         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
36         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
37
38         expect_equal(data, out_data, 3, 2);
39 }
40
41 // An effect that does nothing.
42 class IdentityEffect : public Effect {
43 public:
44         IdentityEffect() {}
45         string effect_type_id() const override { return "IdentityEffect"; }
46         string output_fragment_shader() override { return read_file("identity.frag"); }
47 };
48
49 TEST(EffectChainTest, Identity) {
50         float data[] = {
51                 0.0f, 0.25f, 0.3f,
52                 0.75f, 1.0f, 1.0f,
53         };
54         float out_data[6];
55         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
56         tester.get_chain()->add_effect(new IdentityEffect());
57         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
58
59         expect_equal(data, out_data, 3, 2);
60 }
61
62 // An effect that does nothing, but requests texture bounce.
63 class BouncingIdentityEffect : public Effect {
64 public:
65         BouncingIdentityEffect() {}
66         string effect_type_id() const override { return "IdentityEffect"; }
67         string output_fragment_shader() override { return read_file("identity.frag"); }
68         bool needs_texture_bounce() const override { return true; }
69         AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
70 };
71
72 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
73         float data[] = {
74                 0.0f, 0.25f, 0.3f,
75                 0.75f, 1.0f, 1.0f,
76         };
77         float out_data[6];
78         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
79         tester.get_chain()->add_effect(new BouncingIdentityEffect());
80         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
81
82         expect_equal(data, out_data, 3, 2);
83 }
84
85 TEST(MirrorTest, BasicTest) {
86         float data[] = {
87                 0.0f, 0.25f, 0.3f,
88                 0.75f, 1.0f, 1.0f,
89         };
90         float expected_data[6] = {
91                 0.3f, 0.25f, 0.0f,
92                 1.0f, 1.0f, 0.75f,
93         };
94         float out_data[6];
95         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
96         tester.get_chain()->add_effect(new MirrorEffect());
97         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
98
99         expect_equal(expected_data, out_data, 3, 2);
100 }
101
102 TEST(EffectChainTest, TopLeftOrigin) {
103         float data[] = {
104                 0.0f, 0.25f, 0.3f,
105                 0.75f, 1.0f, 1.0f,
106         };
107         // Note that EffectChainTester assumes bottom-left origin, so by setting
108         // top-left, we will get flipped data back.
109         float expected_data[6] = {
110                 0.75f, 1.0f, 1.0f,
111                 0.0f, 0.25f, 0.3f,
112         };
113         float out_data[6];
114         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
115         tester.get_chain()->set_output_origin(OUTPUT_ORIGIN_TOP_LEFT);
116         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
117
118         expect_equal(expected_data, out_data, 3, 2);
119 }
120
121 // A dummy effect that inverts its input.
122 class InvertEffect : public Effect {
123 public:
124         InvertEffect() {}
125         string effect_type_id() const override { return "InvertEffect"; }
126         string output_fragment_shader() override { return read_file("invert_effect.frag"); }
127
128         // A real invert would actually care about its alpha,
129         // but in this unit test, it only complicates things.
130         AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
131 };
132
133 // Like IdentityEffect, but rewrites itself out of the loop,
134 // splicing in a different effect instead. Also stores the new node,
135 // so we later can check whatever properties we'd like about the graph.
136 template<class T>
137 class RewritingEffect : public Effect {
138 public:
139         RewritingEffect() : effect(new T()), replaced_node(nullptr) {}
140         string effect_type_id() const override { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
141         string output_fragment_shader() override { EXPECT_TRUE(false); return read_file("identity.frag"); }
142         void rewrite_graph(EffectChain *graph, Node *self) override {
143                 replaced_node = graph->add_node(effect);
144                 graph->replace_receiver(self, replaced_node);
145                 graph->replace_sender(self, replaced_node);
146                 self->disabled = true;
147         }
148
149         T *effect;
150         Node *replaced_node;
151 };
152
153 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
154         float data[] = {
155                 0.0f, 0.25f, 0.3f,
156                 0.75f, 1.0f, 1.0f,
157         };
158         float expected_data[6] = {
159                 1.0f, 0.9771f, 0.9673f,
160                 0.7192f, 0.0f, 0.0f,
161         };
162         float out_data[6];
163         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
164         RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
165         tester.get_chain()->add_effect(effect);
166         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
167
168         Node *node = effect->replaced_node;
169         ASSERT_EQ(1, node->incoming_links.size());
170         ASSERT_EQ(1, node->outgoing_links.size());
171         EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
172         EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
173
174         expect_equal(expected_data, out_data, 3, 2);
175 }
176
177 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
178         unsigned char data[] = {
179                   0,   0,   0, 255,
180                  64,  64,  64, 255,
181                 128, 128, 128, 255,
182                 255, 255, 255, 255,
183         };
184         float expected_data[] = {
185                 1.0000f, 1.0000f, 1.0000f, 1.0000f,
186                 0.9771f, 0.9771f, 0.9771f, 1.0000f,
187                 0.8983f, 0.8983f, 0.8983f, 1.0000f,
188                 0.0000f, 0.0000f, 0.0000f, 1.0000f
189         };
190         float out_data[4 * 4];
191         EffectChainTester tester(nullptr, 1, 4);
192         tester.add_input(data, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_sRGB);
193         RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
194         tester.get_chain()->add_effect(effect);
195         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
196
197         Node *node = effect->replaced_node;
198         ASSERT_EQ(1, node->incoming_links.size());
199         ASSERT_EQ(1, node->outgoing_links.size());
200         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
201         EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
202
203         expect_equal(expected_data, out_data, 4, 4);
204 }
205
206 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
207         float data[] = {
208                 0.0f, 0.25f, 0.3f,
209                 0.75f, 1.0f, 1.0f,
210         };
211         float expected_data[6] = {
212                 1.0f, 0.75f, 0.7f,
213                 0.25f, 0.0f, 0.0f,
214         };
215         float out_data[6];
216         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
217         RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
218         tester.get_chain()->add_effect(effect);
219         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
220
221         Node *node = effect->replaced_node;
222         ASSERT_EQ(1, node->incoming_links.size());
223         ASSERT_EQ(1, node->outgoing_links.size());
224         EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
225         EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
226
227         expect_equal(expected_data, out_data, 3, 2);
228 }
229
230 // A fake input that can change its output colorspace and gamma between instantiation
231 // and finalize.
232 class UnknownColorspaceInput : public FlatInput {
233 public:
234         UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
235             : FlatInput(format, pixel_format, type, width, height),
236               overridden_color_space(format.color_space),
237               overridden_gamma_curve(format.gamma_curve) {}
238         string effect_type_id() const override { return "UnknownColorspaceInput"; }
239
240         void set_color_space(Colorspace colorspace) {
241                 overridden_color_space = colorspace;
242         }
243         void set_gamma_curve(GammaCurve gamma_curve) {
244                 overridden_gamma_curve = gamma_curve;
245         }
246         Colorspace get_color_space() const override { return overridden_color_space; }
247         GammaCurve get_gamma_curve() const override { return overridden_gamma_curve; }
248
249 private:
250         Colorspace overridden_color_space;
251         GammaCurve overridden_gamma_curve;
252 };
253
254 TEST(EffectChainTest, HandlesInputChangingColorspace) {
255         const int size = 4;
256
257         float data[size] = {
258                 0.0,
259                 0.5,
260                 0.7,
261                 1.0,
262         };
263         float out_data[size];
264
265         EffectChainTester tester(nullptr, 4, 1, FORMAT_GRAYSCALE);
266
267         // First say that we have sRGB, linear input.
268         ImageFormat format;
269         format.color_space = COLORSPACE_sRGB;
270         format.gamma_curve = GAMMA_LINEAR;
271
272         UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
273         input->set_pixel_data(data);
274         tester.get_chain()->add_input(input);
275
276         // Now we change to Rec. 601 input.
277         input->set_color_space(COLORSPACE_REC_601_625);
278         input->set_gamma_curve(GAMMA_REC_601);
279
280         // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
281         tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
282         expect_equal(data, out_data, 4, 1);
283 }
284
285 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
286         float data[] = {
287                 0.0f, 0.25f, 0.3f,
288                 0.75f, 1.0f, 1.0f,
289         };
290         float expected_data[6] = {
291                 0.3f, 0.25f, 0.0f,
292                 1.0f, 1.0f, 0.75f,
293         };
294         float out_data[6];
295         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
296         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
297         tester.get_chain()->add_effect(effect);
298         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
299
300         Node *node = effect->replaced_node;
301         ASSERT_EQ(1, node->incoming_links.size());
302         EXPECT_EQ(0, node->outgoing_links.size());
303         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
304
305         expect_equal(expected_data, out_data, 3, 2);
306 }
307
308 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
309         float data[] = {
310                 0.0f, 0.25f, 0.3f,
311                 0.75f, 1.0f, 1.0f,
312         };
313         float expected_data[6] = {
314                 0.3f, 0.25f, 0.0f,
315                 1.0f, 1.0f, 0.75f,
316         };
317         float out_data[6];
318         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
319         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
320         tester.get_chain()->add_effect(effect);
321         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
322
323         Node *node = effect->replaced_node;
324         ASSERT_EQ(1, node->incoming_links.size());
325         EXPECT_EQ(0, node->outgoing_links.size());
326         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
327
328         expect_equal(expected_data, out_data, 3, 2);
329 }
330
331 // The identity effect needs linear light, and thus will get conversions on both sides.
332 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
333 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
334         float data[256];
335         for (unsigned i = 0; i < 256; ++i) {
336                 data[i] = i / 255.0;
337         };
338         float out_data[256];
339         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
340         tester.get_chain()->add_effect(new IdentityEffect());
341         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
342
343         expect_equal(data, out_data, 256, 1);
344 }
345
346 // Same, but uses the forward sRGB table from the GPU.
347 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
348         unsigned char data[256];
349         float expected_data[256];
350         for (unsigned i = 0; i < 256; ++i) {
351                 data[i] = i;
352                 expected_data[i] = i / 255.0;
353         };
354         float out_data[256];
355         EffectChainTester tester(nullptr, 256, 1);
356         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
357         tester.get_chain()->add_effect(new IdentityEffect());
358         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
359
360         expect_equal(expected_data, out_data, 256, 1);
361 }
362
363 // Same, for the Rec. 601/709 gamma curve.
364 TEST(EffectChainTest, IdentityThroughRec709) {
365         float data[256];
366         for (unsigned i = 0; i < 256; ++i) {
367                 data[i] = i / 255.0;
368         };
369         float out_data[256];
370         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
371         tester.get_chain()->add_effect(new IdentityEffect());
372         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
373
374         expect_equal(data, out_data, 256, 1);
375 }
376
377 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
378 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
379         const int size = 3;
380         float data[4 * size] = {
381                 0.8f, 0.0f, 0.0f, 0.5f,
382                 0.0f, 0.2f, 0.2f, 0.3f,
383                 0.1f, 0.0f, 1.0f, 1.0f,
384         };
385         float out_data[4 * size];
386         EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
387         tester.get_chain()->add_effect(new IdentityEffect());
388         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
389
390         expect_equal(data, out_data, 4, size);
391 }
392
393 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
394         const int size = 3;
395         float data[4 * size] = {
396                 0.8f, 0.0f, 0.0f, 0.5f,
397                 0.0f, 0.2f, 0.2f, 0.3f,
398                 0.1f, 0.0f, 1.0f, 1.0f,
399         };
400         float expected_data[4 * size] = {
401                 0.1f, 0.0f, 1.0f, 1.0f,
402                 0.0f, 0.2f, 0.2f, 0.3f,
403                 0.8f, 0.0f, 0.0f, 0.5f,
404         };
405         float out_data[4 * size];
406         EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
407         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
408         tester.get_chain()->add_effect(effect);
409         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
410
411         Node *node = effect->replaced_node;
412         ASSERT_EQ(1, node->incoming_links.size());
413         EXPECT_EQ(0, node->outgoing_links.size());
414         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
415
416         expect_equal(expected_data, out_data, 4, size);
417 }
418
419 // An input that outputs only blue, which has blank alpha.
420 class BlueInput : public Input {
421 public:
422         BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
423         string effect_type_id() const override { return "IdentityEffect"; }
424         string output_fragment_shader() override { return read_file("blue.frag"); }
425         AlphaHandling alpha_handling() const override { return OUTPUT_BLANK_ALPHA; }
426         bool can_output_linear_gamma() const override { return true; }
427         unsigned get_width() const override { return 1; }
428         unsigned get_height() const override { return 1; }
429         Colorspace get_color_space() const override { return COLORSPACE_sRGB; }
430         GammaCurve get_gamma_curve() const override { return GAMMA_LINEAR; }
431
432 private:
433         int needs_mipmaps;
434 };
435
436 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
437 // which outputs blank alpha.
438 class RewritingToBlueInput : public Input {
439 public:
440         RewritingToBlueInput() : blue_node(nullptr) { register_int("needs_mipmaps", &needs_mipmaps); }
441         string effect_type_id() const override { return "RewritingToBlueInput"; }
442         string output_fragment_shader() override { EXPECT_TRUE(false); return read_file("identity.frag"); }
443         void rewrite_graph(EffectChain *graph, Node *self) override {
444                 Node *blue_node = graph->add_node(new BlueInput());
445                 graph->replace_receiver(self, blue_node);
446                 graph->replace_sender(self, blue_node);
447
448                 self->disabled = true;
449                 this->blue_node = blue_node;
450         }
451
452         // Dummy values that we need to implement because we inherit from Input.
453         // Same as BlueInput.
454         AlphaHandling alpha_handling() const override { return OUTPUT_BLANK_ALPHA; }
455         bool can_output_linear_gamma() const override { return true; }
456         unsigned get_width() const override { return 1; }
457         unsigned get_height() const override { return 1; }
458         Colorspace get_color_space() const override { return COLORSPACE_sRGB; }
459         GammaCurve get_gamma_curve() const override { return GAMMA_LINEAR; }
460
461         Node *blue_node;
462
463 private:
464         int needs_mipmaps;
465 };
466
467 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
468         const int size = 3;
469         float data[4 * size] = {
470                 0.0f, 0.0f, 1.0f, 1.0f,
471                 0.0f, 0.0f, 1.0f, 1.0f,
472                 0.0f, 0.0f, 1.0f, 1.0f,
473         };
474         float out_data[4 * size];
475         EffectChainTester tester(nullptr, size, 1);
476         RewritingToBlueInput *input = new RewritingToBlueInput();
477         tester.get_chain()->add_input(input);
478         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
479
480         Node *node = input->blue_node;
481         EXPECT_EQ(0, node->incoming_links.size());
482         EXPECT_EQ(0, node->outgoing_links.size());
483
484         expect_equal(data, out_data, 4, size);
485 }
486
487 // An effect that does nothing, and specifies that it preserves blank alpha.
488 class BlankAlphaPreservingEffect : public Effect {
489 public:
490         BlankAlphaPreservingEffect() {}
491         string effect_type_id() const override { return "BlankAlphaPreservingEffect"; }
492         string output_fragment_shader() override { return read_file("identity.frag"); }
493         AlphaHandling alpha_handling() const override { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
494 };
495
496 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
497         const int size = 3;
498         float data[4 * size] = {
499                 0.0f, 0.0f, 1.0f, 1.0f,
500                 0.0f, 0.0f, 1.0f, 1.0f,
501                 0.0f, 0.0f, 1.0f, 1.0f,
502         };
503         float out_data[4 * size];
504         EffectChainTester tester(nullptr, size, 1);
505         tester.get_chain()->add_input(new BlueInput());
506         tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
507         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
508         tester.get_chain()->add_effect(effect);
509         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
510
511         Node *node = effect->replaced_node;
512         EXPECT_EQ(1, node->incoming_links.size());
513         EXPECT_EQ(0, node->outgoing_links.size());
514
515         expect_equal(data, out_data, 4, size);
516 }
517
518 // This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
519 // just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
520 // an alpha conversion _should_ be inserted at the very end. (There is some overlap
521 // with other tests.)
522 TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
523         const int size = 3;
524         float data[4 * size] = {
525                 0.0f, 0.0f, 1.0f, 1.0f,
526                 0.0f, 0.0f, 1.0f, 1.0f,
527                 0.0f, 0.0f, 1.0f, 1.0f,
528         };
529         float out_data[4 * size];
530         EffectChainTester tester(nullptr, size, 1);
531         tester.get_chain()->add_input(new BlueInput());
532         tester.get_chain()->add_effect(new IdentityEffect());  // Not BlankAlphaPreservingEffect.
533         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
534         tester.get_chain()->add_effect(effect);
535         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
536
537         Node *node = effect->replaced_node;
538         EXPECT_EQ(1, node->incoming_links.size());
539         EXPECT_EQ(1, node->outgoing_links.size());
540         EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
541
542         expect_equal(data, out_data, 4, size);
543 }
544
545 // Effectively scales down its input linearly by 4x (and repeating it),
546 // which is not attainable without mipmaps.
547 class MipmapNeedingEffect : public Effect {
548 public:
549         MipmapNeedingEffect() {}
550         bool needs_mipmaps() const override { return true; }
551
552         // To be allowed to mess with the sampler state.
553         bool needs_texture_bounce() const override { return true; }
554
555         string effect_type_id() const override { return "MipmapNeedingEffect"; }
556         string output_fragment_shader() override { return read_file("mipmap_needing_effect.frag"); }
557         void inform_added(EffectChain *chain) override { this->chain = chain; }
558
559         void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num) override
560         {
561                 Node *self = chain->find_node_for_effect(this);
562                 glActiveTexture(chain->get_input_sampler(self, 0));
563                 check_error();
564                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
565                 check_error();
566                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
567                 check_error();
568         }
569
570 private:
571         EffectChain *chain;
572 };
573
574 TEST(EffectChainTest, MipmapGenerationWorks) {
575         float data[] = {  // In 4x4 blocks.
576                 1.0f, 0.0f, 0.0f, 0.0f,
577                 0.0f, 0.0f, 0.0f, 0.0f,
578                 0.0f, 0.0f, 0.0f, 0.0f,
579                 0.0f, 0.0f, 0.0f, 1.0f,
580
581                 0.0f, 0.0f, 0.0f, 0.0f,
582                 0.0f, 0.5f, 0.0f, 0.0f,
583                 0.0f, 0.0f, 1.0f, 0.0f,
584                 0.0f, 0.0f, 0.0f, 0.0f,
585
586                 1.0f, 1.0f, 1.0f, 1.0f,
587                 1.0f, 1.0f, 1.0f, 1.0f,
588                 1.0f, 1.0f, 1.0f, 1.0f,
589                 1.0f, 1.0f, 1.0f, 1.0f,
590
591                 0.0f, 0.0f, 0.0f, 0.0f,
592                 0.0f, 1.0f, 1.0f, 0.0f,
593                 0.0f, 1.0f, 1.0f, 0.0f,
594                 0.0f, 0.0f, 0.0f, 0.0f,
595         };
596         float expected_data[] = {  // Repeated four times each way.
597                 0.125f,   0.125f,   0.125f,   0.125f,
598                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
599                 1.0f,     1.0f,     1.0f,     1.0f,
600                 0.25f,    0.25f,    0.25f,    0.25f,
601
602                 0.125f,   0.125f,   0.125f,   0.125f,
603                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
604                 1.0f,     1.0f,     1.0f,     1.0f,
605                 0.25f,    0.25f,    0.25f,    0.25f,
606
607                 0.125f,   0.125f,   0.125f,   0.125f,
608                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
609                 1.0f,     1.0f,     1.0f,     1.0f,
610                 0.25f,    0.25f,    0.25f,    0.25f,
611
612                 0.125f,   0.125f,   0.125f,   0.125f,
613                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
614                 1.0f,     1.0f,     1.0f,     1.0f,
615                 0.25f,    0.25f,    0.25f,    0.25f,
616         };
617         float out_data[4 * 16];
618         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
619         tester.get_chain()->add_effect(new MipmapNeedingEffect());
620         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
621
622         expect_equal(expected_data, out_data, 4, 16);
623 }
624
625 class NonMipmapCapableInput : public FlatInput {
626 public:
627         NonMipmapCapableInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
628                 : FlatInput(format, pixel_format, type, width, height) {}
629
630         bool can_supply_mipmaps() const override { return false; }
631         bool set_int(const std::string& key, int value) override {
632                 if (key == "needs_mipmaps") {
633                         assert(value == 0);
634                 }
635                 return FlatInput::set_int(key, value);
636         }
637 };
638
639 // The same test as MipmapGenerationWorks, but with an input that refuses
640 // to supply mipmaps.
641 TEST(EffectChainTest, MipmapsWithNonMipmapCapableInput) {
642         float data[] = {  // In 4x4 blocks.
643                 1.0f, 0.0f, 0.0f, 0.0f,
644                 0.0f, 0.0f, 0.0f, 0.0f,
645                 0.0f, 0.0f, 0.0f, 0.0f,
646                 0.0f, 0.0f, 0.0f, 1.0f,
647
648                 0.0f, 0.0f, 0.0f, 0.0f,
649                 0.0f, 0.5f, 0.0f, 0.0f,
650                 0.0f, 0.0f, 1.0f, 0.0f,
651                 0.0f, 0.0f, 0.0f, 0.0f,
652
653                 1.0f, 1.0f, 1.0f, 1.0f,
654                 1.0f, 1.0f, 1.0f, 1.0f,
655                 1.0f, 1.0f, 1.0f, 1.0f,
656                 1.0f, 1.0f, 1.0f, 1.0f,
657
658                 0.0f, 0.0f, 0.0f, 0.0f,
659                 0.0f, 1.0f, 1.0f, 0.0f,
660                 0.0f, 1.0f, 1.0f, 0.0f,
661                 0.0f, 0.0f, 0.0f, 0.0f,
662         };
663         float expected_data[] = {  // Repeated four times each way.
664                 0.125f,   0.125f,   0.125f,   0.125f,
665                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
666                 1.0f,     1.0f,     1.0f,     1.0f,
667                 0.25f,    0.25f,    0.25f,    0.25f,
668
669                 0.125f,   0.125f,   0.125f,   0.125f,
670                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
671                 1.0f,     1.0f,     1.0f,     1.0f,
672                 0.25f,    0.25f,    0.25f,    0.25f,
673
674                 0.125f,   0.125f,   0.125f,   0.125f,
675                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
676                 1.0f,     1.0f,     1.0f,     1.0f,
677                 0.25f,    0.25f,    0.25f,    0.25f,
678
679                 0.125f,   0.125f,   0.125f,   0.125f,
680                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
681                 1.0f,     1.0f,     1.0f,     1.0f,
682                 0.25f,    0.25f,    0.25f,    0.25f,
683         };
684         float out_data[4 * 16];
685         EffectChainTester tester(nullptr, 4, 16, FORMAT_GRAYSCALE);
686
687         ImageFormat format;
688         format.color_space = COLORSPACE_sRGB;
689         format.gamma_curve = GAMMA_LINEAR;
690
691         NonMipmapCapableInput *input = new NonMipmapCapableInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 16);
692         input->set_pixel_data(data);
693         tester.get_chain()->add_input(input);
694         tester.get_chain()->add_effect(new MipmapNeedingEffect());
695         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
696
697         expect_equal(expected_data, out_data, 4, 16);
698 }
699
700 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
701         float data[] = {  // In 4x4 blocks.
702                 1.0f, 0.0f, 0.0f, 0.0f,
703                 0.0f, 0.0f, 0.0f, 0.0f,
704                 0.0f, 0.0f, 0.0f, 0.0f,
705                 0.0f, 0.0f, 0.0f, 1.0f,
706
707                 0.0f, 0.0f, 0.0f, 0.0f,
708                 0.0f, 0.5f, 0.0f, 0.0f,
709                 0.0f, 0.0f, 1.0f, 0.0f,
710                 0.0f, 0.0f, 0.0f, 0.0f,
711
712                 1.0f, 1.0f, 1.0f, 1.0f,
713                 1.0f, 1.0f, 1.0f, 1.0f,
714                 1.0f, 1.0f, 1.0f, 1.0f,
715                 1.0f, 1.0f, 1.0f, 1.0f,
716
717                 0.0f, 0.0f, 0.0f, 0.0f,
718                 0.0f, 1.0f, 1.0f, 0.0f,
719                 0.0f, 1.0f, 1.0f, 0.0f,
720                 0.0f, 0.0f, 0.0f, 0.0f,
721         };
722         float expected_data[] = {  // Repeated four times horizontaly, interpolated vertically.
723                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
724                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
725                 0.1211f, 0.1211f, 0.1211f, 0.1211f,
726                 0.1133f, 0.1133f, 0.1133f, 0.1133f,
727                 0.1055f, 0.1055f, 0.1055f, 0.1055f,
728                 0.0977f, 0.0977f, 0.0977f, 0.0977f,
729                 0.2070f, 0.2070f, 0.2070f, 0.2070f,
730                 0.4336f, 0.4336f, 0.4336f, 0.4336f,
731                 0.6602f, 0.6602f, 0.6602f, 0.6602f,
732                 0.8867f, 0.8867f, 0.8867f, 0.8867f,
733                 0.9062f, 0.9062f, 0.9062f, 0.9062f,
734                 0.7188f, 0.7188f, 0.7188f, 0.7188f,
735                 0.5312f, 0.5312f, 0.5312f, 0.5312f,
736                 0.3438f, 0.3438f, 0.3438f, 0.3438f,
737                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
738                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
739         };
740         float out_data[4 * 16];
741
742         ResizeEffect *downscale = new ResizeEffect();
743         ASSERT_TRUE(downscale->set_int("width", 1));
744         ASSERT_TRUE(downscale->set_int("height", 4));
745
746         ResizeEffect *upscale = new ResizeEffect();
747         ASSERT_TRUE(upscale->set_int("width", 4));
748         ASSERT_TRUE(upscale->set_int("height", 16));
749
750         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
751         tester.get_chain()->add_effect(downscale);
752         tester.get_chain()->add_effect(upscale);
753         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
754
755         expect_equal(expected_data, out_data, 4, 16);
756 }
757
758 // An effect that adds its two inputs together. Used below.
759 class AddEffect : public Effect {
760 public:
761         AddEffect() {}
762         string effect_type_id() const override { return "AddEffect"; }
763         string output_fragment_shader() override { return read_file("add.frag"); }
764         unsigned num_inputs() const override { return 2; }
765         AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
766 };
767
768 // Constructs the graph
769 //
770 //             FlatInput               |
771 //            /         \              |
772 //  MultiplyEffect  MultiplyEffect     |
773 //            \         /              |
774 //             AddEffect               |
775 //
776 // and verifies that it gives the correct output.
777 TEST(EffectChainTest, DiamondGraph) {
778         float data[] = {
779                 1.0f, 1.0f,
780                 1.0f, 0.0f,
781         };
782         float expected_data[] = {
783                 2.5f, 2.5f,
784                 2.5f, 0.0f,
785         };
786         float out_data[2 * 2];
787
788         const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
789         const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
790
791         MultiplyEffect *mul_half = new MultiplyEffect();
792         ASSERT_TRUE(mul_half->set_vec4("factor", half));
793         
794         MultiplyEffect *mul_two = new MultiplyEffect();
795         ASSERT_TRUE(mul_two->set_vec4("factor", two));
796
797         EffectChainTester tester(nullptr, 2, 2);
798
799         ImageFormat format;
800         format.color_space = COLORSPACE_sRGB;
801         format.gamma_curve = GAMMA_LINEAR;
802
803         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
804         input->set_pixel_data(data);
805
806         tester.get_chain()->add_input(input);
807         tester.get_chain()->add_effect(mul_half, input);
808         tester.get_chain()->add_effect(mul_two, input);
809         tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
810         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
811
812         expect_equal(expected_data, out_data, 2, 2);
813 }
814
815 // Constructs the graph
816 //
817 //             FlatInput                     |
818 //            /         \                    |
819 //  MultiplyEffect  MultiplyEffect           |
820 //         \             |                   |
821 //          \    BouncingIdentityEffect      |  
822 //            \         /                    |
823 //             AddEffect                     |
824 //
825 // and verifies that it gives the correct output.
826 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
827         float data[] = {
828                 1.0f, 1.0f,
829                 1.0f, 0.0f,
830         };
831         float expected_data[] = {
832                 2.5f, 2.5f,
833                 2.5f, 0.0f,
834         };
835         float out_data[2 * 2];
836
837         const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
838         const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
839
840         MultiplyEffect *mul_half = new MultiplyEffect();
841         ASSERT_TRUE(mul_half->set_vec4("factor", half));
842         
843         MultiplyEffect *mul_two = new MultiplyEffect();
844         ASSERT_TRUE(mul_two->set_vec4("factor", two));
845         
846         BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
847
848         EffectChainTester tester(nullptr, 2, 2);
849
850         ImageFormat format;
851         format.color_space = COLORSPACE_sRGB;
852         format.gamma_curve = GAMMA_LINEAR;
853
854         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
855         input->set_pixel_data(data);
856
857         tester.get_chain()->add_input(input);
858         tester.get_chain()->add_effect(mul_half, input);
859         tester.get_chain()->add_effect(mul_two, input);
860         tester.get_chain()->add_effect(bounce, mul_two);
861         tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
862         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
863
864         expect_equal(expected_data, out_data, 2, 2);
865 }
866
867 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
868         float data[] = {
869                 0.735f, 0.0f,
870                 0.735f, 0.0f,
871         };
872         float expected_data[] = {
873                 0.0f, 0.5f,  // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
874                 0.0f, 0.5f,
875         };
876         float out_data[2 * 2];
877         
878         EffectChainTester tester(nullptr, 2, 2);
879         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
880
881         // MirrorEffect does not get linear light, so the conversions will be
882         // inserted after it, not before.
883         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
884         tester.get_chain()->add_effect(effect);
885
886         Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
887         Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
888         tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
889         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
890
891         expect_equal(expected_data, out_data, 2, 2);
892
893         Node *node = effect->replaced_node;
894         ASSERT_EQ(1, node->incoming_links.size());
895         ASSERT_EQ(1, node->outgoing_links.size());
896         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
897         EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
898 }
899
900 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
901         float data[] = {
902                 0.5f, 0.0f,
903                 0.5f, 0.0f,
904         };
905         float expected_data[] = {
906                 0.0f, 0.5f,  // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
907                 0.0f, 0.5f,
908         };
909         float out_data[2 * 2];
910         
911         EffectChainTester tester(nullptr, 2, 2);
912         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
913
914         // MirrorEffect does not get linear light, so the conversions will be
915         // inserted after it, not before.
916         RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
917         tester.get_chain()->add_effect(effect);
918
919         Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
920         Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
921         tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
922         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
923
924         expect_equal(expected_data, out_data, 2, 2);
925
926         Node *node = effect->replaced_node;
927         ASSERT_EQ(1, node->incoming_links.size());
928         ASSERT_EQ(1, node->outgoing_links.size());
929         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
930         EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
931 }
932
933 // An effect that does nothing, but requests texture bounce and stores
934 // its input size.
935 class SizeStoringEffect : public BouncingIdentityEffect {
936 public:
937         SizeStoringEffect() : input_width(-1), input_height(-1) {}
938         void inform_input_size(unsigned input_num, unsigned width, unsigned height) override {
939                 assert(input_num == 0);
940                 input_width = width;
941                 input_height = height;
942         }
943         string effect_type_id() const override { return "SizeStoringEffect"; }
944
945         int input_width, input_height;
946 };
947
948 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
949         float data[2 * 2] = {
950                 0.0f, 0.0f,
951                 0.0f, 0.0f,
952         };
953         float out_data[4 * 3];
954         
955         EffectChainTester tester(nullptr, 4, 3);  // Note non-square aspect.
956
957         ImageFormat format;
958         format.color_space = COLORSPACE_sRGB;
959         format.gamma_curve = GAMMA_LINEAR;
960
961         FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
962         input1->set_pixel_data(data);
963         
964         FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
965         input2->set_pixel_data(data);
966
967         SizeStoringEffect *input_store = new SizeStoringEffect();
968
969         tester.get_chain()->add_input(input1);
970         tester.get_chain()->add_input(input2);
971         tester.get_chain()->add_effect(new AddEffect(), input1, input2);
972         tester.get_chain()->add_effect(input_store);
973         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
974
975         EXPECT_EQ(2, input_store->input_width);
976         EXPECT_EQ(2, input_store->input_height);
977 }
978
979 TEST(EffectChainTest, AspectRatioConversion) {
980         float data1[4 * 3] = {
981                 0.0f, 0.0f, 0.0f, 0.0f,
982                 0.0f, 0.0f, 0.0f, 0.0f,
983                 0.0f, 0.0f, 0.0f, 0.0f,
984         };
985         float data2[7 * 7] = {
986                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
987                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
988                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
989                 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
990                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
991                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
992                 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
993         };
994
995         // The right conversion here is that the 7x7 image decides the size,
996         // since it is the biggest, so everything is scaled up to 9x7
997         // (keep the height, round the width 9.333 to 9). 
998         float out_data[9 * 7];
999         
1000         EffectChainTester tester(nullptr, 4, 3);
1001
1002         ImageFormat format;
1003         format.color_space = COLORSPACE_sRGB;
1004         format.gamma_curve = GAMMA_LINEAR;
1005
1006         FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
1007         input1->set_pixel_data(data1);
1008         
1009         FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
1010         input2->set_pixel_data(data2);
1011
1012         SizeStoringEffect *input_store = new SizeStoringEffect();
1013
1014         tester.get_chain()->add_input(input1);
1015         tester.get_chain()->add_input(input2);
1016         tester.get_chain()->add_effect(new AddEffect(), input1, input2);
1017         tester.get_chain()->add_effect(input_store);
1018         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1019
1020         EXPECT_EQ(9, input_store->input_width);
1021         EXPECT_EQ(7, input_store->input_height);
1022 }
1023
1024 // Tests that putting a BlueInput (constant color) into its own pass,
1025 // which creates a phase that doesn't need texture coordinates,
1026 // doesn't mess up a second phase that actually does.
1027 TEST(EffectChainTest, FirstPhaseWithNoTextureCoordinates) {
1028         const int size = 2;
1029         float data[] = {
1030                 1.0f,
1031                 0.0f,
1032         };
1033         float expected_data[] = {
1034                 1.0f, 1.0f, 2.0f, 2.0f,
1035                 0.0f, 0.0f, 1.0f, 2.0f,
1036         };
1037         float out_data[size * 4];
1038         // First say that we have sRGB, linear input.
1039         ImageFormat format;
1040         format.color_space = COLORSPACE_sRGB;
1041         format.gamma_curve = GAMMA_LINEAR;
1042         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 1, size);
1043
1044         input->set_pixel_data(data);
1045         EffectChainTester tester(nullptr, 1, size);
1046         tester.get_chain()->add_input(new BlueInput());
1047         Effect *phase1_end = tester.get_chain()->add_effect(new BouncingIdentityEffect());
1048         tester.get_chain()->add_input(input);
1049         tester.get_chain()->add_effect(new AddEffect(), phase1_end, input);
1050
1051         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1052
1053         expect_equal(expected_data, out_data, 4, size);
1054 }
1055
1056 // An effect that does nothing except changing its output sizes.
1057 class VirtualResizeEffect : public Effect {
1058 public:
1059         VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
1060                 : width(width),
1061                   height(height),
1062                   virtual_width(virtual_width),
1063                   virtual_height(virtual_height) {}
1064         string effect_type_id() const override { return "VirtualResizeEffect"; }
1065         string output_fragment_shader() override { return read_file("identity.frag"); }
1066
1067         bool changes_output_size() const override { return true; }
1068
1069         void get_output_size(unsigned *width, unsigned *height,
1070                              unsigned *virtual_width, unsigned *virtual_height) const override {
1071                 *width = this->width;
1072                 *height = this->height;
1073                 *virtual_width = this->virtual_width;
1074                 *virtual_height = this->virtual_height;
1075         }
1076
1077 private:
1078         int width, height, virtual_width, virtual_height;
1079 };
1080
1081 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
1082         const int size = 2, bigger_size = 3;
1083         float data[size * size] = {
1084                 1.0f, 0.0f,
1085                 0.0f, 1.0f,
1086         };
1087         float out_data[size * size];
1088         
1089         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1090
1091         SizeStoringEffect *size_store = new SizeStoringEffect();
1092
1093         tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
1094         tester.get_chain()->add_effect(size_store);
1095         tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
1096         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1097
1098         EXPECT_EQ(bigger_size, size_store->input_width);
1099         EXPECT_EQ(bigger_size, size_store->input_height);
1100
1101         // If the resize is implemented as non-virtual, we'll fail here,
1102         // since bilinear scaling from 2x2 â†’ 3x3 â†’ 2x2 is not very exact.
1103         expect_equal(data, out_data, size, size);
1104 }
1105
1106 // An effect that is like VirtualResizeEffect, but always has virtual and real
1107 // sizes the same (and promises this).
1108 class NonVirtualResizeEffect : public VirtualResizeEffect {
1109 public:
1110         NonVirtualResizeEffect(int width, int height)
1111                 : VirtualResizeEffect(width, height, width, height) {}
1112         string effect_type_id() const override { return "NonVirtualResizeEffect"; }
1113         bool sets_virtual_output_size() const override { return false; }
1114 };
1115
1116 // An effect that promises one-to-one sampling (unlike IdentityEffect).
1117 class OneToOneEffect : public Effect {
1118 public:
1119         OneToOneEffect() {}
1120         string effect_type_id() const override { return "OneToOneEffect"; }
1121         string output_fragment_shader() override { return read_file("identity.frag"); }
1122         bool one_to_one_sampling() const override { return true; }
1123 };
1124
1125 TEST(EffectChainTest, NoBounceWithOneToOneSampling) {
1126         const int size = 2;
1127         float data[size * size] = {
1128                 1.0f, 0.0f,
1129                 0.0f, 1.0f,
1130         };
1131         float out_data[size * size];
1132
1133         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1134
1135         RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1136         RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1137
1138         tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1139         tester.get_chain()->add_effect(effect1);
1140         tester.get_chain()->add_effect(effect2);
1141         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1142
1143         expect_equal(data, out_data, size, size);
1144
1145         // The first OneToOneEffect should be in the same phase as its input.
1146         ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
1147         EXPECT_EQ(effect1->replaced_node->incoming_links[0]->containing_phase,
1148                   effect1->replaced_node->containing_phase);
1149
1150         // The second OneToOneEffect, too.
1151         EXPECT_EQ(effect1->replaced_node->containing_phase,
1152                   effect2->replaced_node->containing_phase);
1153 }
1154
1155 TEST(EffectChainTest, BounceWhenOneToOneIsBroken) {
1156         const int size = 2;
1157         float data[size * size] = {
1158                 1.0f, 0.0f,
1159                 0.0f, 1.0f,
1160         };
1161         float out_data[size * size];
1162
1163         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1164
1165         RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1166         RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1167         RewritingEffect<IdentityEffect> *effect3 = new RewritingEffect<IdentityEffect>();
1168         RewritingEffect<OneToOneEffect> *effect4 = new RewritingEffect<OneToOneEffect>();
1169
1170         tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1171         tester.get_chain()->add_effect(effect1);
1172         tester.get_chain()->add_effect(effect2);
1173         tester.get_chain()->add_effect(effect3);
1174         tester.get_chain()->add_effect(effect4);
1175         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1176
1177         expect_equal(data, out_data, size, size);
1178
1179         // The NonVirtualResizeEffect should be in a different phase from
1180         // the IdentityEffect (since the latter is not one-to-one),
1181         // ie., the chain should be broken somewhere between them, but exactly
1182         // where doesn't matter.
1183         ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
1184         EXPECT_NE(effect1->replaced_node->incoming_links[0]->containing_phase,
1185                   effect3->replaced_node->containing_phase);
1186
1187         // The last OneToOneEffect should also be in the same phase as the
1188         // IdentityEffect (the phase was already broken).
1189         EXPECT_EQ(effect3->replaced_node->containing_phase,
1190                   effect4->replaced_node->containing_phase);
1191 }
1192
1193 // Does not use EffectChainTest, so that it can construct an EffectChain without
1194 // a shared ResourcePool (which is also properly destroyed afterwards).
1195 // Also turns on debugging to test that code path.
1196 TEST(EffectChainTest, IdentityWithOwnPool) {
1197         const int width = 3, height = 2;
1198         float data[] = {
1199                 0.0f, 0.25f, 0.3f,
1200                 0.75f, 1.0f, 1.0f,
1201         };
1202         const float expected_data[] = {
1203                 0.75f, 1.0f, 1.0f,
1204                 0.0f, 0.25f, 0.3f,
1205         };
1206         float out_data[6], temp[6 * 4];
1207
1208         EffectChain chain(width, height);
1209         movit_debug_level = MOVIT_DEBUG_ON;
1210
1211         ImageFormat format;
1212         format.color_space = COLORSPACE_sRGB;
1213         format.gamma_curve = GAMMA_LINEAR;
1214
1215         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
1216         input->set_pixel_data(data);
1217         chain.add_input(input);
1218         chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1219
1220         GLuint texnum, fbo;
1221         glGenTextures(1, &texnum);
1222         check_error();
1223         glBindTexture(GL_TEXTURE_2D, texnum);
1224         check_error();
1225         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
1226         check_error();
1227
1228         glGenFramebuffers(1, &fbo);
1229         check_error();
1230         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1231         check_error();
1232         glFramebufferTexture2D(
1233                 GL_FRAMEBUFFER,
1234                 GL_COLOR_ATTACHMENT0,
1235                 GL_TEXTURE_2D,
1236                 texnum,
1237                 0);
1238         check_error();
1239         glBindFramebuffer(GL_FRAMEBUFFER, 0);
1240         check_error();
1241
1242         chain.finalize();
1243
1244         chain.render_to_fbo(fbo, width, height);
1245
1246         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1247         check_error();
1248         glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp);
1249         check_error();
1250         for (unsigned i = 0; i < 6; ++i) {
1251                 out_data[i] = temp[i * 4];
1252         }
1253
1254         expect_equal(expected_data, out_data, width, height);
1255
1256         // Reset the debug status again.
1257         movit_debug_level = MOVIT_DEBUG_OFF;
1258 }
1259
1260 // A dummy effect whose only purpose is to test sprintf decimal behavior.
1261 class PrintfingBlueEffect : public Effect {
1262 public:
1263         PrintfingBlueEffect() {}
1264         string effect_type_id() const override { return "PrintfingBlueEffect"; }
1265         string output_fragment_shader() override {
1266                 stringstream ss;
1267                 ss.imbue(locale("C"));
1268                 ss.precision(8);
1269                 ss << "vec4 FUNCNAME(vec2 tc) { return vec4("
1270                    << 0.0f << ", " << 0.0f << ", "
1271                    << 0.5f << ", " << 1.0f << "); }\n";
1272                 return ss.str();
1273         }
1274 };
1275
1276 TEST(EffectChainTest, StringStreamLocalesWork) {
1277         // An example of a locale with comma instead of period as decimal separator.
1278         // Obviously, if you run on a machine without this locale available,
1279         // the test will always succeed. Note that the OpenGL driver might call
1280         // setlocale() behind-the-scenes, and that might corrupt the returned
1281         // pointer, so we need to take our own copy of it here.
1282         char *saved_locale = setlocale(LC_ALL, "nb_NO.UTF_8");
1283         if (saved_locale == nullptr) {
1284                 // The locale wasn't available.
1285                 return;
1286         }
1287         saved_locale = strdup(saved_locale);
1288         float data[] = {
1289                 0.0f, 0.0f, 0.0f, 0.0f,
1290         };
1291         float expected_data[] = {
1292                 0.0f, 0.0f, 0.5f, 1.0f,
1293         };
1294         float out_data[4];
1295         EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1296         tester.get_chain()->add_effect(new PrintfingBlueEffect());
1297         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1298
1299         expect_equal(expected_data, out_data, 4, 1);
1300
1301         setlocale(LC_ALL, saved_locale);
1302         free(saved_locale);
1303 }
1304
1305 // An effect that does nothing, but as a compute shader.
1306 class IdentityComputeEffect : public Effect {
1307 public:
1308         IdentityComputeEffect() {}
1309         string effect_type_id() const override { return "IdentityComputeEffect"; }
1310         bool is_compute_shader() const override { return true; }
1311         string output_fragment_shader() override { return read_file("identity.comp"); }
1312 };
1313
1314 class WithAndWithoutComputeShaderTest : public testing::TestWithParam<string> {
1315 };
1316 INSTANTIATE_TEST_CASE_P(WithAndWithoutComputeShaderTest,
1317                         WithAndWithoutComputeShaderTest,
1318                         testing::Values("fragment", "compute"));
1319
1320 TEST(EffectChainTest, sRGBIntermediate) {
1321         float data[] = {
1322                 0.0f, 0.5f, 0.0f, 1.0f,
1323         };
1324         float out_data[4];
1325         EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1326         tester.get_chain()->set_intermediate_format(GL_SRGB8);
1327         tester.get_chain()->add_effect(new IdentityEffect());
1328         tester.get_chain()->add_effect(new BouncingIdentityEffect());
1329         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1330
1331         EXPECT_GE(fabs(out_data[1] - data[1]), 1e-3)
1332             << "Expected sRGB not to be able to represent 0.5 exactly (got " << out_data[1] << ")";
1333         EXPECT_LT(fabs(out_data[1] - data[1]), 0.1f)
1334             << "Expected sRGB to be able to represent 0.5 approximately (got " << out_data[1] << ")";
1335
1336         // This state should have been preserved.
1337         EXPECT_FALSE(glIsEnabled(GL_FRAMEBUFFER_SRGB));
1338 }
1339
1340 // An effect that is like IdentityEffect, but also does not require linear light.
1341 class PassThroughEffect : public IdentityEffect {
1342 public:
1343         PassThroughEffect() {}
1344         string effect_type_id() const override { return "PassThroughEffect"; }
1345         bool needs_linear_light() const override { return false; }
1346         AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
1347 };
1348
1349 // Same, just also bouncing.
1350 class BouncingPassThroughEffect : public BouncingIdentityEffect {
1351 public:
1352         BouncingPassThroughEffect() {}
1353         string effect_type_id() const override { return "BouncingPassThroughEffect"; }
1354         bool needs_linear_light() const override { return false; }
1355         bool needs_texture_bounce() const override { return true; }
1356         AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
1357 };
1358
1359 TEST(EffectChainTest, Linear10bitIntermediateAccuracy) {
1360         // Note that we do the comparison in sRGB space, which is what we
1361         // typically would want; however, we do the sRGB conversion ourself
1362         // to avoid compounding errors from shader conversions into the
1363         // analysis.
1364         const int size = 4096;  // 12-bit.
1365         float linear_data[size], data[size], out_data[size];
1366
1367         for (int i = 0; i < size; ++i) {
1368                 linear_data[i] = i / double(size - 1);
1369                 data[i] = srgb_to_linear(linear_data[i]);
1370         }
1371
1372         EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
1373         tester.get_chain()->set_intermediate_format(GL_RGB10_A2);
1374         tester.get_chain()->add_effect(new IdentityEffect());
1375         tester.get_chain()->add_effect(new BouncingIdentityEffect());
1376         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1377
1378         for (int i = 0; i < size; ++i) {
1379                 out_data[i] = linear_to_srgb(out_data[i]);
1380         }
1381
1382         // This maximum error is pretty bad; about 6.5 levels of a 10-bit sRGB
1383         // framebuffer. (Slightly more on NVIDIA cards.)
1384         expect_equal(linear_data, out_data, size, 1, 7.5e-3, 2e-5);
1385 }
1386
1387 TEST_P(WithAndWithoutComputeShaderTest, SquareRoot10bitIntermediateAccuracy) {
1388         // Note that we do the comparison in sRGB space, which is what we
1389         // typically would want; however, we do the sRGB conversion ourself
1390         // to avoid compounding errors from shader conversions into the
1391         // analysis.
1392         const int size = 4096;  // 12-bit.
1393         float linear_data[size], data[size], out_data[size];
1394
1395         for (int i = 0; i < size; ++i) {
1396                 linear_data[i] = i / double(size - 1);
1397                 data[i] = srgb_to_linear(linear_data[i]);
1398         }
1399
1400         EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
1401         tester.get_chain()->set_intermediate_format(GL_RGB10_A2, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION);
1402         if (GetParam() == "compute") {
1403                 tester.get_chain()->add_effect(new IdentityComputeEffect());
1404         } else {
1405                 tester.get_chain()->add_effect(new IdentityEffect());
1406         }
1407         tester.get_chain()->add_effect(new BouncingIdentityEffect());
1408         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1409
1410         for (int i = 0; i < size; ++i) {
1411                 out_data[i] = linear_to_srgb(out_data[i]);
1412         }
1413
1414         // This maximum error is much better; about 0.7 levels of a 10-bit sRGB
1415         // framebuffer (ideal would be 0.5). That is an order of magnitude better
1416         // than in the linear test above. The RMS error is much better, too.
1417         expect_equal(linear_data, out_data, size, 1, 7.5e-4, 5e-6);
1418 }
1419
1420 TEST(EffectChainTest, SquareRootIntermediateIsTurnedOffForNonLinearData) {
1421         const int size = 256;  // 8-bit.
1422         float data[size], out_data[size];
1423
1424         for (int i = 0; i < size; ++i) {
1425                 data[i] = i / double(size - 1);
1426         }
1427
1428         EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_601, GL_RGBA32F);
1429         tester.get_chain()->set_intermediate_format(GL_RGB8, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION);
1430         tester.get_chain()->add_effect(new PassThroughEffect());
1431         tester.get_chain()->add_effect(new BouncingPassThroughEffect());
1432         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_601);
1433
1434         // The data should be passed through nearly exactly, since there is no effect
1435         // on the path that requires linear light. (Actually, it _is_ exact modulo
1436         // fp32 errors, but the error bounds is strictly _less than_, not zero.)
1437         expect_equal(data, out_data, size, 1, 1e-6, 1e-6);
1438 }
1439
1440 // An effect that stores which program number was last run under.
1441 class RecordingIdentityEffect : public Effect {
1442 public:
1443         RecordingIdentityEffect() {}
1444         string effect_type_id() const override { return "RecordingIdentityEffect"; }
1445         string output_fragment_shader() override { return read_file("identity.frag"); }
1446
1447         GLuint last_glsl_program_num;
1448         void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num) override
1449         {
1450                 last_glsl_program_num = glsl_program_num;
1451         }
1452 };
1453
1454 TEST(EffectChainTest, ProgramsAreClonedForMultipleThreads) {
1455         float data[] = {
1456                 0.0f, 0.25f, 0.3f,
1457                 0.75f, 1.0f, 1.0f,
1458         };
1459         float out_data[6];
1460         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1461         RecordingIdentityEffect *effect = new RecordingIdentityEffect();
1462         tester.get_chain()->add_effect(effect);
1463         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1464
1465         expect_equal(data, out_data, 3, 2);
1466
1467         ASSERT_NE(0, effect->last_glsl_program_num);
1468
1469         // Now pretend some other effect is using this program number;
1470         // ResourcePool will then need to clone it.
1471         ResourcePool *resource_pool = tester.get_chain()->get_resource_pool();
1472         GLuint master_program_num = resource_pool->use_glsl_program(effect->last_glsl_program_num);
1473         EXPECT_EQ(effect->last_glsl_program_num, master_program_num);
1474
1475         // Re-run should still give the correct data, but it should have run
1476         // with a different program.
1477         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1478         expect_equal(data, out_data, 3, 2);
1479         EXPECT_NE(effect->last_glsl_program_num, master_program_num);
1480
1481         // Release the program, and check one final time.
1482         resource_pool->unuse_glsl_program(master_program_num);
1483         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1484         expect_equal(data, out_data, 3, 2);
1485 }
1486
1487 TEST(ComputeShaderTest, Identity) {
1488         float data[] = {
1489                 0.0f, 0.25f, 0.3f,
1490                 0.75f, 1.0f, 1.0f,
1491         };
1492         float out_data[6];
1493         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1494         if (!movit_compute_shaders_supported) {
1495                 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1496                 return;
1497         }
1498         tester.get_chain()->add_effect(new IdentityComputeEffect());
1499         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1500
1501         expect_equal(data, out_data, 3, 2);
1502 }
1503
1504 // Like IdentityComputeEffect, but due to the alpha handling, this will be
1505 // the very last effect in the chain, which means we can't output it directly
1506 // to the screen.
1507 class IdentityAlphaComputeEffect : public IdentityComputeEffect {
1508         AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
1509 };
1510
1511 TEST(ComputeShaderTest, LastEffectInChain) {
1512         float data[] = {
1513                 0.0f, 0.25f, 0.3f,
1514                 0.75f, 1.0f, 1.0f,
1515         };
1516         float out_data[6];
1517         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1518         if (!movit_compute_shaders_supported) {
1519                 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1520                 return;
1521         }
1522         tester.get_chain()->add_effect(new IdentityAlphaComputeEffect());
1523         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1524
1525         expect_equal(data, out_data, 3, 2);
1526 }
1527
1528 TEST(ComputeShaderTest, Render8BitTo8Bit) {
1529         uint8_t data[] = {
1530                 14, 200, 80,
1531                 90, 100, 110,
1532         };
1533         uint8_t out_data[6];
1534         EffectChainTester tester(nullptr, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
1535         if (!movit_compute_shaders_supported) {
1536                 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1537                 return;
1538         }
1539         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 3, 2);
1540         tester.get_chain()->add_effect(new IdentityAlphaComputeEffect());
1541         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1542
1543         expect_equal(data, out_data, 3, 2);
1544 }
1545
1546 }  // namespace movit