Add tests to check that rewriting works, and that gamma conversions are or are not...
[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 }