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