1 // Unit tests for EffectChain.
3 // Note that this also contains the tests for some of the simpler effects.
5 #include "effect_chain.h"
6 #include "flat_input.h"
7 #include "gtest/gtest.h"
8 #include "mirror_effect.h"
10 #include "test_util.h"
12 TEST(EffectChainTest, EmptyChain) {
18 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
19 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
21 expect_equal(data, out_data, 3, 2);
24 // An effect that does nothing.
25 class IdentityEffect : public Effect {
28 virtual std::string effect_type_id() const { return "IdentityEffect"; }
29 std::string output_fragment_shader() { return read_file("identity.frag"); }
32 TEST(EffectChainTest, Identity) {
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);
42 expect_equal(data, out_data, 3, 2);
45 // An effect that does nothing, but requests texture bounce.
46 class BouncingIdentityEffect : public Effect {
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; }
54 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
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);
64 expect_equal(data, out_data, 3, 2);
67 TEST(MirrorTest, BasicTest) {
72 float expected_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);
81 expect_equal(expected_data, out_data, 3, 2);
84 // A dummy effect that inverts its input.
85 class InvertEffect : public Effect {
88 virtual std::string effect_type_id() const { return "InvertEffect"; }
89 std::string output_fragment_shader() { return read_file("invert_effect.frag"); }
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
96 class RewritingToInvertEffect : public Effect {
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);
106 self->disabled = true;
107 this->invert_node = invert_node;
113 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
118 float expected_data[6] = {
119 1.0f, 0.9771f, 0.9673f,
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);
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());
134 expect_equal(expected_data, out_data, 3, 2);
137 // Like RewritingToInvertEffect, but splicing in a MirrorEffect instead,
138 // which does not need linear light.
139 class RewritingToMirrorEffect : public Effect {
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);
149 self->disabled = true;
150 this->mirror_node = mirror_node;
156 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
161 float expected_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);
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());
176 expect_equal(expected_data, out_data, 3, 2);
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) {
183 for (unsigned i = 0; i < 256; ++i) {
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);
191 expect_equal(data, out_data, 256, 1);
194 // Same, for the Rec. 601/709 gamma curve.
195 TEST(EffectChainTest, IdentityThroughRec709) {
197 for (unsigned i = 0; i < 256; ++i) {
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);
205 expect_equal(data, out_data, 256, 1);
208 // Effectively scales down its input linearly by 4x (and repeating it),
209 // which is not attainable without mipmaps.
210 class MipmapNeedingEffect : public Effect {
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)
218 glActiveTexture(GL_TEXTURE0);
220 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
222 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
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,
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,
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,
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,
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,
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,
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,
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,
270 float out_data[16 * 4];
271 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
272 tester.get_chain()->add_effect(new MipmapNeedingEffect());
273 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
275 expect_equal(expected_data, out_data, 4, 16);