Add a unit test for BlurEffect.
[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 "effect_chain.h"
6 #include "flat_input.h"
7 #include "gtest/gtest.h"
8 #include "mirror_effect.h"
9 #include "resize_effect.h"
10 #include "opengl.h"
11 #include "test_util.h"
12
13 TEST(EffectChainTest, EmptyChain) {
14         float data[] = {
15                 0.0f, 0.25f, 0.3f,
16                 0.75f, 1.0f, 1.0f,
17         };
18         float out_data[6];
19         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
20         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
21
22         expect_equal(data, out_data, 3, 2);
23 }
24
25 // An effect that does nothing.
26 class IdentityEffect : public Effect {
27 public:
28         IdentityEffect() {}
29         virtual std::string effect_type_id() const { return "IdentityEffect"; }
30         std::string output_fragment_shader() { return read_file("identity.frag"); }
31 };
32
33 TEST(EffectChainTest, Identity) {
34         float data[] = {
35                 0.0f, 0.25f, 0.3f,
36                 0.75f, 1.0f, 1.0f,
37         };
38         float out_data[6];
39         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
40         tester.get_chain()->add_effect(new IdentityEffect());
41         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
42
43         expect_equal(data, out_data, 3, 2);
44 }
45
46 // An effect that does nothing, but requests texture bounce.
47 class BouncingIdentityEffect : public Effect {
48 public:
49         BouncingIdentityEffect() {}
50         virtual std::string effect_type_id() const { return "IdentityEffect"; }
51         std::string output_fragment_shader() { return read_file("identity.frag"); }
52         bool needs_texture_bounce() const { return true; }
53 };
54
55 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
56         float data[] = {
57                 0.0f, 0.25f, 0.3f,
58                 0.75f, 1.0f, 1.0f,
59         };
60         float out_data[6];
61         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
62         tester.get_chain()->add_effect(new BouncingIdentityEffect());
63         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
64
65         expect_equal(data, out_data, 3, 2);
66 }
67
68 TEST(MirrorTest, BasicTest) {
69         float data[] = {
70                 0.0f, 0.25f, 0.3f,
71                 0.75f, 1.0f, 1.0f,
72         };
73         float expected_data[6] = {
74                 0.3f, 0.25f, 0.0f,
75                 1.0f, 1.0f, 0.75f,
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 MirrorEffect());
80         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
81
82         expect_equal(expected_data, out_data, 3, 2);
83 }
84
85 // A dummy effect that inverts its input.
86 class InvertEffect : public Effect {
87 public:
88         InvertEffect() {}
89         virtual std::string effect_type_id() const { return "InvertEffect"; }
90         std::string output_fragment_shader() { return read_file("invert_effect.frag"); }
91 };
92
93 // Like IdentityEffect, but rewrites itself out of the loop,
94 // splicing in a InvertEffect instead. Also stores the new node,
95 // so we later can check that there are gamma conversion effects
96 // on both sides.
97 class RewritingToInvertEffect : public Effect {
98 public:
99         RewritingToInvertEffect() {}
100         virtual std::string effect_type_id() const { return "RewritingToInvertEffect"; }
101         std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
102         virtual void rewrite_graph(EffectChain *graph, Node *self) {
103                 Node *invert_node = graph->add_node(new InvertEffect());
104                 graph->replace_receiver(self, invert_node);
105                 graph->replace_sender(self, invert_node);
106
107                 self->disabled = true;
108                 this->invert_node = invert_node;
109         }
110
111         Node *invert_node;      
112 };
113
114 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
115         float data[] = {
116                 0.0f, 0.25f, 0.3f,
117                 0.75f, 1.0f, 1.0f,
118         };
119         float expected_data[6] = {
120                 1.0f, 0.9771f, 0.9673f,
121                 0.7192f, 0.0f, 0.0f,
122         };
123         float out_data[6];
124         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
125         RewritingToInvertEffect *effect = new RewritingToInvertEffect();
126         tester.get_chain()->add_effect(effect);
127         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
128
129         Node *node = effect->invert_node;
130         ASSERT_EQ(1, node->incoming_links.size());
131         ASSERT_EQ(1, node->outgoing_links.size());
132         EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
133         EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
134
135         expect_equal(expected_data, out_data, 3, 2);
136 }
137
138 // Like RewritingToInvertEffect, but splicing in a MirrorEffect instead,
139 // which does not need linear light.
140 class RewritingToMirrorEffect : public Effect {
141 public:
142         RewritingToMirrorEffect() {}
143         virtual std::string effect_type_id() const { return "RewritingToMirrorEffect"; }
144         std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
145         virtual void rewrite_graph(EffectChain *graph, Node *self) {
146                 Node *mirror_node = graph->add_node(new MirrorEffect());
147                 graph->replace_receiver(self, mirror_node);
148                 graph->replace_sender(self, mirror_node);
149
150                 self->disabled = true;
151                 this->mirror_node = mirror_node;
152         }
153
154         Node *mirror_node;
155 };
156
157 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
158         float data[] = {
159                 0.0f, 0.25f, 0.3f,
160                 0.75f, 1.0f, 1.0f,
161         };
162         float expected_data[6] = {
163                 0.3f, 0.25f, 0.0f,
164                 1.0f, 1.0f, 0.75f,
165         };
166         float out_data[6];
167         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
168         RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
169         tester.get_chain()->add_effect(effect);
170         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
171
172         Node *node = effect->mirror_node;
173         ASSERT_EQ(1, node->incoming_links.size());
174         EXPECT_EQ(0, node->outgoing_links.size());
175         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
176
177         expect_equal(expected_data, out_data, 3, 2);
178 }
179
180 // The identity effect needs linear light, and thus will get conversions on both sides.
181 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
182 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
183         float data[256];
184         for (unsigned i = 0; i < 256; ++i) {
185                 data[i] = i / 255.0;
186         };
187         float out_data[256];
188         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
189         tester.get_chain()->add_effect(new IdentityEffect());
190         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
191
192         expect_equal(data, out_data, 256, 1);
193 }
194
195 // Same, for the Rec. 601/709 gamma curve.
196 TEST(EffectChainTest, IdentityThroughRec709) {
197         float data[256];
198         for (unsigned i = 0; i < 256; ++i) {
199                 data[i] = i / 255.0;
200         };
201         float out_data[256];
202         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
203         tester.get_chain()->add_effect(new IdentityEffect());
204         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
205
206         expect_equal(data, out_data, 256, 1);
207 }
208
209 // Effectively scales down its input linearly by 4x (and repeating it),
210 // which is not attainable without mipmaps.
211 class MipmapNeedingEffect : public Effect {
212 public:
213         MipmapNeedingEffect() {}
214         virtual bool needs_mipmaps() const { return true; }
215         virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
216         std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
217         void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
218         {
219                 glActiveTexture(GL_TEXTURE0);
220                 check_error();
221                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
222                 check_error();
223                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
224                 check_error();
225         }
226 };
227
228 TEST(EffectChainTest, MipmapGenerationWorks) {
229         float data[] = {  // In 4x4 blocks.
230                 1.0f, 0.0f, 0.0f, 0.0f,
231                 0.0f, 0.0f, 0.0f, 0.0f,
232                 0.0f, 0.0f, 0.0f, 0.0f,
233                 0.0f, 0.0f, 0.0f, 1.0f,
234
235                 0.0f, 0.0f, 0.0f, 0.0f,
236                 0.0f, 0.5f, 0.0f, 0.0f,
237                 0.0f, 0.0f, 1.0f, 0.0f,
238                 0.0f, 0.0f, 0.0f, 0.0f,
239
240                 1.0f, 1.0f, 1.0f, 1.0f,
241                 1.0f, 1.0f, 1.0f, 1.0f,
242                 1.0f, 1.0f, 1.0f, 1.0f,
243                 1.0f, 1.0f, 1.0f, 1.0f,
244
245                 0.0f, 0.0f, 0.0f, 0.0f,
246                 0.0f, 1.0f, 1.0f, 0.0f,
247                 0.0f, 1.0f, 1.0f, 0.0f,
248                 0.0f, 0.0f, 0.0f, 0.0f,
249         };
250         float expected_data[] = {  // Repeated four times each way.
251                 0.125f,   0.125f,   0.125f,   0.125f,
252                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
253                 1.0f,     1.0f,     1.0f,     1.0f,
254                 0.25f,    0.25f,    0.25f,    0.25f,
255
256                 0.125f,   0.125f,   0.125f,   0.125f,
257                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
258                 1.0f,     1.0f,     1.0f,     1.0f,
259                 0.25f,    0.25f,    0.25f,    0.25f,
260
261                 0.125f,   0.125f,   0.125f,   0.125f,
262                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
263                 1.0f,     1.0f,     1.0f,     1.0f,
264                 0.25f,    0.25f,    0.25f,    0.25f,
265
266                 0.125f,   0.125f,   0.125f,   0.125f,
267                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
268                 1.0f,     1.0f,     1.0f,     1.0f,
269                 0.25f,    0.25f,    0.25f,    0.25f,
270         };
271         float out_data[4 * 16];
272         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
273         tester.get_chain()->add_effect(new MipmapNeedingEffect());
274         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
275
276         expect_equal(expected_data, out_data, 4, 16);
277 }
278
279 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
280         float data[] = {  // In 4x4 blocks.
281                 1.0f, 0.0f, 0.0f, 0.0f,
282                 0.0f, 0.0f, 0.0f, 0.0f,
283                 0.0f, 0.0f, 0.0f, 0.0f,
284                 0.0f, 0.0f, 0.0f, 1.0f,
285
286                 0.0f, 0.0f, 0.0f, 0.0f,
287                 0.0f, 0.5f, 0.0f, 0.0f,
288                 0.0f, 0.0f, 1.0f, 0.0f,
289                 0.0f, 0.0f, 0.0f, 0.0f,
290
291                 1.0f, 1.0f, 1.0f, 1.0f,
292                 1.0f, 1.0f, 1.0f, 1.0f,
293                 1.0f, 1.0f, 1.0f, 1.0f,
294                 1.0f, 1.0f, 1.0f, 1.0f,
295
296                 0.0f, 0.0f, 0.0f, 0.0f,
297                 0.0f, 1.0f, 1.0f, 0.0f,
298                 0.0f, 1.0f, 1.0f, 0.0f,
299                 0.0f, 0.0f, 0.0f, 0.0f,
300         };
301         float expected_data[] = {  // Repeated four times horizontaly, interpolated vertically.
302                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
303                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
304                 0.1211f, 0.1211f, 0.1211f, 0.1211f,
305                 0.1133f, 0.1133f, 0.1133f, 0.1133f,
306                 0.1055f, 0.1055f, 0.1055f, 0.1055f,
307                 0.0977f, 0.0977f, 0.0977f, 0.0977f,
308                 0.2070f, 0.2070f, 0.2070f, 0.2070f,
309                 0.4336f, 0.4336f, 0.4336f, 0.4336f,
310                 0.6602f, 0.6602f, 0.6602f, 0.6602f,
311                 0.8867f, 0.8867f, 0.8867f, 0.8867f,
312                 0.9062f, 0.9062f, 0.9062f, 0.9062f,
313                 0.7188f, 0.7188f, 0.7188f, 0.7188f,
314                 0.5312f, 0.5312f, 0.5312f, 0.5312f,
315                 0.3438f, 0.3438f, 0.3438f, 0.3438f,
316                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
317                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
318         };
319         float out_data[4 * 16];
320
321         ResizeEffect *downscale = new ResizeEffect();
322         ASSERT_TRUE(downscale->set_int("width", 1));
323         ASSERT_TRUE(downscale->set_int("height", 4));
324
325         ResizeEffect *upscale = new ResizeEffect();
326         ASSERT_TRUE(upscale->set_int("width", 4));
327         ASSERT_TRUE(upscale->set_int("height", 16));
328
329         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
330         tester.get_chain()->add_effect(downscale);
331         tester.get_chain()->add_effect(upscale);
332         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
333
334         expect_equal(expected_data, out_data, 4, 16);
335 }