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 virtual string effect_type_id() const { return "IdentityEffect"; }
46 string output_fragment_shader() { 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 virtual string effect_type_id() const { return "IdentityEffect"; }
67 string output_fragment_shader() { return read_file("identity.frag"); }
68 bool needs_texture_bounce() const { return true; }
69 AlphaHandling alpha_handling() const { 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 virtual string effect_type_id() const { return "InvertEffect"; }
126 string output_fragment_shader() { 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 virtual AlphaHandling alpha_handling() const { 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 virtual string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
141 string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
142 virtual void rewrite_graph(EffectChain *graph, Node *self) {
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 virtual string effect_type_id() const { 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 { return overridden_color_space; }
247 GammaCurve get_gamma_curve() const { 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 virtual string effect_type_id() const { return "IdentityEffect"; }
424 string output_fragment_shader() { return read_file("blue.frag"); }
425 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
426 virtual void finalize() {}
427 virtual bool can_output_linear_gamma() const { return true; }
428 virtual unsigned get_width() const { return 1; }
429 virtual unsigned get_height() const { return 1; }
430 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
431 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
437 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
438 // which outputs blank alpha.
439 class RewritingToBlueInput : public Input {
441 RewritingToBlueInput() : blue_node(nullptr) { register_int("needs_mipmaps", &needs_mipmaps); }
442 virtual string effect_type_id() const { return "RewritingToBlueInput"; }
443 string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
444 virtual void rewrite_graph(EffectChain *graph, Node *self) {
445 Node *blue_node = graph->add_node(new BlueInput());
446 graph->replace_receiver(self, blue_node);
447 graph->replace_sender(self, blue_node);
449 self->disabled = true;
450 this->blue_node = blue_node;
453 // Dummy values that we need to implement because we inherit from Input.
454 // Same as BlueInput.
455 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
456 virtual void finalize() {}
457 virtual bool can_output_linear_gamma() const { return true; }
458 virtual unsigned get_width() const { return 1; }
459 virtual unsigned get_height() const { return 1; }
460 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
461 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
469 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
471 float data[4 * size] = {
472 0.0f, 0.0f, 1.0f, 1.0f,
473 0.0f, 0.0f, 1.0f, 1.0f,
474 0.0f, 0.0f, 1.0f, 1.0f,
476 float out_data[4 * size];
477 EffectChainTester tester(nullptr, size, 1);
478 RewritingToBlueInput *input = new RewritingToBlueInput();
479 tester.get_chain()->add_input(input);
480 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
482 Node *node = input->blue_node;
483 EXPECT_EQ(0, node->incoming_links.size());
484 EXPECT_EQ(0, node->outgoing_links.size());
486 expect_equal(data, out_data, 4, size);
489 // An effect that does nothing, and specifies that it preserves blank alpha.
490 class BlankAlphaPreservingEffect : public Effect {
492 BlankAlphaPreservingEffect() {}
493 virtual string effect_type_id() const { return "BlankAlphaPreservingEffect"; }
494 string output_fragment_shader() { return read_file("identity.frag"); }
495 virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
498 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
500 float data[4 * size] = {
501 0.0f, 0.0f, 1.0f, 1.0f,
502 0.0f, 0.0f, 1.0f, 1.0f,
503 0.0f, 0.0f, 1.0f, 1.0f,
505 float out_data[4 * size];
506 EffectChainTester tester(nullptr, size, 1);
507 tester.get_chain()->add_input(new BlueInput());
508 tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
509 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
510 tester.get_chain()->add_effect(effect);
511 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
513 Node *node = effect->replaced_node;
514 EXPECT_EQ(1, node->incoming_links.size());
515 EXPECT_EQ(0, node->outgoing_links.size());
517 expect_equal(data, out_data, 4, size);
520 // This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
521 // just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
522 // an alpha conversion _should_ be inserted at the very end. (There is some overlap
523 // with other tests.)
524 TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
526 float data[4 * size] = {
527 0.0f, 0.0f, 1.0f, 1.0f,
528 0.0f, 0.0f, 1.0f, 1.0f,
529 0.0f, 0.0f, 1.0f, 1.0f,
531 float out_data[4 * size];
532 EffectChainTester tester(nullptr, size, 1);
533 tester.get_chain()->add_input(new BlueInput());
534 tester.get_chain()->add_effect(new IdentityEffect()); // Not BlankAlphaPreservingEffect.
535 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
536 tester.get_chain()->add_effect(effect);
537 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
539 Node *node = effect->replaced_node;
540 EXPECT_EQ(1, node->incoming_links.size());
541 EXPECT_EQ(1, node->outgoing_links.size());
542 EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
544 expect_equal(data, out_data, 4, size);
547 // Effectively scales down its input linearly by 4x (and repeating it),
548 // which is not attainable without mipmaps.
549 class MipmapNeedingEffect : public Effect {
551 MipmapNeedingEffect() {}
552 virtual bool needs_mipmaps() const { return true; }
554 // To be allowed to mess with the sampler state.
555 virtual bool needs_texture_bounce() const { return true; }
557 virtual string effect_type_id() const { return "MipmapNeedingEffect"; }
558 string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
559 virtual void inform_added(EffectChain *chain) { this->chain = chain; }
561 void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
563 Node *self = chain->find_node_for_effect(this);
564 glActiveTexture(chain->get_input_sampler(self, 0));
566 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
568 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
576 TEST(EffectChainTest, MipmapGenerationWorks) {
577 float data[] = { // In 4x4 blocks.
578 1.0f, 0.0f, 0.0f, 0.0f,
579 0.0f, 0.0f, 0.0f, 0.0f,
580 0.0f, 0.0f, 0.0f, 0.0f,
581 0.0f, 0.0f, 0.0f, 1.0f,
583 0.0f, 0.0f, 0.0f, 0.0f,
584 0.0f, 0.5f, 0.0f, 0.0f,
585 0.0f, 0.0f, 1.0f, 0.0f,
586 0.0f, 0.0f, 0.0f, 0.0f,
588 1.0f, 1.0f, 1.0f, 1.0f,
589 1.0f, 1.0f, 1.0f, 1.0f,
590 1.0f, 1.0f, 1.0f, 1.0f,
591 1.0f, 1.0f, 1.0f, 1.0f,
593 0.0f, 0.0f, 0.0f, 0.0f,
594 0.0f, 1.0f, 1.0f, 0.0f,
595 0.0f, 1.0f, 1.0f, 0.0f,
596 0.0f, 0.0f, 0.0f, 0.0f,
598 float expected_data[] = { // Repeated four times each way.
599 0.125f, 0.125f, 0.125f, 0.125f,
600 0.09375f, 0.09375f, 0.09375f, 0.09375f,
601 1.0f, 1.0f, 1.0f, 1.0f,
602 0.25f, 0.25f, 0.25f, 0.25f,
604 0.125f, 0.125f, 0.125f, 0.125f,
605 0.09375f, 0.09375f, 0.09375f, 0.09375f,
606 1.0f, 1.0f, 1.0f, 1.0f,
607 0.25f, 0.25f, 0.25f, 0.25f,
609 0.125f, 0.125f, 0.125f, 0.125f,
610 0.09375f, 0.09375f, 0.09375f, 0.09375f,
611 1.0f, 1.0f, 1.0f, 1.0f,
612 0.25f, 0.25f, 0.25f, 0.25f,
614 0.125f, 0.125f, 0.125f, 0.125f,
615 0.09375f, 0.09375f, 0.09375f, 0.09375f,
616 1.0f, 1.0f, 1.0f, 1.0f,
617 0.25f, 0.25f, 0.25f, 0.25f,
619 float out_data[4 * 16];
620 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
621 tester.get_chain()->add_effect(new MipmapNeedingEffect());
622 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
624 expect_equal(expected_data, out_data, 4, 16);
627 class NonMipmapCapableInput : public FlatInput {
629 NonMipmapCapableInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
630 : FlatInput(format, pixel_format, type, width, height) {}
632 virtual bool can_supply_mipmaps() const { return false; }
633 bool set_int(const std::string& key, int value) {
634 if (key == "needs_mipmaps") {
637 return FlatInput::set_int(key, value);
641 // The same test as MipmapGenerationWorks, but with an input that refuses
642 // to supply mipmaps.
643 TEST(EffectChainTest, MipmapsWithNonMipmapCapableInput) {
644 float data[] = { // In 4x4 blocks.
645 1.0f, 0.0f, 0.0f, 0.0f,
646 0.0f, 0.0f, 0.0f, 0.0f,
647 0.0f, 0.0f, 0.0f, 0.0f,
648 0.0f, 0.0f, 0.0f, 1.0f,
650 0.0f, 0.0f, 0.0f, 0.0f,
651 0.0f, 0.5f, 0.0f, 0.0f,
652 0.0f, 0.0f, 1.0f, 0.0f,
653 0.0f, 0.0f, 0.0f, 0.0f,
655 1.0f, 1.0f, 1.0f, 1.0f,
656 1.0f, 1.0f, 1.0f, 1.0f,
657 1.0f, 1.0f, 1.0f, 1.0f,
658 1.0f, 1.0f, 1.0f, 1.0f,
660 0.0f, 0.0f, 0.0f, 0.0f,
661 0.0f, 1.0f, 1.0f, 0.0f,
662 0.0f, 1.0f, 1.0f, 0.0f,
663 0.0f, 0.0f, 0.0f, 0.0f,
665 float expected_data[] = { // Repeated four times each way.
666 0.125f, 0.125f, 0.125f, 0.125f,
667 0.09375f, 0.09375f, 0.09375f, 0.09375f,
668 1.0f, 1.0f, 1.0f, 1.0f,
669 0.25f, 0.25f, 0.25f, 0.25f,
671 0.125f, 0.125f, 0.125f, 0.125f,
672 0.09375f, 0.09375f, 0.09375f, 0.09375f,
673 1.0f, 1.0f, 1.0f, 1.0f,
674 0.25f, 0.25f, 0.25f, 0.25f,
676 0.125f, 0.125f, 0.125f, 0.125f,
677 0.09375f, 0.09375f, 0.09375f, 0.09375f,
678 1.0f, 1.0f, 1.0f, 1.0f,
679 0.25f, 0.25f, 0.25f, 0.25f,
681 0.125f, 0.125f, 0.125f, 0.125f,
682 0.09375f, 0.09375f, 0.09375f, 0.09375f,
683 1.0f, 1.0f, 1.0f, 1.0f,
684 0.25f, 0.25f, 0.25f, 0.25f,
686 float out_data[4 * 16];
687 EffectChainTester tester(nullptr, 4, 16, FORMAT_GRAYSCALE);
690 format.color_space = COLORSPACE_sRGB;
691 format.gamma_curve = GAMMA_LINEAR;
693 NonMipmapCapableInput *input = new NonMipmapCapableInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 16);
694 input->set_pixel_data(data);
695 tester.get_chain()->add_input(input);
696 tester.get_chain()->add_effect(new MipmapNeedingEffect());
697 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
699 expect_equal(expected_data, out_data, 4, 16);
702 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
703 float data[] = { // In 4x4 blocks.
704 1.0f, 0.0f, 0.0f, 0.0f,
705 0.0f, 0.0f, 0.0f, 0.0f,
706 0.0f, 0.0f, 0.0f, 0.0f,
707 0.0f, 0.0f, 0.0f, 1.0f,
709 0.0f, 0.0f, 0.0f, 0.0f,
710 0.0f, 0.5f, 0.0f, 0.0f,
711 0.0f, 0.0f, 1.0f, 0.0f,
712 0.0f, 0.0f, 0.0f, 0.0f,
714 1.0f, 1.0f, 1.0f, 1.0f,
715 1.0f, 1.0f, 1.0f, 1.0f,
716 1.0f, 1.0f, 1.0f, 1.0f,
717 1.0f, 1.0f, 1.0f, 1.0f,
719 0.0f, 0.0f, 0.0f, 0.0f,
720 0.0f, 1.0f, 1.0f, 0.0f,
721 0.0f, 1.0f, 1.0f, 0.0f,
722 0.0f, 0.0f, 0.0f, 0.0f,
724 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
725 0.1250f, 0.1250f, 0.1250f, 0.1250f,
726 0.1250f, 0.1250f, 0.1250f, 0.1250f,
727 0.1211f, 0.1211f, 0.1211f, 0.1211f,
728 0.1133f, 0.1133f, 0.1133f, 0.1133f,
729 0.1055f, 0.1055f, 0.1055f, 0.1055f,
730 0.0977f, 0.0977f, 0.0977f, 0.0977f,
731 0.2070f, 0.2070f, 0.2070f, 0.2070f,
732 0.4336f, 0.4336f, 0.4336f, 0.4336f,
733 0.6602f, 0.6602f, 0.6602f, 0.6602f,
734 0.8867f, 0.8867f, 0.8867f, 0.8867f,
735 0.9062f, 0.9062f, 0.9062f, 0.9062f,
736 0.7188f, 0.7188f, 0.7188f, 0.7188f,
737 0.5312f, 0.5312f, 0.5312f, 0.5312f,
738 0.3438f, 0.3438f, 0.3438f, 0.3438f,
739 0.2500f, 0.2500f, 0.2500f, 0.2500f,
740 0.2500f, 0.2500f, 0.2500f, 0.2500f,
742 float out_data[4 * 16];
744 ResizeEffect *downscale = new ResizeEffect();
745 ASSERT_TRUE(downscale->set_int("width", 1));
746 ASSERT_TRUE(downscale->set_int("height", 4));
748 ResizeEffect *upscale = new ResizeEffect();
749 ASSERT_TRUE(upscale->set_int("width", 4));
750 ASSERT_TRUE(upscale->set_int("height", 16));
752 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
753 tester.get_chain()->add_effect(downscale);
754 tester.get_chain()->add_effect(upscale);
755 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
757 expect_equal(expected_data, out_data, 4, 16);
760 // An effect that adds its two inputs together. Used below.
761 class AddEffect : public Effect {
764 virtual string effect_type_id() const { return "AddEffect"; }
765 string output_fragment_shader() { return read_file("add.frag"); }
766 virtual unsigned num_inputs() const { return 2; }
767 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
770 // Constructs the graph
774 // MultiplyEffect MultiplyEffect |
778 // and verifies that it gives the correct output.
779 TEST(EffectChainTest, DiamondGraph) {
784 float expected_data[] = {
788 float out_data[2 * 2];
790 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
791 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
793 MultiplyEffect *mul_half = new MultiplyEffect();
794 ASSERT_TRUE(mul_half->set_vec4("factor", half));
796 MultiplyEffect *mul_two = new MultiplyEffect();
797 ASSERT_TRUE(mul_two->set_vec4("factor", two));
799 EffectChainTester tester(nullptr, 2, 2);
802 format.color_space = COLORSPACE_sRGB;
803 format.gamma_curve = GAMMA_LINEAR;
805 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
806 input->set_pixel_data(data);
808 tester.get_chain()->add_input(input);
809 tester.get_chain()->add_effect(mul_half, input);
810 tester.get_chain()->add_effect(mul_two, input);
811 tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
812 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
814 expect_equal(expected_data, out_data, 2, 2);
817 // Constructs the graph
821 // MultiplyEffect MultiplyEffect |
823 // \ BouncingIdentityEffect |
827 // and verifies that it gives the correct output.
828 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
833 float expected_data[] = {
837 float out_data[2 * 2];
839 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
840 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
842 MultiplyEffect *mul_half = new MultiplyEffect();
843 ASSERT_TRUE(mul_half->set_vec4("factor", half));
845 MultiplyEffect *mul_two = new MultiplyEffect();
846 ASSERT_TRUE(mul_two->set_vec4("factor", two));
848 BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
850 EffectChainTester tester(nullptr, 2, 2);
853 format.color_space = COLORSPACE_sRGB;
854 format.gamma_curve = GAMMA_LINEAR;
856 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
857 input->set_pixel_data(data);
859 tester.get_chain()->add_input(input);
860 tester.get_chain()->add_effect(mul_half, input);
861 tester.get_chain()->add_effect(mul_two, input);
862 tester.get_chain()->add_effect(bounce, mul_two);
863 tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
864 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
866 expect_equal(expected_data, out_data, 2, 2);
869 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
874 float expected_data[] = {
875 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
878 float out_data[2 * 2];
880 EffectChainTester tester(nullptr, 2, 2);
881 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
883 // MirrorEffect does not get linear light, so the conversions will be
884 // inserted after it, not before.
885 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
886 tester.get_chain()->add_effect(effect);
888 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
889 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
890 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
891 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
893 expect_equal(expected_data, out_data, 2, 2);
895 Node *node = effect->replaced_node;
896 ASSERT_EQ(1, node->incoming_links.size());
897 ASSERT_EQ(1, node->outgoing_links.size());
898 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
899 EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
902 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
907 float expected_data[] = {
908 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
911 float out_data[2 * 2];
913 EffectChainTester tester(nullptr, 2, 2);
914 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
916 // MirrorEffect does not get linear light, so the conversions will be
917 // inserted after it, not before.
918 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
919 tester.get_chain()->add_effect(effect);
921 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
922 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
923 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
924 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
926 expect_equal(expected_data, out_data, 2, 2);
928 Node *node = effect->replaced_node;
929 ASSERT_EQ(1, node->incoming_links.size());
930 ASSERT_EQ(1, node->outgoing_links.size());
931 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
932 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
935 // An effect that does nothing, but requests texture bounce and stores
937 class SizeStoringEffect : public BouncingIdentityEffect {
939 SizeStoringEffect() : input_width(-1), input_height(-1) {}
940 virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) {
941 assert(input_num == 0);
943 input_height = height;
945 virtual string effect_type_id() const { return "SizeStoringEffect"; }
947 int input_width, input_height;
950 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
951 float data[2 * 2] = {
955 float out_data[4 * 3];
957 EffectChainTester tester(nullptr, 4, 3); // Note non-square aspect.
960 format.color_space = COLORSPACE_sRGB;
961 format.gamma_curve = GAMMA_LINEAR;
963 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
964 input1->set_pixel_data(data);
966 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
967 input2->set_pixel_data(data);
969 SizeStoringEffect *input_store = new SizeStoringEffect();
971 tester.get_chain()->add_input(input1);
972 tester.get_chain()->add_input(input2);
973 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
974 tester.get_chain()->add_effect(input_store);
975 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
977 EXPECT_EQ(2, input_store->input_width);
978 EXPECT_EQ(2, input_store->input_height);
981 TEST(EffectChainTest, AspectRatioConversion) {
982 float data1[4 * 3] = {
983 0.0f, 0.0f, 0.0f, 0.0f,
984 0.0f, 0.0f, 0.0f, 0.0f,
985 0.0f, 0.0f, 0.0f, 0.0f,
987 float data2[7 * 7] = {
988 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
989 0.0f, 0.0f, 0.0f, 0.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, 1.0f, 0.0f, 0.0f, 0.0f,
992 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
993 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
994 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
997 // The right conversion here is that the 7x7 image decides the size,
998 // since it is the biggest, so everything is scaled up to 9x7
999 // (keep the height, round the width 9.333 to 9).
1000 float out_data[9 * 7];
1002 EffectChainTester tester(nullptr, 4, 3);
1005 format.color_space = COLORSPACE_sRGB;
1006 format.gamma_curve = GAMMA_LINEAR;
1008 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
1009 input1->set_pixel_data(data1);
1011 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
1012 input2->set_pixel_data(data2);
1014 SizeStoringEffect *input_store = new SizeStoringEffect();
1016 tester.get_chain()->add_input(input1);
1017 tester.get_chain()->add_input(input2);
1018 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
1019 tester.get_chain()->add_effect(input_store);
1020 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1022 EXPECT_EQ(9, input_store->input_width);
1023 EXPECT_EQ(7, input_store->input_height);
1026 // Tests that putting a BlueInput (constant color) into its own pass,
1027 // which creates a phase that doesn't need texture coordinates,
1028 // doesn't mess up a second phase that actually does.
1029 TEST(EffectChainTest, FirstPhaseWithNoTextureCoordinates) {
1035 float expected_data[] = {
1036 1.0f, 1.0f, 2.0f, 2.0f,
1037 0.0f, 0.0f, 1.0f, 2.0f,
1039 float out_data[size * 4];
1040 // First say that we have sRGB, linear input.
1042 format.color_space = COLORSPACE_sRGB;
1043 format.gamma_curve = GAMMA_LINEAR;
1044 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 1, size);
1046 input->set_pixel_data(data);
1047 EffectChainTester tester(nullptr, 1, size);
1048 tester.get_chain()->add_input(new BlueInput());
1049 Effect *phase1_end = tester.get_chain()->add_effect(new BouncingIdentityEffect());
1050 tester.get_chain()->add_input(input);
1051 tester.get_chain()->add_effect(new AddEffect(), phase1_end, input);
1053 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1055 expect_equal(expected_data, out_data, 4, size);
1058 // An effect that does nothing except changing its output sizes.
1059 class VirtualResizeEffect : public Effect {
1061 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
1064 virtual_width(virtual_width),
1065 virtual_height(virtual_height) {}
1066 virtual string effect_type_id() const { return "VirtualResizeEffect"; }
1067 string output_fragment_shader() { return read_file("identity.frag"); }
1069 virtual bool changes_output_size() const { return true; }
1071 virtual void get_output_size(unsigned *width, unsigned *height,
1072 unsigned *virtual_width, unsigned *virtual_height) const {
1073 *width = this->width;
1074 *height = this->height;
1075 *virtual_width = this->virtual_width;
1076 *virtual_height = this->virtual_height;
1080 int width, height, virtual_width, virtual_height;
1083 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
1084 const int size = 2, bigger_size = 3;
1085 float data[size * size] = {
1089 float out_data[size * size];
1091 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1093 SizeStoringEffect *size_store = new SizeStoringEffect();
1095 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
1096 tester.get_chain()->add_effect(size_store);
1097 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
1098 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1100 EXPECT_EQ(bigger_size, size_store->input_width);
1101 EXPECT_EQ(bigger_size, size_store->input_height);
1103 // If the resize is implemented as non-virtual, we'll fail here,
1104 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
1105 expect_equal(data, out_data, size, size);
1108 // An effect that is like VirtualResizeEffect, but always has virtual and real
1109 // sizes the same (and promises this).
1110 class NonVirtualResizeEffect : public VirtualResizeEffect {
1112 NonVirtualResizeEffect(int width, int height)
1113 : VirtualResizeEffect(width, height, width, height) {}
1114 virtual string effect_type_id() const { return "NonVirtualResizeEffect"; }
1115 virtual bool sets_virtual_output_size() const { return false; }
1118 // An effect that promises one-to-one sampling (unlike IdentityEffect).
1119 class OneToOneEffect : public Effect {
1122 virtual string effect_type_id() const { return "OneToOneEffect"; }
1123 string output_fragment_shader() { return read_file("identity.frag"); }
1124 virtual bool one_to_one_sampling() const { return true; }
1127 TEST(EffectChainTest, NoBounceWithOneToOneSampling) {
1129 float data[size * size] = {
1133 float out_data[size * size];
1135 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1137 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1138 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1140 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1141 tester.get_chain()->add_effect(effect1);
1142 tester.get_chain()->add_effect(effect2);
1143 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1145 expect_equal(data, out_data, size, size);
1147 // The first OneToOneEffect should be in the same phase as its input.
1148 ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
1149 EXPECT_EQ(effect1->replaced_node->incoming_links[0]->containing_phase,
1150 effect1->replaced_node->containing_phase);
1152 // The second OneToOneEffect, too.
1153 EXPECT_EQ(effect1->replaced_node->containing_phase,
1154 effect2->replaced_node->containing_phase);
1157 TEST(EffectChainTest, BounceWhenOneToOneIsBroken) {
1159 float data[size * size] = {
1163 float out_data[size * size];
1165 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1167 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1168 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1169 RewritingEffect<IdentityEffect> *effect3 = new RewritingEffect<IdentityEffect>();
1170 RewritingEffect<OneToOneEffect> *effect4 = new RewritingEffect<OneToOneEffect>();
1172 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1173 tester.get_chain()->add_effect(effect1);
1174 tester.get_chain()->add_effect(effect2);
1175 tester.get_chain()->add_effect(effect3);
1176 tester.get_chain()->add_effect(effect4);
1177 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1179 expect_equal(data, out_data, size, size);
1181 // The NonVirtualResizeEffect should be in a different phase from
1182 // the IdentityEffect (since the latter is not one-to-one),
1183 // ie., the chain should be broken somewhere between them, but exactly
1184 // where doesn't matter.
1185 ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
1186 EXPECT_NE(effect1->replaced_node->incoming_links[0]->containing_phase,
1187 effect3->replaced_node->containing_phase);
1189 // The last OneToOneEffect should also be in the same phase as the
1190 // IdentityEffect (the phase was already broken).
1191 EXPECT_EQ(effect3->replaced_node->containing_phase,
1192 effect4->replaced_node->containing_phase);
1195 // Does not use EffectChainTest, so that it can construct an EffectChain without
1196 // a shared ResourcePool (which is also properly destroyed afterwards).
1197 // Also turns on debugging to test that code path.
1198 TEST(EffectChainTest, IdentityWithOwnPool) {
1199 const int width = 3, height = 2;
1204 const float expected_data[] = {
1208 float out_data[6], temp[6 * 4];
1210 EffectChain chain(width, height);
1211 movit_debug_level = MOVIT_DEBUG_ON;
1214 format.color_space = COLORSPACE_sRGB;
1215 format.gamma_curve = GAMMA_LINEAR;
1217 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
1218 input->set_pixel_data(data);
1219 chain.add_input(input);
1220 chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1223 glGenTextures(1, &texnum);
1225 glBindTexture(GL_TEXTURE_2D, texnum);
1227 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
1230 glGenFramebuffers(1, &fbo);
1232 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1234 glFramebufferTexture2D(
1236 GL_COLOR_ATTACHMENT0,
1241 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1246 chain.render_to_fbo(fbo, width, height);
1248 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1250 glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp);
1252 for (unsigned i = 0; i < 6; ++i) {
1253 out_data[i] = temp[i * 4];
1256 expect_equal(expected_data, out_data, width, height);
1258 // Reset the debug status again.
1259 movit_debug_level = MOVIT_DEBUG_OFF;
1262 // A dummy effect whose only purpose is to test sprintf decimal behavior.
1263 class PrintfingBlueEffect : public Effect {
1265 PrintfingBlueEffect() {}
1266 virtual string effect_type_id() const { return "PrintfingBlueEffect"; }
1267 string output_fragment_shader() {
1269 ss.imbue(locale("C"));
1271 ss << "vec4 FUNCNAME(vec2 tc) { return vec4("
1272 << 0.0f << ", " << 0.0f << ", "
1273 << 0.5f << ", " << 1.0f << "); }\n";
1278 TEST(EffectChainTest, StringStreamLocalesWork) {
1279 // An example of a locale with comma instead of period as decimal separator.
1280 // Obviously, if you run on a machine without this locale available,
1281 // the test will always succeed. Note that the OpenGL driver might call
1282 // setlocale() behind-the-scenes, and that might corrupt the returned
1283 // pointer, so we need to take our own copy of it here.
1284 char *saved_locale = setlocale(LC_ALL, "nb_NO.UTF_8");
1285 if (saved_locale == nullptr) {
1286 // The locale wasn't available.
1289 saved_locale = strdup(saved_locale);
1291 0.0f, 0.0f, 0.0f, 0.0f,
1293 float expected_data[] = {
1294 0.0f, 0.0f, 0.5f, 1.0f,
1297 EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1298 tester.get_chain()->add_effect(new PrintfingBlueEffect());
1299 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1301 expect_equal(expected_data, out_data, 4, 1);
1303 setlocale(LC_ALL, saved_locale);
1307 TEST(EffectChainTest, sRGBIntermediate) {
1309 0.0f, 0.5f, 0.0f, 1.0f,
1312 EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1313 tester.get_chain()->set_intermediate_format(GL_SRGB8);
1314 tester.get_chain()->add_effect(new IdentityEffect());
1315 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1316 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1318 EXPECT_GE(fabs(out_data[1] - data[1]), 1e-3)
1319 << "Expected sRGB not to be able to represent 0.5 exactly (got " << out_data[1] << ")";
1320 EXPECT_LT(fabs(out_data[1] - data[1]), 0.1f)
1321 << "Expected sRGB to be able to represent 0.5 approximately (got " << out_data[1] << ")";
1323 // This state should have been preserved.
1324 EXPECT_FALSE(glIsEnabled(GL_FRAMEBUFFER_SRGB));
1327 // An effect that is like IdentityEffect, but also does not require linear light.
1328 class PassThroughEffect : public IdentityEffect {
1330 PassThroughEffect() {}
1331 virtual string effect_type_id() const { return "PassThroughEffect"; }
1332 virtual bool needs_linear_light() const { return false; }
1333 AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
1336 // Same, just also bouncing.
1337 class BouncingPassThroughEffect : public BouncingIdentityEffect {
1339 BouncingPassThroughEffect() {}
1340 virtual string effect_type_id() const { return "BouncingPassThroughEffect"; }
1341 virtual bool needs_linear_light() const { return false; }
1342 bool needs_texture_bounce() const { return true; }
1343 AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
1346 TEST(EffectChainTest, Linear10bitIntermediateAccuracy) {
1347 // Note that we do the comparison in sRGB space, which is what we
1348 // typically would want; however, we do the sRGB conversion ourself
1349 // to avoid compounding errors from shader conversions into the
1351 const int size = 4096; // 12-bit.
1352 float linear_data[size], data[size], out_data[size];
1354 for (int i = 0; i < size; ++i) {
1355 linear_data[i] = i / double(size - 1);
1356 data[i] = srgb_to_linear(linear_data[i]);
1359 EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
1360 tester.get_chain()->set_intermediate_format(GL_RGB10_A2);
1361 tester.get_chain()->add_effect(new IdentityEffect());
1362 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1363 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1365 for (int i = 0; i < size; ++i) {
1366 out_data[i] = linear_to_srgb(out_data[i]);
1369 // This maximum error is pretty bad; about 6.5 levels of a 10-bit sRGB
1370 // framebuffer. (Slightly more on NVIDIA cards.)
1371 expect_equal(linear_data, out_data, size, 1, 7.5e-3, 2e-5);
1374 TEST(EffectChainTest, SquareRoot10bitIntermediateAccuracy) {
1375 // Note that we do the comparison in sRGB space, which is what we
1376 // typically would want; however, we do the sRGB conversion ourself
1377 // to avoid compounding errors from shader conversions into the
1379 const int size = 4096; // 12-bit.
1380 float linear_data[size], data[size], out_data[size];
1382 for (int i = 0; i < size; ++i) {
1383 linear_data[i] = i / double(size - 1);
1384 data[i] = srgb_to_linear(linear_data[i]);
1387 EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
1388 tester.get_chain()->set_intermediate_format(GL_RGB10_A2, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION);
1389 tester.get_chain()->add_effect(new IdentityEffect());
1390 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1391 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1393 for (int i = 0; i < size; ++i) {
1394 out_data[i] = linear_to_srgb(out_data[i]);
1397 // This maximum error is much better; about 0.7 levels of a 10-bit sRGB
1398 // framebuffer (ideal would be 0.5). That is an order of magnitude better
1399 // than in the linear test above. The RMS error is much better, too.
1400 expect_equal(linear_data, out_data, size, 1, 7.5e-4, 5e-6);
1403 TEST(EffectChainTest, SquareRootIntermediateIsTurnedOffForNonLinearData) {
1404 const int size = 256; // 8-bit.
1405 float data[size], out_data[size];
1407 for (int i = 0; i < size; ++i) {
1408 data[i] = i / double(size - 1);
1411 EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_601, GL_RGBA32F);
1412 tester.get_chain()->set_intermediate_format(GL_RGB8, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION);
1413 tester.get_chain()->add_effect(new PassThroughEffect());
1414 tester.get_chain()->add_effect(new BouncingPassThroughEffect());
1415 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_601);
1417 // The data should be passed through nearly exactly, since there is no effect
1418 // on the path that requires linear light. (Actually, it _is_ exact modulo
1419 // fp32 errors, but the error bounds is strictly _less than_, not zero.)
1420 expect_equal(data, out_data, size, 1, 1e-6, 1e-6);
1423 // An effect that stores which program number was last run under.
1424 class RecordingIdentityEffect : public Effect {
1426 RecordingIdentityEffect() {}
1427 virtual string effect_type_id() const { return "RecordingIdentityEffect"; }
1428 string output_fragment_shader() { return read_file("identity.frag"); }
1430 GLuint last_glsl_program_num;
1431 void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
1433 last_glsl_program_num = glsl_program_num;
1437 TEST(EffectChainTest, ProgramsAreClonedForMultipleThreads) {
1443 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1444 RecordingIdentityEffect *effect = new RecordingIdentityEffect();
1445 tester.get_chain()->add_effect(effect);
1446 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1448 expect_equal(data, out_data, 3, 2);
1450 ASSERT_NE(0, effect->last_glsl_program_num);
1452 // Now pretend some other effect is using this program number;
1453 // ResourcePool will then need to clone it.
1454 ResourcePool *resource_pool = tester.get_chain()->get_resource_pool();
1455 GLuint master_program_num = resource_pool->use_glsl_program(effect->last_glsl_program_num);
1456 EXPECT_EQ(effect->last_glsl_program_num, master_program_num);
1458 // Re-run should still give the correct data, but it should have run
1459 // with a different program.
1460 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1461 expect_equal(data, out_data, 3, 2);
1462 EXPECT_NE(effect->last_glsl_program_num, master_program_num);
1464 // Release the program, and check one final time.
1465 resource_pool->unuse_glsl_program(master_program_num);
1466 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1467 expect_equal(data, out_data, 3, 2);
1470 } // namespace movit