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"
14 #include "mirror_effect.h"
15 #include "multiply_effect.h"
16 #include "resize_effect.h"
17 #include "test_util.h"
24 TEST(EffectChainTest, EmptyChain) {
30 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
31 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
33 expect_equal(data, out_data, 3, 2);
36 // An effect that does nothing.
37 class IdentityEffect : public Effect {
40 virtual string effect_type_id() const { return "IdentityEffect"; }
41 string output_fragment_shader() { return read_file("identity.frag"); }
44 TEST(EffectChainTest, Identity) {
50 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
51 tester.get_chain()->add_effect(new IdentityEffect());
52 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
54 expect_equal(data, out_data, 3, 2);
57 // An effect that does nothing, but requests texture bounce.
58 class BouncingIdentityEffect : public Effect {
60 BouncingIdentityEffect() {}
61 virtual string effect_type_id() const { return "IdentityEffect"; }
62 string output_fragment_shader() { return read_file("identity.frag"); }
63 bool needs_texture_bounce() const { return true; }
64 AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
67 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
73 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
74 tester.get_chain()->add_effect(new BouncingIdentityEffect());
75 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
77 expect_equal(data, out_data, 3, 2);
80 TEST(MirrorTest, BasicTest) {
85 float expected_data[6] = {
90 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
91 tester.get_chain()->add_effect(new MirrorEffect());
92 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
94 expect_equal(expected_data, out_data, 3, 2);
97 // A dummy effect that inverts its input.
98 class InvertEffect : public Effect {
101 virtual string effect_type_id() const { return "InvertEffect"; }
102 string output_fragment_shader() { return read_file("invert_effect.frag"); }
104 // A real invert would actually care about its alpha,
105 // but in this unit test, it only complicates things.
106 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
109 // Like IdentityEffect, but rewrites itself out of the loop,
110 // splicing in a different effect instead. Also stores the new node,
111 // so we later can check whatever properties we'd like about the graph.
113 class RewritingEffect : public Effect {
115 RewritingEffect() : effect(new T()), replaced_node(NULL) {}
116 virtual string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
117 string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
118 virtual void rewrite_graph(EffectChain *graph, Node *self) {
119 replaced_node = graph->add_node(effect);
120 graph->replace_receiver(self, replaced_node);
121 graph->replace_sender(self, replaced_node);
122 self->disabled = true;
129 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
134 float expected_data[6] = {
135 1.0f, 0.9771f, 0.9673f,
139 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
140 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
141 tester.get_chain()->add_effect(effect);
142 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
144 Node *node = effect->replaced_node;
145 ASSERT_EQ(1, node->incoming_links.size());
146 ASSERT_EQ(1, node->outgoing_links.size());
147 EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
148 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
150 expect_equal(expected_data, out_data, 3, 2);
153 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
154 unsigned char data[] = {
158 float expected_data[4] = {
163 EffectChainTester tester(NULL, 2, 2);
164 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
165 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
166 tester.get_chain()->add_effect(effect);
167 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
169 Node *node = effect->replaced_node;
170 ASSERT_EQ(1, node->incoming_links.size());
171 ASSERT_EQ(1, node->outgoing_links.size());
172 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
173 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
175 expect_equal(expected_data, out_data, 2, 2);
178 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
183 float expected_data[6] = {
188 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
189 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
190 tester.get_chain()->add_effect(effect);
191 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
193 Node *node = effect->replaced_node;
194 ASSERT_EQ(1, node->incoming_links.size());
195 ASSERT_EQ(1, node->outgoing_links.size());
196 EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
197 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
199 expect_equal(expected_data, out_data, 3, 2);
202 // A fake input that can change its output colorspace and gamma between instantiation
204 class UnknownColorspaceInput : public FlatInput {
206 UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
207 : FlatInput(format, pixel_format, type, width, height),
208 overridden_color_space(format.color_space),
209 overridden_gamma_curve(format.gamma_curve) {}
210 virtual string effect_type_id() const { return "UnknownColorspaceInput"; }
212 void set_color_space(Colorspace colorspace) {
213 overridden_color_space = colorspace;
215 void set_gamma_curve(GammaCurve gamma_curve) {
216 overridden_gamma_curve = gamma_curve;
218 Colorspace get_color_space() const { return overridden_color_space; }
219 GammaCurve get_gamma_curve() const { return overridden_gamma_curve; }
222 Colorspace overridden_color_space;
223 GammaCurve overridden_gamma_curve;
226 TEST(EffectChainTest, HandlesInputChangingColorspace) {
235 float out_data[size];
237 EffectChainTester tester(NULL, 4, 1, FORMAT_GRAYSCALE);
239 // First say that we have sRGB, linear input.
241 format.color_space = COLORSPACE_sRGB;
242 format.gamma_curve = GAMMA_LINEAR;
244 UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
245 input->set_pixel_data(data);
246 tester.get_chain()->add_input(input);
248 // Now we change to Rec. 601 input.
249 input->set_color_space(COLORSPACE_REC_601_625);
250 input->set_gamma_curve(GAMMA_REC_601);
252 // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
253 tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
254 expect_equal(data, out_data, 4, 1);
257 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
262 float expected_data[6] = {
267 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
268 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
269 tester.get_chain()->add_effect(effect);
270 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
272 Node *node = effect->replaced_node;
273 ASSERT_EQ(1, node->incoming_links.size());
274 EXPECT_EQ(0, node->outgoing_links.size());
275 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
277 expect_equal(expected_data, out_data, 3, 2);
280 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
285 float expected_data[6] = {
290 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
291 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
292 tester.get_chain()->add_effect(effect);
293 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
295 Node *node = effect->replaced_node;
296 ASSERT_EQ(1, node->incoming_links.size());
297 EXPECT_EQ(0, node->outgoing_links.size());
298 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
300 expect_equal(expected_data, out_data, 3, 2);
303 // The identity effect needs linear light, and thus will get conversions on both sides.
304 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
305 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
307 for (unsigned i = 0; i < 256; ++i) {
311 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
312 tester.get_chain()->add_effect(new IdentityEffect());
313 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
315 expect_equal(data, out_data, 256, 1);
318 // Same, but uses the forward sRGB table from the GPU.
319 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
320 unsigned char data[256];
321 float expected_data[256];
322 for (unsigned i = 0; i < 256; ++i) {
324 expected_data[i] = i / 255.0;
327 EffectChainTester tester(NULL, 256, 1);
328 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
329 tester.get_chain()->add_effect(new IdentityEffect());
330 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
332 expect_equal(expected_data, out_data, 256, 1);
335 // Same, for the Rec. 601/709 gamma curve.
336 TEST(EffectChainTest, IdentityThroughRec709) {
338 for (unsigned i = 0; i < 256; ++i) {
342 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
343 tester.get_chain()->add_effect(new IdentityEffect());
344 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
346 expect_equal(data, out_data, 256, 1);
349 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
350 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
352 float data[4 * size] = {
353 0.8f, 0.0f, 0.0f, 0.5f,
354 0.0f, 0.2f, 0.2f, 0.3f,
355 0.1f, 0.0f, 1.0f, 1.0f,
357 float out_data[4 * size];
358 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
359 tester.get_chain()->add_effect(new IdentityEffect());
360 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
362 expect_equal(data, out_data, 4, size);
365 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
367 float data[4 * size] = {
368 0.8f, 0.0f, 0.0f, 0.5f,
369 0.0f, 0.2f, 0.2f, 0.3f,
370 0.1f, 0.0f, 1.0f, 1.0f,
372 float expected_data[4 * size] = {
373 0.1f, 0.0f, 1.0f, 1.0f,
374 0.0f, 0.2f, 0.2f, 0.3f,
375 0.8f, 0.0f, 0.0f, 0.5f,
377 float out_data[4 * size];
378 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
379 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
380 tester.get_chain()->add_effect(effect);
381 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
383 Node *node = effect->replaced_node;
384 ASSERT_EQ(1, node->incoming_links.size());
385 EXPECT_EQ(0, node->outgoing_links.size());
386 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
388 expect_equal(expected_data, out_data, 4, size);
391 // An input that outputs only blue, which has blank alpha.
392 class BlueInput : public Input {
394 BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
395 virtual string effect_type_id() const { return "IdentityEffect"; }
396 string output_fragment_shader() { return read_file("blue.frag"); }
397 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
398 virtual void finalize() {}
399 virtual bool can_output_linear_gamma() const { return true; }
400 virtual unsigned get_width() const { return 1; }
401 virtual unsigned get_height() const { return 1; }
402 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
403 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
409 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
410 // which outputs blank alpha.
411 class RewritingToBlueInput : public Input {
413 RewritingToBlueInput() : blue_node(NULL) { register_int("needs_mipmaps", &needs_mipmaps); }
414 virtual string effect_type_id() const { return "RewritingToBlueInput"; }
415 string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
416 virtual void rewrite_graph(EffectChain *graph, Node *self) {
417 Node *blue_node = graph->add_node(new BlueInput());
418 graph->replace_receiver(self, blue_node);
419 graph->replace_sender(self, blue_node);
421 self->disabled = true;
422 this->blue_node = blue_node;
425 // Dummy values that we need to implement because we inherit from Input.
426 // Same as BlueInput.
427 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
428 virtual void finalize() {}
429 virtual bool can_output_linear_gamma() const { return true; }
430 virtual unsigned get_width() const { return 1; }
431 virtual unsigned get_height() const { return 1; }
432 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
433 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
441 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
443 float data[4 * size] = {
444 0.0f, 0.0f, 1.0f, 1.0f,
445 0.0f, 0.0f, 1.0f, 1.0f,
446 0.0f, 0.0f, 1.0f, 1.0f,
448 float out_data[4 * size];
449 EffectChainTester tester(NULL, size, 1);
450 RewritingToBlueInput *input = new RewritingToBlueInput();
451 tester.get_chain()->add_input(input);
452 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
454 Node *node = input->blue_node;
455 EXPECT_EQ(0, node->incoming_links.size());
456 EXPECT_EQ(0, node->outgoing_links.size());
458 expect_equal(data, out_data, 4, size);
461 // An effect that does nothing, and specifies that it preserves blank alpha.
462 class BlankAlphaPreservingEffect : public Effect {
464 BlankAlphaPreservingEffect() {}
465 virtual string effect_type_id() const { return "BlankAlphaPreservingEffect"; }
466 string output_fragment_shader() { return read_file("identity.frag"); }
467 virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
470 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
472 float data[4 * size] = {
473 0.0f, 0.0f, 1.0f, 1.0f,
474 0.0f, 0.0f, 1.0f, 1.0f,
475 0.0f, 0.0f, 1.0f, 1.0f,
477 float out_data[4 * size];
478 EffectChainTester tester(NULL, size, 1);
479 tester.get_chain()->add_input(new BlueInput());
480 tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
481 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
482 tester.get_chain()->add_effect(effect);
483 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
485 Node *node = effect->replaced_node;
486 EXPECT_EQ(1, node->incoming_links.size());
487 EXPECT_EQ(0, node->outgoing_links.size());
489 expect_equal(data, out_data, 4, size);
492 // This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
493 // just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
494 // an alpha conversion _should_ be inserted at the very end. (There is some overlap
495 // with other tests.)
496 TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
498 float data[4 * size] = {
499 0.0f, 0.0f, 1.0f, 1.0f,
500 0.0f, 0.0f, 1.0f, 1.0f,
501 0.0f, 0.0f, 1.0f, 1.0f,
503 float out_data[4 * size];
504 EffectChainTester tester(NULL, size, 1);
505 tester.get_chain()->add_input(new BlueInput());
506 tester.get_chain()->add_effect(new IdentityEffect()); // Not BlankAlphaPreservingEffect.
507 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
508 tester.get_chain()->add_effect(effect);
509 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
511 Node *node = effect->replaced_node;
512 EXPECT_EQ(1, node->incoming_links.size());
513 EXPECT_EQ(1, node->outgoing_links.size());
514 EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
516 expect_equal(data, out_data, 4, size);
519 // Effectively scales down its input linearly by 4x (and repeating it),
520 // which is not attainable without mipmaps.
521 class MipmapNeedingEffect : public Effect {
523 MipmapNeedingEffect() {}
524 virtual bool needs_mipmaps() const { return true; }
526 // To be allowed to mess with the sampler state.
527 virtual bool needs_texture_bounce() const { return true; }
529 virtual string effect_type_id() const { return "MipmapNeedingEffect"; }
530 string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
531 virtual void inform_added(EffectChain *chain) { this->chain = chain; }
533 void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
535 Node *self = chain->find_node_for_effect(this);
536 glActiveTexture(chain->get_input_sampler(self, 0));
538 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
540 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
548 TEST(EffectChainTest, MipmapGenerationWorks) {
549 float data[] = { // In 4x4 blocks.
550 1.0f, 0.0f, 0.0f, 0.0f,
551 0.0f, 0.0f, 0.0f, 0.0f,
552 0.0f, 0.0f, 0.0f, 0.0f,
553 0.0f, 0.0f, 0.0f, 1.0f,
555 0.0f, 0.0f, 0.0f, 0.0f,
556 0.0f, 0.5f, 0.0f, 0.0f,
557 0.0f, 0.0f, 1.0f, 0.0f,
558 0.0f, 0.0f, 0.0f, 0.0f,
560 1.0f, 1.0f, 1.0f, 1.0f,
561 1.0f, 1.0f, 1.0f, 1.0f,
562 1.0f, 1.0f, 1.0f, 1.0f,
563 1.0f, 1.0f, 1.0f, 1.0f,
565 0.0f, 0.0f, 0.0f, 0.0f,
566 0.0f, 1.0f, 1.0f, 0.0f,
567 0.0f, 1.0f, 1.0f, 0.0f,
568 0.0f, 0.0f, 0.0f, 0.0f,
570 float expected_data[] = { // Repeated four times each way.
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 0.125f, 0.125f, 0.125f, 0.125f,
577 0.09375f, 0.09375f, 0.09375f, 0.09375f,
578 1.0f, 1.0f, 1.0f, 1.0f,
579 0.25f, 0.25f, 0.25f, 0.25f,
581 0.125f, 0.125f, 0.125f, 0.125f,
582 0.09375f, 0.09375f, 0.09375f, 0.09375f,
583 1.0f, 1.0f, 1.0f, 1.0f,
584 0.25f, 0.25f, 0.25f, 0.25f,
586 0.125f, 0.125f, 0.125f, 0.125f,
587 0.09375f, 0.09375f, 0.09375f, 0.09375f,
588 1.0f, 1.0f, 1.0f, 1.0f,
589 0.25f, 0.25f, 0.25f, 0.25f,
591 float out_data[4 * 16];
592 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
593 tester.get_chain()->add_effect(new MipmapNeedingEffect());
594 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
596 expect_equal(expected_data, out_data, 4, 16);
599 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
600 float data[] = { // In 4x4 blocks.
601 1.0f, 0.0f, 0.0f, 0.0f,
602 0.0f, 0.0f, 0.0f, 0.0f,
603 0.0f, 0.0f, 0.0f, 0.0f,
604 0.0f, 0.0f, 0.0f, 1.0f,
606 0.0f, 0.0f, 0.0f, 0.0f,
607 0.0f, 0.5f, 0.0f, 0.0f,
608 0.0f, 0.0f, 1.0f, 0.0f,
609 0.0f, 0.0f, 0.0f, 0.0f,
611 1.0f, 1.0f, 1.0f, 1.0f,
612 1.0f, 1.0f, 1.0f, 1.0f,
613 1.0f, 1.0f, 1.0f, 1.0f,
614 1.0f, 1.0f, 1.0f, 1.0f,
616 0.0f, 0.0f, 0.0f, 0.0f,
617 0.0f, 1.0f, 1.0f, 0.0f,
618 0.0f, 1.0f, 1.0f, 0.0f,
619 0.0f, 0.0f, 0.0f, 0.0f,
621 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
622 0.1250f, 0.1250f, 0.1250f, 0.1250f,
623 0.1250f, 0.1250f, 0.1250f, 0.1250f,
624 0.1211f, 0.1211f, 0.1211f, 0.1211f,
625 0.1133f, 0.1133f, 0.1133f, 0.1133f,
626 0.1055f, 0.1055f, 0.1055f, 0.1055f,
627 0.0977f, 0.0977f, 0.0977f, 0.0977f,
628 0.2070f, 0.2070f, 0.2070f, 0.2070f,
629 0.4336f, 0.4336f, 0.4336f, 0.4336f,
630 0.6602f, 0.6602f, 0.6602f, 0.6602f,
631 0.8867f, 0.8867f, 0.8867f, 0.8867f,
632 0.9062f, 0.9062f, 0.9062f, 0.9062f,
633 0.7188f, 0.7188f, 0.7188f, 0.7188f,
634 0.5312f, 0.5312f, 0.5312f, 0.5312f,
635 0.3438f, 0.3438f, 0.3438f, 0.3438f,
636 0.2500f, 0.2500f, 0.2500f, 0.2500f,
637 0.2500f, 0.2500f, 0.2500f, 0.2500f,
639 float out_data[4 * 16];
641 ResizeEffect *downscale = new ResizeEffect();
642 ASSERT_TRUE(downscale->set_int("width", 1));
643 ASSERT_TRUE(downscale->set_int("height", 4));
645 ResizeEffect *upscale = new ResizeEffect();
646 ASSERT_TRUE(upscale->set_int("width", 4));
647 ASSERT_TRUE(upscale->set_int("height", 16));
649 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
650 tester.get_chain()->add_effect(downscale);
651 tester.get_chain()->add_effect(upscale);
652 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
654 expect_equal(expected_data, out_data, 4, 16);
657 // An effect that adds its two inputs together. Used below.
658 class AddEffect : public Effect {
661 virtual string effect_type_id() const { return "AddEffect"; }
662 string output_fragment_shader() { return read_file("add.frag"); }
663 virtual unsigned num_inputs() const { return 2; }
664 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
667 // Constructs the graph
671 // MultiplyEffect MultiplyEffect |
675 // and verifies that it gives the correct output.
676 TEST(EffectChainTest, DiamondGraph) {
681 float expected_data[] = {
685 float out_data[2 * 2];
687 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
688 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
690 MultiplyEffect *mul_half = new MultiplyEffect();
691 ASSERT_TRUE(mul_half->set_vec4("factor", half));
693 MultiplyEffect *mul_two = new MultiplyEffect();
694 ASSERT_TRUE(mul_two->set_vec4("factor", two));
696 EffectChainTester tester(NULL, 2, 2);
699 format.color_space = COLORSPACE_sRGB;
700 format.gamma_curve = GAMMA_LINEAR;
702 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
703 input->set_pixel_data(data);
705 tester.get_chain()->add_input(input);
706 tester.get_chain()->add_effect(mul_half, input);
707 tester.get_chain()->add_effect(mul_two, input);
708 tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
709 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
711 expect_equal(expected_data, out_data, 2, 2);
714 // Constructs the graph
718 // MultiplyEffect MultiplyEffect |
720 // \ BouncingIdentityEffect |
724 // and verifies that it gives the correct output.
725 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
730 float expected_data[] = {
734 float out_data[2 * 2];
736 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
737 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
739 MultiplyEffect *mul_half = new MultiplyEffect();
740 ASSERT_TRUE(mul_half->set_vec4("factor", half));
742 MultiplyEffect *mul_two = new MultiplyEffect();
743 ASSERT_TRUE(mul_two->set_vec4("factor", two));
745 BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
747 EffectChainTester tester(NULL, 2, 2);
750 format.color_space = COLORSPACE_sRGB;
751 format.gamma_curve = GAMMA_LINEAR;
753 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
754 input->set_pixel_data(data);
756 tester.get_chain()->add_input(input);
757 tester.get_chain()->add_effect(mul_half, input);
758 tester.get_chain()->add_effect(mul_two, input);
759 tester.get_chain()->add_effect(bounce, mul_two);
760 tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
761 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
763 expect_equal(expected_data, out_data, 2, 2);
766 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
771 float expected_data[] = {
772 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
775 float out_data[2 * 2];
777 EffectChainTester tester(NULL, 2, 2);
778 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
780 // MirrorEffect does not get linear light, so the conversions will be
781 // inserted after it, not before.
782 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
783 tester.get_chain()->add_effect(effect);
785 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
786 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
787 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
788 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
790 expect_equal(expected_data, out_data, 2, 2);
792 Node *node = effect->replaced_node;
793 ASSERT_EQ(1, node->incoming_links.size());
794 ASSERT_EQ(1, node->outgoing_links.size());
795 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
796 EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
799 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
804 float expected_data[] = {
805 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
808 float out_data[2 * 2];
810 EffectChainTester tester(NULL, 2, 2);
811 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
813 // MirrorEffect does not get linear light, so the conversions will be
814 // inserted after it, not before.
815 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
816 tester.get_chain()->add_effect(effect);
818 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
819 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
820 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
821 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
823 expect_equal(expected_data, out_data, 2, 2);
825 Node *node = effect->replaced_node;
826 ASSERT_EQ(1, node->incoming_links.size());
827 ASSERT_EQ(1, node->outgoing_links.size());
828 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
829 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
832 // An effect that does nothing, but requests texture bounce and stores
834 class SizeStoringEffect : public BouncingIdentityEffect {
836 SizeStoringEffect() : input_width(-1), input_height(-1) {}
837 virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) {
838 assert(input_num == 0);
840 input_height = height;
842 virtual string effect_type_id() const { return "SizeStoringEffect"; }
844 int input_width, input_height;
847 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
848 float data[2 * 2] = {
852 float out_data[2 * 2];
854 EffectChainTester tester(NULL, 4, 3); // Note non-square aspect.
857 format.color_space = COLORSPACE_sRGB;
858 format.gamma_curve = GAMMA_LINEAR;
860 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
861 input1->set_pixel_data(data);
863 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
864 input2->set_pixel_data(data);
866 SizeStoringEffect *input_store = new SizeStoringEffect();
868 tester.get_chain()->add_input(input1);
869 tester.get_chain()->add_input(input2);
870 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
871 tester.get_chain()->add_effect(input_store);
872 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
874 EXPECT_EQ(2, input_store->input_width);
875 EXPECT_EQ(2, input_store->input_height);
878 TEST(EffectChainTest, AspectRatioConversion) {
879 float data1[4 * 3] = {
880 0.0f, 0.0f, 0.0f, 0.0f,
881 0.0f, 0.0f, 0.0f, 0.0f,
882 0.0f, 0.0f, 0.0f, 0.0f,
884 float data2[7 * 7] = {
885 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
886 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
887 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
888 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
889 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
890 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
891 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
894 // The right conversion here is that the 7x7 image decides the size,
895 // since it is the biggest, so everything is scaled up to 9x7
896 // (keep the height, round the width 9.333 to 9).
897 float out_data[9 * 7];
899 EffectChainTester tester(NULL, 4, 3);
902 format.color_space = COLORSPACE_sRGB;
903 format.gamma_curve = GAMMA_LINEAR;
905 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
906 input1->set_pixel_data(data1);
908 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
909 input2->set_pixel_data(data2);
911 SizeStoringEffect *input_store = new SizeStoringEffect();
913 tester.get_chain()->add_input(input1);
914 tester.get_chain()->add_input(input2);
915 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
916 tester.get_chain()->add_effect(input_store);
917 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
919 EXPECT_EQ(9, input_store->input_width);
920 EXPECT_EQ(7, input_store->input_height);
923 // An effect that does nothing except changing its output sizes.
924 class VirtualResizeEffect : public Effect {
926 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
929 virtual_width(virtual_width),
930 virtual_height(virtual_height) {}
931 virtual string effect_type_id() const { return "VirtualResizeEffect"; }
932 string output_fragment_shader() { return read_file("identity.frag"); }
934 virtual bool changes_output_size() const { return true; }
936 virtual void get_output_size(unsigned *width, unsigned *height,
937 unsigned *virtual_width, unsigned *virtual_height) const {
938 *width = this->width;
939 *height = this->height;
940 *virtual_width = this->virtual_width;
941 *virtual_height = this->virtual_height;
945 int width, height, virtual_width, virtual_height;
948 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
949 const int size = 2, bigger_size = 3;
950 float data[size * size] = {
954 float out_data[size * size];
956 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
958 SizeStoringEffect *size_store = new SizeStoringEffect();
960 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
961 tester.get_chain()->add_effect(size_store);
962 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
963 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
965 EXPECT_EQ(bigger_size, size_store->input_width);
966 EXPECT_EQ(bigger_size, size_store->input_height);
968 // If the resize is implemented as non-virtual, we'll fail here,
969 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
970 expect_equal(data, out_data, size, size);
973 // Does not use EffectChainTest, so that it can construct an EffectChain without
974 // a shared ResourcePool (which is also properly destroyed afterwards).
975 // Also turns on debugging to test that code path.
976 TEST(EffectChainTest, IdentityWithOwnPool) {
977 const int width = 3, height = 2;
982 const float expected_data[] = {
986 float out_data[6], temp[6 * 4];
988 EffectChain chain(width, height);
989 movit_debug_level = MOVIT_DEBUG_ON;
992 format.color_space = COLORSPACE_sRGB;
993 format.gamma_curve = GAMMA_LINEAR;
995 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
996 input->set_pixel_data(data);
997 chain.add_input(input);
998 chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1001 glGenTextures(1, &texnum);
1003 glBindTexture(GL_TEXTURE_2D, texnum);
1005 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
1008 glGenFramebuffers(1, &fbo);
1010 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1012 glFramebufferTexture2D(
1014 GL_COLOR_ATTACHMENT0,
1019 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1024 chain.render_to_fbo(fbo, width, height);
1026 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1028 glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp);
1030 for (unsigned i = 0; i < 6; ++i) {
1031 out_data[i] = temp[i * 4];
1034 expect_equal(expected_data, out_data, width, height);
1036 // Reset the debug status again.
1037 movit_debug_level = MOVIT_DEBUG_OFF;
1040 } // namespace movit