1 // Unit tests for EffectChain.
3 // Note that this also contains the tests for some of the simpler effects.
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"
14 TEST(EffectChainTest, EmptyChain) {
20 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
21 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
23 expect_equal(data, out_data, 3, 2);
26 // An effect that does nothing.
27 class IdentityEffect : public Effect {
30 virtual std::string effect_type_id() const { return "IdentityEffect"; }
31 std::string output_fragment_shader() { return read_file("identity.frag"); }
34 TEST(EffectChainTest, Identity) {
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);
44 expect_equal(data, out_data, 3, 2);
47 // An effect that does nothing, but requests texture bounce.
48 class BouncingIdentityEffect : public Effect {
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; }
56 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
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);
66 expect_equal(data, out_data, 3, 2);
69 TEST(MirrorTest, BasicTest) {
74 float expected_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);
83 expect_equal(expected_data, out_data, 3, 2);
86 // A dummy effect that inverts its input.
87 class InvertEffect : public Effect {
90 virtual std::string effect_type_id() const { return "InvertEffect"; }
91 std::string output_fragment_shader() { return read_file("invert_effect.frag"); }
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
98 class RewritingToInvertEffect : public Effect {
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);
108 self->disabled = true;
109 this->invert_node = invert_node;
115 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
120 float expected_data[6] = {
121 1.0f, 0.9771f, 0.9673f,
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);
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());
136 expect_equal(expected_data, out_data, 3, 2);
139 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
140 unsigned char data[] = {
144 float expected_data[4] = {
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);
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());
161 expect_equal(expected_data, out_data, 2, 2);
164 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
169 float expected_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);
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());
185 expect_equal(expected_data, out_data, 3, 2);
188 // A fake input that can change its output colorspace and gamma between instantiation
190 class UnknownColorspaceInput : public FlatInput {
192 UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
193 : FlatInput(format, pixel_format, type, width, height),
194 overridden_color_space(format.color_space),
195 overridden_gamma_curve(format.gamma_curve) {}
196 virtual std::string effect_type_id() const { return "UnknownColorspaceInput"; }
198 void set_color_space(Colorspace colorspace) {
199 overridden_color_space = colorspace;
201 void set_gamma_curve(GammaCurve gamma_curve) {
202 overridden_gamma_curve = gamma_curve;
204 Colorspace get_color_space() const { return overridden_color_space; }
205 GammaCurve get_gamma_curve() const { return overridden_gamma_curve; }
208 Colorspace overridden_color_space;
209 GammaCurve overridden_gamma_curve;
212 TEST(EffectChainTester, HandlesInputChangingColorspace) {
221 float out_data[size];
223 EffectChainTester tester(NULL, 4, 1, FORMAT_GRAYSCALE);
225 // First say that we have sRGB, linear input.
227 format.color_space = COLORSPACE_sRGB;
228 format.gamma_curve = GAMMA_LINEAR;
230 UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
231 input->set_pixel_data(data);
232 tester.get_chain()->add_input(input);
234 // Now we change to Rec. 601 input.
235 input->set_color_space(COLORSPACE_REC_601_625);
236 input->set_gamma_curve(GAMMA_REC_601);
238 // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
239 tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
240 expect_equal(data, out_data, 4, 1);
243 // Like RewritingToInvertEffect, but splicing in a MirrorEffect instead,
244 // which does not need linear light or sRGB primaries.
245 class RewritingToMirrorEffect : public Effect {
247 RewritingToMirrorEffect() {}
248 virtual std::string effect_type_id() const { return "RewritingToMirrorEffect"; }
249 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
250 virtual void rewrite_graph(EffectChain *graph, Node *self) {
251 Node *mirror_node = graph->add_node(new MirrorEffect());
252 graph->replace_receiver(self, mirror_node);
253 graph->replace_sender(self, mirror_node);
255 self->disabled = true;
256 this->mirror_node = mirror_node;
262 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
267 float expected_data[6] = {
272 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
273 RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
274 tester.get_chain()->add_effect(effect);
275 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
277 Node *node = effect->mirror_node;
278 ASSERT_EQ(1, node->incoming_links.size());
279 EXPECT_EQ(0, node->outgoing_links.size());
280 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
282 expect_equal(expected_data, out_data, 3, 2);
285 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
290 float expected_data[6] = {
295 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
296 RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
297 tester.get_chain()->add_effect(effect);
298 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
300 Node *node = effect->mirror_node;
301 ASSERT_EQ(1, node->incoming_links.size());
302 EXPECT_EQ(0, node->outgoing_links.size());
303 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
305 expect_equal(expected_data, out_data, 3, 2);
308 // The identity effect needs linear light, and thus will get conversions on both sides.
309 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
310 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
312 for (unsigned i = 0; i < 256; ++i) {
316 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
317 tester.get_chain()->add_effect(new IdentityEffect());
318 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
320 expect_equal(data, out_data, 256, 1);
323 // Same, but uses the forward sRGB table from the GPU.
324 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
325 unsigned char data[256];
326 float expected_data[256];
327 for (unsigned i = 0; i < 256; ++i) {
329 expected_data[i] = i / 255.0;
332 EffectChainTester tester(NULL, 256, 1);
333 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
334 tester.get_chain()->add_effect(new IdentityEffect());
335 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
337 expect_equal(expected_data, out_data, 256, 1);
340 // Same, for the Rec. 601/709 gamma curve.
341 TEST(EffectChainTest, IdentityThroughRec709) {
343 for (unsigned i = 0; i < 256; ++i) {
347 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
348 tester.get_chain()->add_effect(new IdentityEffect());
349 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
351 expect_equal(data, out_data, 256, 1);
354 // Effectively scales down its input linearly by 4x (and repeating it),
355 // which is not attainable without mipmaps.
356 class MipmapNeedingEffect : public Effect {
358 MipmapNeedingEffect() {}
359 virtual bool needs_mipmaps() const { return true; }
360 virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
361 std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
362 void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
364 glActiveTexture(GL_TEXTURE0);
366 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
368 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
373 TEST(EffectChainTest, MipmapGenerationWorks) {
374 float data[] = { // In 4x4 blocks.
375 1.0f, 0.0f, 0.0f, 0.0f,
376 0.0f, 0.0f, 0.0f, 0.0f,
377 0.0f, 0.0f, 0.0f, 0.0f,
378 0.0f, 0.0f, 0.0f, 1.0f,
380 0.0f, 0.0f, 0.0f, 0.0f,
381 0.0f, 0.5f, 0.0f, 0.0f,
382 0.0f, 0.0f, 1.0f, 0.0f,
383 0.0f, 0.0f, 0.0f, 0.0f,
385 1.0f, 1.0f, 1.0f, 1.0f,
386 1.0f, 1.0f, 1.0f, 1.0f,
387 1.0f, 1.0f, 1.0f, 1.0f,
388 1.0f, 1.0f, 1.0f, 1.0f,
390 0.0f, 0.0f, 0.0f, 0.0f,
391 0.0f, 1.0f, 1.0f, 0.0f,
392 0.0f, 1.0f, 1.0f, 0.0f,
393 0.0f, 0.0f, 0.0f, 0.0f,
395 float expected_data[] = { // Repeated four times each way.
396 0.125f, 0.125f, 0.125f, 0.125f,
397 0.09375f, 0.09375f, 0.09375f, 0.09375f,
398 1.0f, 1.0f, 1.0f, 1.0f,
399 0.25f, 0.25f, 0.25f, 0.25f,
401 0.125f, 0.125f, 0.125f, 0.125f,
402 0.09375f, 0.09375f, 0.09375f, 0.09375f,
403 1.0f, 1.0f, 1.0f, 1.0f,
404 0.25f, 0.25f, 0.25f, 0.25f,
406 0.125f, 0.125f, 0.125f, 0.125f,
407 0.09375f, 0.09375f, 0.09375f, 0.09375f,
408 1.0f, 1.0f, 1.0f, 1.0f,
409 0.25f, 0.25f, 0.25f, 0.25f,
411 0.125f, 0.125f, 0.125f, 0.125f,
412 0.09375f, 0.09375f, 0.09375f, 0.09375f,
413 1.0f, 1.0f, 1.0f, 1.0f,
414 0.25f, 0.25f, 0.25f, 0.25f,
416 float out_data[4 * 16];
417 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
418 tester.get_chain()->add_effect(new MipmapNeedingEffect());
419 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
421 expect_equal(expected_data, out_data, 4, 16);
424 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
425 float data[] = { // In 4x4 blocks.
426 1.0f, 0.0f, 0.0f, 0.0f,
427 0.0f, 0.0f, 0.0f, 0.0f,
428 0.0f, 0.0f, 0.0f, 0.0f,
429 0.0f, 0.0f, 0.0f, 1.0f,
431 0.0f, 0.0f, 0.0f, 0.0f,
432 0.0f, 0.5f, 0.0f, 0.0f,
433 0.0f, 0.0f, 1.0f, 0.0f,
434 0.0f, 0.0f, 0.0f, 0.0f,
436 1.0f, 1.0f, 1.0f, 1.0f,
437 1.0f, 1.0f, 1.0f, 1.0f,
438 1.0f, 1.0f, 1.0f, 1.0f,
439 1.0f, 1.0f, 1.0f, 1.0f,
441 0.0f, 0.0f, 0.0f, 0.0f,
442 0.0f, 1.0f, 1.0f, 0.0f,
443 0.0f, 1.0f, 1.0f, 0.0f,
444 0.0f, 0.0f, 0.0f, 0.0f,
446 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
447 0.1250f, 0.1250f, 0.1250f, 0.1250f,
448 0.1250f, 0.1250f, 0.1250f, 0.1250f,
449 0.1211f, 0.1211f, 0.1211f, 0.1211f,
450 0.1133f, 0.1133f, 0.1133f, 0.1133f,
451 0.1055f, 0.1055f, 0.1055f, 0.1055f,
452 0.0977f, 0.0977f, 0.0977f, 0.0977f,
453 0.2070f, 0.2070f, 0.2070f, 0.2070f,
454 0.4336f, 0.4336f, 0.4336f, 0.4336f,
455 0.6602f, 0.6602f, 0.6602f, 0.6602f,
456 0.8867f, 0.8867f, 0.8867f, 0.8867f,
457 0.9062f, 0.9062f, 0.9062f, 0.9062f,
458 0.7188f, 0.7188f, 0.7188f, 0.7188f,
459 0.5312f, 0.5312f, 0.5312f, 0.5312f,
460 0.3438f, 0.3438f, 0.3438f, 0.3438f,
461 0.2500f, 0.2500f, 0.2500f, 0.2500f,
462 0.2500f, 0.2500f, 0.2500f, 0.2500f,
464 float out_data[4 * 16];
466 ResizeEffect *downscale = new ResizeEffect();
467 ASSERT_TRUE(downscale->set_int("width", 1));
468 ASSERT_TRUE(downscale->set_int("height", 4));
470 ResizeEffect *upscale = new ResizeEffect();
471 ASSERT_TRUE(upscale->set_int("width", 4));
472 ASSERT_TRUE(upscale->set_int("height", 16));
474 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
475 tester.get_chain()->add_effect(downscale);
476 tester.get_chain()->add_effect(upscale);
477 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
479 expect_equal(expected_data, out_data, 4, 16);