1 // Unit tests for EffectChain.
3 // Note that this also contains the tests for some of the simpler effects.
10 #include "effect_chain.h"
11 #include "flat_input.h"
12 #include "gtest/gtest.h"
14 #include "mirror_effect.h"
15 #include "resize_effect.h"
16 #include "test_util.h"
19 TEST(EffectChainTest, EmptyChain) {
25 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
26 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
28 expect_equal(data, out_data, 3, 2);
31 // An effect that does nothing.
32 class IdentityEffect : public Effect {
35 virtual std::string effect_type_id() const { return "IdentityEffect"; }
36 std::string output_fragment_shader() { return read_file("identity.frag"); }
39 TEST(EffectChainTest, Identity) {
45 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
46 tester.get_chain()->add_effect(new IdentityEffect());
47 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
49 expect_equal(data, out_data, 3, 2);
52 // An effect that does nothing, but requests texture bounce.
53 class BouncingIdentityEffect : public Effect {
55 BouncingIdentityEffect() {}
56 virtual std::string effect_type_id() const { return "IdentityEffect"; }
57 std::string output_fragment_shader() { return read_file("identity.frag"); }
58 bool needs_texture_bounce() const { return true; }
59 AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
62 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
68 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
69 tester.get_chain()->add_effect(new BouncingIdentityEffect());
70 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
72 expect_equal(data, out_data, 3, 2);
75 TEST(MirrorTest, BasicTest) {
80 float expected_data[6] = {
85 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
86 tester.get_chain()->add_effect(new MirrorEffect());
87 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
89 expect_equal(expected_data, out_data, 3, 2);
92 // A dummy effect that inverts its input.
93 class InvertEffect : public Effect {
96 virtual std::string effect_type_id() const { return "InvertEffect"; }
97 std::string output_fragment_shader() { return read_file("invert_effect.frag"); }
99 // A real invert would actually care about its alpha,
100 // but in this unit test, it only complicates things.
101 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
104 // Like IdentityEffect, but rewrites itself out of the loop,
105 // splicing in a different effect instead. Also stores the new node,
106 // so we later can check whatever properties we'd like about the graph.
108 class RewritingEffect : public Effect {
110 RewritingEffect() : effect(new T()), replaced_node(NULL) {}
111 virtual std::string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
112 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
113 virtual void rewrite_graph(EffectChain *graph, Node *self) {
114 replaced_node = graph->add_node(effect);
115 graph->replace_receiver(self, replaced_node);
116 graph->replace_sender(self, replaced_node);
117 self->disabled = true;
124 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
129 float expected_data[6] = {
130 1.0f, 0.9771f, 0.9673f,
134 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
135 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
136 tester.get_chain()->add_effect(effect);
137 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
139 Node *node = effect->replaced_node;
140 ASSERT_EQ(1, node->incoming_links.size());
141 ASSERT_EQ(1, node->outgoing_links.size());
142 EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
143 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
145 expect_equal(expected_data, out_data, 3, 2);
148 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
149 unsigned char data[] = {
153 float expected_data[4] = {
158 EffectChainTester tester(NULL, 2, 2);
159 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
160 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
161 tester.get_chain()->add_effect(effect);
162 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
164 Node *node = effect->replaced_node;
165 ASSERT_EQ(1, node->incoming_links.size());
166 ASSERT_EQ(1, node->outgoing_links.size());
167 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
168 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
170 expect_equal(expected_data, out_data, 2, 2);
173 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
178 float expected_data[6] = {
183 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
184 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
185 tester.get_chain()->add_effect(effect);
186 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
188 Node *node = effect->replaced_node;
189 ASSERT_EQ(1, node->incoming_links.size());
190 ASSERT_EQ(1, node->outgoing_links.size());
191 EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
192 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
194 expect_equal(expected_data, out_data, 3, 2);
197 // A fake input that can change its output colorspace and gamma between instantiation
199 class UnknownColorspaceInput : public FlatInput {
201 UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
202 : FlatInput(format, pixel_format, type, width, height),
203 overridden_color_space(format.color_space),
204 overridden_gamma_curve(format.gamma_curve) {}
205 virtual std::string effect_type_id() const { return "UnknownColorspaceInput"; }
207 void set_color_space(Colorspace colorspace) {
208 overridden_color_space = colorspace;
210 void set_gamma_curve(GammaCurve gamma_curve) {
211 overridden_gamma_curve = gamma_curve;
213 Colorspace get_color_space() const { return overridden_color_space; }
214 GammaCurve get_gamma_curve() const { return overridden_gamma_curve; }
217 Colorspace overridden_color_space;
218 GammaCurve overridden_gamma_curve;
221 TEST(EffectChainTester, HandlesInputChangingColorspace) {
230 float out_data[size];
232 EffectChainTester tester(NULL, 4, 1, FORMAT_GRAYSCALE);
234 // First say that we have sRGB, linear input.
236 format.color_space = COLORSPACE_sRGB;
237 format.gamma_curve = GAMMA_LINEAR;
239 UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
240 input->set_pixel_data(data);
241 tester.get_chain()->add_input(input);
243 // Now we change to Rec. 601 input.
244 input->set_color_space(COLORSPACE_REC_601_625);
245 input->set_gamma_curve(GAMMA_REC_601);
247 // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
248 tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
249 expect_equal(data, out_data, 4, 1);
252 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
257 float expected_data[6] = {
262 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
263 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
264 tester.get_chain()->add_effect(effect);
265 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
267 Node *node = effect->replaced_node;
268 ASSERT_EQ(1, node->incoming_links.size());
269 EXPECT_EQ(0, node->outgoing_links.size());
270 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
272 expect_equal(expected_data, out_data, 3, 2);
275 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
280 float expected_data[6] = {
285 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
286 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
287 tester.get_chain()->add_effect(effect);
288 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
290 Node *node = effect->replaced_node;
291 ASSERT_EQ(1, node->incoming_links.size());
292 EXPECT_EQ(0, node->outgoing_links.size());
293 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
295 expect_equal(expected_data, out_data, 3, 2);
298 // The identity effect needs linear light, and thus will get conversions on both sides.
299 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
300 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
302 for (unsigned i = 0; i < 256; ++i) {
306 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
307 tester.get_chain()->add_effect(new IdentityEffect());
308 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
310 expect_equal(data, out_data, 256, 1);
313 // Same, but uses the forward sRGB table from the GPU.
314 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
315 unsigned char data[256];
316 float expected_data[256];
317 for (unsigned i = 0; i < 256; ++i) {
319 expected_data[i] = i / 255.0;
322 EffectChainTester tester(NULL, 256, 1);
323 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
324 tester.get_chain()->add_effect(new IdentityEffect());
325 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
327 expect_equal(expected_data, out_data, 256, 1);
330 // Same, for the Rec. 601/709 gamma curve.
331 TEST(EffectChainTest, IdentityThroughRec709) {
333 for (unsigned i = 0; i < 256; ++i) {
337 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
338 tester.get_chain()->add_effect(new IdentityEffect());
339 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
341 expect_equal(data, out_data, 256, 1);
344 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
345 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
347 float data[4 * size] = {
348 0.8f, 0.0f, 0.0f, 0.5f,
349 0.0f, 0.2f, 0.2f, 0.3f,
350 0.1f, 0.0f, 1.0f, 1.0f,
352 float out_data[4 * size];
353 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
354 tester.get_chain()->add_effect(new IdentityEffect());
355 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
357 expect_equal(data, out_data, 4, size);
360 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
362 float data[4 * size] = {
363 0.8f, 0.0f, 0.0f, 0.5f,
364 0.0f, 0.2f, 0.2f, 0.3f,
365 0.1f, 0.0f, 1.0f, 1.0f,
367 float expected_data[4 * size] = {
368 0.1f, 0.0f, 1.0f, 1.0f,
369 0.0f, 0.2f, 0.2f, 0.3f,
370 0.8f, 0.0f, 0.0f, 0.5f,
372 float out_data[4 * size];
373 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
374 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
375 tester.get_chain()->add_effect(effect);
376 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
378 Node *node = effect->replaced_node;
379 ASSERT_EQ(1, node->incoming_links.size());
380 EXPECT_EQ(0, node->outgoing_links.size());
381 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
383 expect_equal(expected_data, out_data, 4, size);
386 // An input that outputs only blue, which has blank alpha.
387 class BlueInput : public Input {
389 BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
390 virtual std::string effect_type_id() const { return "IdentityEffect"; }
391 std::string output_fragment_shader() { return read_file("blue.frag"); }
392 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
393 virtual void finalize() {}
394 virtual bool can_output_linear_gamma() const { return true; }
395 virtual unsigned get_width() const { return 1; }
396 virtual unsigned get_height() const { return 1; }
397 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
398 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
404 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
405 // which outputs blank alpha.
406 class RewritingToBlueInput : public Input {
408 RewritingToBlueInput() : blue_node(NULL) { register_int("needs_mipmaps", &needs_mipmaps); }
409 virtual std::string effect_type_id() const { return "RewritingToBlueInput"; }
410 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
411 virtual void rewrite_graph(EffectChain *graph, Node *self) {
412 Node *blue_node = graph->add_node(new BlueInput());
413 graph->replace_receiver(self, blue_node);
414 graph->replace_sender(self, blue_node);
416 self->disabled = true;
417 this->blue_node = blue_node;
420 // Dummy values that we need to implement because we inherit from Input.
421 // Same as BlueInput.
422 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
423 virtual void finalize() {}
424 virtual bool can_output_linear_gamma() const { return true; }
425 virtual unsigned get_width() const { return 1; }
426 virtual unsigned get_height() const { return 1; }
427 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
428 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
436 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
438 float data[4 * size] = {
439 0.0f, 0.0f, 1.0f, 1.0f,
440 0.0f, 0.0f, 1.0f, 1.0f,
441 0.0f, 0.0f, 1.0f, 1.0f,
443 float out_data[4 * size];
444 EffectChainTester tester(NULL, size, 1);
445 RewritingToBlueInput *input = new RewritingToBlueInput();
446 tester.get_chain()->add_input(input);
447 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
449 Node *node = input->blue_node;
450 EXPECT_EQ(0, node->incoming_links.size());
451 EXPECT_EQ(0, node->outgoing_links.size());
453 expect_equal(data, out_data, 4, size);
456 // An effect that does nothing, and specifies that it preserves blank alpha.
457 class BlankAlphaPreservingEffect : public Effect {
459 BlankAlphaPreservingEffect() {}
460 virtual std::string effect_type_id() const { return "BlankAlphaPreservingEffect"; }
461 std::string output_fragment_shader() { return read_file("identity.frag"); }
462 virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
465 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
467 float data[4 * size] = {
468 0.0f, 0.0f, 1.0f, 1.0f,
469 0.0f, 0.0f, 1.0f, 1.0f,
470 0.0f, 0.0f, 1.0f, 1.0f,
472 float out_data[4 * size];
473 EffectChainTester tester(NULL, size, 1);
474 tester.get_chain()->add_input(new BlueInput());
475 tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
476 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
477 tester.get_chain()->add_effect(effect);
478 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
480 Node *node = effect->replaced_node;
481 EXPECT_EQ(1, node->incoming_links.size());
482 EXPECT_EQ(0, node->outgoing_links.size());
484 expect_equal(data, out_data, 4, size);
487 // This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
488 // just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
489 // an alpha conversion _should_ be inserted at the very end. (There is some overlap
490 // with other tests.)
491 TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
493 float data[4 * size] = {
494 0.0f, 0.0f, 1.0f, 1.0f,
495 0.0f, 0.0f, 1.0f, 1.0f,
496 0.0f, 0.0f, 1.0f, 1.0f,
498 float out_data[4 * size];
499 EffectChainTester tester(NULL, size, 1);
500 tester.get_chain()->add_input(new BlueInput());
501 tester.get_chain()->add_effect(new IdentityEffect()); // Not BlankAlphaPreservingEffect.
502 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
503 tester.get_chain()->add_effect(effect);
504 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
506 Node *node = effect->replaced_node;
507 EXPECT_EQ(1, node->incoming_links.size());
508 EXPECT_EQ(1, node->outgoing_links.size());
509 EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
511 expect_equal(data, out_data, 4, size);
514 // Effectively scales down its input linearly by 4x (and repeating it),
515 // which is not attainable without mipmaps.
516 class MipmapNeedingEffect : public Effect {
518 MipmapNeedingEffect() {}
519 virtual bool needs_mipmaps() const { return true; }
520 virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
521 std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
522 void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
524 glActiveTexture(GL_TEXTURE0);
526 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
528 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
533 TEST(EffectChainTest, MipmapGenerationWorks) {
534 float data[] = { // In 4x4 blocks.
535 1.0f, 0.0f, 0.0f, 0.0f,
536 0.0f, 0.0f, 0.0f, 0.0f,
537 0.0f, 0.0f, 0.0f, 0.0f,
538 0.0f, 0.0f, 0.0f, 1.0f,
540 0.0f, 0.0f, 0.0f, 0.0f,
541 0.0f, 0.5f, 0.0f, 0.0f,
542 0.0f, 0.0f, 1.0f, 0.0f,
543 0.0f, 0.0f, 0.0f, 0.0f,
545 1.0f, 1.0f, 1.0f, 1.0f,
546 1.0f, 1.0f, 1.0f, 1.0f,
547 1.0f, 1.0f, 1.0f, 1.0f,
548 1.0f, 1.0f, 1.0f, 1.0f,
550 0.0f, 0.0f, 0.0f, 0.0f,
551 0.0f, 1.0f, 1.0f, 0.0f,
552 0.0f, 1.0f, 1.0f, 0.0f,
553 0.0f, 0.0f, 0.0f, 0.0f,
555 float expected_data[] = { // Repeated four times each way.
556 0.125f, 0.125f, 0.125f, 0.125f,
557 0.09375f, 0.09375f, 0.09375f, 0.09375f,
558 1.0f, 1.0f, 1.0f, 1.0f,
559 0.25f, 0.25f, 0.25f, 0.25f,
561 0.125f, 0.125f, 0.125f, 0.125f,
562 0.09375f, 0.09375f, 0.09375f, 0.09375f,
563 1.0f, 1.0f, 1.0f, 1.0f,
564 0.25f, 0.25f, 0.25f, 0.25f,
566 0.125f, 0.125f, 0.125f, 0.125f,
567 0.09375f, 0.09375f, 0.09375f, 0.09375f,
568 1.0f, 1.0f, 1.0f, 1.0f,
569 0.25f, 0.25f, 0.25f, 0.25f,
571 0.125f, 0.125f, 0.125f, 0.125f,
572 0.09375f, 0.09375f, 0.09375f, 0.09375f,
573 1.0f, 1.0f, 1.0f, 1.0f,
574 0.25f, 0.25f, 0.25f, 0.25f,
576 float out_data[4 * 16];
577 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
578 tester.get_chain()->add_effect(new MipmapNeedingEffect());
579 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
581 expect_equal(expected_data, out_data, 4, 16);
584 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
585 float data[] = { // In 4x4 blocks.
586 1.0f, 0.0f, 0.0f, 0.0f,
587 0.0f, 0.0f, 0.0f, 0.0f,
588 0.0f, 0.0f, 0.0f, 0.0f,
589 0.0f, 0.0f, 0.0f, 1.0f,
591 0.0f, 0.0f, 0.0f, 0.0f,
592 0.0f, 0.5f, 0.0f, 0.0f,
593 0.0f, 0.0f, 1.0f, 0.0f,
594 0.0f, 0.0f, 0.0f, 0.0f,
596 1.0f, 1.0f, 1.0f, 1.0f,
597 1.0f, 1.0f, 1.0f, 1.0f,
598 1.0f, 1.0f, 1.0f, 1.0f,
599 1.0f, 1.0f, 1.0f, 1.0f,
601 0.0f, 0.0f, 0.0f, 0.0f,
602 0.0f, 1.0f, 1.0f, 0.0f,
603 0.0f, 1.0f, 1.0f, 0.0f,
604 0.0f, 0.0f, 0.0f, 0.0f,
606 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
607 0.1250f, 0.1250f, 0.1250f, 0.1250f,
608 0.1250f, 0.1250f, 0.1250f, 0.1250f,
609 0.1211f, 0.1211f, 0.1211f, 0.1211f,
610 0.1133f, 0.1133f, 0.1133f, 0.1133f,
611 0.1055f, 0.1055f, 0.1055f, 0.1055f,
612 0.0977f, 0.0977f, 0.0977f, 0.0977f,
613 0.2070f, 0.2070f, 0.2070f, 0.2070f,
614 0.4336f, 0.4336f, 0.4336f, 0.4336f,
615 0.6602f, 0.6602f, 0.6602f, 0.6602f,
616 0.8867f, 0.8867f, 0.8867f, 0.8867f,
617 0.9062f, 0.9062f, 0.9062f, 0.9062f,
618 0.7188f, 0.7188f, 0.7188f, 0.7188f,
619 0.5312f, 0.5312f, 0.5312f, 0.5312f,
620 0.3438f, 0.3438f, 0.3438f, 0.3438f,
621 0.2500f, 0.2500f, 0.2500f, 0.2500f,
622 0.2500f, 0.2500f, 0.2500f, 0.2500f,
624 float out_data[4 * 16];
626 ResizeEffect *downscale = new ResizeEffect();
627 ASSERT_TRUE(downscale->set_int("width", 1));
628 ASSERT_TRUE(downscale->set_int("height", 4));
630 ResizeEffect *upscale = new ResizeEffect();
631 ASSERT_TRUE(upscale->set_int("width", 4));
632 ASSERT_TRUE(upscale->set_int("height", 16));
634 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
635 tester.get_chain()->add_effect(downscale);
636 tester.get_chain()->add_effect(upscale);
637 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
639 expect_equal(expected_data, out_data, 4, 16);
642 // An effect that multiplies with a constant. Used below.
643 class MultiplyEffect : public Effect {
645 MultiplyEffect() { register_float("factor", &factor); }
646 virtual std::string effect_type_id() const { return "MultiplyEffect"; }
647 std::string output_fragment_shader() { return read_file("multiply.frag"); }
648 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
654 // An effect that adds its two inputs together. Used below.
655 class AddEffect : public Effect {
658 virtual std::string effect_type_id() const { return "AddEffect"; }
659 std::string output_fragment_shader() { return read_file("add.frag"); }
660 virtual unsigned num_inputs() const { return 2; }
661 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
664 // Constructs the graph
668 // MultiplyEffect MultiplyEffect |
672 // and verifies that it gives the correct output.
673 TEST(EffectChainTest, DiamondGraph) {
678 float expected_data[] = {
682 float out_data[2 * 2];
684 MultiplyEffect *mul_half = new MultiplyEffect();
685 ASSERT_TRUE(mul_half->set_float("factor", 0.5f));
687 MultiplyEffect *mul_two = new MultiplyEffect();
688 ASSERT_TRUE(mul_two->set_float("factor", 2.0f));
690 EffectChainTester tester(NULL, 2, 2);
693 format.color_space = COLORSPACE_sRGB;
694 format.gamma_curve = GAMMA_LINEAR;
696 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
697 input->set_pixel_data(data);
699 tester.get_chain()->add_input(input);
700 tester.get_chain()->add_effect(mul_half, input);
701 tester.get_chain()->add_effect(mul_two, input);
702 tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
703 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
705 expect_equal(expected_data, out_data, 2, 2);
708 // Constructs the graph
712 // MultiplyEffect MultiplyEffect |
714 // \ BouncingIdentityEffect |
718 // and verifies that it gives the correct output.
719 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
724 float expected_data[] = {
728 float out_data[2 * 2];
730 MultiplyEffect *mul_half = new MultiplyEffect();
731 ASSERT_TRUE(mul_half->set_float("factor", 0.5f));
733 MultiplyEffect *mul_two = new MultiplyEffect();
734 ASSERT_TRUE(mul_two->set_float("factor", 2.0f));
736 BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
738 EffectChainTester tester(NULL, 2, 2);
741 format.color_space = COLORSPACE_sRGB;
742 format.gamma_curve = GAMMA_LINEAR;
744 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
745 input->set_pixel_data(data);
747 tester.get_chain()->add_input(input);
748 tester.get_chain()->add_effect(mul_half, input);
749 tester.get_chain()->add_effect(mul_two, input);
750 tester.get_chain()->add_effect(bounce, mul_two);
751 tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
752 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
754 expect_equal(expected_data, out_data, 2, 2);
757 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
762 float expected_data[] = {
763 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
766 float out_data[2 * 2];
768 EffectChainTester tester(NULL, 2, 2);
769 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
771 // MirrorEffect does not get linear light, so the conversions will be
772 // inserted after it, not before.
773 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
774 tester.get_chain()->add_effect(effect);
776 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
777 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
778 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
779 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
781 expect_equal(expected_data, out_data, 2, 2);
783 Node *node = effect->replaced_node;
784 ASSERT_EQ(1, node->incoming_links.size());
785 ASSERT_EQ(1, node->outgoing_links.size());
786 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
787 EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
790 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
795 float expected_data[] = {
796 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
799 float out_data[2 * 2];
801 EffectChainTester tester(NULL, 2, 2);
802 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
804 // MirrorEffect does not get linear light, so the conversions will be
805 // inserted after it, not before.
806 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
807 tester.get_chain()->add_effect(effect);
809 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
810 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
811 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
812 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
814 expect_equal(expected_data, out_data, 2, 2);
816 Node *node = effect->replaced_node;
817 ASSERT_EQ(1, node->incoming_links.size());
818 ASSERT_EQ(1, node->outgoing_links.size());
819 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
820 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
823 // An effect that does nothing, but requests texture bounce and stores
825 class SizeStoringEffect : public BouncingIdentityEffect {
827 SizeStoringEffect() : input_width(-1), input_height(-1) {}
828 virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) {
829 assert(input_num == 0);
831 input_height = height;
833 virtual std::string effect_type_id() const { return "SizeStoringEffect"; }
835 int input_width, input_height;
838 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
839 float data[2 * 2] = {
843 float out_data[2 * 2];
845 EffectChainTester tester(NULL, 4, 3); // Note non-square aspect.
848 format.color_space = COLORSPACE_sRGB;
849 format.gamma_curve = GAMMA_LINEAR;
851 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
852 input1->set_pixel_data(data);
854 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
855 input2->set_pixel_data(data);
857 SizeStoringEffect *input_store = new SizeStoringEffect();
859 tester.get_chain()->add_input(input1);
860 tester.get_chain()->add_input(input2);
861 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
862 tester.get_chain()->add_effect(input_store);
863 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
865 EXPECT_EQ(2, input_store->input_width);
866 EXPECT_EQ(2, input_store->input_height);
869 TEST(EffectChainTest, AspectRatioConversion) {
870 float data1[4 * 3] = {
871 0.0f, 0.0f, 0.0f, 0.0f,
872 0.0f, 0.0f, 0.0f, 0.0f,
873 0.0f, 0.0f, 0.0f, 0.0f,
875 float data2[7 * 7] = {
876 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
877 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
878 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
879 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
880 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
881 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
882 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
885 // The right conversion here is that the 7x7 image decides the size,
886 // since it is the biggest, so everything is scaled up to 9x7
887 // (keep the height, round the width 9.333 to 9).
888 float out_data[9 * 7];
890 EffectChainTester tester(NULL, 4, 3);
893 format.color_space = COLORSPACE_sRGB;
894 format.gamma_curve = GAMMA_LINEAR;
896 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
897 input1->set_pixel_data(data1);
899 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
900 input2->set_pixel_data(data2);
902 SizeStoringEffect *input_store = new SizeStoringEffect();
904 tester.get_chain()->add_input(input1);
905 tester.get_chain()->add_input(input2);
906 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
907 tester.get_chain()->add_effect(input_store);
908 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
910 EXPECT_EQ(9, input_store->input_width);
911 EXPECT_EQ(7, input_store->input_height);
914 // An effect that does nothing except changing its output sizes.
915 class VirtualResizeEffect : public Effect {
917 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
920 virtual_width(virtual_width),
921 virtual_height(virtual_height) {}
922 virtual std::string effect_type_id() const { return "VirtualResizeEffect"; }
923 std::string output_fragment_shader() { return read_file("identity.frag"); }
925 virtual bool changes_output_size() const { return true; }
927 virtual void get_output_size(unsigned *width, unsigned *height,
928 unsigned *virtual_width, unsigned *virtual_height) const {
929 *width = this->width;
930 *height = this->height;
931 *virtual_width = this->virtual_width;
932 *virtual_height = this->virtual_height;
936 int width, height, virtual_width, virtual_height;
939 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
940 const int size = 2, bigger_size = 3;
941 float data[size * size] = {
945 float out_data[size * size];
947 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
949 SizeStoringEffect *size_store = new SizeStoringEffect();
951 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
952 tester.get_chain()->add_effect(size_store);
953 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
954 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
956 EXPECT_EQ(bigger_size, size_store->input_width);
957 EXPECT_EQ(bigger_size, size_store->input_height);
959 // If the resize is implemented as non-virtual, we'll fail here,
960 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
961 expect_equal(data, out_data, size, size);