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