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[] = {
160 float expected_data[] = {
161 1.0000f, 1.0000f, 1.0000f, 1.0000f,
162 0.9771f, 0.9771f, 0.9771f, 1.0000f,
163 0.8983f, 0.8983f, 0.8983f, 1.0000f,
164 0.0000f, 0.0000f, 0.0000f, 1.0000f
166 float out_data[4 * 4];
167 EffectChainTester tester(NULL, 1, 4);
168 tester.add_input(data, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_sRGB);
169 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
170 tester.get_chain()->add_effect(effect);
171 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
173 Node *node = effect->replaced_node;
174 ASSERT_EQ(1, node->incoming_links.size());
175 ASSERT_EQ(1, node->outgoing_links.size());
176 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
177 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
179 expect_equal(expected_data, out_data, 4, 4);
182 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
187 float expected_data[6] = {
192 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
193 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
194 tester.get_chain()->add_effect(effect);
195 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
197 Node *node = effect->replaced_node;
198 ASSERT_EQ(1, node->incoming_links.size());
199 ASSERT_EQ(1, node->outgoing_links.size());
200 EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
201 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
203 expect_equal(expected_data, out_data, 3, 2);
206 // A fake input that can change its output colorspace and gamma between instantiation
208 class UnknownColorspaceInput : public FlatInput {
210 UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
211 : FlatInput(format, pixel_format, type, width, height),
212 overridden_color_space(format.color_space),
213 overridden_gamma_curve(format.gamma_curve) {}
214 virtual string effect_type_id() const { return "UnknownColorspaceInput"; }
216 void set_color_space(Colorspace colorspace) {
217 overridden_color_space = colorspace;
219 void set_gamma_curve(GammaCurve gamma_curve) {
220 overridden_gamma_curve = gamma_curve;
222 Colorspace get_color_space() const { return overridden_color_space; }
223 GammaCurve get_gamma_curve() const { return overridden_gamma_curve; }
226 Colorspace overridden_color_space;
227 GammaCurve overridden_gamma_curve;
230 TEST(EffectChainTest, HandlesInputChangingColorspace) {
239 float out_data[size];
241 EffectChainTester tester(NULL, 4, 1, FORMAT_GRAYSCALE);
243 // First say that we have sRGB, linear input.
245 format.color_space = COLORSPACE_sRGB;
246 format.gamma_curve = GAMMA_LINEAR;
248 UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
249 input->set_pixel_data(data);
250 tester.get_chain()->add_input(input);
252 // Now we change to Rec. 601 input.
253 input->set_color_space(COLORSPACE_REC_601_625);
254 input->set_gamma_curve(GAMMA_REC_601);
256 // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
257 tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
258 expect_equal(data, out_data, 4, 1);
261 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
266 float expected_data[6] = {
271 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
272 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
273 tester.get_chain()->add_effect(effect);
274 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
276 Node *node = effect->replaced_node;
277 ASSERT_EQ(1, node->incoming_links.size());
278 EXPECT_EQ(0, node->outgoing_links.size());
279 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
281 expect_equal(expected_data, out_data, 3, 2);
284 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
289 float expected_data[6] = {
294 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
295 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
296 tester.get_chain()->add_effect(effect);
297 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
299 Node *node = effect->replaced_node;
300 ASSERT_EQ(1, node->incoming_links.size());
301 EXPECT_EQ(0, node->outgoing_links.size());
302 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
304 expect_equal(expected_data, out_data, 3, 2);
307 // The identity effect needs linear light, and thus will get conversions on both sides.
308 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
309 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
311 for (unsigned i = 0; i < 256; ++i) {
315 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
316 tester.get_chain()->add_effect(new IdentityEffect());
317 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
319 expect_equal(data, out_data, 256, 1);
322 // Same, but uses the forward sRGB table from the GPU.
323 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
324 unsigned char data[256];
325 float expected_data[256];
326 for (unsigned i = 0; i < 256; ++i) {
328 expected_data[i] = i / 255.0;
331 EffectChainTester tester(NULL, 256, 1);
332 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
333 tester.get_chain()->add_effect(new IdentityEffect());
334 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
336 expect_equal(expected_data, out_data, 256, 1);
339 // Same, for the Rec. 601/709 gamma curve.
340 TEST(EffectChainTest, IdentityThroughRec709) {
342 for (unsigned i = 0; i < 256; ++i) {
346 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
347 tester.get_chain()->add_effect(new IdentityEffect());
348 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
350 expect_equal(data, out_data, 256, 1);
353 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
354 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
356 float data[4 * size] = {
357 0.8f, 0.0f, 0.0f, 0.5f,
358 0.0f, 0.2f, 0.2f, 0.3f,
359 0.1f, 0.0f, 1.0f, 1.0f,
361 float out_data[4 * size];
362 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
363 tester.get_chain()->add_effect(new IdentityEffect());
364 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
366 expect_equal(data, out_data, 4, size);
369 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
371 float data[4 * size] = {
372 0.8f, 0.0f, 0.0f, 0.5f,
373 0.0f, 0.2f, 0.2f, 0.3f,
374 0.1f, 0.0f, 1.0f, 1.0f,
376 float expected_data[4 * size] = {
377 0.1f, 0.0f, 1.0f, 1.0f,
378 0.0f, 0.2f, 0.2f, 0.3f,
379 0.8f, 0.0f, 0.0f, 0.5f,
381 float out_data[4 * size];
382 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
383 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
384 tester.get_chain()->add_effect(effect);
385 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
387 Node *node = effect->replaced_node;
388 ASSERT_EQ(1, node->incoming_links.size());
389 EXPECT_EQ(0, node->outgoing_links.size());
390 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
392 expect_equal(expected_data, out_data, 4, size);
395 // An input that outputs only blue, which has blank alpha.
396 class BlueInput : public Input {
398 BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
399 virtual string effect_type_id() const { return "IdentityEffect"; }
400 string output_fragment_shader() { return read_file("blue.frag"); }
401 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
402 virtual void finalize() {}
403 virtual bool can_output_linear_gamma() const { return true; }
404 virtual unsigned get_width() const { return 1; }
405 virtual unsigned get_height() const { return 1; }
406 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
407 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
413 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
414 // which outputs blank alpha.
415 class RewritingToBlueInput : public Input {
417 RewritingToBlueInput() : blue_node(NULL) { register_int("needs_mipmaps", &needs_mipmaps); }
418 virtual string effect_type_id() const { return "RewritingToBlueInput"; }
419 string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
420 virtual void rewrite_graph(EffectChain *graph, Node *self) {
421 Node *blue_node = graph->add_node(new BlueInput());
422 graph->replace_receiver(self, blue_node);
423 graph->replace_sender(self, blue_node);
425 self->disabled = true;
426 this->blue_node = blue_node;
429 // Dummy values that we need to implement because we inherit from Input.
430 // Same as BlueInput.
431 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
432 virtual void finalize() {}
433 virtual bool can_output_linear_gamma() const { return true; }
434 virtual unsigned get_width() const { return 1; }
435 virtual unsigned get_height() const { return 1; }
436 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
437 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
445 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
447 float data[4 * size] = {
448 0.0f, 0.0f, 1.0f, 1.0f,
449 0.0f, 0.0f, 1.0f, 1.0f,
450 0.0f, 0.0f, 1.0f, 1.0f,
452 float out_data[4 * size];
453 EffectChainTester tester(NULL, size, 1);
454 RewritingToBlueInput *input = new RewritingToBlueInput();
455 tester.get_chain()->add_input(input);
456 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
458 Node *node = input->blue_node;
459 EXPECT_EQ(0, node->incoming_links.size());
460 EXPECT_EQ(0, node->outgoing_links.size());
462 expect_equal(data, out_data, 4, size);
465 // An effect that does nothing, and specifies that it preserves blank alpha.
466 class BlankAlphaPreservingEffect : public Effect {
468 BlankAlphaPreservingEffect() {}
469 virtual string effect_type_id() const { return "BlankAlphaPreservingEffect"; }
470 string output_fragment_shader() { return read_file("identity.frag"); }
471 virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
474 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
476 float data[4 * size] = {
477 0.0f, 0.0f, 1.0f, 1.0f,
478 0.0f, 0.0f, 1.0f, 1.0f,
479 0.0f, 0.0f, 1.0f, 1.0f,
481 float out_data[4 * size];
482 EffectChainTester tester(NULL, size, 1);
483 tester.get_chain()->add_input(new BlueInput());
484 tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
485 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
486 tester.get_chain()->add_effect(effect);
487 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
489 Node *node = effect->replaced_node;
490 EXPECT_EQ(1, node->incoming_links.size());
491 EXPECT_EQ(0, node->outgoing_links.size());
493 expect_equal(data, out_data, 4, size);
496 // This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
497 // just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
498 // an alpha conversion _should_ be inserted at the very end. (There is some overlap
499 // with other tests.)
500 TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
502 float data[4 * size] = {
503 0.0f, 0.0f, 1.0f, 1.0f,
504 0.0f, 0.0f, 1.0f, 1.0f,
505 0.0f, 0.0f, 1.0f, 1.0f,
507 float out_data[4 * size];
508 EffectChainTester tester(NULL, size, 1);
509 tester.get_chain()->add_input(new BlueInput());
510 tester.get_chain()->add_effect(new IdentityEffect()); // Not BlankAlphaPreservingEffect.
511 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
512 tester.get_chain()->add_effect(effect);
513 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
515 Node *node = effect->replaced_node;
516 EXPECT_EQ(1, node->incoming_links.size());
517 EXPECT_EQ(1, node->outgoing_links.size());
518 EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
520 expect_equal(data, out_data, 4, size);
523 // Effectively scales down its input linearly by 4x (and repeating it),
524 // which is not attainable without mipmaps.
525 class MipmapNeedingEffect : public Effect {
527 MipmapNeedingEffect() {}
528 virtual bool needs_mipmaps() const { return true; }
530 // To be allowed to mess with the sampler state.
531 virtual bool needs_texture_bounce() const { return true; }
533 virtual string effect_type_id() const { return "MipmapNeedingEffect"; }
534 string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
535 virtual void inform_added(EffectChain *chain) { this->chain = chain; }
537 void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
539 Node *self = chain->find_node_for_effect(this);
540 glActiveTexture(chain->get_input_sampler(self, 0));
542 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
544 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
552 TEST(EffectChainTest, MipmapGenerationWorks) {
553 float data[] = { // In 4x4 blocks.
554 1.0f, 0.0f, 0.0f, 0.0f,
555 0.0f, 0.0f, 0.0f, 0.0f,
556 0.0f, 0.0f, 0.0f, 0.0f,
557 0.0f, 0.0f, 0.0f, 1.0f,
559 0.0f, 0.0f, 0.0f, 0.0f,
560 0.0f, 0.5f, 0.0f, 0.0f,
561 0.0f, 0.0f, 1.0f, 0.0f,
562 0.0f, 0.0f, 0.0f, 0.0f,
564 1.0f, 1.0f, 1.0f, 1.0f,
565 1.0f, 1.0f, 1.0f, 1.0f,
566 1.0f, 1.0f, 1.0f, 1.0f,
567 1.0f, 1.0f, 1.0f, 1.0f,
569 0.0f, 0.0f, 0.0f, 0.0f,
570 0.0f, 1.0f, 1.0f, 0.0f,
571 0.0f, 1.0f, 1.0f, 0.0f,
572 0.0f, 0.0f, 0.0f, 0.0f,
574 float expected_data[] = { // Repeated four times each way.
575 0.125f, 0.125f, 0.125f, 0.125f,
576 0.09375f, 0.09375f, 0.09375f, 0.09375f,
577 1.0f, 1.0f, 1.0f, 1.0f,
578 0.25f, 0.25f, 0.25f, 0.25f,
580 0.125f, 0.125f, 0.125f, 0.125f,
581 0.09375f, 0.09375f, 0.09375f, 0.09375f,
582 1.0f, 1.0f, 1.0f, 1.0f,
583 0.25f, 0.25f, 0.25f, 0.25f,
585 0.125f, 0.125f, 0.125f, 0.125f,
586 0.09375f, 0.09375f, 0.09375f, 0.09375f,
587 1.0f, 1.0f, 1.0f, 1.0f,
588 0.25f, 0.25f, 0.25f, 0.25f,
590 0.125f, 0.125f, 0.125f, 0.125f,
591 0.09375f, 0.09375f, 0.09375f, 0.09375f,
592 1.0f, 1.0f, 1.0f, 1.0f,
593 0.25f, 0.25f, 0.25f, 0.25f,
595 float out_data[4 * 16];
596 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
597 tester.get_chain()->add_effect(new MipmapNeedingEffect());
598 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
600 expect_equal(expected_data, out_data, 4, 16);
603 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
604 float data[] = { // In 4x4 blocks.
605 1.0f, 0.0f, 0.0f, 0.0f,
606 0.0f, 0.0f, 0.0f, 0.0f,
607 0.0f, 0.0f, 0.0f, 0.0f,
608 0.0f, 0.0f, 0.0f, 1.0f,
610 0.0f, 0.0f, 0.0f, 0.0f,
611 0.0f, 0.5f, 0.0f, 0.0f,
612 0.0f, 0.0f, 1.0f, 0.0f,
613 0.0f, 0.0f, 0.0f, 0.0f,
615 1.0f, 1.0f, 1.0f, 1.0f,
616 1.0f, 1.0f, 1.0f, 1.0f,
617 1.0f, 1.0f, 1.0f, 1.0f,
618 1.0f, 1.0f, 1.0f, 1.0f,
620 0.0f, 0.0f, 0.0f, 0.0f,
621 0.0f, 1.0f, 1.0f, 0.0f,
622 0.0f, 1.0f, 1.0f, 0.0f,
623 0.0f, 0.0f, 0.0f, 0.0f,
625 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
626 0.1250f, 0.1250f, 0.1250f, 0.1250f,
627 0.1250f, 0.1250f, 0.1250f, 0.1250f,
628 0.1211f, 0.1211f, 0.1211f, 0.1211f,
629 0.1133f, 0.1133f, 0.1133f, 0.1133f,
630 0.1055f, 0.1055f, 0.1055f, 0.1055f,
631 0.0977f, 0.0977f, 0.0977f, 0.0977f,
632 0.2070f, 0.2070f, 0.2070f, 0.2070f,
633 0.4336f, 0.4336f, 0.4336f, 0.4336f,
634 0.6602f, 0.6602f, 0.6602f, 0.6602f,
635 0.8867f, 0.8867f, 0.8867f, 0.8867f,
636 0.9062f, 0.9062f, 0.9062f, 0.9062f,
637 0.7188f, 0.7188f, 0.7188f, 0.7188f,
638 0.5312f, 0.5312f, 0.5312f, 0.5312f,
639 0.3438f, 0.3438f, 0.3438f, 0.3438f,
640 0.2500f, 0.2500f, 0.2500f, 0.2500f,
641 0.2500f, 0.2500f, 0.2500f, 0.2500f,
643 float out_data[4 * 16];
645 ResizeEffect *downscale = new ResizeEffect();
646 ASSERT_TRUE(downscale->set_int("width", 1));
647 ASSERT_TRUE(downscale->set_int("height", 4));
649 ResizeEffect *upscale = new ResizeEffect();
650 ASSERT_TRUE(upscale->set_int("width", 4));
651 ASSERT_TRUE(upscale->set_int("height", 16));
653 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
654 tester.get_chain()->add_effect(downscale);
655 tester.get_chain()->add_effect(upscale);
656 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
658 expect_equal(expected_data, out_data, 4, 16);
661 // An effect that adds its two inputs together. Used below.
662 class AddEffect : public Effect {
665 virtual string effect_type_id() const { return "AddEffect"; }
666 string output_fragment_shader() { return read_file("add.frag"); }
667 virtual unsigned num_inputs() const { return 2; }
668 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
671 // Constructs the graph
675 // MultiplyEffect MultiplyEffect |
679 // and verifies that it gives the correct output.
680 TEST(EffectChainTest, DiamondGraph) {
685 float expected_data[] = {
689 float out_data[2 * 2];
691 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
692 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
694 MultiplyEffect *mul_half = new MultiplyEffect();
695 ASSERT_TRUE(mul_half->set_vec4("factor", half));
697 MultiplyEffect *mul_two = new MultiplyEffect();
698 ASSERT_TRUE(mul_two->set_vec4("factor", two));
700 EffectChainTester tester(NULL, 2, 2);
703 format.color_space = COLORSPACE_sRGB;
704 format.gamma_curve = GAMMA_LINEAR;
706 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
707 input->set_pixel_data(data);
709 tester.get_chain()->add_input(input);
710 tester.get_chain()->add_effect(mul_half, input);
711 tester.get_chain()->add_effect(mul_two, input);
712 tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
713 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
715 expect_equal(expected_data, out_data, 2, 2);
718 // Constructs the graph
722 // MultiplyEffect MultiplyEffect |
724 // \ BouncingIdentityEffect |
728 // and verifies that it gives the correct output.
729 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
734 float expected_data[] = {
738 float out_data[2 * 2];
740 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
741 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
743 MultiplyEffect *mul_half = new MultiplyEffect();
744 ASSERT_TRUE(mul_half->set_vec4("factor", half));
746 MultiplyEffect *mul_two = new MultiplyEffect();
747 ASSERT_TRUE(mul_two->set_vec4("factor", two));
749 BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
751 EffectChainTester tester(NULL, 2, 2);
754 format.color_space = COLORSPACE_sRGB;
755 format.gamma_curve = GAMMA_LINEAR;
757 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
758 input->set_pixel_data(data);
760 tester.get_chain()->add_input(input);
761 tester.get_chain()->add_effect(mul_half, input);
762 tester.get_chain()->add_effect(mul_two, input);
763 tester.get_chain()->add_effect(bounce, mul_two);
764 tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
765 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
767 expect_equal(expected_data, out_data, 2, 2);
770 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
775 float expected_data[] = {
776 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
779 float out_data[2 * 2];
781 EffectChainTester tester(NULL, 2, 2);
782 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
784 // MirrorEffect does not get linear light, so the conversions will be
785 // inserted after it, not before.
786 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
787 tester.get_chain()->add_effect(effect);
789 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
790 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
791 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
792 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
794 expect_equal(expected_data, out_data, 2, 2);
796 Node *node = effect->replaced_node;
797 ASSERT_EQ(1, node->incoming_links.size());
798 ASSERT_EQ(1, node->outgoing_links.size());
799 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
800 EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
803 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
808 float expected_data[] = {
809 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
812 float out_data[2 * 2];
814 EffectChainTester tester(NULL, 2, 2);
815 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
817 // MirrorEffect does not get linear light, so the conversions will be
818 // inserted after it, not before.
819 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
820 tester.get_chain()->add_effect(effect);
822 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
823 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
824 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
825 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
827 expect_equal(expected_data, out_data, 2, 2);
829 Node *node = effect->replaced_node;
830 ASSERT_EQ(1, node->incoming_links.size());
831 ASSERT_EQ(1, node->outgoing_links.size());
832 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
833 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
836 // An effect that does nothing, but requests texture bounce and stores
838 class SizeStoringEffect : public BouncingIdentityEffect {
840 SizeStoringEffect() : input_width(-1), input_height(-1) {}
841 virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) {
842 assert(input_num == 0);
844 input_height = height;
846 virtual string effect_type_id() const { return "SizeStoringEffect"; }
848 int input_width, input_height;
851 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
852 float data[2 * 2] = {
856 float out_data[2 * 2];
858 EffectChainTester tester(NULL, 4, 3); // Note non-square aspect.
861 format.color_space = COLORSPACE_sRGB;
862 format.gamma_curve = GAMMA_LINEAR;
864 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
865 input1->set_pixel_data(data);
867 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
868 input2->set_pixel_data(data);
870 SizeStoringEffect *input_store = new SizeStoringEffect();
872 tester.get_chain()->add_input(input1);
873 tester.get_chain()->add_input(input2);
874 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
875 tester.get_chain()->add_effect(input_store);
876 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
878 EXPECT_EQ(2, input_store->input_width);
879 EXPECT_EQ(2, input_store->input_height);
882 TEST(EffectChainTest, AspectRatioConversion) {
883 float data1[4 * 3] = {
884 0.0f, 0.0f, 0.0f, 0.0f,
885 0.0f, 0.0f, 0.0f, 0.0f,
886 0.0f, 0.0f, 0.0f, 0.0f,
888 float data2[7 * 7] = {
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,
892 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
893 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
894 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
895 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
898 // The right conversion here is that the 7x7 image decides the size,
899 // since it is the biggest, so everything is scaled up to 9x7
900 // (keep the height, round the width 9.333 to 9).
901 float out_data[9 * 7];
903 EffectChainTester tester(NULL, 4, 3);
906 format.color_space = COLORSPACE_sRGB;
907 format.gamma_curve = GAMMA_LINEAR;
909 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
910 input1->set_pixel_data(data1);
912 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
913 input2->set_pixel_data(data2);
915 SizeStoringEffect *input_store = new SizeStoringEffect();
917 tester.get_chain()->add_input(input1);
918 tester.get_chain()->add_input(input2);
919 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
920 tester.get_chain()->add_effect(input_store);
921 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
923 EXPECT_EQ(9, input_store->input_width);
924 EXPECT_EQ(7, input_store->input_height);
927 // An effect that does nothing except changing its output sizes.
928 class VirtualResizeEffect : public Effect {
930 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
933 virtual_width(virtual_width),
934 virtual_height(virtual_height) {}
935 virtual string effect_type_id() const { return "VirtualResizeEffect"; }
936 string output_fragment_shader() { return read_file("identity.frag"); }
938 virtual bool changes_output_size() const { return true; }
940 virtual void get_output_size(unsigned *width, unsigned *height,
941 unsigned *virtual_width, unsigned *virtual_height) const {
942 *width = this->width;
943 *height = this->height;
944 *virtual_width = this->virtual_width;
945 *virtual_height = this->virtual_height;
949 int width, height, virtual_width, virtual_height;
952 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
953 const int size = 2, bigger_size = 3;
954 float data[size * size] = {
958 float out_data[size * size];
960 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
962 SizeStoringEffect *size_store = new SizeStoringEffect();
964 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
965 tester.get_chain()->add_effect(size_store);
966 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
967 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
969 EXPECT_EQ(bigger_size, size_store->input_width);
970 EXPECT_EQ(bigger_size, size_store->input_height);
972 // If the resize is implemented as non-virtual, we'll fail here,
973 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
974 expect_equal(data, out_data, size, size);
977 // Does not use EffectChainTest, so that it can construct an EffectChain without
978 // a shared ResourcePool (which is also properly destroyed afterwards).
979 // Also turns on debugging to test that code path.
980 TEST(EffectChainTest, IdentityWithOwnPool) {
981 const int width = 3, height = 2;
986 const float expected_data[] = {
990 float out_data[6], temp[6 * 4];
992 EffectChain chain(width, height);
993 movit_debug_level = MOVIT_DEBUG_ON;
996 format.color_space = COLORSPACE_sRGB;
997 format.gamma_curve = GAMMA_LINEAR;
999 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
1000 input->set_pixel_data(data);
1001 chain.add_input(input);
1002 chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1005 glGenTextures(1, &texnum);
1007 glBindTexture(GL_TEXTURE_2D, texnum);
1009 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
1012 glGenFramebuffers(1, &fbo);
1014 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1016 glFramebufferTexture2D(
1018 GL_COLOR_ATTACHMENT0,
1023 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1028 chain.render_to_fbo(fbo, width, height);
1030 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1032 glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp);
1034 for (unsigned i = 0; i < 6; ++i) {
1035 out_data[i] = temp[i * 4];
1038 expect_equal(expected_data, out_data, width, height);
1040 // Reset the debug status again.
1041 movit_debug_level = MOVIT_DEBUG_OFF;
1044 } // namespace movit