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"
9 #include "resize_effect.h"
11 #include "test_util.h"
13 TEST(EffectChainTest, EmptyChain) {
19 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
20 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
22 expect_equal(data, out_data, 3, 2);
25 // An effect that does nothing.
26 class IdentityEffect : public Effect {
29 virtual std::string effect_type_id() const { return "IdentityEffect"; }
30 std::string output_fragment_shader() { return read_file("identity.frag"); }
33 TEST(EffectChainTest, Identity) {
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);
43 expect_equal(data, out_data, 3, 2);
46 // An effect that does nothing, but requests texture bounce.
47 class BouncingIdentityEffect : public Effect {
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; }
55 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
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);
65 expect_equal(data, out_data, 3, 2);
68 TEST(MirrorTest, BasicTest) {
73 float expected_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);
82 expect_equal(expected_data, out_data, 3, 2);
85 // A dummy effect that inverts its input.
86 class InvertEffect : public Effect {
89 virtual std::string effect_type_id() const { return "InvertEffect"; }
90 std::string output_fragment_shader() { return read_file("invert_effect.frag"); }
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
97 class RewritingToInvertEffect : public Effect {
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);
107 self->disabled = true;
108 this->invert_node = invert_node;
114 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
119 float expected_data[6] = {
120 1.0f, 0.9771f, 0.9673f,
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);
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());
135 expect_equal(expected_data, out_data, 3, 2);
138 // Like RewritingToInvertEffect, but splicing in a MirrorEffect instead,
139 // which does not need linear light.
140 class RewritingToMirrorEffect : public Effect {
142 RewritingToMirrorEffect() {}
143 virtual std::string effect_type_id() const { return "RewritingToMirrorEffect"; }
144 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
145 virtual void rewrite_graph(EffectChain *graph, Node *self) {
146 Node *mirror_node = graph->add_node(new MirrorEffect());
147 graph->replace_receiver(self, mirror_node);
148 graph->replace_sender(self, mirror_node);
150 self->disabled = true;
151 this->mirror_node = mirror_node;
157 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
162 float expected_data[6] = {
167 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
168 RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
169 tester.get_chain()->add_effect(effect);
170 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
172 Node *node = effect->mirror_node;
173 ASSERT_EQ(1, node->incoming_links.size());
174 EXPECT_EQ(0, node->outgoing_links.size());
175 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
177 expect_equal(expected_data, out_data, 3, 2);
180 // The identity effect needs linear light, and thus will get conversions on both sides.
181 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
182 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
184 for (unsigned i = 0; i < 256; ++i) {
188 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
189 tester.get_chain()->add_effect(new IdentityEffect());
190 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
192 expect_equal(data, out_data, 256, 1);
195 // Same, for the Rec. 601/709 gamma curve.
196 TEST(EffectChainTest, IdentityThroughRec709) {
198 for (unsigned i = 0; i < 256; ++i) {
202 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
203 tester.get_chain()->add_effect(new IdentityEffect());
204 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
206 expect_equal(data, out_data, 256, 1);
209 // Effectively scales down its input linearly by 4x (and repeating it),
210 // which is not attainable without mipmaps.
211 class MipmapNeedingEffect : public Effect {
213 MipmapNeedingEffect() {}
214 virtual bool needs_mipmaps() const { return true; }
215 virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
216 std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
217 void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
219 glActiveTexture(GL_TEXTURE0);
221 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
223 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
228 TEST(EffectChainTest, MipmapGenerationWorks) {
229 float data[] = { // In 4x4 blocks.
230 1.0f, 0.0f, 0.0f, 0.0f,
231 0.0f, 0.0f, 0.0f, 0.0f,
232 0.0f, 0.0f, 0.0f, 0.0f,
233 0.0f, 0.0f, 0.0f, 1.0f,
235 0.0f, 0.0f, 0.0f, 0.0f,
236 0.0f, 0.5f, 0.0f, 0.0f,
237 0.0f, 0.0f, 1.0f, 0.0f,
238 0.0f, 0.0f, 0.0f, 0.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 1.0f, 1.0f, 1.0f, 1.0f,
245 0.0f, 0.0f, 0.0f, 0.0f,
246 0.0f, 1.0f, 1.0f, 0.0f,
247 0.0f, 1.0f, 1.0f, 0.0f,
248 0.0f, 0.0f, 0.0f, 0.0f,
250 float expected_data[] = { // Repeated four times each way.
251 0.125f, 0.125f, 0.125f, 0.125f,
252 0.09375f, 0.09375f, 0.09375f, 0.09375f,
253 1.0f, 1.0f, 1.0f, 1.0f,
254 0.25f, 0.25f, 0.25f, 0.25f,
256 0.125f, 0.125f, 0.125f, 0.125f,
257 0.09375f, 0.09375f, 0.09375f, 0.09375f,
258 1.0f, 1.0f, 1.0f, 1.0f,
259 0.25f, 0.25f, 0.25f, 0.25f,
261 0.125f, 0.125f, 0.125f, 0.125f,
262 0.09375f, 0.09375f, 0.09375f, 0.09375f,
263 1.0f, 1.0f, 1.0f, 1.0f,
264 0.25f, 0.25f, 0.25f, 0.25f,
266 0.125f, 0.125f, 0.125f, 0.125f,
267 0.09375f, 0.09375f, 0.09375f, 0.09375f,
268 1.0f, 1.0f, 1.0f, 1.0f,
269 0.25f, 0.25f, 0.25f, 0.25f,
271 float out_data[4 * 16];
272 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
273 tester.get_chain()->add_effect(new MipmapNeedingEffect());
274 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
276 expect_equal(expected_data, out_data, 4, 16);
279 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
280 float data[] = { // In 4x4 blocks.
281 1.0f, 0.0f, 0.0f, 0.0f,
282 0.0f, 0.0f, 0.0f, 0.0f,
283 0.0f, 0.0f, 0.0f, 0.0f,
284 0.0f, 0.0f, 0.0f, 1.0f,
286 0.0f, 0.0f, 0.0f, 0.0f,
287 0.0f, 0.5f, 0.0f, 0.0f,
288 0.0f, 0.0f, 1.0f, 0.0f,
289 0.0f, 0.0f, 0.0f, 0.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 1.0f, 1.0f, 1.0f, 1.0f,
296 0.0f, 0.0f, 0.0f, 0.0f,
297 0.0f, 1.0f, 1.0f, 0.0f,
298 0.0f, 1.0f, 1.0f, 0.0f,
299 0.0f, 0.0f, 0.0f, 0.0f,
301 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
302 0.1250f, 0.1250f, 0.1250f, 0.1250f,
303 0.1250f, 0.1250f, 0.1250f, 0.1250f,
304 0.1211f, 0.1211f, 0.1211f, 0.1211f,
305 0.1133f, 0.1133f, 0.1133f, 0.1133f,
306 0.1055f, 0.1055f, 0.1055f, 0.1055f,
307 0.0977f, 0.0977f, 0.0977f, 0.0977f,
308 0.2070f, 0.2070f, 0.2070f, 0.2070f,
309 0.4336f, 0.4336f, 0.4336f, 0.4336f,
310 0.6602f, 0.6602f, 0.6602f, 0.6602f,
311 0.8867f, 0.8867f, 0.8867f, 0.8867f,
312 0.9062f, 0.9062f, 0.9062f, 0.9062f,
313 0.7188f, 0.7188f, 0.7188f, 0.7188f,
314 0.5312f, 0.5312f, 0.5312f, 0.5312f,
315 0.3438f, 0.3438f, 0.3438f, 0.3438f,
316 0.2500f, 0.2500f, 0.2500f, 0.2500f,
317 0.2500f, 0.2500f, 0.2500f, 0.2500f,
319 float out_data[4 * 16];
321 ResizeEffect *downscale = new ResizeEffect();
322 ASSERT_TRUE(downscale->set_int("width", 1));
323 ASSERT_TRUE(downscale->set_int("height", 4));
325 ResizeEffect *upscale = new ResizeEffect();
326 ASSERT_TRUE(upscale->set_int("width", 4));
327 ASSERT_TRUE(upscale->set_int("height", 16));
329 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
330 tester.get_chain()->add_effect(downscale);
331 tester.get_chain()->add_effect(upscale);
332 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
334 expect_equal(expected_data, out_data, 4, 16);