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 "resize_effect.h"
15 #include "test_util.h"
18 TEST(EffectChainTest, EmptyChain) {
24 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
25 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
27 expect_equal(data, out_data, 3, 2);
30 // An effect that does nothing.
31 class IdentityEffect : public Effect {
34 virtual std::string effect_type_id() const { return "IdentityEffect"; }
35 std::string output_fragment_shader() { return read_file("identity.frag"); }
38 TEST(EffectChainTest, Identity) {
44 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
45 tester.get_chain()->add_effect(new IdentityEffect());
46 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
48 expect_equal(data, out_data, 3, 2);
51 // An effect that does nothing, but requests texture bounce.
52 class BouncingIdentityEffect : public Effect {
54 BouncingIdentityEffect() {}
55 virtual std::string effect_type_id() const { return "IdentityEffect"; }
56 std::string output_fragment_shader() { return read_file("identity.frag"); }
57 bool needs_texture_bounce() const { return true; }
58 AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
61 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
67 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
68 tester.get_chain()->add_effect(new BouncingIdentityEffect());
69 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
71 expect_equal(data, out_data, 3, 2);
74 TEST(MirrorTest, BasicTest) {
79 float expected_data[6] = {
84 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
85 tester.get_chain()->add_effect(new MirrorEffect());
86 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
88 expect_equal(expected_data, out_data, 3, 2);
91 // A dummy effect that inverts its input.
92 class InvertEffect : public Effect {
95 virtual std::string effect_type_id() const { return "InvertEffect"; }
96 std::string output_fragment_shader() { return read_file("invert_effect.frag"); }
98 // A real invert would actually care about its alpha,
99 // but in this unit test, it only complicates things.
100 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
103 // Like IdentityEffect, but rewrites itself out of the loop,
104 // splicing in a different effect instead. Also stores the new node,
105 // so we later can check whatever properties we'd like about the graph.
107 class RewritingEffect : public Effect {
109 RewritingEffect() : effect(new T()), replaced_node(NULL) {}
110 virtual std::string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
111 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
112 virtual void rewrite_graph(EffectChain *graph, Node *self) {
113 replaced_node = graph->add_node(effect);
114 graph->replace_receiver(self, replaced_node);
115 graph->replace_sender(self, replaced_node);
116 self->disabled = true;
123 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
128 float expected_data[6] = {
129 1.0f, 0.9771f, 0.9673f,
133 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
134 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
135 tester.get_chain()->add_effect(effect);
136 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
138 Node *node = effect->replaced_node;
139 ASSERT_EQ(1, node->incoming_links.size());
140 ASSERT_EQ(1, node->outgoing_links.size());
141 EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
142 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
144 expect_equal(expected_data, out_data, 3, 2);
147 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
148 unsigned char data[] = {
152 float expected_data[4] = {
157 EffectChainTester tester(NULL, 2, 2);
158 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
159 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
160 tester.get_chain()->add_effect(effect);
161 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
163 Node *node = effect->replaced_node;
164 ASSERT_EQ(1, node->incoming_links.size());
165 ASSERT_EQ(1, node->outgoing_links.size());
166 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
167 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
169 expect_equal(expected_data, out_data, 2, 2);
172 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
177 float expected_data[6] = {
182 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
183 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
184 tester.get_chain()->add_effect(effect);
185 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
187 Node *node = effect->replaced_node;
188 ASSERT_EQ(1, node->incoming_links.size());
189 ASSERT_EQ(1, node->outgoing_links.size());
190 EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
191 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
193 expect_equal(expected_data, out_data, 3, 2);
196 // A fake input that can change its output colorspace and gamma between instantiation
198 class UnknownColorspaceInput : public FlatInput {
200 UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
201 : FlatInput(format, pixel_format, type, width, height),
202 overridden_color_space(format.color_space),
203 overridden_gamma_curve(format.gamma_curve) {}
204 virtual std::string effect_type_id() const { return "UnknownColorspaceInput"; }
206 void set_color_space(Colorspace colorspace) {
207 overridden_color_space = colorspace;
209 void set_gamma_curve(GammaCurve gamma_curve) {
210 overridden_gamma_curve = gamma_curve;
212 Colorspace get_color_space() const { return overridden_color_space; }
213 GammaCurve get_gamma_curve() const { return overridden_gamma_curve; }
216 Colorspace overridden_color_space;
217 GammaCurve overridden_gamma_curve;
220 TEST(EffectChainTester, HandlesInputChangingColorspace) {
229 float out_data[size];
231 EffectChainTester tester(NULL, 4, 1, FORMAT_GRAYSCALE);
233 // First say that we have sRGB, linear input.
235 format.color_space = COLORSPACE_sRGB;
236 format.gamma_curve = GAMMA_LINEAR;
238 UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
239 input->set_pixel_data(data);
240 tester.get_chain()->add_input(input);
242 // Now we change to Rec. 601 input.
243 input->set_color_space(COLORSPACE_REC_601_625);
244 input->set_gamma_curve(GAMMA_REC_601);
246 // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
247 tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
248 expect_equal(data, out_data, 4, 1);
251 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
256 float expected_data[6] = {
261 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
262 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
263 tester.get_chain()->add_effect(effect);
264 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
266 Node *node = effect->replaced_node;
267 ASSERT_EQ(1, node->incoming_links.size());
268 EXPECT_EQ(0, node->outgoing_links.size());
269 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
271 expect_equal(expected_data, out_data, 3, 2);
274 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
279 float expected_data[6] = {
284 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
285 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
286 tester.get_chain()->add_effect(effect);
287 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
289 Node *node = effect->replaced_node;
290 ASSERT_EQ(1, node->incoming_links.size());
291 EXPECT_EQ(0, node->outgoing_links.size());
292 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
294 expect_equal(expected_data, out_data, 3, 2);
297 // The identity effect needs linear light, and thus will get conversions on both sides.
298 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
299 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
301 for (unsigned i = 0; i < 256; ++i) {
305 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
306 tester.get_chain()->add_effect(new IdentityEffect());
307 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
309 expect_equal(data, out_data, 256, 1);
312 // Same, but uses the forward sRGB table from the GPU.
313 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
314 unsigned char data[256];
315 float expected_data[256];
316 for (unsigned i = 0; i < 256; ++i) {
318 expected_data[i] = i / 255.0;
321 EffectChainTester tester(NULL, 256, 1);
322 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
323 tester.get_chain()->add_effect(new IdentityEffect());
324 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
326 expect_equal(expected_data, out_data, 256, 1);
329 // Same, for the Rec. 601/709 gamma curve.
330 TEST(EffectChainTest, IdentityThroughRec709) {
332 for (unsigned i = 0; i < 256; ++i) {
336 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
337 tester.get_chain()->add_effect(new IdentityEffect());
338 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
340 expect_equal(data, out_data, 256, 1);
343 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
344 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
346 float data[4 * size] = {
347 0.8f, 0.0f, 0.0f, 0.5f,
348 0.0f, 0.2f, 0.2f, 0.3f,
349 0.1f, 0.0f, 1.0f, 1.0f,
351 float out_data[4 * size];
352 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
353 tester.get_chain()->add_effect(new IdentityEffect());
354 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
356 expect_equal(data, out_data, 4, size);
359 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
361 float data[4 * size] = {
362 0.8f, 0.0f, 0.0f, 0.5f,
363 0.0f, 0.2f, 0.2f, 0.3f,
364 0.1f, 0.0f, 1.0f, 1.0f,
366 float expected_data[4 * size] = {
367 0.1f, 0.0f, 1.0f, 1.0f,
368 0.0f, 0.2f, 0.2f, 0.3f,
369 0.8f, 0.0f, 0.0f, 0.5f,
371 float out_data[4 * size];
372 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
373 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
374 tester.get_chain()->add_effect(effect);
375 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
377 Node *node = effect->replaced_node;
378 ASSERT_EQ(1, node->incoming_links.size());
379 EXPECT_EQ(0, node->outgoing_links.size());
380 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
382 expect_equal(expected_data, out_data, 4, size);
385 // An input that outputs only blue, which has blank alpha.
386 class BlueInput : public Input {
388 BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
389 virtual std::string effect_type_id() const { return "IdentityEffect"; }
390 std::string output_fragment_shader() { return read_file("blue.frag"); }
391 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
392 virtual void finalize() {}
393 virtual bool can_output_linear_gamma() const { return true; }
394 virtual unsigned get_width() const { return 1; }
395 virtual unsigned get_height() const { return 1; }
396 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
397 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
403 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
404 // which outputs blank alpha.
405 class RewritingToBlueInput : public Input {
407 RewritingToBlueInput() : blue_node(NULL) { register_int("needs_mipmaps", &needs_mipmaps); }
408 virtual std::string effect_type_id() const { return "RewritingToBlueInput"; }
409 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
410 virtual void rewrite_graph(EffectChain *graph, Node *self) {
411 Node *blue_node = graph->add_node(new BlueInput());
412 graph->replace_receiver(self, blue_node);
413 graph->replace_sender(self, blue_node);
415 self->disabled = true;
416 this->blue_node = blue_node;
419 // Dummy values that we need to implement because we inherit from Input.
420 // Same as BlueInput.
421 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
422 virtual void finalize() {}
423 virtual bool can_output_linear_gamma() const { return true; }
424 virtual unsigned get_width() const { return 1; }
425 virtual unsigned get_height() const { return 1; }
426 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
427 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
435 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
437 float data[4 * size] = {
438 0.0f, 0.0f, 1.0f, 1.0f,
439 0.0f, 0.0f, 1.0f, 1.0f,
440 0.0f, 0.0f, 1.0f, 1.0f,
442 float out_data[4 * size];
443 EffectChainTester tester(NULL, size, 1);
444 RewritingToBlueInput *input = new RewritingToBlueInput();
445 tester.get_chain()->add_input(input);
446 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
448 Node *node = input->blue_node;
449 EXPECT_EQ(0, node->incoming_links.size());
450 EXPECT_EQ(0, node->outgoing_links.size());
452 expect_equal(data, out_data, 4, size);
455 // An effect that does nothing, and specifies that it preserves blank alpha.
456 class BlankAlphaPreservingEffect : public Effect {
458 BlankAlphaPreservingEffect() {}
459 virtual std::string effect_type_id() const { return "BlankAlphaPreservingEffect"; }
460 std::string output_fragment_shader() { return read_file("identity.frag"); }
461 virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
464 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
466 float data[4 * size] = {
467 0.0f, 0.0f, 1.0f, 1.0f,
468 0.0f, 0.0f, 1.0f, 1.0f,
469 0.0f, 0.0f, 1.0f, 1.0f,
471 float out_data[4 * size];
472 EffectChainTester tester(NULL, size, 1);
473 tester.get_chain()->add_input(new BlueInput());
474 tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
475 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
476 tester.get_chain()->add_effect(effect);
477 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
479 Node *node = effect->replaced_node;
480 EXPECT_EQ(1, node->incoming_links.size());
481 EXPECT_EQ(0, node->outgoing_links.size());
483 expect_equal(data, out_data, 4, size);
486 // This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
487 // just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
488 // an alpha conversion _should_ be inserted at the very end. (There is some overlap
489 // with other tests.)
490 TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
492 float data[4 * size] = {
493 0.0f, 0.0f, 1.0f, 1.0f,
494 0.0f, 0.0f, 1.0f, 1.0f,
495 0.0f, 0.0f, 1.0f, 1.0f,
497 float out_data[4 * size];
498 EffectChainTester tester(NULL, size, 1);
499 tester.get_chain()->add_input(new BlueInput());
500 tester.get_chain()->add_effect(new IdentityEffect()); // Not BlankAlphaPreservingEffect.
501 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
502 tester.get_chain()->add_effect(effect);
503 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
505 Node *node = effect->replaced_node;
506 EXPECT_EQ(1, node->incoming_links.size());
507 EXPECT_EQ(1, node->outgoing_links.size());
508 EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
510 expect_equal(data, out_data, 4, size);
513 // Effectively scales down its input linearly by 4x (and repeating it),
514 // which is not attainable without mipmaps.
515 class MipmapNeedingEffect : public Effect {
517 MipmapNeedingEffect() {}
518 virtual bool needs_mipmaps() const { return true; }
519 virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
520 std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
521 void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
523 glActiveTexture(GL_TEXTURE0);
525 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
527 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
532 TEST(EffectChainTest, MipmapGenerationWorks) {
533 float data[] = { // In 4x4 blocks.
534 1.0f, 0.0f, 0.0f, 0.0f,
535 0.0f, 0.0f, 0.0f, 0.0f,
536 0.0f, 0.0f, 0.0f, 0.0f,
537 0.0f, 0.0f, 0.0f, 1.0f,
539 0.0f, 0.0f, 0.0f, 0.0f,
540 0.0f, 0.5f, 0.0f, 0.0f,
541 0.0f, 0.0f, 1.0f, 0.0f,
542 0.0f, 0.0f, 0.0f, 0.0f,
544 1.0f, 1.0f, 1.0f, 1.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,
549 0.0f, 0.0f, 0.0f, 0.0f,
550 0.0f, 1.0f, 1.0f, 0.0f,
551 0.0f, 1.0f, 1.0f, 0.0f,
552 0.0f, 0.0f, 0.0f, 0.0f,
554 float expected_data[] = { // Repeated four times each way.
555 0.125f, 0.125f, 0.125f, 0.125f,
556 0.09375f, 0.09375f, 0.09375f, 0.09375f,
557 1.0f, 1.0f, 1.0f, 1.0f,
558 0.25f, 0.25f, 0.25f, 0.25f,
560 0.125f, 0.125f, 0.125f, 0.125f,
561 0.09375f, 0.09375f, 0.09375f, 0.09375f,
562 1.0f, 1.0f, 1.0f, 1.0f,
563 0.25f, 0.25f, 0.25f, 0.25f,
565 0.125f, 0.125f, 0.125f, 0.125f,
566 0.09375f, 0.09375f, 0.09375f, 0.09375f,
567 1.0f, 1.0f, 1.0f, 1.0f,
568 0.25f, 0.25f, 0.25f, 0.25f,
570 0.125f, 0.125f, 0.125f, 0.125f,
571 0.09375f, 0.09375f, 0.09375f, 0.09375f,
572 1.0f, 1.0f, 1.0f, 1.0f,
573 0.25f, 0.25f, 0.25f, 0.25f,
575 float out_data[4 * 16];
576 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
577 tester.get_chain()->add_effect(new MipmapNeedingEffect());
578 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
580 expect_equal(expected_data, out_data, 4, 16);
583 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
584 float data[] = { // In 4x4 blocks.
585 1.0f, 0.0f, 0.0f, 0.0f,
586 0.0f, 0.0f, 0.0f, 0.0f,
587 0.0f, 0.0f, 0.0f, 0.0f,
588 0.0f, 0.0f, 0.0f, 1.0f,
590 0.0f, 0.0f, 0.0f, 0.0f,
591 0.0f, 0.5f, 0.0f, 0.0f,
592 0.0f, 0.0f, 1.0f, 0.0f,
593 0.0f, 0.0f, 0.0f, 0.0f,
595 1.0f, 1.0f, 1.0f, 1.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,
600 0.0f, 0.0f, 0.0f, 0.0f,
601 0.0f, 1.0f, 1.0f, 0.0f,
602 0.0f, 1.0f, 1.0f, 0.0f,
603 0.0f, 0.0f, 0.0f, 0.0f,
605 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
606 0.1250f, 0.1250f, 0.1250f, 0.1250f,
607 0.1250f, 0.1250f, 0.1250f, 0.1250f,
608 0.1211f, 0.1211f, 0.1211f, 0.1211f,
609 0.1133f, 0.1133f, 0.1133f, 0.1133f,
610 0.1055f, 0.1055f, 0.1055f, 0.1055f,
611 0.0977f, 0.0977f, 0.0977f, 0.0977f,
612 0.2070f, 0.2070f, 0.2070f, 0.2070f,
613 0.4336f, 0.4336f, 0.4336f, 0.4336f,
614 0.6602f, 0.6602f, 0.6602f, 0.6602f,
615 0.8867f, 0.8867f, 0.8867f, 0.8867f,
616 0.9062f, 0.9062f, 0.9062f, 0.9062f,
617 0.7188f, 0.7188f, 0.7188f, 0.7188f,
618 0.5312f, 0.5312f, 0.5312f, 0.5312f,
619 0.3438f, 0.3438f, 0.3438f, 0.3438f,
620 0.2500f, 0.2500f, 0.2500f, 0.2500f,
621 0.2500f, 0.2500f, 0.2500f, 0.2500f,
623 float out_data[4 * 16];
625 ResizeEffect *downscale = new ResizeEffect();
626 ASSERT_TRUE(downscale->set_int("width", 1));
627 ASSERT_TRUE(downscale->set_int("height", 4));
629 ResizeEffect *upscale = new ResizeEffect();
630 ASSERT_TRUE(upscale->set_int("width", 4));
631 ASSERT_TRUE(upscale->set_int("height", 16));
633 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
634 tester.get_chain()->add_effect(downscale);
635 tester.get_chain()->add_effect(upscale);
636 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
638 expect_equal(expected_data, out_data, 4, 16);
641 // An effect that multiplies with a constant. Used below.
642 class MultiplyEffect : public Effect {
644 MultiplyEffect() { register_float("factor", &factor); }
645 virtual std::string effect_type_id() const { return "MultiplyEffect"; }
646 std::string output_fragment_shader() { return read_file("multiply.frag"); }
647 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
653 // An effect that adds its two inputs together. Used below.
654 class AddEffect : public Effect {
657 virtual std::string effect_type_id() const { return "AddEffect"; }
658 std::string output_fragment_shader() { return read_file("add.frag"); }
659 virtual unsigned num_inputs() const { return 2; }
660 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
663 // Constructs the graph
667 // MultiplyEffect MultiplyEffect |
671 // and verifies that it gives the correct output.
672 TEST(EffectChainTest, DiamondGraph) {
677 float expected_data[] = {
681 float out_data[2 * 2];
683 MultiplyEffect *mul_half = new MultiplyEffect();
684 ASSERT_TRUE(mul_half->set_float("factor", 0.5f));
686 MultiplyEffect *mul_two = new MultiplyEffect();
687 ASSERT_TRUE(mul_two->set_float("factor", 2.0f));
689 EffectChainTester tester(NULL, 2, 2);
692 format.color_space = COLORSPACE_sRGB;
693 format.gamma_curve = GAMMA_LINEAR;
695 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
696 input->set_pixel_data(data);
698 tester.get_chain()->add_input(input);
699 tester.get_chain()->add_effect(mul_half, input);
700 tester.get_chain()->add_effect(mul_two, input);
701 tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
702 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
704 expect_equal(expected_data, out_data, 2, 2);
707 // Constructs the graph
711 // MultiplyEffect MultiplyEffect |
713 // \ BouncingIdentityEffect |
717 // and verifies that it gives the correct output.
718 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
723 float expected_data[] = {
727 float out_data[2 * 2];
729 MultiplyEffect *mul_half = new MultiplyEffect();
730 ASSERT_TRUE(mul_half->set_float("factor", 0.5f));
732 MultiplyEffect *mul_two = new MultiplyEffect();
733 ASSERT_TRUE(mul_two->set_float("factor", 2.0f));
735 BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
737 EffectChainTester tester(NULL, 2, 2);
740 format.color_space = COLORSPACE_sRGB;
741 format.gamma_curve = GAMMA_LINEAR;
743 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
744 input->set_pixel_data(data);
746 tester.get_chain()->add_input(input);
747 tester.get_chain()->add_effect(mul_half, input);
748 tester.get_chain()->add_effect(mul_two, input);
749 tester.get_chain()->add_effect(bounce, mul_two);
750 tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
751 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
753 expect_equal(expected_data, out_data, 2, 2);
756 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
761 float expected_data[] = {
762 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
765 float out_data[2 * 2];
767 EffectChainTester tester(NULL, 2, 2);
768 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
770 // MirrorEffect does not get linear light, so the conversions will be
771 // inserted after it, not before.
772 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
773 tester.get_chain()->add_effect(effect);
775 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
776 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
777 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
778 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
780 expect_equal(expected_data, out_data, 2, 2);
782 Node *node = effect->replaced_node;
783 ASSERT_EQ(1, node->incoming_links.size());
784 ASSERT_EQ(1, node->outgoing_links.size());
785 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
786 EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
789 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
794 float expected_data[] = {
795 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
798 float out_data[2 * 2];
800 EffectChainTester tester(NULL, 2, 2);
801 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
803 // MirrorEffect does not get linear light, so the conversions will be
804 // inserted after it, not before.
805 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
806 tester.get_chain()->add_effect(effect);
808 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
809 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
810 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
811 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
813 expect_equal(expected_data, out_data, 2, 2);
815 Node *node = effect->replaced_node;
816 ASSERT_EQ(1, node->incoming_links.size());
817 ASSERT_EQ(1, node->outgoing_links.size());
818 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
819 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
822 // An effect that does nothing, but requests texture bounce and stores
824 class SizeStoringEffect : public BouncingIdentityEffect {
826 SizeStoringEffect() : input_width(-1), input_height(-1) {}
827 virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) {
828 assert(input_num == 0);
830 input_height = height;
832 virtual std::string effect_type_id() const { return "SizeStoringEffect"; }
834 int input_width, input_height;
837 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
838 float data[2 * 2] = {
842 float out_data[2 * 2];
844 EffectChainTester tester(NULL, 4, 3); // Note non-square aspect.
847 format.color_space = COLORSPACE_sRGB;
848 format.gamma_curve = GAMMA_LINEAR;
850 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
851 input1->set_pixel_data(data);
853 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
854 input2->set_pixel_data(data);
856 SizeStoringEffect *input_store = new SizeStoringEffect();
858 tester.get_chain()->add_input(input1);
859 tester.get_chain()->add_input(input2);
860 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
861 tester.get_chain()->add_effect(input_store);
862 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
864 EXPECT_EQ(2, input_store->input_width);
865 EXPECT_EQ(2, input_store->input_height);
868 TEST(EffectChainTest, AspectRatioConversion) {
869 float data1[4 * 3] = {
870 0.0f, 0.0f, 0.0f, 0.0f,
871 0.0f, 0.0f, 0.0f, 0.0f,
872 0.0f, 0.0f, 0.0f, 0.0f,
874 float data2[7 * 7] = {
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,
877 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
878 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
879 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
880 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
881 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
884 // The right conversion here is that the 7x7 image decides the size,
885 // since it is the biggest, so everything is scaled up to 9x7
886 // (keep the height, round the width 9.333 to 9).
887 float out_data[9 * 7];
889 EffectChainTester tester(NULL, 4, 3);
892 format.color_space = COLORSPACE_sRGB;
893 format.gamma_curve = GAMMA_LINEAR;
895 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
896 input1->set_pixel_data(data1);
898 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
899 input2->set_pixel_data(data2);
901 SizeStoringEffect *input_store = new SizeStoringEffect();
903 tester.get_chain()->add_input(input1);
904 tester.get_chain()->add_input(input2);
905 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
906 tester.get_chain()->add_effect(input_store);
907 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
909 EXPECT_EQ(9, input_store->input_width);
910 EXPECT_EQ(7, input_store->input_height);
913 // An effect that does nothing except changing its output sizes.
914 class VirtualResizeEffect : public Effect {
916 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
919 virtual_width(virtual_width),
920 virtual_height(virtual_height) {}
921 virtual std::string effect_type_id() const { return "VirtualResizeEffect"; }
922 std::string output_fragment_shader() { return read_file("identity.frag"); }
924 virtual bool changes_output_size() const { return true; }
926 virtual void get_output_size(unsigned *width, unsigned *height,
927 unsigned *virtual_width, unsigned *virtual_height) const {
928 *width = this->width;
929 *height = this->height;
930 *virtual_width = this->virtual_width;
931 *virtual_height = this->virtual_height;
935 int width, height, virtual_width, virtual_height;
938 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
939 const int size = 2, bigger_size = 3;
940 float data[size * size] = {
944 float out_data[size * size];
946 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
948 SizeStoringEffect *size_store = new SizeStoringEffect();
950 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
951 tester.get_chain()->add_effect(size_store);
952 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
953 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
955 EXPECT_EQ(bigger_size, size_store->input_width);
956 EXPECT_EQ(bigger_size, size_store->input_height);
958 // If the resize is implemented as non-virtual, we'll fail here,
959 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
960 expect_equal(data, out_data, size, size);