]> git.sesse.net Git - movit/blob - effect_chain_test.cpp
Fix some warnings.
[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(1u, node->incoming_links.size());
188         ASSERT_EQ(1u, 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(1u, node->incoming_links.size());
217         ASSERT_EQ(1u, 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(1u, node->incoming_links.size());
241         ASSERT_EQ(1u, 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(1u, node->incoming_links.size());
320         EXPECT_EQ(0u, 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(1u, node->incoming_links.size());
343         EXPECT_EQ(0u, 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(1u, node->incoming_links.size());
431         EXPECT_EQ(0u, 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(0u, node->incoming_links.size());
500         EXPECT_EQ(0u, 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(1u, node->incoming_links.size());
531         EXPECT_EQ(0u, 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(1u, node->incoming_links.size());
557         EXPECT_EQ(1u, 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(1u, node->incoming_links.size());
913         ASSERT_EQ(1u, 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(1u, node->incoming_links.size());
946         ASSERT_EQ(1u, 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(1u, 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(1u, 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         MovitDebugLevel old_movit_debug_level = movit_debug_level;
1232         movit_debug_level = MOVIT_DEBUG_ON;
1233
1234         ImageFormat format;
1235         format.color_space = COLORSPACE_sRGB;
1236         format.gamma_curve = GAMMA_LINEAR;
1237
1238         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
1239         input->set_pixel_data(data);
1240         chain.add_input(input);
1241         chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1242
1243         GLuint texnum, fbo;
1244         glGenTextures(1, &texnum);
1245         check_error();
1246         glBindTexture(GL_TEXTURE_2D, texnum);
1247         check_error();
1248         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
1249         check_error();
1250
1251         glGenFramebuffers(1, &fbo);
1252         check_error();
1253         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1254         check_error();
1255         glFramebufferTexture2D(
1256                 GL_FRAMEBUFFER,
1257                 GL_COLOR_ATTACHMENT0,
1258                 GL_TEXTURE_2D,
1259                 texnum,
1260                 0);
1261         check_error();
1262         glBindFramebuffer(GL_FRAMEBUFFER, 0);
1263         check_error();
1264
1265         chain.finalize();
1266
1267         chain.render_to_fbo(fbo, width, height);
1268
1269         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1270         check_error();
1271         glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp);
1272         check_error();
1273         for (unsigned i = 0; i < 6; ++i) {
1274                 out_data[i] = temp[i * 4];
1275         }
1276
1277         expect_equal(expected_data, out_data, width, height);
1278
1279         // Reset the debug status again.
1280         movit_debug_level = old_movit_debug_level;
1281 }
1282
1283 // A dummy effect whose only purpose is to test sprintf decimal behavior.
1284 class PrintfingBlueEffect : public Effect {
1285 public:
1286         PrintfingBlueEffect() {}
1287         string effect_type_id() const override { return "PrintfingBlueEffect"; }
1288         string output_fragment_shader() override {
1289                 stringstream ss;
1290                 ss.imbue(locale("C"));
1291                 ss.precision(8);
1292                 ss << "vec4 FUNCNAME(vec2 tc) { return vec4("
1293                    << 0.0f << ", " << 0.0f << ", "
1294                    << 0.5f << ", " << 1.0f << "); }\n";
1295                 return ss.str();
1296         }
1297 };
1298
1299 TEST(EffectChainTest, StringStreamLocalesWork) {
1300         // An example of a locale with comma instead of period as decimal separator.
1301         // Obviously, if you run on a machine without this locale available,
1302         // the test will always succeed. Note that the OpenGL driver might call
1303         // setlocale() behind-the-scenes, and that might corrupt the returned
1304         // pointer, so we need to take our own copy of it here.
1305         char *saved_locale = setlocale(LC_ALL, "nb_NO.UTF_8");
1306         if (saved_locale == nullptr) {
1307                 // The locale wasn't available.
1308                 return;
1309         }
1310         saved_locale = strdup(saved_locale);
1311         float data[] = {
1312                 0.0f, 0.0f, 0.0f, 0.0f,
1313         };
1314         float expected_data[] = {
1315                 0.0f, 0.0f, 0.5f, 1.0f,
1316         };
1317         float out_data[4];
1318         EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1319         tester.get_chain()->add_effect(new PrintfingBlueEffect());
1320         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1321
1322         expect_equal(expected_data, out_data, 4, 1);
1323
1324         setlocale(LC_ALL, saved_locale);
1325         free(saved_locale);
1326 }
1327
1328 TEST(EffectChainTest, sRGBIntermediate) {
1329         float data[] = {
1330                 0.0f, 0.5f, 0.0f, 1.0f,
1331         };
1332         float out_data[4];
1333         EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1334         tester.get_chain()->set_intermediate_format(GL_SRGB8);
1335         tester.get_chain()->add_effect(new IdentityEffect());
1336         tester.get_chain()->add_effect(new BouncingIdentityEffect());
1337         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1338
1339         EXPECT_GE(fabs(out_data[1] - data[1]), 1e-3)
1340             << "Expected sRGB not to be able to represent 0.5 exactly (got " << out_data[1] << ")";
1341         EXPECT_LT(fabs(out_data[1] - data[1]), 0.1f)
1342             << "Expected sRGB to be able to represent 0.5 approximately (got " << out_data[1] << ")";
1343
1344         // This state should have been preserved.
1345         EXPECT_FALSE(glIsEnabled(GL_FRAMEBUFFER_SRGB));
1346 }
1347
1348 // An effect that is like IdentityEffect, but also does not require linear light.
1349 class PassThroughEffect : public IdentityEffect {
1350 public:
1351         PassThroughEffect() {}
1352         string effect_type_id() const override { return "PassThroughEffect"; }
1353         bool needs_linear_light() const override { return false; }
1354         AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
1355 };
1356
1357 // Same, just also bouncing.
1358 class BouncingPassThroughEffect : public BouncingIdentityEffect {
1359 public:
1360         BouncingPassThroughEffect() {}
1361         string effect_type_id() const override { return "BouncingPassThroughEffect"; }
1362         bool needs_linear_light() const override { return false; }
1363         bool needs_texture_bounce() const override { return true; }
1364         AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
1365 };
1366
1367 TEST(EffectChainTest, Linear10bitIntermediateAccuracy) {
1368         // Note that we do the comparison in sRGB space, which is what we
1369         // typically would want; however, we do the sRGB conversion ourself
1370         // to avoid compounding errors from shader conversions into the
1371         // analysis.
1372         const int size = 4096;  // 12-bit.
1373         float linear_data[size], data[size], out_data[size];
1374
1375         for (int i = 0; i < size; ++i) {
1376                 linear_data[i] = i / double(size - 1);
1377                 data[i] = srgb_to_linear(linear_data[i]);
1378         }
1379
1380         EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
1381         tester.get_chain()->set_intermediate_format(GL_RGB10_A2);
1382         tester.get_chain()->add_effect(new IdentityEffect());
1383         tester.get_chain()->add_effect(new BouncingIdentityEffect());
1384         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1385
1386         for (int i = 0; i < size; ++i) {
1387                 out_data[i] = linear_to_srgb(out_data[i]);
1388         }
1389
1390         // This maximum error is pretty bad; about 6.5 levels of a 10-bit sRGB
1391         // framebuffer. (Slightly more on NVIDIA cards.)
1392         expect_equal(linear_data, out_data, size, 1, 7.5e-3, 2e-5);
1393 }
1394
1395 TEST_P(WithAndWithoutComputeShaderTest, SquareRoot10bitIntermediateAccuracy) {
1396         // Note that we do the comparison in sRGB space, which is what we
1397         // typically would want; however, we do the sRGB conversion ourself
1398         // to avoid compounding errors from shader conversions into the
1399         // analysis.
1400         const int size = 4096;  // 12-bit.
1401         float linear_data[size], data[size], out_data[size];
1402
1403         for (int i = 0; i < size; ++i) {
1404                 linear_data[i] = i / double(size - 1);
1405                 data[i] = srgb_to_linear(linear_data[i]);
1406         }
1407
1408         EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
1409         tester.get_chain()->set_intermediate_format(GL_RGB10_A2, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION);
1410         if (GetParam() == "compute") {
1411                 tester.get_chain()->add_effect(new IdentityComputeEffect());
1412         } else {
1413                 tester.get_chain()->add_effect(new IdentityEffect());
1414         }
1415         tester.get_chain()->add_effect(new BouncingIdentityEffect());
1416         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1417
1418         for (int i = 0; i < size; ++i) {
1419                 out_data[i] = linear_to_srgb(out_data[i]);
1420         }
1421
1422         // This maximum error is much better; about 0.7 levels of a 10-bit sRGB
1423         // framebuffer (ideal would be 0.5). That is an order of magnitude better
1424         // than in the linear test above. The RMS error is much better, too.
1425         expect_equal(linear_data, out_data, size, 1, 7.5e-4, 5e-6);
1426 }
1427
1428 TEST(EffectChainTest, SquareRootIntermediateIsTurnedOffForNonLinearData) {
1429         const int size = 256;  // 8-bit.
1430         float data[size], out_data[size];
1431
1432         for (int i = 0; i < size; ++i) {
1433                 data[i] = i / double(size - 1);
1434         }
1435
1436         EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_601, GL_RGBA32F);
1437         tester.get_chain()->set_intermediate_format(GL_RGB8, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION);
1438         tester.get_chain()->add_effect(new PassThroughEffect());
1439         tester.get_chain()->add_effect(new BouncingPassThroughEffect());
1440         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_601);
1441
1442         // The data should be passed through nearly exactly, since there is no effect
1443         // on the path that requires linear light. (Actually, it _is_ exact modulo
1444         // fp32 errors, but the error bounds is strictly _less than_, not zero.)
1445         expect_equal(data, out_data, size, 1, 1e-6, 1e-6);
1446 }
1447
1448 // An effect that stores which program number was last run under.
1449 class RecordingIdentityEffect : public Effect {
1450 public:
1451         RecordingIdentityEffect() {}
1452         string effect_type_id() const override { return "RecordingIdentityEffect"; }
1453         string output_fragment_shader() override { return read_file("identity.frag"); }
1454
1455         GLuint last_glsl_program_num;
1456         void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num) override
1457         {
1458                 last_glsl_program_num = glsl_program_num;
1459         }
1460 };
1461
1462 TEST(EffectChainTest, ProgramsAreClonedForMultipleThreads) {
1463         float data[] = {
1464                 0.0f, 0.25f, 0.3f,
1465                 0.75f, 1.0f, 1.0f,
1466         };
1467         float out_data[6];
1468         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1469         RecordingIdentityEffect *effect = new RecordingIdentityEffect();
1470         tester.get_chain()->add_effect(effect);
1471         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1472
1473         expect_equal(data, out_data, 3, 2);
1474
1475         ASSERT_NE(0u, effect->last_glsl_program_num);
1476
1477         // Now pretend some other effect is using this program number;
1478         // ResourcePool will then need to clone it.
1479         ResourcePool *resource_pool = tester.get_chain()->get_resource_pool();
1480         GLuint master_program_num = resource_pool->use_glsl_program(effect->last_glsl_program_num);
1481         EXPECT_EQ(effect->last_glsl_program_num, master_program_num);
1482
1483         // Re-run should still give the correct data, but it should have run
1484         // with a different program.
1485         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1486         expect_equal(data, out_data, 3, 2);
1487         EXPECT_NE(effect->last_glsl_program_num, master_program_num);
1488
1489         // Release the program, and check one final time.
1490         resource_pool->unuse_glsl_program(master_program_num);
1491         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1492         expect_equal(data, out_data, 3, 2);
1493 }
1494
1495 TEST(ComputeShaderTest, Identity) {
1496         float data[] = {
1497                 0.0f, 0.25f, 0.3f,
1498                 0.75f, 1.0f, 1.0f,
1499         };
1500         float out_data[6];
1501         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1502         if (!movit_compute_shaders_supported) {
1503                 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1504                 return;
1505         }
1506         tester.get_chain()->add_effect(new IdentityComputeEffect());
1507         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1508
1509         expect_equal(data, out_data, 3, 2);
1510 }
1511
1512 // Like IdentityComputeEffect, but due to the alpha handling, this will be
1513 // the very last effect in the chain, which means we can't output it directly
1514 // to the screen.
1515 class IdentityAlphaComputeEffect : public IdentityComputeEffect {
1516         AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
1517 };
1518
1519 TEST(ComputeShaderTest, LastEffectInChain) {
1520         float data[] = {
1521                 0.0f, 0.25f, 0.3f,
1522                 0.75f, 1.0f, 1.0f,
1523         };
1524         float out_data[6];
1525         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1526         if (!movit_compute_shaders_supported) {
1527                 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1528                 return;
1529         }
1530         tester.get_chain()->add_effect(new IdentityAlphaComputeEffect());
1531         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1532
1533         expect_equal(data, out_data, 3, 2);
1534 }
1535
1536 TEST(ComputeShaderTest, Render8BitTo8Bit) {
1537         uint8_t data[] = {
1538                 14, 200, 80,
1539                 90, 100, 110,
1540         };
1541         uint8_t out_data[6];
1542         EffectChainTester tester(nullptr, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
1543         if (!movit_compute_shaders_supported) {
1544                 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1545                 return;
1546         }
1547         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 3, 2);
1548         tester.get_chain()->add_effect(new IdentityAlphaComputeEffect());
1549         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1550
1551         expect_equal(data, out_data, 3, 2);
1552 }
1553
1554 // A compute shader to mirror the inputs, in 2x2 blocks.
1555 class MirrorComputeEffect : public Effect {
1556 public:
1557         MirrorComputeEffect() {}
1558         string effect_type_id() const override { return "MirrorComputeEffect"; }
1559         bool is_compute_shader() const override { return true; }
1560         string output_fragment_shader() override { return read_file("mirror.comp"); }
1561         void get_compute_dimensions(unsigned output_width, unsigned output_height,
1562                                     unsigned *x, unsigned *y, unsigned *z) const override {
1563                 *x = output_width / 2;
1564                 *y = output_height / 2;
1565                 *z = 1;
1566         }
1567 };
1568
1569 TEST(ComputeShaderTest, ComputeThenOneToOne) {
1570         float data[] = {
1571                 0.0f, 0.25f, 0.3f, 0.8f,
1572                 0.75f, 1.0f, 1.0f, 0.2f,
1573         };
1574         float expected_data[] = {
1575                 0.8f, 0.3f, 0.25f, 0.0f,
1576                 0.2f, 1.0f, 1.0f, 0.75f,
1577         };
1578         float out_data[8];
1579         EffectChainTester tester(data, 4, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1580         tester.get_chain()->add_effect(new MirrorComputeEffect());
1581         tester.get_chain()->add_effect(new OneToOneEffect());
1582         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1583
1584         expect_equal(expected_data, out_data, 4, 2);
1585 }
1586
1587 // A compute shader that also resizes its input, taking the upper-left pixel
1588 // of every 2x2 group. (The shader is hard-coded to 4x2 input for simplicity.)
1589 class Downscale2xComputeEffect : public Effect {
1590 public:
1591         Downscale2xComputeEffect() {}
1592         string effect_type_id() const override { return "Downscale2xComputeEffect"; }
1593         bool is_compute_shader() const override { return true; }
1594         string output_fragment_shader() override { return read_file("downscale2x.comp"); }
1595         bool changes_output_size() const override { return true; }
1596         void inform_input_size(unsigned input_num, unsigned width, unsigned height) override
1597         {
1598                 this->width = width;
1599                 this->height = height;
1600         }
1601         void get_output_size(unsigned *width, unsigned *height,
1602                              unsigned *virtual_width, unsigned *virtual_height) const override {
1603                 *width = *virtual_width = this->width / 2;
1604                 *height = *virtual_height = this->height / 2;
1605         }
1606
1607 private:
1608         unsigned width, height;
1609 };
1610
1611 // Even if the compute shader is not the last effect, it's the one that should decide
1612 // the output size of the phase.
1613 TEST(ComputeShaderTest, ResizingComputeThenOneToOne) {
1614         float data[] = {
1615                 0.0f, 0.25f, 0.3f, 0.8f,
1616                 0.75f, 1.0f, 1.0f, 0.2f,
1617         };
1618         float expected_data[] = {
1619                 0.0f, 0.3f,
1620         };
1621         float out_data[2];
1622         EffectChainTester tester(nullptr, 2, 1);
1623         tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 4, 2);
1624
1625         RewritingEffect<Downscale2xComputeEffect> *downscale_effect = new RewritingEffect<Downscale2xComputeEffect>();
1626         tester.get_chain()->add_effect(downscale_effect);
1627         tester.get_chain()->add_effect(new OneToOneEffect());
1628         tester.get_chain()->add_effect(new BouncingIdentityEffect());
1629         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1630
1631         expect_equal(expected_data, out_data, 2, 1);
1632
1633         Phase *phase = downscale_effect->replaced_node->containing_phase;
1634         EXPECT_EQ(2u, phase->output_width);
1635         EXPECT_EQ(1u, phase->output_height);
1636 }
1637
1638 }  // namespace movit