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