778259f7da5ca7961510a9739d9915e491e21698
[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 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
139         float data[] = {
140                 0.0f, 0.25f, 0.3f,
141                 0.75f, 1.0f, 1.0f,
142         };
143         float expected_data[6] = {
144                 1.0f, 0.75f, 0.7f,
145                 0.25f, 0.0f, 0.0f,
146         };
147         float out_data[6];
148         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
149         RewritingToInvertEffect *effect = new RewritingToInvertEffect();
150         tester.get_chain()->add_effect(effect);
151         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
152
153         Node *node = effect->invert_node;
154         ASSERT_EQ(1, node->incoming_links.size());
155         ASSERT_EQ(1, node->outgoing_links.size());
156         EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
157         EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
158
159         expect_equal(expected_data, out_data, 3, 2);
160 }
161
162 // Like RewritingToInvertEffect, but splicing in a MirrorEffect instead,
163 // which does not need linear light or sRGB primaries.
164 class RewritingToMirrorEffect : public Effect {
165 public:
166         RewritingToMirrorEffect() {}
167         virtual std::string effect_type_id() const { return "RewritingToMirrorEffect"; }
168         std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
169         virtual void rewrite_graph(EffectChain *graph, Node *self) {
170                 Node *mirror_node = graph->add_node(new MirrorEffect());
171                 graph->replace_receiver(self, mirror_node);
172                 graph->replace_sender(self, mirror_node);
173
174                 self->disabled = true;
175                 this->mirror_node = mirror_node;
176         }
177
178         Node *mirror_node;
179 };
180
181 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
182         float data[] = {
183                 0.0f, 0.25f, 0.3f,
184                 0.75f, 1.0f, 1.0f,
185         };
186         float expected_data[6] = {
187                 0.3f, 0.25f, 0.0f,
188                 1.0f, 1.0f, 0.75f,
189         };
190         float out_data[6];
191         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
192         RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
193         tester.get_chain()->add_effect(effect);
194         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
195
196         Node *node = effect->mirror_node;
197         ASSERT_EQ(1, node->incoming_links.size());
198         EXPECT_EQ(0, node->outgoing_links.size());
199         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
200
201         expect_equal(expected_data, out_data, 3, 2);
202 }
203
204 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
205         float data[] = {
206                 0.0f, 0.25f, 0.3f,
207                 0.75f, 1.0f, 1.0f,
208         };
209         float expected_data[6] = {
210                 0.3f, 0.25f, 0.0f,
211                 1.0f, 1.0f, 0.75f,
212         };
213         float out_data[6];
214         EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
215         RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
216         tester.get_chain()->add_effect(effect);
217         tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
218
219         Node *node = effect->mirror_node;
220         ASSERT_EQ(1, node->incoming_links.size());
221         EXPECT_EQ(0, node->outgoing_links.size());
222         EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
223
224         expect_equal(expected_data, out_data, 3, 2);
225 }
226
227 // The identity effect needs linear light, and thus will get conversions on both sides.
228 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
229 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
230         float data[256];
231         for (unsigned i = 0; i < 256; ++i) {
232                 data[i] = i / 255.0;
233         };
234         float out_data[256];
235         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
236         tester.get_chain()->add_effect(new IdentityEffect());
237         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
238
239         expect_equal(data, out_data, 256, 1);
240 }
241
242 // Same, for the Rec. 601/709 gamma curve.
243 TEST(EffectChainTest, IdentityThroughRec709) {
244         float data[256];
245         for (unsigned i = 0; i < 256; ++i) {
246                 data[i] = i / 255.0;
247         };
248         float out_data[256];
249         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
250         tester.get_chain()->add_effect(new IdentityEffect());
251         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
252
253         expect_equal(data, out_data, 256, 1);
254 }
255
256 // Effectively scales down its input linearly by 4x (and repeating it),
257 // which is not attainable without mipmaps.
258 class MipmapNeedingEffect : public Effect {
259 public:
260         MipmapNeedingEffect() {}
261         virtual bool needs_mipmaps() const { return true; }
262         virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
263         std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
264         void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
265         {
266                 glActiveTexture(GL_TEXTURE0);
267                 check_error();
268                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
269                 check_error();
270                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
271                 check_error();
272         }
273 };
274
275 TEST(EffectChainTest, MipmapGenerationWorks) {
276         float data[] = {  // In 4x4 blocks.
277                 1.0f, 0.0f, 0.0f, 0.0f,
278                 0.0f, 0.0f, 0.0f, 0.0f,
279                 0.0f, 0.0f, 0.0f, 0.0f,
280                 0.0f, 0.0f, 0.0f, 1.0f,
281
282                 0.0f, 0.0f, 0.0f, 0.0f,
283                 0.0f, 0.5f, 0.0f, 0.0f,
284                 0.0f, 0.0f, 1.0f, 0.0f,
285                 0.0f, 0.0f, 0.0f, 0.0f,
286
287                 1.0f, 1.0f, 1.0f, 1.0f,
288                 1.0f, 1.0f, 1.0f, 1.0f,
289                 1.0f, 1.0f, 1.0f, 1.0f,
290                 1.0f, 1.0f, 1.0f, 1.0f,
291
292                 0.0f, 0.0f, 0.0f, 0.0f,
293                 0.0f, 1.0f, 1.0f, 0.0f,
294                 0.0f, 1.0f, 1.0f, 0.0f,
295                 0.0f, 0.0f, 0.0f, 0.0f,
296         };
297         float expected_data[] = {  // Repeated four times each way.
298                 0.125f,   0.125f,   0.125f,   0.125f,
299                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
300                 1.0f,     1.0f,     1.0f,     1.0f,
301                 0.25f,    0.25f,    0.25f,    0.25f,
302
303                 0.125f,   0.125f,   0.125f,   0.125f,
304                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
305                 1.0f,     1.0f,     1.0f,     1.0f,
306                 0.25f,    0.25f,    0.25f,    0.25f,
307
308                 0.125f,   0.125f,   0.125f,   0.125f,
309                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
310                 1.0f,     1.0f,     1.0f,     1.0f,
311                 0.25f,    0.25f,    0.25f,    0.25f,
312
313                 0.125f,   0.125f,   0.125f,   0.125f,
314                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
315                 1.0f,     1.0f,     1.0f,     1.0f,
316                 0.25f,    0.25f,    0.25f,    0.25f,
317         };
318         float out_data[4 * 16];
319         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
320         tester.get_chain()->add_effect(new MipmapNeedingEffect());
321         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
322
323         expect_equal(expected_data, out_data, 4, 16);
324 }
325
326 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
327         float data[] = {  // In 4x4 blocks.
328                 1.0f, 0.0f, 0.0f, 0.0f,
329                 0.0f, 0.0f, 0.0f, 0.0f,
330                 0.0f, 0.0f, 0.0f, 0.0f,
331                 0.0f, 0.0f, 0.0f, 1.0f,
332
333                 0.0f, 0.0f, 0.0f, 0.0f,
334                 0.0f, 0.5f, 0.0f, 0.0f,
335                 0.0f, 0.0f, 1.0f, 0.0f,
336                 0.0f, 0.0f, 0.0f, 0.0f,
337
338                 1.0f, 1.0f, 1.0f, 1.0f,
339                 1.0f, 1.0f, 1.0f, 1.0f,
340                 1.0f, 1.0f, 1.0f, 1.0f,
341                 1.0f, 1.0f, 1.0f, 1.0f,
342
343                 0.0f, 0.0f, 0.0f, 0.0f,
344                 0.0f, 1.0f, 1.0f, 0.0f,
345                 0.0f, 1.0f, 1.0f, 0.0f,
346                 0.0f, 0.0f, 0.0f, 0.0f,
347         };
348         float expected_data[] = {  // Repeated four times horizontaly, interpolated vertically.
349                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
350                 0.1250f, 0.1250f, 0.1250f, 0.1250f,
351                 0.1211f, 0.1211f, 0.1211f, 0.1211f,
352                 0.1133f, 0.1133f, 0.1133f, 0.1133f,
353                 0.1055f, 0.1055f, 0.1055f, 0.1055f,
354                 0.0977f, 0.0977f, 0.0977f, 0.0977f,
355                 0.2070f, 0.2070f, 0.2070f, 0.2070f,
356                 0.4336f, 0.4336f, 0.4336f, 0.4336f,
357                 0.6602f, 0.6602f, 0.6602f, 0.6602f,
358                 0.8867f, 0.8867f, 0.8867f, 0.8867f,
359                 0.9062f, 0.9062f, 0.9062f, 0.9062f,
360                 0.7188f, 0.7188f, 0.7188f, 0.7188f,
361                 0.5312f, 0.5312f, 0.5312f, 0.5312f,
362                 0.3438f, 0.3438f, 0.3438f, 0.3438f,
363                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
364                 0.2500f, 0.2500f, 0.2500f, 0.2500f,
365         };
366         float out_data[4 * 16];
367
368         ResizeEffect *downscale = new ResizeEffect();
369         ASSERT_TRUE(downscale->set_int("width", 1));
370         ASSERT_TRUE(downscale->set_int("height", 4));
371
372         ResizeEffect *upscale = new ResizeEffect();
373         ASSERT_TRUE(upscale->set_int("width", 4));
374         ASSERT_TRUE(upscale->set_int("height", 16));
375
376         EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
377         tester.get_chain()->add_effect(downscale);
378         tester.get_chain()->add_effect(upscale);
379         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
380
381         expect_equal(expected_data, out_data, 4, 16);
382 }