1 // Unit tests for EffectChain.
3 // Note that this also contains the tests for some of the simpler effects.
7 #include "effect_chain.h"
8 #include "flat_input.h"
9 #include "gtest/gtest.h"
10 #include "mirror_effect.h"
11 #include "resize_effect.h"
12 #include "test_util.h"
14 TEST(EffectChainTest, EmptyChain) {
20 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
21 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
23 expect_equal(data, out_data, 3, 2);
26 // An effect that does nothing.
27 class IdentityEffect : public Effect {
30 virtual std::string effect_type_id() const { return "IdentityEffect"; }
31 std::string output_fragment_shader() { return read_file("identity.frag"); }
34 TEST(EffectChainTest, Identity) {
40 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
41 tester.get_chain()->add_effect(new IdentityEffect());
42 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
44 expect_equal(data, out_data, 3, 2);
47 // An effect that does nothing, but requests texture bounce.
48 class BouncingIdentityEffect : public Effect {
50 BouncingIdentityEffect() {}
51 virtual std::string effect_type_id() const { return "IdentityEffect"; }
52 std::string output_fragment_shader() { return read_file("identity.frag"); }
53 bool needs_texture_bounce() const { return true; }
54 AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
57 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
63 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
64 tester.get_chain()->add_effect(new BouncingIdentityEffect());
65 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
67 expect_equal(data, out_data, 3, 2);
70 TEST(MirrorTest, BasicTest) {
75 float expected_data[6] = {
80 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
81 tester.get_chain()->add_effect(new MirrorEffect());
82 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
84 expect_equal(expected_data, out_data, 3, 2);
87 // A dummy effect that inverts its input.
88 class InvertEffect : public Effect {
91 virtual std::string effect_type_id() const { return "InvertEffect"; }
92 std::string output_fragment_shader() { return read_file("invert_effect.frag"); }
94 // A real invert would actually care about its alpha,
95 // but in this unit test, it only complicates things.
96 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
99 // Like IdentityEffect, but rewrites itself out of the loop,
100 // splicing in a different effect instead. Also stores the new node,
101 // so we later can check whatever properties we'd like about the graph.
103 class RewritingEffect : public Effect {
105 RewritingEffect() : effect(new T()), replaced_node(NULL) {}
106 virtual std::string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
107 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
108 virtual void rewrite_graph(EffectChain *graph, Node *self) {
109 replaced_node = graph->add_node(effect);
110 graph->replace_receiver(self, replaced_node);
111 graph->replace_sender(self, replaced_node);
112 self->disabled = true;
119 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
124 float expected_data[6] = {
125 1.0f, 0.9771f, 0.9673f,
129 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
130 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
131 tester.get_chain()->add_effect(effect);
132 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
134 Node *node = effect->replaced_node;
135 ASSERT_EQ(1, node->incoming_links.size());
136 ASSERT_EQ(1, node->outgoing_links.size());
137 EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
138 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
140 expect_equal(expected_data, out_data, 3, 2);
143 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
144 unsigned char data[] = {
148 float expected_data[4] = {
153 EffectChainTester tester(NULL, 2, 2);
154 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
155 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
156 tester.get_chain()->add_effect(effect);
157 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
159 Node *node = effect->replaced_node;
160 ASSERT_EQ(1, node->incoming_links.size());
161 ASSERT_EQ(1, node->outgoing_links.size());
162 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
163 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
165 expect_equal(expected_data, out_data, 2, 2);
168 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
173 float expected_data[6] = {
178 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
179 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
180 tester.get_chain()->add_effect(effect);
181 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
183 Node *node = effect->replaced_node;
184 ASSERT_EQ(1, node->incoming_links.size());
185 ASSERT_EQ(1, node->outgoing_links.size());
186 EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
187 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
189 expect_equal(expected_data, out_data, 3, 2);
192 // A fake input that can change its output colorspace and gamma between instantiation
194 class UnknownColorspaceInput : public FlatInput {
196 UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
197 : FlatInput(format, pixel_format, type, width, height),
198 overridden_color_space(format.color_space),
199 overridden_gamma_curve(format.gamma_curve) {}
200 virtual std::string effect_type_id() const { return "UnknownColorspaceInput"; }
202 void set_color_space(Colorspace colorspace) {
203 overridden_color_space = colorspace;
205 void set_gamma_curve(GammaCurve gamma_curve) {
206 overridden_gamma_curve = gamma_curve;
208 Colorspace get_color_space() const { return overridden_color_space; }
209 GammaCurve get_gamma_curve() const { return overridden_gamma_curve; }
212 Colorspace overridden_color_space;
213 GammaCurve overridden_gamma_curve;
216 TEST(EffectChainTester, HandlesInputChangingColorspace) {
225 float out_data[size];
227 EffectChainTester tester(NULL, 4, 1, FORMAT_GRAYSCALE);
229 // First say that we have sRGB, linear input.
231 format.color_space = COLORSPACE_sRGB;
232 format.gamma_curve = GAMMA_LINEAR;
234 UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
235 input->set_pixel_data(data);
236 tester.get_chain()->add_input(input);
238 // Now we change to Rec. 601 input.
239 input->set_color_space(COLORSPACE_REC_601_625);
240 input->set_gamma_curve(GAMMA_REC_601);
242 // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
243 tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
244 expect_equal(data, out_data, 4, 1);
247 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
252 float expected_data[6] = {
257 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
258 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
259 tester.get_chain()->add_effect(effect);
260 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
262 Node *node = effect->replaced_node;
263 ASSERT_EQ(1, node->incoming_links.size());
264 EXPECT_EQ(0, node->outgoing_links.size());
265 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
267 expect_equal(expected_data, out_data, 3, 2);
270 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
275 float expected_data[6] = {
280 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
281 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
282 tester.get_chain()->add_effect(effect);
283 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
285 Node *node = effect->replaced_node;
286 ASSERT_EQ(1, node->incoming_links.size());
287 EXPECT_EQ(0, node->outgoing_links.size());
288 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
290 expect_equal(expected_data, out_data, 3, 2);
293 // The identity effect needs linear light, and thus will get conversions on both sides.
294 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
295 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
297 for (unsigned i = 0; i < 256; ++i) {
301 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
302 tester.get_chain()->add_effect(new IdentityEffect());
303 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
305 expect_equal(data, out_data, 256, 1);
308 // Same, but uses the forward sRGB table from the GPU.
309 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
310 unsigned char data[256];
311 float expected_data[256];
312 for (unsigned i = 0; i < 256; ++i) {
314 expected_data[i] = i / 255.0;
317 EffectChainTester tester(NULL, 256, 1);
318 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
319 tester.get_chain()->add_effect(new IdentityEffect());
320 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
322 expect_equal(expected_data, out_data, 256, 1);
325 // Same, for the Rec. 601/709 gamma curve.
326 TEST(EffectChainTest, IdentityThroughRec709) {
328 for (unsigned i = 0; i < 256; ++i) {
332 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
333 tester.get_chain()->add_effect(new IdentityEffect());
334 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
336 expect_equal(data, out_data, 256, 1);
339 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
340 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
342 float data[4 * size] = {
343 0.8f, 0.0f, 0.0f, 0.5f,
344 0.0f, 0.2f, 0.2f, 0.3f,
345 0.1f, 0.0f, 1.0f, 1.0f,
347 float out_data[4 * size];
348 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
349 tester.get_chain()->add_effect(new IdentityEffect());
350 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
352 expect_equal(data, out_data, 4, size);
355 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
357 float data[4 * size] = {
358 0.8f, 0.0f, 0.0f, 0.5f,
359 0.0f, 0.2f, 0.2f, 0.3f,
360 0.1f, 0.0f, 1.0f, 1.0f,
362 float expected_data[4 * size] = {
363 0.1f, 0.0f, 1.0f, 1.0f,
364 0.0f, 0.2f, 0.2f, 0.3f,
365 0.8f, 0.0f, 0.0f, 0.5f,
367 float out_data[4 * size];
368 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
369 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
370 tester.get_chain()->add_effect(effect);
371 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
373 Node *node = effect->replaced_node;
374 ASSERT_EQ(1, node->incoming_links.size());
375 EXPECT_EQ(0, node->outgoing_links.size());
376 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
378 expect_equal(expected_data, out_data, 4, size);
381 // An input that outputs only blue, which has blank alpha.
382 class BlueInput : public Input {
384 BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
385 virtual std::string effect_type_id() const { return "IdentityEffect"; }
386 std::string output_fragment_shader() { return read_file("blue.frag"); }
387 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
388 virtual void finalize() {}
389 virtual bool can_output_linear_gamma() const { return true; }
390 virtual unsigned get_width() const { return 1; }
391 virtual unsigned get_height() const { return 1; }
392 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
393 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
399 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
400 // which outputs blank alpha.
401 class RewritingToBlueInput : public Input {
403 RewritingToBlueInput() : blue_node(NULL) { register_int("needs_mipmaps", &needs_mipmaps); }
404 virtual std::string effect_type_id() const { return "RewritingToBlueInput"; }
405 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
406 virtual void rewrite_graph(EffectChain *graph, Node *self) {
407 Node *blue_node = graph->add_node(new BlueInput());
408 graph->replace_receiver(self, blue_node);
409 graph->replace_sender(self, blue_node);
411 self->disabled = true;
412 this->blue_node = blue_node;
415 // Dummy values that we need to implement because we inherit from Input.
416 // Same as BlueInput.
417 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
418 virtual void finalize() {}
419 virtual bool can_output_linear_gamma() const { return true; }
420 virtual unsigned get_width() const { return 1; }
421 virtual unsigned get_height() const { return 1; }
422 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
423 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
431 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
433 float data[4 * size] = {
434 0.0f, 0.0f, 1.0f, 1.0f,
435 0.0f, 0.0f, 1.0f, 1.0f,
436 0.0f, 0.0f, 1.0f, 1.0f,
438 float out_data[4 * size];
439 EffectChainTester tester(NULL, size, 1);
440 RewritingToBlueInput *input = new RewritingToBlueInput();
441 tester.get_chain()->add_input(input);
442 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
444 Node *node = input->blue_node;
445 EXPECT_EQ(0, node->incoming_links.size());
446 EXPECT_EQ(0, node->outgoing_links.size());
448 expect_equal(data, out_data, 4, size);
451 // An effect that does nothing, and specifies that it preserves blank alpha.
452 class BlankAlphaPreservingEffect : public Effect {
454 BlankAlphaPreservingEffect() {}
455 virtual std::string effect_type_id() const { return "BlankAlphaPreservingEffect"; }
456 std::string output_fragment_shader() { return read_file("identity.frag"); }
457 virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
460 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
462 float data[4 * size] = {
463 0.0f, 0.0f, 1.0f, 1.0f,
464 0.0f, 0.0f, 1.0f, 1.0f,
465 0.0f, 0.0f, 1.0f, 1.0f,
467 float out_data[4 * size];
468 EffectChainTester tester(NULL, size, 1);
469 tester.get_chain()->add_input(new BlueInput());
470 tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
471 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
472 tester.get_chain()->add_effect(effect);
473 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
475 Node *node = effect->replaced_node;
476 EXPECT_EQ(1, node->incoming_links.size());
477 EXPECT_EQ(0, node->outgoing_links.size());
479 expect_equal(data, out_data, 4, size);
482 // This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
483 // just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
484 // an alpha conversion _should_ be inserted at the very end. (There is some overlap
485 // with other tests.)
486 TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
488 float data[4 * size] = {
489 0.0f, 0.0f, 1.0f, 1.0f,
490 0.0f, 0.0f, 1.0f, 1.0f,
491 0.0f, 0.0f, 1.0f, 1.0f,
493 float out_data[4 * size];
494 EffectChainTester tester(NULL, size, 1);
495 tester.get_chain()->add_input(new BlueInput());
496 tester.get_chain()->add_effect(new IdentityEffect()); // Not BlankAlphaPreservingEffect.
497 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
498 tester.get_chain()->add_effect(effect);
499 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
501 Node *node = effect->replaced_node;
502 EXPECT_EQ(1, node->incoming_links.size());
503 EXPECT_EQ(1, node->outgoing_links.size());
504 EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
506 expect_equal(data, out_data, 4, size);
509 // Effectively scales down its input linearly by 4x (and repeating it),
510 // which is not attainable without mipmaps.
511 class MipmapNeedingEffect : public Effect {
513 MipmapNeedingEffect() {}
514 virtual bool needs_mipmaps() const { return true; }
515 virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
516 std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
517 void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
519 glActiveTexture(GL_TEXTURE0);
521 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
523 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
528 TEST(EffectChainTest, MipmapGenerationWorks) {
529 float data[] = { // In 4x4 blocks.
530 1.0f, 0.0f, 0.0f, 0.0f,
531 0.0f, 0.0f, 0.0f, 0.0f,
532 0.0f, 0.0f, 0.0f, 0.0f,
533 0.0f, 0.0f, 0.0f, 1.0f,
535 0.0f, 0.0f, 0.0f, 0.0f,
536 0.0f, 0.5f, 0.0f, 0.0f,
537 0.0f, 0.0f, 1.0f, 0.0f,
538 0.0f, 0.0f, 0.0f, 0.0f,
540 1.0f, 1.0f, 1.0f, 1.0f,
541 1.0f, 1.0f, 1.0f, 1.0f,
542 1.0f, 1.0f, 1.0f, 1.0f,
543 1.0f, 1.0f, 1.0f, 1.0f,
545 0.0f, 0.0f, 0.0f, 0.0f,
546 0.0f, 1.0f, 1.0f, 0.0f,
547 0.0f, 1.0f, 1.0f, 0.0f,
548 0.0f, 0.0f, 0.0f, 0.0f,
550 float expected_data[] = { // Repeated four times each way.
551 0.125f, 0.125f, 0.125f, 0.125f,
552 0.09375f, 0.09375f, 0.09375f, 0.09375f,
553 1.0f, 1.0f, 1.0f, 1.0f,
554 0.25f, 0.25f, 0.25f, 0.25f,
556 0.125f, 0.125f, 0.125f, 0.125f,
557 0.09375f, 0.09375f, 0.09375f, 0.09375f,
558 1.0f, 1.0f, 1.0f, 1.0f,
559 0.25f, 0.25f, 0.25f, 0.25f,
561 0.125f, 0.125f, 0.125f, 0.125f,
562 0.09375f, 0.09375f, 0.09375f, 0.09375f,
563 1.0f, 1.0f, 1.0f, 1.0f,
564 0.25f, 0.25f, 0.25f, 0.25f,
566 0.125f, 0.125f, 0.125f, 0.125f,
567 0.09375f, 0.09375f, 0.09375f, 0.09375f,
568 1.0f, 1.0f, 1.0f, 1.0f,
569 0.25f, 0.25f, 0.25f, 0.25f,
571 float out_data[4 * 16];
572 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
573 tester.get_chain()->add_effect(new MipmapNeedingEffect());
574 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
576 expect_equal(expected_data, out_data, 4, 16);
579 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
580 float data[] = { // In 4x4 blocks.
581 1.0f, 0.0f, 0.0f, 0.0f,
582 0.0f, 0.0f, 0.0f, 0.0f,
583 0.0f, 0.0f, 0.0f, 0.0f,
584 0.0f, 0.0f, 0.0f, 1.0f,
586 0.0f, 0.0f, 0.0f, 0.0f,
587 0.0f, 0.5f, 0.0f, 0.0f,
588 0.0f, 0.0f, 1.0f, 0.0f,
589 0.0f, 0.0f, 0.0f, 0.0f,
591 1.0f, 1.0f, 1.0f, 1.0f,
592 1.0f, 1.0f, 1.0f, 1.0f,
593 1.0f, 1.0f, 1.0f, 1.0f,
594 1.0f, 1.0f, 1.0f, 1.0f,
596 0.0f, 0.0f, 0.0f, 0.0f,
597 0.0f, 1.0f, 1.0f, 0.0f,
598 0.0f, 1.0f, 1.0f, 0.0f,
599 0.0f, 0.0f, 0.0f, 0.0f,
601 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
602 0.1250f, 0.1250f, 0.1250f, 0.1250f,
603 0.1250f, 0.1250f, 0.1250f, 0.1250f,
604 0.1211f, 0.1211f, 0.1211f, 0.1211f,
605 0.1133f, 0.1133f, 0.1133f, 0.1133f,
606 0.1055f, 0.1055f, 0.1055f, 0.1055f,
607 0.0977f, 0.0977f, 0.0977f, 0.0977f,
608 0.2070f, 0.2070f, 0.2070f, 0.2070f,
609 0.4336f, 0.4336f, 0.4336f, 0.4336f,
610 0.6602f, 0.6602f, 0.6602f, 0.6602f,
611 0.8867f, 0.8867f, 0.8867f, 0.8867f,
612 0.9062f, 0.9062f, 0.9062f, 0.9062f,
613 0.7188f, 0.7188f, 0.7188f, 0.7188f,
614 0.5312f, 0.5312f, 0.5312f, 0.5312f,
615 0.3438f, 0.3438f, 0.3438f, 0.3438f,
616 0.2500f, 0.2500f, 0.2500f, 0.2500f,
617 0.2500f, 0.2500f, 0.2500f, 0.2500f,
619 float out_data[4 * 16];
621 ResizeEffect *downscale = new ResizeEffect();
622 ASSERT_TRUE(downscale->set_int("width", 1));
623 ASSERT_TRUE(downscale->set_int("height", 4));
625 ResizeEffect *upscale = new ResizeEffect();
626 ASSERT_TRUE(upscale->set_int("width", 4));
627 ASSERT_TRUE(upscale->set_int("height", 16));
629 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
630 tester.get_chain()->add_effect(downscale);
631 tester.get_chain()->add_effect(upscale);
632 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
634 expect_equal(expected_data, out_data, 4, 16);
637 // An effect that multiplies with a constant. Used below.
638 class MultiplyEffect : public Effect {
640 MultiplyEffect() { register_float("factor", &factor); }
641 virtual std::string effect_type_id() const { return "MultiplyEffect"; }
642 std::string output_fragment_shader() { return read_file("multiply.frag"); }
643 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
649 // An effect that adds its two inputs together. Used below.
650 class AddEffect : public Effect {
653 virtual std::string effect_type_id() const { return "AddEffect"; }
654 std::string output_fragment_shader() { return read_file("add.frag"); }
655 virtual unsigned num_inputs() const { return 2; }
656 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
659 // Constructs the graph
663 // MultiplyEffect MultiplyEffect |
667 // and verifies that it gives the correct output.
668 TEST(EffectChainTest, DiamondGraph) {
673 float expected_data[] = {
677 float out_data[2 * 2];
679 MultiplyEffect *mul_half = new MultiplyEffect();
680 ASSERT_TRUE(mul_half->set_float("factor", 0.5f));
682 MultiplyEffect *mul_two = new MultiplyEffect();
683 ASSERT_TRUE(mul_two->set_float("factor", 2.0f));
685 EffectChainTester tester(NULL, 2, 2);
688 format.color_space = COLORSPACE_sRGB;
689 format.gamma_curve = GAMMA_LINEAR;
691 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
692 input->set_pixel_data(data);
694 tester.get_chain()->add_input(input);
695 tester.get_chain()->add_effect(mul_half, input);
696 tester.get_chain()->add_effect(mul_two, input);
697 tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
698 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
700 expect_equal(expected_data, out_data, 2, 2);
703 // Constructs the graph
707 // MultiplyEffect MultiplyEffect |
709 // \ BouncingIdentityEffect |
713 // and verifies that it gives the correct output.
714 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
719 float expected_data[] = {
723 float out_data[2 * 2];
725 MultiplyEffect *mul_half = new MultiplyEffect();
726 ASSERT_TRUE(mul_half->set_float("factor", 0.5f));
728 MultiplyEffect *mul_two = new MultiplyEffect();
729 ASSERT_TRUE(mul_two->set_float("factor", 2.0f));
731 BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
733 EffectChainTester tester(NULL, 2, 2);
736 format.color_space = COLORSPACE_sRGB;
737 format.gamma_curve = GAMMA_LINEAR;
739 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
740 input->set_pixel_data(data);
742 tester.get_chain()->add_input(input);
743 tester.get_chain()->add_effect(mul_half, input);
744 tester.get_chain()->add_effect(mul_two, input);
745 tester.get_chain()->add_effect(bounce, mul_two);
746 tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
747 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
749 expect_equal(expected_data, out_data, 2, 2);
752 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
757 float expected_data[] = {
758 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
761 float out_data[2 * 2];
763 EffectChainTester tester(NULL, 2, 2);
764 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
766 // MirrorEffect does not get linear light, so the conversions will be
767 // inserted after it, not before.
768 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
769 tester.get_chain()->add_effect(effect);
771 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
772 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
773 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
774 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
776 expect_equal(expected_data, out_data, 2, 2);
778 Node *node = effect->replaced_node;
779 ASSERT_EQ(1, node->incoming_links.size());
780 ASSERT_EQ(1, node->outgoing_links.size());
781 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
782 EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
785 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
790 float expected_data[] = {
791 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
794 float out_data[2 * 2];
796 EffectChainTester tester(NULL, 2, 2);
797 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
799 // MirrorEffect does not get linear light, so the conversions will be
800 // inserted after it, not before.
801 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
802 tester.get_chain()->add_effect(effect);
804 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
805 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
806 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
807 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
809 expect_equal(expected_data, out_data, 2, 2);
811 Node *node = effect->replaced_node;
812 ASSERT_EQ(1, node->incoming_links.size());
813 ASSERT_EQ(1, node->outgoing_links.size());
814 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
815 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
818 // An effect that does nothing, but requests texture bounce and stores
820 class SizeStoringEffect : public BouncingIdentityEffect {
822 SizeStoringEffect() : input_width(-1), input_height(-1) {}
823 virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) {
824 assert(input_num == 0);
826 input_height = height;
828 virtual std::string effect_type_id() const { return "SizeStoringEffect"; }
830 int input_width, input_height;
833 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
834 float data[2 * 2] = {
838 float out_data[2 * 2];
840 EffectChainTester tester(NULL, 4, 3); // Note non-square aspect.
843 format.color_space = COLORSPACE_sRGB;
844 format.gamma_curve = GAMMA_LINEAR;
846 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
847 input1->set_pixel_data(data);
849 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
850 input2->set_pixel_data(data);
852 SizeStoringEffect *input_store = new SizeStoringEffect();
854 tester.get_chain()->add_input(input1);
855 tester.get_chain()->add_input(input2);
856 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
857 tester.get_chain()->add_effect(input_store);
858 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
860 EXPECT_EQ(2, input_store->input_width);
861 EXPECT_EQ(2, input_store->input_height);
864 TEST(EffectChainTest, AspectRatioConversion) {
865 float data1[4 * 3] = {
866 0.0f, 0.0f, 0.0f, 0.0f,
867 0.0f, 0.0f, 0.0f, 0.0f,
868 0.0f, 0.0f, 0.0f, 0.0f,
870 float data2[7 * 7] = {
871 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
872 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
873 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
874 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
875 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
876 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
877 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
880 // The right conversion here is that the 7x7 image decides the size,
881 // since it is the biggest, so everything is scaled up to 9x7
882 // (keep the height, round the width 9.333 to 9).
883 float out_data[9 * 7];
885 EffectChainTester tester(NULL, 4, 3);
888 format.color_space = COLORSPACE_sRGB;
889 format.gamma_curve = GAMMA_LINEAR;
891 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
892 input1->set_pixel_data(data1);
894 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
895 input2->set_pixel_data(data2);
897 SizeStoringEffect *input_store = new SizeStoringEffect();
899 tester.get_chain()->add_input(input1);
900 tester.get_chain()->add_input(input2);
901 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
902 tester.get_chain()->add_effect(input_store);
903 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
905 EXPECT_EQ(9, input_store->input_width);
906 EXPECT_EQ(7, input_store->input_height);
909 // An effect that does nothing except changing its output sizes.
910 class VirtualResizeEffect : public Effect {
912 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
915 virtual_width(virtual_width),
916 virtual_height(virtual_height) {}
917 virtual std::string effect_type_id() const { return "VirtualResizeEffect"; }
918 std::string output_fragment_shader() { return read_file("identity.frag"); }
920 virtual bool changes_output_size() const { return true; }
922 virtual void get_output_size(unsigned *width, unsigned *height,
923 unsigned *virtual_width, unsigned *virtual_height) const {
924 *width = this->width;
925 *height = this->height;
926 *virtual_width = this->virtual_width;
927 *virtual_height = this->virtual_height;
931 int width, height, virtual_width, virtual_height;
934 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
935 const int size = 2, bigger_size = 3;
936 float data[size * size] = {
940 float out_data[size * size];
942 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
944 SizeStoringEffect *size_store = new SizeStoringEffect();
946 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
947 tester.get_chain()->add_effect(size_store);
948 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
949 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
951 EXPECT_EQ(bigger_size, size_store->input_width);
952 EXPECT_EQ(bigger_size, size_store->input_height);
954 // If the resize is implemented as non-virtual, we'll fail here,
955 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
956 expect_equal(data, out_data, size, size);