1 // Unit tests for EffectChain.
3 // Note that this also contains the tests for some of the simpler effects.
13 #include "effect_chain.h"
14 #include "flat_input.h"
15 #include "gtest/gtest.h"
18 #include "mirror_effect.h"
19 #include "multiply_effect.h"
20 #include "resize_effect.h"
21 #include "resource_pool.h"
22 #include "test_util.h"
29 TEST(EffectChainTest, EmptyChain) {
35 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
36 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
38 expect_equal(data, out_data, 3, 2);
41 // An effect that does nothing.
42 class IdentityEffect : public Effect {
45 string effect_type_id() const override { return "IdentityEffect"; }
46 string output_fragment_shader() override { return read_file("identity.frag"); }
49 TEST(EffectChainTest, Identity) {
55 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
56 tester.get_chain()->add_effect(new IdentityEffect());
57 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
59 expect_equal(data, out_data, 3, 2);
62 // An effect that does nothing, but requests texture bounce.
63 class BouncingIdentityEffect : public Effect {
65 BouncingIdentityEffect() {}
66 string effect_type_id() const override { return "IdentityEffect"; }
67 string output_fragment_shader() override { return read_file("identity.frag"); }
68 bool needs_texture_bounce() const override { return true; }
69 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
72 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
78 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
79 tester.get_chain()->add_effect(new BouncingIdentityEffect());
80 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
82 expect_equal(data, out_data, 3, 2);
85 TEST(MirrorTest, BasicTest) {
90 float expected_data[6] = {
95 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
96 tester.get_chain()->add_effect(new MirrorEffect());
97 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
99 expect_equal(expected_data, out_data, 3, 2);
102 TEST(EffectChainTest, TopLeftOrigin) {
107 // Note that EffectChainTester assumes bottom-left origin, so by setting
108 // top-left, we will get flipped data back.
109 float expected_data[6] = {
114 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
115 tester.get_chain()->set_output_origin(OUTPUT_ORIGIN_TOP_LEFT);
116 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
118 expect_equal(expected_data, out_data, 3, 2);
121 // A dummy effect that inverts its input.
122 class InvertEffect : public Effect {
125 string effect_type_id() const override { return "InvertEffect"; }
126 string output_fragment_shader() override { return read_file("invert_effect.frag"); }
128 // A real invert would actually care about its alpha,
129 // but in this unit test, it only complicates things.
130 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
133 // Like IdentityEffect, but rewrites itself out of the loop,
134 // splicing in a different effect instead. Also stores the new node,
135 // so we later can check whatever properties we'd like about the graph.
137 class RewritingEffect : public Effect {
139 RewritingEffect() : effect(new T()), replaced_node(nullptr) {}
140 string effect_type_id() const override { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
141 string output_fragment_shader() override { EXPECT_TRUE(false); return read_file("identity.frag"); }
142 void rewrite_graph(EffectChain *graph, Node *self) override {
143 replaced_node = graph->add_node(effect);
144 graph->replace_receiver(self, replaced_node);
145 graph->replace_sender(self, replaced_node);
146 self->disabled = true;
153 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
158 float expected_data[6] = {
159 1.0f, 0.9771f, 0.9673f,
163 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
164 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
165 tester.get_chain()->add_effect(effect);
166 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
168 Node *node = effect->replaced_node;
169 ASSERT_EQ(1, node->incoming_links.size());
170 ASSERT_EQ(1, node->outgoing_links.size());
171 EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
172 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
174 expect_equal(expected_data, out_data, 3, 2);
177 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
178 unsigned char data[] = {
184 float expected_data[] = {
185 1.0000f, 1.0000f, 1.0000f, 1.0000f,
186 0.9771f, 0.9771f, 0.9771f, 1.0000f,
187 0.8983f, 0.8983f, 0.8983f, 1.0000f,
188 0.0000f, 0.0000f, 0.0000f, 1.0000f
190 float out_data[4 * 4];
191 EffectChainTester tester(nullptr, 1, 4);
192 tester.add_input(data, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_sRGB);
193 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
194 tester.get_chain()->add_effect(effect);
195 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
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("FlatInput", node->incoming_links[0]->effect->effect_type_id());
201 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
203 expect_equal(expected_data, out_data, 4, 4);
206 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
211 float expected_data[6] = {
216 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
217 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
218 tester.get_chain()->add_effect(effect);
219 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
221 Node *node = effect->replaced_node;
222 ASSERT_EQ(1, node->incoming_links.size());
223 ASSERT_EQ(1, node->outgoing_links.size());
224 EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
225 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
227 expect_equal(expected_data, out_data, 3, 2);
230 // A fake input that can change its output colorspace and gamma between instantiation
232 class UnknownColorspaceInput : public FlatInput {
234 UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
235 : FlatInput(format, pixel_format, type, width, height),
236 overridden_color_space(format.color_space),
237 overridden_gamma_curve(format.gamma_curve) {}
238 string effect_type_id() const override { return "UnknownColorspaceInput"; }
240 void set_color_space(Colorspace colorspace) {
241 overridden_color_space = colorspace;
243 void set_gamma_curve(GammaCurve gamma_curve) {
244 overridden_gamma_curve = gamma_curve;
246 Colorspace get_color_space() const override { return overridden_color_space; }
247 GammaCurve get_gamma_curve() const override { return overridden_gamma_curve; }
250 Colorspace overridden_color_space;
251 GammaCurve overridden_gamma_curve;
254 TEST(EffectChainTest, HandlesInputChangingColorspace) {
263 float out_data[size];
265 EffectChainTester tester(nullptr, 4, 1, FORMAT_GRAYSCALE);
267 // First say that we have sRGB, linear input.
269 format.color_space = COLORSPACE_sRGB;
270 format.gamma_curve = GAMMA_LINEAR;
272 UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
273 input->set_pixel_data(data);
274 tester.get_chain()->add_input(input);
276 // Now we change to Rec. 601 input.
277 input->set_color_space(COLORSPACE_REC_601_625);
278 input->set_gamma_curve(GAMMA_REC_601);
280 // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
281 tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
282 expect_equal(data, out_data, 4, 1);
285 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
290 float expected_data[6] = {
295 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
296 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
297 tester.get_chain()->add_effect(effect);
298 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
300 Node *node = effect->replaced_node;
301 ASSERT_EQ(1, node->incoming_links.size());
302 EXPECT_EQ(0, node->outgoing_links.size());
303 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
305 expect_equal(expected_data, out_data, 3, 2);
308 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
313 float expected_data[6] = {
318 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
319 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
320 tester.get_chain()->add_effect(effect);
321 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
323 Node *node = effect->replaced_node;
324 ASSERT_EQ(1, node->incoming_links.size());
325 EXPECT_EQ(0, node->outgoing_links.size());
326 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
328 expect_equal(expected_data, out_data, 3, 2);
331 // The identity effect needs linear light, and thus will get conversions on both sides.
332 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
333 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
335 for (unsigned i = 0; i < 256; ++i) {
339 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
340 tester.get_chain()->add_effect(new IdentityEffect());
341 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
343 expect_equal(data, out_data, 256, 1);
346 // Same, but uses the forward sRGB table from the GPU.
347 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
348 unsigned char data[256];
349 float expected_data[256];
350 for (unsigned i = 0; i < 256; ++i) {
352 expected_data[i] = i / 255.0;
355 EffectChainTester tester(nullptr, 256, 1);
356 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
357 tester.get_chain()->add_effect(new IdentityEffect());
358 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
360 expect_equal(expected_data, out_data, 256, 1);
363 // Same, for the Rec. 601/709 gamma curve.
364 TEST(EffectChainTest, IdentityThroughRec709) {
366 for (unsigned i = 0; i < 256; ++i) {
370 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
371 tester.get_chain()->add_effect(new IdentityEffect());
372 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
374 expect_equal(data, out_data, 256, 1);
377 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
378 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
380 float data[4 * size] = {
381 0.8f, 0.0f, 0.0f, 0.5f,
382 0.0f, 0.2f, 0.2f, 0.3f,
383 0.1f, 0.0f, 1.0f, 1.0f,
385 float out_data[4 * size];
386 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
387 tester.get_chain()->add_effect(new IdentityEffect());
388 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
390 expect_equal(data, out_data, 4, size);
393 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
395 float data[4 * size] = {
396 0.8f, 0.0f, 0.0f, 0.5f,
397 0.0f, 0.2f, 0.2f, 0.3f,
398 0.1f, 0.0f, 1.0f, 1.0f,
400 float expected_data[4 * size] = {
401 0.1f, 0.0f, 1.0f, 1.0f,
402 0.0f, 0.2f, 0.2f, 0.3f,
403 0.8f, 0.0f, 0.0f, 0.5f,
405 float out_data[4 * size];
406 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
407 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
408 tester.get_chain()->add_effect(effect);
409 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
411 Node *node = effect->replaced_node;
412 ASSERT_EQ(1, node->incoming_links.size());
413 EXPECT_EQ(0, node->outgoing_links.size());
414 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
416 expect_equal(expected_data, out_data, 4, size);
419 // An input that outputs only blue, which has blank alpha.
420 class BlueInput : public Input {
422 BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
423 string effect_type_id() const override { return "IdentityEffect"; }
424 string output_fragment_shader() override { return read_file("blue.frag"); }
425 AlphaHandling alpha_handling() const override { return OUTPUT_BLANK_ALPHA; }
426 bool can_output_linear_gamma() const override { return true; }
427 unsigned get_width() const override { return 1; }
428 unsigned get_height() const override { return 1; }
429 Colorspace get_color_space() const override { return COLORSPACE_sRGB; }
430 GammaCurve get_gamma_curve() const override { return GAMMA_LINEAR; }
436 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
437 // which outputs blank alpha.
438 class RewritingToBlueInput : public Input {
440 RewritingToBlueInput() : blue_node(nullptr) { register_int("needs_mipmaps", &needs_mipmaps); }
441 string effect_type_id() const override { return "RewritingToBlueInput"; }
442 string output_fragment_shader() override { EXPECT_TRUE(false); return read_file("identity.frag"); }
443 void rewrite_graph(EffectChain *graph, Node *self) override {
444 Node *blue_node = graph->add_node(new BlueInput());
445 graph->replace_receiver(self, blue_node);
446 graph->replace_sender(self, blue_node);
448 self->disabled = true;
449 this->blue_node = blue_node;
452 // Dummy values that we need to implement because we inherit from Input.
453 // Same as BlueInput.
454 AlphaHandling alpha_handling() const override { return OUTPUT_BLANK_ALPHA; }
455 bool can_output_linear_gamma() const override { return true; }
456 unsigned get_width() const override { return 1; }
457 unsigned get_height() const override { return 1; }
458 Colorspace get_color_space() const override { return COLORSPACE_sRGB; }
459 GammaCurve get_gamma_curve() const override { return GAMMA_LINEAR; }
467 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
469 float data[4 * size] = {
470 0.0f, 0.0f, 1.0f, 1.0f,
471 0.0f, 0.0f, 1.0f, 1.0f,
472 0.0f, 0.0f, 1.0f, 1.0f,
474 float out_data[4 * size];
475 EffectChainTester tester(nullptr, size, 1);
476 RewritingToBlueInput *input = new RewritingToBlueInput();
477 tester.get_chain()->add_input(input);
478 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
480 Node *node = input->blue_node;
481 EXPECT_EQ(0, node->incoming_links.size());
482 EXPECT_EQ(0, node->outgoing_links.size());
484 expect_equal(data, out_data, 4, size);
487 // An effect that does nothing, and specifies that it preserves blank alpha.
488 class BlankAlphaPreservingEffect : public Effect {
490 BlankAlphaPreservingEffect() {}
491 string effect_type_id() const override { return "BlankAlphaPreservingEffect"; }
492 string output_fragment_shader() override { return read_file("identity.frag"); }
493 AlphaHandling alpha_handling() const override { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
496 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
498 float data[4 * size] = {
499 0.0f, 0.0f, 1.0f, 1.0f,
500 0.0f, 0.0f, 1.0f, 1.0f,
501 0.0f, 0.0f, 1.0f, 1.0f,
503 float out_data[4 * size];
504 EffectChainTester tester(nullptr, size, 1);
505 tester.get_chain()->add_input(new BlueInput());
506 tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
507 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
508 tester.get_chain()->add_effect(effect);
509 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
511 Node *node = effect->replaced_node;
512 EXPECT_EQ(1, node->incoming_links.size());
513 EXPECT_EQ(0, node->outgoing_links.size());
515 expect_equal(data, out_data, 4, size);
518 // This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
519 // just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
520 // an alpha conversion _should_ be inserted at the very end. (There is some overlap
521 // with other tests.)
522 TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
524 float data[4 * size] = {
525 0.0f, 0.0f, 1.0f, 1.0f,
526 0.0f, 0.0f, 1.0f, 1.0f,
527 0.0f, 0.0f, 1.0f, 1.0f,
529 float out_data[4 * size];
530 EffectChainTester tester(nullptr, size, 1);
531 tester.get_chain()->add_input(new BlueInput());
532 tester.get_chain()->add_effect(new IdentityEffect()); // Not BlankAlphaPreservingEffect.
533 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
534 tester.get_chain()->add_effect(effect);
535 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
537 Node *node = effect->replaced_node;
538 EXPECT_EQ(1, node->incoming_links.size());
539 EXPECT_EQ(1, node->outgoing_links.size());
540 EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
542 expect_equal(data, out_data, 4, size);
545 // Effectively scales down its input linearly by 4x (and repeating it),
546 // which is not attainable without mipmaps.
547 class MipmapNeedingEffect : public Effect {
549 MipmapNeedingEffect() {}
550 bool needs_mipmaps() const override { return true; }
552 // To be allowed to mess with the sampler state.
553 bool needs_texture_bounce() const override { return true; }
555 string effect_type_id() const override { return "MipmapNeedingEffect"; }
556 string output_fragment_shader() override { return read_file("mipmap_needing_effect.frag"); }
557 void inform_added(EffectChain *chain) override { this->chain = chain; }
559 void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num) override
561 Node *self = chain->find_node_for_effect(this);
562 glActiveTexture(chain->get_input_sampler(self, 0));
564 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
566 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
574 TEST(EffectChainTest, MipmapGenerationWorks) {
575 float data[] = { // In 4x4 blocks.
576 1.0f, 0.0f, 0.0f, 0.0f,
577 0.0f, 0.0f, 0.0f, 0.0f,
578 0.0f, 0.0f, 0.0f, 0.0f,
579 0.0f, 0.0f, 0.0f, 1.0f,
581 0.0f, 0.0f, 0.0f, 0.0f,
582 0.0f, 0.5f, 0.0f, 0.0f,
583 0.0f, 0.0f, 1.0f, 0.0f,
584 0.0f, 0.0f, 0.0f, 0.0f,
586 1.0f, 1.0f, 1.0f, 1.0f,
587 1.0f, 1.0f, 1.0f, 1.0f,
588 1.0f, 1.0f, 1.0f, 1.0f,
589 1.0f, 1.0f, 1.0f, 1.0f,
591 0.0f, 0.0f, 0.0f, 0.0f,
592 0.0f, 1.0f, 1.0f, 0.0f,
593 0.0f, 1.0f, 1.0f, 0.0f,
594 0.0f, 0.0f, 0.0f, 0.0f,
596 float expected_data[] = { // Repeated four times each way.
597 0.125f, 0.125f, 0.125f, 0.125f,
598 0.09375f, 0.09375f, 0.09375f, 0.09375f,
599 1.0f, 1.0f, 1.0f, 1.0f,
600 0.25f, 0.25f, 0.25f, 0.25f,
602 0.125f, 0.125f, 0.125f, 0.125f,
603 0.09375f, 0.09375f, 0.09375f, 0.09375f,
604 1.0f, 1.0f, 1.0f, 1.0f,
605 0.25f, 0.25f, 0.25f, 0.25f,
607 0.125f, 0.125f, 0.125f, 0.125f,
608 0.09375f, 0.09375f, 0.09375f, 0.09375f,
609 1.0f, 1.0f, 1.0f, 1.0f,
610 0.25f, 0.25f, 0.25f, 0.25f,
612 0.125f, 0.125f, 0.125f, 0.125f,
613 0.09375f, 0.09375f, 0.09375f, 0.09375f,
614 1.0f, 1.0f, 1.0f, 1.0f,
615 0.25f, 0.25f, 0.25f, 0.25f,
617 float out_data[4 * 16];
618 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
619 tester.get_chain()->add_effect(new MipmapNeedingEffect());
620 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
622 expect_equal(expected_data, out_data, 4, 16);
625 class NonMipmapCapableInput : public FlatInput {
627 NonMipmapCapableInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
628 : FlatInput(format, pixel_format, type, width, height) {}
630 bool can_supply_mipmaps() const override { return false; }
631 bool set_int(const std::string& key, int value) override {
632 if (key == "needs_mipmaps") {
635 return FlatInput::set_int(key, value);
639 // The same test as MipmapGenerationWorks, but with an input that refuses
640 // to supply mipmaps.
641 TEST(EffectChainTest, MipmapsWithNonMipmapCapableInput) {
642 float data[] = { // In 4x4 blocks.
643 1.0f, 0.0f, 0.0f, 0.0f,
644 0.0f, 0.0f, 0.0f, 0.0f,
645 0.0f, 0.0f, 0.0f, 0.0f,
646 0.0f, 0.0f, 0.0f, 1.0f,
648 0.0f, 0.0f, 0.0f, 0.0f,
649 0.0f, 0.5f, 0.0f, 0.0f,
650 0.0f, 0.0f, 1.0f, 0.0f,
651 0.0f, 0.0f, 0.0f, 0.0f,
653 1.0f, 1.0f, 1.0f, 1.0f,
654 1.0f, 1.0f, 1.0f, 1.0f,
655 1.0f, 1.0f, 1.0f, 1.0f,
656 1.0f, 1.0f, 1.0f, 1.0f,
658 0.0f, 0.0f, 0.0f, 0.0f,
659 0.0f, 1.0f, 1.0f, 0.0f,
660 0.0f, 1.0f, 1.0f, 0.0f,
661 0.0f, 0.0f, 0.0f, 0.0f,
663 float expected_data[] = { // Repeated four times each way.
664 0.125f, 0.125f, 0.125f, 0.125f,
665 0.09375f, 0.09375f, 0.09375f, 0.09375f,
666 1.0f, 1.0f, 1.0f, 1.0f,
667 0.25f, 0.25f, 0.25f, 0.25f,
669 0.125f, 0.125f, 0.125f, 0.125f,
670 0.09375f, 0.09375f, 0.09375f, 0.09375f,
671 1.0f, 1.0f, 1.0f, 1.0f,
672 0.25f, 0.25f, 0.25f, 0.25f,
674 0.125f, 0.125f, 0.125f, 0.125f,
675 0.09375f, 0.09375f, 0.09375f, 0.09375f,
676 1.0f, 1.0f, 1.0f, 1.0f,
677 0.25f, 0.25f, 0.25f, 0.25f,
679 0.125f, 0.125f, 0.125f, 0.125f,
680 0.09375f, 0.09375f, 0.09375f, 0.09375f,
681 1.0f, 1.0f, 1.0f, 1.0f,
682 0.25f, 0.25f, 0.25f, 0.25f,
684 float out_data[4 * 16];
685 EffectChainTester tester(nullptr, 4, 16, FORMAT_GRAYSCALE);
688 format.color_space = COLORSPACE_sRGB;
689 format.gamma_curve = GAMMA_LINEAR;
691 NonMipmapCapableInput *input = new NonMipmapCapableInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 16);
692 input->set_pixel_data(data);
693 tester.get_chain()->add_input(input);
694 tester.get_chain()->add_effect(new MipmapNeedingEffect());
695 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
697 expect_equal(expected_data, out_data, 4, 16);
700 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
701 float data[] = { // In 4x4 blocks.
702 1.0f, 0.0f, 0.0f, 0.0f,
703 0.0f, 0.0f, 0.0f, 0.0f,
704 0.0f, 0.0f, 0.0f, 0.0f,
705 0.0f, 0.0f, 0.0f, 1.0f,
707 0.0f, 0.0f, 0.0f, 0.0f,
708 0.0f, 0.5f, 0.0f, 0.0f,
709 0.0f, 0.0f, 1.0f, 0.0f,
710 0.0f, 0.0f, 0.0f, 0.0f,
712 1.0f, 1.0f, 1.0f, 1.0f,
713 1.0f, 1.0f, 1.0f, 1.0f,
714 1.0f, 1.0f, 1.0f, 1.0f,
715 1.0f, 1.0f, 1.0f, 1.0f,
717 0.0f, 0.0f, 0.0f, 0.0f,
718 0.0f, 1.0f, 1.0f, 0.0f,
719 0.0f, 1.0f, 1.0f, 0.0f,
720 0.0f, 0.0f, 0.0f, 0.0f,
722 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
723 0.1250f, 0.1250f, 0.1250f, 0.1250f,
724 0.1250f, 0.1250f, 0.1250f, 0.1250f,
725 0.1211f, 0.1211f, 0.1211f, 0.1211f,
726 0.1133f, 0.1133f, 0.1133f, 0.1133f,
727 0.1055f, 0.1055f, 0.1055f, 0.1055f,
728 0.0977f, 0.0977f, 0.0977f, 0.0977f,
729 0.2070f, 0.2070f, 0.2070f, 0.2070f,
730 0.4336f, 0.4336f, 0.4336f, 0.4336f,
731 0.6602f, 0.6602f, 0.6602f, 0.6602f,
732 0.8867f, 0.8867f, 0.8867f, 0.8867f,
733 0.9062f, 0.9062f, 0.9062f, 0.9062f,
734 0.7188f, 0.7188f, 0.7188f, 0.7188f,
735 0.5312f, 0.5312f, 0.5312f, 0.5312f,
736 0.3438f, 0.3438f, 0.3438f, 0.3438f,
737 0.2500f, 0.2500f, 0.2500f, 0.2500f,
738 0.2500f, 0.2500f, 0.2500f, 0.2500f,
740 float out_data[4 * 16];
742 ResizeEffect *downscale = new ResizeEffect();
743 ASSERT_TRUE(downscale->set_int("width", 1));
744 ASSERT_TRUE(downscale->set_int("height", 4));
746 ResizeEffect *upscale = new ResizeEffect();
747 ASSERT_TRUE(upscale->set_int("width", 4));
748 ASSERT_TRUE(upscale->set_int("height", 16));
750 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
751 tester.get_chain()->add_effect(downscale);
752 tester.get_chain()->add_effect(upscale);
753 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
755 expect_equal(expected_data, out_data, 4, 16);
758 // An effect that adds its two inputs together. Used below.
759 class AddEffect : public Effect {
762 string effect_type_id() const override { return "AddEffect"; }
763 string output_fragment_shader() override { return read_file("add.frag"); }
764 unsigned num_inputs() const override { return 2; }
765 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
768 // Constructs the graph
772 // MultiplyEffect MultiplyEffect |
776 // and verifies that it gives the correct output.
777 TEST(EffectChainTest, DiamondGraph) {
782 float expected_data[] = {
786 float out_data[2 * 2];
788 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
789 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
791 MultiplyEffect *mul_half = new MultiplyEffect();
792 ASSERT_TRUE(mul_half->set_vec4("factor", half));
794 MultiplyEffect *mul_two = new MultiplyEffect();
795 ASSERT_TRUE(mul_two->set_vec4("factor", two));
797 EffectChainTester tester(nullptr, 2, 2);
800 format.color_space = COLORSPACE_sRGB;
801 format.gamma_curve = GAMMA_LINEAR;
803 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
804 input->set_pixel_data(data);
806 tester.get_chain()->add_input(input);
807 tester.get_chain()->add_effect(mul_half, input);
808 tester.get_chain()->add_effect(mul_two, input);
809 tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
810 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
812 expect_equal(expected_data, out_data, 2, 2);
815 // Constructs the graph
819 // MultiplyEffect MultiplyEffect |
821 // \ BouncingIdentityEffect |
825 // and verifies that it gives the correct output.
826 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
831 float expected_data[] = {
835 float out_data[2 * 2];
837 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
838 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
840 MultiplyEffect *mul_half = new MultiplyEffect();
841 ASSERT_TRUE(mul_half->set_vec4("factor", half));
843 MultiplyEffect *mul_two = new MultiplyEffect();
844 ASSERT_TRUE(mul_two->set_vec4("factor", two));
846 BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
848 EffectChainTester tester(nullptr, 2, 2);
851 format.color_space = COLORSPACE_sRGB;
852 format.gamma_curve = GAMMA_LINEAR;
854 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
855 input->set_pixel_data(data);
857 tester.get_chain()->add_input(input);
858 tester.get_chain()->add_effect(mul_half, input);
859 tester.get_chain()->add_effect(mul_two, input);
860 tester.get_chain()->add_effect(bounce, mul_two);
861 tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
862 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
864 expect_equal(expected_data, out_data, 2, 2);
867 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
872 float expected_data[] = {
873 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
876 float out_data[2 * 2];
878 EffectChainTester tester(nullptr, 2, 2);
879 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
881 // MirrorEffect does not get linear light, so the conversions will be
882 // inserted after it, not before.
883 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
884 tester.get_chain()->add_effect(effect);
886 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
887 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
888 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
889 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
891 expect_equal(expected_data, out_data, 2, 2);
893 Node *node = effect->replaced_node;
894 ASSERT_EQ(1, node->incoming_links.size());
895 ASSERT_EQ(1, node->outgoing_links.size());
896 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
897 EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
900 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
905 float expected_data[] = {
906 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
909 float out_data[2 * 2];
911 EffectChainTester tester(nullptr, 2, 2);
912 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
914 // MirrorEffect does not get linear light, so the conversions will be
915 // inserted after it, not before.
916 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
917 tester.get_chain()->add_effect(effect);
919 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
920 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
921 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
922 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
924 expect_equal(expected_data, out_data, 2, 2);
926 Node *node = effect->replaced_node;
927 ASSERT_EQ(1, node->incoming_links.size());
928 ASSERT_EQ(1, node->outgoing_links.size());
929 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
930 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
933 // An effect that does nothing, but requests texture bounce and stores
935 class SizeStoringEffect : public BouncingIdentityEffect {
937 SizeStoringEffect() : input_width(-1), input_height(-1) {}
938 void inform_input_size(unsigned input_num, unsigned width, unsigned height) override {
939 assert(input_num == 0);
941 input_height = height;
943 string effect_type_id() const override { return "SizeStoringEffect"; }
945 int input_width, input_height;
948 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
949 float data[2 * 2] = {
953 float out_data[4 * 3];
955 EffectChainTester tester(nullptr, 4, 3); // Note non-square aspect.
958 format.color_space = COLORSPACE_sRGB;
959 format.gamma_curve = GAMMA_LINEAR;
961 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
962 input1->set_pixel_data(data);
964 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
965 input2->set_pixel_data(data);
967 SizeStoringEffect *input_store = new SizeStoringEffect();
969 tester.get_chain()->add_input(input1);
970 tester.get_chain()->add_input(input2);
971 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
972 tester.get_chain()->add_effect(input_store);
973 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
975 EXPECT_EQ(2, input_store->input_width);
976 EXPECT_EQ(2, input_store->input_height);
979 TEST(EffectChainTest, AspectRatioConversion) {
980 float data1[4 * 3] = {
981 0.0f, 0.0f, 0.0f, 0.0f,
982 0.0f, 0.0f, 0.0f, 0.0f,
983 0.0f, 0.0f, 0.0f, 0.0f,
985 float data2[7 * 7] = {
986 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
987 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
988 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
989 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
990 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
991 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
992 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
995 // The right conversion here is that the 7x7 image decides the size,
996 // since it is the biggest, so everything is scaled up to 9x7
997 // (keep the height, round the width 9.333 to 9).
998 float out_data[9 * 7];
1000 EffectChainTester tester(nullptr, 4, 3);
1003 format.color_space = COLORSPACE_sRGB;
1004 format.gamma_curve = GAMMA_LINEAR;
1006 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
1007 input1->set_pixel_data(data1);
1009 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
1010 input2->set_pixel_data(data2);
1012 SizeStoringEffect *input_store = new SizeStoringEffect();
1014 tester.get_chain()->add_input(input1);
1015 tester.get_chain()->add_input(input2);
1016 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
1017 tester.get_chain()->add_effect(input_store);
1018 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1020 EXPECT_EQ(9, input_store->input_width);
1021 EXPECT_EQ(7, input_store->input_height);
1024 // Tests that putting a BlueInput (constant color) into its own pass,
1025 // which creates a phase that doesn't need texture coordinates,
1026 // doesn't mess up a second phase that actually does.
1027 TEST(EffectChainTest, FirstPhaseWithNoTextureCoordinates) {
1033 float expected_data[] = {
1034 1.0f, 1.0f, 2.0f, 2.0f,
1035 0.0f, 0.0f, 1.0f, 2.0f,
1037 float out_data[size * 4];
1038 // First say that we have sRGB, linear input.
1040 format.color_space = COLORSPACE_sRGB;
1041 format.gamma_curve = GAMMA_LINEAR;
1042 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 1, size);
1044 input->set_pixel_data(data);
1045 EffectChainTester tester(nullptr, 1, size);
1046 tester.get_chain()->add_input(new BlueInput());
1047 Effect *phase1_end = tester.get_chain()->add_effect(new BouncingIdentityEffect());
1048 tester.get_chain()->add_input(input);
1049 tester.get_chain()->add_effect(new AddEffect(), phase1_end, input);
1051 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1053 expect_equal(expected_data, out_data, 4, size);
1056 // An effect that does nothing except changing its output sizes.
1057 class VirtualResizeEffect : public Effect {
1059 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
1062 virtual_width(virtual_width),
1063 virtual_height(virtual_height) {}
1064 string effect_type_id() const override { return "VirtualResizeEffect"; }
1065 string output_fragment_shader() override { return read_file("identity.frag"); }
1067 bool changes_output_size() const override { return true; }
1069 void get_output_size(unsigned *width, unsigned *height,
1070 unsigned *virtual_width, unsigned *virtual_height) const override {
1071 *width = this->width;
1072 *height = this->height;
1073 *virtual_width = this->virtual_width;
1074 *virtual_height = this->virtual_height;
1078 int width, height, virtual_width, virtual_height;
1081 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
1082 const int size = 2, bigger_size = 3;
1083 float data[size * size] = {
1087 float out_data[size * size];
1089 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1091 SizeStoringEffect *size_store = new SizeStoringEffect();
1093 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
1094 tester.get_chain()->add_effect(size_store);
1095 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
1096 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1098 EXPECT_EQ(bigger_size, size_store->input_width);
1099 EXPECT_EQ(bigger_size, size_store->input_height);
1101 // If the resize is implemented as non-virtual, we'll fail here,
1102 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
1103 expect_equal(data, out_data, size, size);
1106 // An effect that is like VirtualResizeEffect, but always has virtual and real
1107 // sizes the same (and promises this).
1108 class NonVirtualResizeEffect : public VirtualResizeEffect {
1110 NonVirtualResizeEffect(int width, int height)
1111 : VirtualResizeEffect(width, height, width, height) {}
1112 string effect_type_id() const override { return "NonVirtualResizeEffect"; }
1113 bool sets_virtual_output_size() const override { return false; }
1116 class WithAndWithoutComputeShaderTest : public testing::TestWithParam<string> {
1118 INSTANTIATE_TEST_CASE_P(WithAndWithoutComputeShaderTest,
1119 WithAndWithoutComputeShaderTest,
1120 testing::Values("fragment", "compute"));
1122 // An effect that does nothing, but as a compute shader.
1123 class IdentityComputeEffect : public Effect {
1125 IdentityComputeEffect() {}
1126 virtual string effect_type_id() const { return "IdentityComputeEffect"; }
1127 virtual bool is_compute_shader() const { return true; }
1128 string output_fragment_shader() { return read_file("identity.comp"); }
1131 // An effect that promises one-to-one sampling (unlike IdentityEffect).
1132 class OneToOneEffect : public Effect {
1135 string effect_type_id() const override { return "OneToOneEffect"; }
1136 string output_fragment_shader() override { return read_file("identity.frag"); }
1137 bool strong_one_to_one_sampling() const override { return true; }
1140 TEST_P(WithAndWithoutComputeShaderTest, NoBounceWithOneToOneSampling) {
1142 float data[size * size] = {
1146 float out_data[size * size];
1148 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1150 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1151 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1153 if (GetParam() == "compute") {
1154 tester.get_chain()->add_effect(new IdentityComputeEffect());
1156 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1158 tester.get_chain()->add_effect(effect1);
1159 tester.get_chain()->add_effect(effect2);
1160 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1162 expect_equal(data, out_data, size, size);
1164 // The first OneToOneEffect should be in the same phase as its input.
1165 ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
1166 EXPECT_EQ(effect1->replaced_node->incoming_links[0]->containing_phase,
1167 effect1->replaced_node->containing_phase);
1169 // The second OneToOneEffect, too.
1170 EXPECT_EQ(effect1->replaced_node->containing_phase,
1171 effect2->replaced_node->containing_phase);
1174 TEST(EffectChainTest, BounceWhenOneToOneIsBroken) {
1176 float data[size * size] = {
1180 float out_data[size * size];
1182 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1184 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1185 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1186 RewritingEffect<IdentityEffect> *effect3 = new RewritingEffect<IdentityEffect>();
1187 RewritingEffect<OneToOneEffect> *effect4 = new RewritingEffect<OneToOneEffect>();
1189 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1190 tester.get_chain()->add_effect(effect1);
1191 tester.get_chain()->add_effect(effect2);
1192 tester.get_chain()->add_effect(effect3);
1193 tester.get_chain()->add_effect(effect4);
1194 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1196 expect_equal(data, out_data, size, size);
1198 // The NonVirtualResizeEffect should be in a different phase from
1199 // the IdentityEffect (since the latter is not one-to-one),
1200 // ie., the chain should be broken somewhere between them, but exactly
1201 // where doesn't matter.
1202 ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
1203 EXPECT_NE(effect1->replaced_node->incoming_links[0]->containing_phase,
1204 effect3->replaced_node->containing_phase);
1206 // The last OneToOneEffect should also be in the same phase as the
1207 // IdentityEffect (the phase was already broken).
1208 EXPECT_EQ(effect3->replaced_node->containing_phase,
1209 effect4->replaced_node->containing_phase);
1212 // Does not use EffectChainTest, so that it can construct an EffectChain without
1213 // a shared ResourcePool (which is also properly destroyed afterwards).
1214 // Also turns on debugging to test that code path.
1215 TEST(EffectChainTest, IdentityWithOwnPool) {
1216 const int width = 3, height = 2;
1221 const float expected_data[] = {
1225 float out_data[6], temp[6 * 4];
1227 EffectChain chain(width, height);
1228 movit_debug_level = MOVIT_DEBUG_ON;
1231 format.color_space = COLORSPACE_sRGB;
1232 format.gamma_curve = GAMMA_LINEAR;
1234 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
1235 input->set_pixel_data(data);
1236 chain.add_input(input);
1237 chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1240 glGenTextures(1, &texnum);
1242 glBindTexture(GL_TEXTURE_2D, texnum);
1244 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
1247 glGenFramebuffers(1, &fbo);
1249 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1251 glFramebufferTexture2D(
1253 GL_COLOR_ATTACHMENT0,
1258 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1263 chain.render_to_fbo(fbo, width, height);
1265 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1267 glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp);
1269 for (unsigned i = 0; i < 6; ++i) {
1270 out_data[i] = temp[i * 4];
1273 expect_equal(expected_data, out_data, width, height);
1275 // Reset the debug status again.
1276 movit_debug_level = MOVIT_DEBUG_OFF;
1279 // A dummy effect whose only purpose is to test sprintf decimal behavior.
1280 class PrintfingBlueEffect : public Effect {
1282 PrintfingBlueEffect() {}
1283 string effect_type_id() const override { return "PrintfingBlueEffect"; }
1284 string output_fragment_shader() override {
1286 ss.imbue(locale("C"));
1288 ss << "vec4 FUNCNAME(vec2 tc) { return vec4("
1289 << 0.0f << ", " << 0.0f << ", "
1290 << 0.5f << ", " << 1.0f << "); }\n";
1295 TEST(EffectChainTest, StringStreamLocalesWork) {
1296 // An example of a locale with comma instead of period as decimal separator.
1297 // Obviously, if you run on a machine without this locale available,
1298 // the test will always succeed. Note that the OpenGL driver might call
1299 // setlocale() behind-the-scenes, and that might corrupt the returned
1300 // pointer, so we need to take our own copy of it here.
1301 char *saved_locale = setlocale(LC_ALL, "nb_NO.UTF_8");
1302 if (saved_locale == nullptr) {
1303 // The locale wasn't available.
1306 saved_locale = strdup(saved_locale);
1308 0.0f, 0.0f, 0.0f, 0.0f,
1310 float expected_data[] = {
1311 0.0f, 0.0f, 0.5f, 1.0f,
1314 EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1315 tester.get_chain()->add_effect(new PrintfingBlueEffect());
1316 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1318 expect_equal(expected_data, out_data, 4, 1);
1320 setlocale(LC_ALL, saved_locale);
1324 TEST(EffectChainTest, sRGBIntermediate) {
1326 0.0f, 0.5f, 0.0f, 1.0f,
1329 EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1330 tester.get_chain()->set_intermediate_format(GL_SRGB8);
1331 tester.get_chain()->add_effect(new IdentityEffect());
1332 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1333 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1335 EXPECT_GE(fabs(out_data[1] - data[1]), 1e-3)
1336 << "Expected sRGB not to be able to represent 0.5 exactly (got " << out_data[1] << ")";
1337 EXPECT_LT(fabs(out_data[1] - data[1]), 0.1f)
1338 << "Expected sRGB to be able to represent 0.5 approximately (got " << out_data[1] << ")";
1340 // This state should have been preserved.
1341 EXPECT_FALSE(glIsEnabled(GL_FRAMEBUFFER_SRGB));
1344 // An effect that is like IdentityEffect, but also does not require linear light.
1345 class PassThroughEffect : public IdentityEffect {
1347 PassThroughEffect() {}
1348 string effect_type_id() const override { return "PassThroughEffect"; }
1349 bool needs_linear_light() const override { return false; }
1350 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
1353 // Same, just also bouncing.
1354 class BouncingPassThroughEffect : public BouncingIdentityEffect {
1356 BouncingPassThroughEffect() {}
1357 string effect_type_id() const override { return "BouncingPassThroughEffect"; }
1358 bool needs_linear_light() const override { return false; }
1359 bool needs_texture_bounce() const override { return true; }
1360 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
1363 TEST(EffectChainTest, Linear10bitIntermediateAccuracy) {
1364 // Note that we do the comparison in sRGB space, which is what we
1365 // typically would want; however, we do the sRGB conversion ourself
1366 // to avoid compounding errors from shader conversions into the
1368 const int size = 4096; // 12-bit.
1369 float linear_data[size], data[size], out_data[size];
1371 for (int i = 0; i < size; ++i) {
1372 linear_data[i] = i / double(size - 1);
1373 data[i] = srgb_to_linear(linear_data[i]);
1376 EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
1377 tester.get_chain()->set_intermediate_format(GL_RGB10_A2);
1378 tester.get_chain()->add_effect(new IdentityEffect());
1379 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1380 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1382 for (int i = 0; i < size; ++i) {
1383 out_data[i] = linear_to_srgb(out_data[i]);
1386 // This maximum error is pretty bad; about 6.5 levels of a 10-bit sRGB
1387 // framebuffer. (Slightly more on NVIDIA cards.)
1388 expect_equal(linear_data, out_data, size, 1, 7.5e-3, 2e-5);
1391 TEST_P(WithAndWithoutComputeShaderTest, SquareRoot10bitIntermediateAccuracy) {
1392 // Note that we do the comparison in sRGB space, which is what we
1393 // typically would want; however, we do the sRGB conversion ourself
1394 // to avoid compounding errors from shader conversions into the
1396 const int size = 4096; // 12-bit.
1397 float linear_data[size], data[size], out_data[size];
1399 for (int i = 0; i < size; ++i) {
1400 linear_data[i] = i / double(size - 1);
1401 data[i] = srgb_to_linear(linear_data[i]);
1404 EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
1405 tester.get_chain()->set_intermediate_format(GL_RGB10_A2, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION);
1406 if (GetParam() == "compute") {
1407 tester.get_chain()->add_effect(new IdentityComputeEffect());
1409 tester.get_chain()->add_effect(new IdentityEffect());
1411 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1412 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1414 for (int i = 0; i < size; ++i) {
1415 out_data[i] = linear_to_srgb(out_data[i]);
1418 // This maximum error is much better; about 0.7 levels of a 10-bit sRGB
1419 // framebuffer (ideal would be 0.5). That is an order of magnitude better
1420 // than in the linear test above. The RMS error is much better, too.
1421 expect_equal(linear_data, out_data, size, 1, 7.5e-4, 5e-6);
1424 TEST(EffectChainTest, SquareRootIntermediateIsTurnedOffForNonLinearData) {
1425 const int size = 256; // 8-bit.
1426 float data[size], out_data[size];
1428 for (int i = 0; i < size; ++i) {
1429 data[i] = i / double(size - 1);
1432 EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_601, GL_RGBA32F);
1433 tester.get_chain()->set_intermediate_format(GL_RGB8, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION);
1434 tester.get_chain()->add_effect(new PassThroughEffect());
1435 tester.get_chain()->add_effect(new BouncingPassThroughEffect());
1436 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_601);
1438 // The data should be passed through nearly exactly, since there is no effect
1439 // on the path that requires linear light. (Actually, it _is_ exact modulo
1440 // fp32 errors, but the error bounds is strictly _less than_, not zero.)
1441 expect_equal(data, out_data, size, 1, 1e-6, 1e-6);
1444 // An effect that stores which program number was last run under.
1445 class RecordingIdentityEffect : public Effect {
1447 RecordingIdentityEffect() {}
1448 string effect_type_id() const override { return "RecordingIdentityEffect"; }
1449 string output_fragment_shader() override { return read_file("identity.frag"); }
1451 GLuint last_glsl_program_num;
1452 void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num) override
1454 last_glsl_program_num = glsl_program_num;
1458 TEST(EffectChainTest, ProgramsAreClonedForMultipleThreads) {
1464 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1465 RecordingIdentityEffect *effect = new RecordingIdentityEffect();
1466 tester.get_chain()->add_effect(effect);
1467 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1469 expect_equal(data, out_data, 3, 2);
1471 ASSERT_NE(0, effect->last_glsl_program_num);
1473 // Now pretend some other effect is using this program number;
1474 // ResourcePool will then need to clone it.
1475 ResourcePool *resource_pool = tester.get_chain()->get_resource_pool();
1476 GLuint master_program_num = resource_pool->use_glsl_program(effect->last_glsl_program_num);
1477 EXPECT_EQ(effect->last_glsl_program_num, master_program_num);
1479 // Re-run should still give the correct data, but it should have run
1480 // with a different program.
1481 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1482 expect_equal(data, out_data, 3, 2);
1483 EXPECT_NE(effect->last_glsl_program_num, master_program_num);
1485 // Release the program, and check one final time.
1486 resource_pool->unuse_glsl_program(master_program_num);
1487 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1488 expect_equal(data, out_data, 3, 2);
1491 TEST(ComputeShaderTest, Identity) {
1497 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1498 if (!movit_compute_shaders_supported) {
1499 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1502 tester.get_chain()->add_effect(new IdentityComputeEffect());
1503 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1505 expect_equal(data, out_data, 3, 2);
1508 // Like IdentityComputeEffect, but due to the alpha handling, this will be
1509 // the very last effect in the chain, which means we can't output it directly
1511 class IdentityAlphaComputeEffect : public IdentityComputeEffect {
1512 AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
1515 TEST(ComputeShaderTest, LastEffectInChain) {
1521 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1522 if (!movit_compute_shaders_supported) {
1523 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1526 tester.get_chain()->add_effect(new IdentityAlphaComputeEffect());
1527 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1529 expect_equal(data, out_data, 3, 2);
1532 TEST(ComputeShaderTest, Render8BitTo8Bit) {
1537 uint8_t out_data[6];
1538 EffectChainTester tester(nullptr, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
1539 if (!movit_compute_shaders_supported) {
1540 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1543 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 3, 2);
1544 tester.get_chain()->add_effect(new IdentityAlphaComputeEffect());
1545 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1547 expect_equal(data, out_data, 3, 2);
1550 } // namespace movit