1 // Unit tests for EffectChain.
3 // Note that this also contains the tests for some of the simpler effects.
9 #include "effect_chain.h"
10 #include "flat_input.h"
11 #include "gtest/gtest.h"
13 #include "mirror_effect.h"
14 #include "multiply_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 adds its two inputs together. Used below.
643 class AddEffect : public Effect {
646 virtual std::string effect_type_id() const { return "AddEffect"; }
647 std::string output_fragment_shader() { return read_file("add.frag"); }
648 virtual unsigned num_inputs() const { return 2; }
649 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
652 // Constructs the graph
656 // MultiplyEffect MultiplyEffect |
660 // and verifies that it gives the correct output.
661 TEST(EffectChainTest, DiamondGraph) {
666 float expected_data[] = {
670 float out_data[2 * 2];
672 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
673 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
675 MultiplyEffect *mul_half = new MultiplyEffect();
676 ASSERT_TRUE(mul_half->set_vec4("factor", half));
678 MultiplyEffect *mul_two = new MultiplyEffect();
679 ASSERT_TRUE(mul_two->set_vec4("factor", two));
681 EffectChainTester tester(NULL, 2, 2);
684 format.color_space = COLORSPACE_sRGB;
685 format.gamma_curve = GAMMA_LINEAR;
687 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
688 input->set_pixel_data(data);
690 tester.get_chain()->add_input(input);
691 tester.get_chain()->add_effect(mul_half, input);
692 tester.get_chain()->add_effect(mul_two, input);
693 tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
694 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
696 expect_equal(expected_data, out_data, 2, 2);
699 // Constructs the graph
703 // MultiplyEffect MultiplyEffect |
705 // \ BouncingIdentityEffect |
709 // and verifies that it gives the correct output.
710 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
715 float expected_data[] = {
719 float out_data[2 * 2];
721 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
722 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
724 MultiplyEffect *mul_half = new MultiplyEffect();
725 ASSERT_TRUE(mul_half->set_vec4("factor", half));
727 MultiplyEffect *mul_two = new MultiplyEffect();
728 ASSERT_TRUE(mul_two->set_vec4("factor", two));
730 BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
732 EffectChainTester tester(NULL, 2, 2);
735 format.color_space = COLORSPACE_sRGB;
736 format.gamma_curve = GAMMA_LINEAR;
738 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
739 input->set_pixel_data(data);
741 tester.get_chain()->add_input(input);
742 tester.get_chain()->add_effect(mul_half, input);
743 tester.get_chain()->add_effect(mul_two, input);
744 tester.get_chain()->add_effect(bounce, mul_two);
745 tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
746 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
748 expect_equal(expected_data, out_data, 2, 2);
751 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
756 float expected_data[] = {
757 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
760 float out_data[2 * 2];
762 EffectChainTester tester(NULL, 2, 2);
763 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
765 // MirrorEffect does not get linear light, so the conversions will be
766 // inserted after it, not before.
767 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
768 tester.get_chain()->add_effect(effect);
770 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
771 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
772 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
773 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
775 expect_equal(expected_data, out_data, 2, 2);
777 Node *node = effect->replaced_node;
778 ASSERT_EQ(1, node->incoming_links.size());
779 ASSERT_EQ(1, node->outgoing_links.size());
780 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
781 EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
784 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
789 float expected_data[] = {
790 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
793 float out_data[2 * 2];
795 EffectChainTester tester(NULL, 2, 2);
796 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
798 // MirrorEffect does not get linear light, so the conversions will be
799 // inserted after it, not before.
800 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
801 tester.get_chain()->add_effect(effect);
803 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
804 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
805 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
806 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
808 expect_equal(expected_data, out_data, 2, 2);
810 Node *node = effect->replaced_node;
811 ASSERT_EQ(1, node->incoming_links.size());
812 ASSERT_EQ(1, node->outgoing_links.size());
813 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
814 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
817 // An effect that does nothing, but requests texture bounce and stores
819 class SizeStoringEffect : public BouncingIdentityEffect {
821 SizeStoringEffect() : input_width(-1), input_height(-1) {}
822 virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) {
823 assert(input_num == 0);
825 input_height = height;
827 virtual std::string effect_type_id() const { return "SizeStoringEffect"; }
829 int input_width, input_height;
832 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
833 float data[2 * 2] = {
837 float out_data[2 * 2];
839 EffectChainTester tester(NULL, 4, 3); // Note non-square aspect.
842 format.color_space = COLORSPACE_sRGB;
843 format.gamma_curve = GAMMA_LINEAR;
845 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
846 input1->set_pixel_data(data);
848 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
849 input2->set_pixel_data(data);
851 SizeStoringEffect *input_store = new SizeStoringEffect();
853 tester.get_chain()->add_input(input1);
854 tester.get_chain()->add_input(input2);
855 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
856 tester.get_chain()->add_effect(input_store);
857 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
859 EXPECT_EQ(2, input_store->input_width);
860 EXPECT_EQ(2, input_store->input_height);
863 TEST(EffectChainTest, AspectRatioConversion) {
864 float data1[4 * 3] = {
865 0.0f, 0.0f, 0.0f, 0.0f,
866 0.0f, 0.0f, 0.0f, 0.0f,
867 0.0f, 0.0f, 0.0f, 0.0f,
869 float data2[7 * 7] = {
870 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
871 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
872 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
873 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
874 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
875 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
876 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
879 // The right conversion here is that the 7x7 image decides the size,
880 // since it is the biggest, so everything is scaled up to 9x7
881 // (keep the height, round the width 9.333 to 9).
882 float out_data[9 * 7];
884 EffectChainTester tester(NULL, 4, 3);
887 format.color_space = COLORSPACE_sRGB;
888 format.gamma_curve = GAMMA_LINEAR;
890 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
891 input1->set_pixel_data(data1);
893 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
894 input2->set_pixel_data(data2);
896 SizeStoringEffect *input_store = new SizeStoringEffect();
898 tester.get_chain()->add_input(input1);
899 tester.get_chain()->add_input(input2);
900 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
901 tester.get_chain()->add_effect(input_store);
902 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
904 EXPECT_EQ(9, input_store->input_width);
905 EXPECT_EQ(7, input_store->input_height);
908 // An effect that does nothing except changing its output sizes.
909 class VirtualResizeEffect : public Effect {
911 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
914 virtual_width(virtual_width),
915 virtual_height(virtual_height) {}
916 virtual std::string effect_type_id() const { return "VirtualResizeEffect"; }
917 std::string output_fragment_shader() { return read_file("identity.frag"); }
919 virtual bool changes_output_size() const { return true; }
921 virtual void get_output_size(unsigned *width, unsigned *height,
922 unsigned *virtual_width, unsigned *virtual_height) const {
923 *width = this->width;
924 *height = this->height;
925 *virtual_width = this->virtual_width;
926 *virtual_height = this->virtual_height;
930 int width, height, virtual_width, virtual_height;
933 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
934 const int size = 2, bigger_size = 3;
935 float data[size * size] = {
939 float out_data[size * size];
941 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
943 SizeStoringEffect *size_store = new SizeStoringEffect();
945 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
946 tester.get_chain()->add_effect(size_store);
947 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
948 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
950 EXPECT_EQ(bigger_size, size_store->input_width);
951 EXPECT_EQ(bigger_size, size_store->input_height);
953 // If the resize is implemented as non-virtual, we'll fail here,
954 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
955 expect_equal(data, out_data, size, size);