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