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 "test_util.h"
28 TEST(EffectChainTest, EmptyChain) {
34 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
35 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
37 expect_equal(data, out_data, 3, 2);
40 // An effect that does nothing.
41 class IdentityEffect : public Effect {
44 virtual string effect_type_id() const { return "IdentityEffect"; }
45 string output_fragment_shader() { return read_file("identity.frag"); }
48 TEST(EffectChainTest, Identity) {
54 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
55 tester.get_chain()->add_effect(new IdentityEffect());
56 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
58 expect_equal(data, out_data, 3, 2);
61 // An effect that does nothing, but requests texture bounce.
62 class BouncingIdentityEffect : public Effect {
64 BouncingIdentityEffect() {}
65 virtual string effect_type_id() const { return "IdentityEffect"; }
66 string output_fragment_shader() { return read_file("identity.frag"); }
67 bool needs_texture_bounce() const { return true; }
68 AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
71 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
77 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
78 tester.get_chain()->add_effect(new BouncingIdentityEffect());
79 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
81 expect_equal(data, out_data, 3, 2);
84 TEST(MirrorTest, BasicTest) {
89 float expected_data[6] = {
94 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
95 tester.get_chain()->add_effect(new MirrorEffect());
96 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
98 expect_equal(expected_data, out_data, 3, 2);
101 // A dummy effect that inverts its input.
102 class InvertEffect : public Effect {
105 virtual string effect_type_id() const { return "InvertEffect"; }
106 string output_fragment_shader() { return read_file("invert_effect.frag"); }
108 // A real invert would actually care about its alpha,
109 // but in this unit test, it only complicates things.
110 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
113 // Like IdentityEffect, but rewrites itself out of the loop,
114 // splicing in a different effect instead. Also stores the new node,
115 // so we later can check whatever properties we'd like about the graph.
117 class RewritingEffect : public Effect {
119 RewritingEffect() : effect(new T()), replaced_node(NULL) {}
120 virtual string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
121 string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
122 virtual void rewrite_graph(EffectChain *graph, Node *self) {
123 replaced_node = graph->add_node(effect);
124 graph->replace_receiver(self, replaced_node);
125 graph->replace_sender(self, replaced_node);
126 self->disabled = true;
133 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
138 float expected_data[6] = {
139 1.0f, 0.9771f, 0.9673f,
143 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
144 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
145 tester.get_chain()->add_effect(effect);
146 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
148 Node *node = effect->replaced_node;
149 ASSERT_EQ(1, node->incoming_links.size());
150 ASSERT_EQ(1, node->outgoing_links.size());
151 EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
152 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
154 expect_equal(expected_data, out_data, 3, 2);
157 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
158 unsigned char data[] = {
164 float expected_data[] = {
165 1.0000f, 1.0000f, 1.0000f, 1.0000f,
166 0.9771f, 0.9771f, 0.9771f, 1.0000f,
167 0.8983f, 0.8983f, 0.8983f, 1.0000f,
168 0.0000f, 0.0000f, 0.0000f, 1.0000f
170 float out_data[4 * 4];
171 EffectChainTester tester(NULL, 1, 4);
172 tester.add_input(data, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_sRGB);
173 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
174 tester.get_chain()->add_effect(effect);
175 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
177 Node *node = effect->replaced_node;
178 ASSERT_EQ(1, node->incoming_links.size());
179 ASSERT_EQ(1, node->outgoing_links.size());
180 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
181 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
183 expect_equal(expected_data, out_data, 4, 4);
186 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
191 float expected_data[6] = {
196 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
197 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
198 tester.get_chain()->add_effect(effect);
199 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
201 Node *node = effect->replaced_node;
202 ASSERT_EQ(1, node->incoming_links.size());
203 ASSERT_EQ(1, node->outgoing_links.size());
204 EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
205 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
207 expect_equal(expected_data, out_data, 3, 2);
210 // A fake input that can change its output colorspace and gamma between instantiation
212 class UnknownColorspaceInput : public FlatInput {
214 UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
215 : FlatInput(format, pixel_format, type, width, height),
216 overridden_color_space(format.color_space),
217 overridden_gamma_curve(format.gamma_curve) {}
218 virtual string effect_type_id() const { return "UnknownColorspaceInput"; }
220 void set_color_space(Colorspace colorspace) {
221 overridden_color_space = colorspace;
223 void set_gamma_curve(GammaCurve gamma_curve) {
224 overridden_gamma_curve = gamma_curve;
226 Colorspace get_color_space() const { return overridden_color_space; }
227 GammaCurve get_gamma_curve() const { return overridden_gamma_curve; }
230 Colorspace overridden_color_space;
231 GammaCurve overridden_gamma_curve;
234 TEST(EffectChainTest, HandlesInputChangingColorspace) {
243 float out_data[size];
245 EffectChainTester tester(NULL, 4, 1, FORMAT_GRAYSCALE);
247 // First say that we have sRGB, linear input.
249 format.color_space = COLORSPACE_sRGB;
250 format.gamma_curve = GAMMA_LINEAR;
252 UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
253 input->set_pixel_data(data);
254 tester.get_chain()->add_input(input);
256 // Now we change to Rec. 601 input.
257 input->set_color_space(COLORSPACE_REC_601_625);
258 input->set_gamma_curve(GAMMA_REC_601);
260 // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
261 tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
262 expect_equal(data, out_data, 4, 1);
265 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
270 float expected_data[6] = {
275 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
276 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
277 tester.get_chain()->add_effect(effect);
278 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
280 Node *node = effect->replaced_node;
281 ASSERT_EQ(1, node->incoming_links.size());
282 EXPECT_EQ(0, node->outgoing_links.size());
283 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
285 expect_equal(expected_data, out_data, 3, 2);
288 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
293 float expected_data[6] = {
298 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
299 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
300 tester.get_chain()->add_effect(effect);
301 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
303 Node *node = effect->replaced_node;
304 ASSERT_EQ(1, node->incoming_links.size());
305 EXPECT_EQ(0, node->outgoing_links.size());
306 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
308 expect_equal(expected_data, out_data, 3, 2);
311 // The identity effect needs linear light, and thus will get conversions on both sides.
312 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
313 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
315 for (unsigned i = 0; i < 256; ++i) {
319 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
320 tester.get_chain()->add_effect(new IdentityEffect());
321 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
323 expect_equal(data, out_data, 256, 1);
326 // Same, but uses the forward sRGB table from the GPU.
327 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
328 unsigned char data[256];
329 float expected_data[256];
330 for (unsigned i = 0; i < 256; ++i) {
332 expected_data[i] = i / 255.0;
335 EffectChainTester tester(NULL, 256, 1);
336 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
337 tester.get_chain()->add_effect(new IdentityEffect());
338 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
340 expect_equal(expected_data, out_data, 256, 1);
343 // Same, for the Rec. 601/709 gamma curve.
344 TEST(EffectChainTest, IdentityThroughRec709) {
346 for (unsigned i = 0; i < 256; ++i) {
350 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
351 tester.get_chain()->add_effect(new IdentityEffect());
352 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
354 expect_equal(data, out_data, 256, 1);
357 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
358 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
360 float data[4 * size] = {
361 0.8f, 0.0f, 0.0f, 0.5f,
362 0.0f, 0.2f, 0.2f, 0.3f,
363 0.1f, 0.0f, 1.0f, 1.0f,
365 float out_data[4 * size];
366 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
367 tester.get_chain()->add_effect(new IdentityEffect());
368 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
370 expect_equal(data, out_data, 4, size);
373 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
375 float data[4 * size] = {
376 0.8f, 0.0f, 0.0f, 0.5f,
377 0.0f, 0.2f, 0.2f, 0.3f,
378 0.1f, 0.0f, 1.0f, 1.0f,
380 float expected_data[4 * size] = {
381 0.1f, 0.0f, 1.0f, 1.0f,
382 0.0f, 0.2f, 0.2f, 0.3f,
383 0.8f, 0.0f, 0.0f, 0.5f,
385 float out_data[4 * size];
386 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
387 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
388 tester.get_chain()->add_effect(effect);
389 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
391 Node *node = effect->replaced_node;
392 ASSERT_EQ(1, node->incoming_links.size());
393 EXPECT_EQ(0, node->outgoing_links.size());
394 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
396 expect_equal(expected_data, out_data, 4, size);
399 // An input that outputs only blue, which has blank alpha.
400 class BlueInput : public Input {
402 BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
403 virtual string effect_type_id() const { return "IdentityEffect"; }
404 string output_fragment_shader() { return read_file("blue.frag"); }
405 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
406 virtual void finalize() {}
407 virtual bool can_output_linear_gamma() const { return true; }
408 virtual unsigned get_width() const { return 1; }
409 virtual unsigned get_height() const { return 1; }
410 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
411 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
417 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
418 // which outputs blank alpha.
419 class RewritingToBlueInput : public Input {
421 RewritingToBlueInput() : blue_node(NULL) { register_int("needs_mipmaps", &needs_mipmaps); }
422 virtual string effect_type_id() const { return "RewritingToBlueInput"; }
423 string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
424 virtual void rewrite_graph(EffectChain *graph, Node *self) {
425 Node *blue_node = graph->add_node(new BlueInput());
426 graph->replace_receiver(self, blue_node);
427 graph->replace_sender(self, blue_node);
429 self->disabled = true;
430 this->blue_node = blue_node;
433 // Dummy values that we need to implement because we inherit from Input.
434 // Same as BlueInput.
435 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
436 virtual void finalize() {}
437 virtual bool can_output_linear_gamma() const { return true; }
438 virtual unsigned get_width() const { return 1; }
439 virtual unsigned get_height() const { return 1; }
440 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
441 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
449 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
451 float data[4 * size] = {
452 0.0f, 0.0f, 1.0f, 1.0f,
453 0.0f, 0.0f, 1.0f, 1.0f,
454 0.0f, 0.0f, 1.0f, 1.0f,
456 float out_data[4 * size];
457 EffectChainTester tester(NULL, size, 1);
458 RewritingToBlueInput *input = new RewritingToBlueInput();
459 tester.get_chain()->add_input(input);
460 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
462 Node *node = input->blue_node;
463 EXPECT_EQ(0, node->incoming_links.size());
464 EXPECT_EQ(0, node->outgoing_links.size());
466 expect_equal(data, out_data, 4, size);
469 // An effect that does nothing, and specifies that it preserves blank alpha.
470 class BlankAlphaPreservingEffect : public Effect {
472 BlankAlphaPreservingEffect() {}
473 virtual string effect_type_id() const { return "BlankAlphaPreservingEffect"; }
474 string output_fragment_shader() { return read_file("identity.frag"); }
475 virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
478 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
480 float data[4 * size] = {
481 0.0f, 0.0f, 1.0f, 1.0f,
482 0.0f, 0.0f, 1.0f, 1.0f,
483 0.0f, 0.0f, 1.0f, 1.0f,
485 float out_data[4 * size];
486 EffectChainTester tester(NULL, size, 1);
487 tester.get_chain()->add_input(new BlueInput());
488 tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
489 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
490 tester.get_chain()->add_effect(effect);
491 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
493 Node *node = effect->replaced_node;
494 EXPECT_EQ(1, node->incoming_links.size());
495 EXPECT_EQ(0, node->outgoing_links.size());
497 expect_equal(data, out_data, 4, size);
500 // This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
501 // just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
502 // an alpha conversion _should_ be inserted at the very end. (There is some overlap
503 // with other tests.)
504 TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
506 float data[4 * size] = {
507 0.0f, 0.0f, 1.0f, 1.0f,
508 0.0f, 0.0f, 1.0f, 1.0f,
509 0.0f, 0.0f, 1.0f, 1.0f,
511 float out_data[4 * size];
512 EffectChainTester tester(NULL, size, 1);
513 tester.get_chain()->add_input(new BlueInput());
514 tester.get_chain()->add_effect(new IdentityEffect()); // Not BlankAlphaPreservingEffect.
515 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
516 tester.get_chain()->add_effect(effect);
517 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
519 Node *node = effect->replaced_node;
520 EXPECT_EQ(1, node->incoming_links.size());
521 EXPECT_EQ(1, node->outgoing_links.size());
522 EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
524 expect_equal(data, out_data, 4, size);
527 // Effectively scales down its input linearly by 4x (and repeating it),
528 // which is not attainable without mipmaps.
529 class MipmapNeedingEffect : public Effect {
531 MipmapNeedingEffect() {}
532 virtual bool needs_mipmaps() const { return true; }
534 // To be allowed to mess with the sampler state.
535 virtual bool needs_texture_bounce() const { return true; }
537 virtual string effect_type_id() const { return "MipmapNeedingEffect"; }
538 string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
539 virtual void inform_added(EffectChain *chain) { this->chain = chain; }
541 void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
543 Node *self = chain->find_node_for_effect(this);
544 glActiveTexture(chain->get_input_sampler(self, 0));
546 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
548 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
556 TEST(EffectChainTest, MipmapGenerationWorks) {
557 float data[] = { // In 4x4 blocks.
558 1.0f, 0.0f, 0.0f, 0.0f,
559 0.0f, 0.0f, 0.0f, 0.0f,
560 0.0f, 0.0f, 0.0f, 0.0f,
561 0.0f, 0.0f, 0.0f, 1.0f,
563 0.0f, 0.0f, 0.0f, 0.0f,
564 0.0f, 0.5f, 0.0f, 0.0f,
565 0.0f, 0.0f, 1.0f, 0.0f,
566 0.0f, 0.0f, 0.0f, 0.0f,
568 1.0f, 1.0f, 1.0f, 1.0f,
569 1.0f, 1.0f, 1.0f, 1.0f,
570 1.0f, 1.0f, 1.0f, 1.0f,
571 1.0f, 1.0f, 1.0f, 1.0f,
573 0.0f, 0.0f, 0.0f, 0.0f,
574 0.0f, 1.0f, 1.0f, 0.0f,
575 0.0f, 1.0f, 1.0f, 0.0f,
576 0.0f, 0.0f, 0.0f, 0.0f,
578 float expected_data[] = { // Repeated four times each way.
579 0.125f, 0.125f, 0.125f, 0.125f,
580 0.09375f, 0.09375f, 0.09375f, 0.09375f,
581 1.0f, 1.0f, 1.0f, 1.0f,
582 0.25f, 0.25f, 0.25f, 0.25f,
584 0.125f, 0.125f, 0.125f, 0.125f,
585 0.09375f, 0.09375f, 0.09375f, 0.09375f,
586 1.0f, 1.0f, 1.0f, 1.0f,
587 0.25f, 0.25f, 0.25f, 0.25f,
589 0.125f, 0.125f, 0.125f, 0.125f,
590 0.09375f, 0.09375f, 0.09375f, 0.09375f,
591 1.0f, 1.0f, 1.0f, 1.0f,
592 0.25f, 0.25f, 0.25f, 0.25f,
594 0.125f, 0.125f, 0.125f, 0.125f,
595 0.09375f, 0.09375f, 0.09375f, 0.09375f,
596 1.0f, 1.0f, 1.0f, 1.0f,
597 0.25f, 0.25f, 0.25f, 0.25f,
599 float out_data[4 * 16];
600 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
601 tester.get_chain()->add_effect(new MipmapNeedingEffect());
602 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
604 expect_equal(expected_data, out_data, 4, 16);
607 class NonMipmapCapableInput : public FlatInput {
609 NonMipmapCapableInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
610 : FlatInput(format, pixel_format, type, width, height) {}
612 virtual bool can_supply_mipmaps() const { return false; }
613 bool set_int(const std::string& key, int value) {
614 if (key == "needs_mipmaps") {
617 return FlatInput::set_int(key, value);
621 // The same test as MipmapGenerationWorks, but with an input that refuses
622 // to supply mipmaps.
623 TEST(EffectChainTest, MipmapsWithNonMipmapCapableInput) {
624 float data[] = { // In 4x4 blocks.
625 1.0f, 0.0f, 0.0f, 0.0f,
626 0.0f, 0.0f, 0.0f, 0.0f,
627 0.0f, 0.0f, 0.0f, 0.0f,
628 0.0f, 0.0f, 0.0f, 1.0f,
630 0.0f, 0.0f, 0.0f, 0.0f,
631 0.0f, 0.5f, 0.0f, 0.0f,
632 0.0f, 0.0f, 1.0f, 0.0f,
633 0.0f, 0.0f, 0.0f, 0.0f,
635 1.0f, 1.0f, 1.0f, 1.0f,
636 1.0f, 1.0f, 1.0f, 1.0f,
637 1.0f, 1.0f, 1.0f, 1.0f,
638 1.0f, 1.0f, 1.0f, 1.0f,
640 0.0f, 0.0f, 0.0f, 0.0f,
641 0.0f, 1.0f, 1.0f, 0.0f,
642 0.0f, 1.0f, 1.0f, 0.0f,
643 0.0f, 0.0f, 0.0f, 0.0f,
645 float expected_data[] = { // Repeated four times each way.
646 0.125f, 0.125f, 0.125f, 0.125f,
647 0.09375f, 0.09375f, 0.09375f, 0.09375f,
648 1.0f, 1.0f, 1.0f, 1.0f,
649 0.25f, 0.25f, 0.25f, 0.25f,
651 0.125f, 0.125f, 0.125f, 0.125f,
652 0.09375f, 0.09375f, 0.09375f, 0.09375f,
653 1.0f, 1.0f, 1.0f, 1.0f,
654 0.25f, 0.25f, 0.25f, 0.25f,
656 0.125f, 0.125f, 0.125f, 0.125f,
657 0.09375f, 0.09375f, 0.09375f, 0.09375f,
658 1.0f, 1.0f, 1.0f, 1.0f,
659 0.25f, 0.25f, 0.25f, 0.25f,
661 0.125f, 0.125f, 0.125f, 0.125f,
662 0.09375f, 0.09375f, 0.09375f, 0.09375f,
663 1.0f, 1.0f, 1.0f, 1.0f,
664 0.25f, 0.25f, 0.25f, 0.25f,
666 float out_data[4 * 16];
667 EffectChainTester tester(NULL, 4, 16, FORMAT_GRAYSCALE);
670 format.color_space = COLORSPACE_sRGB;
671 format.gamma_curve = GAMMA_LINEAR;
673 NonMipmapCapableInput *input = new NonMipmapCapableInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 16);
674 input->set_pixel_data(data);
675 tester.get_chain()->add_input(input);
676 tester.get_chain()->add_effect(new MipmapNeedingEffect());
677 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
679 expect_equal(expected_data, out_data, 4, 16);
682 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
683 float data[] = { // In 4x4 blocks.
684 1.0f, 0.0f, 0.0f, 0.0f,
685 0.0f, 0.0f, 0.0f, 0.0f,
686 0.0f, 0.0f, 0.0f, 0.0f,
687 0.0f, 0.0f, 0.0f, 1.0f,
689 0.0f, 0.0f, 0.0f, 0.0f,
690 0.0f, 0.5f, 0.0f, 0.0f,
691 0.0f, 0.0f, 1.0f, 0.0f,
692 0.0f, 0.0f, 0.0f, 0.0f,
694 1.0f, 1.0f, 1.0f, 1.0f,
695 1.0f, 1.0f, 1.0f, 1.0f,
696 1.0f, 1.0f, 1.0f, 1.0f,
697 1.0f, 1.0f, 1.0f, 1.0f,
699 0.0f, 0.0f, 0.0f, 0.0f,
700 0.0f, 1.0f, 1.0f, 0.0f,
701 0.0f, 1.0f, 1.0f, 0.0f,
702 0.0f, 0.0f, 0.0f, 0.0f,
704 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
705 0.1250f, 0.1250f, 0.1250f, 0.1250f,
706 0.1250f, 0.1250f, 0.1250f, 0.1250f,
707 0.1211f, 0.1211f, 0.1211f, 0.1211f,
708 0.1133f, 0.1133f, 0.1133f, 0.1133f,
709 0.1055f, 0.1055f, 0.1055f, 0.1055f,
710 0.0977f, 0.0977f, 0.0977f, 0.0977f,
711 0.2070f, 0.2070f, 0.2070f, 0.2070f,
712 0.4336f, 0.4336f, 0.4336f, 0.4336f,
713 0.6602f, 0.6602f, 0.6602f, 0.6602f,
714 0.8867f, 0.8867f, 0.8867f, 0.8867f,
715 0.9062f, 0.9062f, 0.9062f, 0.9062f,
716 0.7188f, 0.7188f, 0.7188f, 0.7188f,
717 0.5312f, 0.5312f, 0.5312f, 0.5312f,
718 0.3438f, 0.3438f, 0.3438f, 0.3438f,
719 0.2500f, 0.2500f, 0.2500f, 0.2500f,
720 0.2500f, 0.2500f, 0.2500f, 0.2500f,
722 float out_data[4 * 16];
724 ResizeEffect *downscale = new ResizeEffect();
725 ASSERT_TRUE(downscale->set_int("width", 1));
726 ASSERT_TRUE(downscale->set_int("height", 4));
728 ResizeEffect *upscale = new ResizeEffect();
729 ASSERT_TRUE(upscale->set_int("width", 4));
730 ASSERT_TRUE(upscale->set_int("height", 16));
732 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
733 tester.get_chain()->add_effect(downscale);
734 tester.get_chain()->add_effect(upscale);
735 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
737 expect_equal(expected_data, out_data, 4, 16);
740 // An effect that adds its two inputs together. Used below.
741 class AddEffect : public Effect {
744 virtual string effect_type_id() const { return "AddEffect"; }
745 string output_fragment_shader() { return read_file("add.frag"); }
746 virtual unsigned num_inputs() const { return 2; }
747 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
750 // Constructs the graph
754 // MultiplyEffect MultiplyEffect |
758 // and verifies that it gives the correct output.
759 TEST(EffectChainTest, DiamondGraph) {
764 float expected_data[] = {
768 float out_data[2 * 2];
770 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
771 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
773 MultiplyEffect *mul_half = new MultiplyEffect();
774 ASSERT_TRUE(mul_half->set_vec4("factor", half));
776 MultiplyEffect *mul_two = new MultiplyEffect();
777 ASSERT_TRUE(mul_two->set_vec4("factor", two));
779 EffectChainTester tester(NULL, 2, 2);
782 format.color_space = COLORSPACE_sRGB;
783 format.gamma_curve = GAMMA_LINEAR;
785 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
786 input->set_pixel_data(data);
788 tester.get_chain()->add_input(input);
789 tester.get_chain()->add_effect(mul_half, input);
790 tester.get_chain()->add_effect(mul_two, input);
791 tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
792 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
794 expect_equal(expected_data, out_data, 2, 2);
797 // Constructs the graph
801 // MultiplyEffect MultiplyEffect |
803 // \ BouncingIdentityEffect |
807 // and verifies that it gives the correct output.
808 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
813 float expected_data[] = {
817 float out_data[2 * 2];
819 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
820 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
822 MultiplyEffect *mul_half = new MultiplyEffect();
823 ASSERT_TRUE(mul_half->set_vec4("factor", half));
825 MultiplyEffect *mul_two = new MultiplyEffect();
826 ASSERT_TRUE(mul_two->set_vec4("factor", two));
828 BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
830 EffectChainTester tester(NULL, 2, 2);
833 format.color_space = COLORSPACE_sRGB;
834 format.gamma_curve = GAMMA_LINEAR;
836 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
837 input->set_pixel_data(data);
839 tester.get_chain()->add_input(input);
840 tester.get_chain()->add_effect(mul_half, input);
841 tester.get_chain()->add_effect(mul_two, input);
842 tester.get_chain()->add_effect(bounce, mul_two);
843 tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
844 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
846 expect_equal(expected_data, out_data, 2, 2);
849 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
854 float expected_data[] = {
855 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
858 float out_data[2 * 2];
860 EffectChainTester tester(NULL, 2, 2);
861 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
863 // MirrorEffect does not get linear light, so the conversions will be
864 // inserted after it, not before.
865 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
866 tester.get_chain()->add_effect(effect);
868 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
869 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
870 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
871 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
873 expect_equal(expected_data, out_data, 2, 2);
875 Node *node = effect->replaced_node;
876 ASSERT_EQ(1, node->incoming_links.size());
877 ASSERT_EQ(1, node->outgoing_links.size());
878 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
879 EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
882 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
887 float expected_data[] = {
888 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
891 float out_data[2 * 2];
893 EffectChainTester tester(NULL, 2, 2);
894 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
896 // MirrorEffect does not get linear light, so the conversions will be
897 // inserted after it, not before.
898 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
899 tester.get_chain()->add_effect(effect);
901 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
902 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
903 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
904 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
906 expect_equal(expected_data, out_data, 2, 2);
908 Node *node = effect->replaced_node;
909 ASSERT_EQ(1, node->incoming_links.size());
910 ASSERT_EQ(1, node->outgoing_links.size());
911 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
912 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
915 // An effect that does nothing, but requests texture bounce and stores
917 class SizeStoringEffect : public BouncingIdentityEffect {
919 SizeStoringEffect() : input_width(-1), input_height(-1) {}
920 virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) {
921 assert(input_num == 0);
923 input_height = height;
925 virtual string effect_type_id() const { return "SizeStoringEffect"; }
927 int input_width, input_height;
930 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
931 float data[2 * 2] = {
935 float out_data[2 * 2];
937 EffectChainTester tester(NULL, 4, 3); // Note non-square aspect.
940 format.color_space = COLORSPACE_sRGB;
941 format.gamma_curve = GAMMA_LINEAR;
943 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
944 input1->set_pixel_data(data);
946 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
947 input2->set_pixel_data(data);
949 SizeStoringEffect *input_store = new SizeStoringEffect();
951 tester.get_chain()->add_input(input1);
952 tester.get_chain()->add_input(input2);
953 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
954 tester.get_chain()->add_effect(input_store);
955 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
957 EXPECT_EQ(2, input_store->input_width);
958 EXPECT_EQ(2, input_store->input_height);
961 TEST(EffectChainTest, AspectRatioConversion) {
962 float data1[4 * 3] = {
963 0.0f, 0.0f, 0.0f, 0.0f,
964 0.0f, 0.0f, 0.0f, 0.0f,
965 0.0f, 0.0f, 0.0f, 0.0f,
967 float data2[7 * 7] = {
968 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
969 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
970 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
971 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
972 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
973 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
974 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
977 // The right conversion here is that the 7x7 image decides the size,
978 // since it is the biggest, so everything is scaled up to 9x7
979 // (keep the height, round the width 9.333 to 9).
980 float out_data[9 * 7];
982 EffectChainTester tester(NULL, 4, 3);
985 format.color_space = COLORSPACE_sRGB;
986 format.gamma_curve = GAMMA_LINEAR;
988 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
989 input1->set_pixel_data(data1);
991 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
992 input2->set_pixel_data(data2);
994 SizeStoringEffect *input_store = new SizeStoringEffect();
996 tester.get_chain()->add_input(input1);
997 tester.get_chain()->add_input(input2);
998 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
999 tester.get_chain()->add_effect(input_store);
1000 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1002 EXPECT_EQ(9, input_store->input_width);
1003 EXPECT_EQ(7, input_store->input_height);
1006 // An effect that does nothing except changing its output sizes.
1007 class VirtualResizeEffect : public Effect {
1009 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
1012 virtual_width(virtual_width),
1013 virtual_height(virtual_height) {}
1014 virtual string effect_type_id() const { return "VirtualResizeEffect"; }
1015 string output_fragment_shader() { return read_file("identity.frag"); }
1017 virtual bool changes_output_size() const { return true; }
1019 virtual void get_output_size(unsigned *width, unsigned *height,
1020 unsigned *virtual_width, unsigned *virtual_height) const {
1021 *width = this->width;
1022 *height = this->height;
1023 *virtual_width = this->virtual_width;
1024 *virtual_height = this->virtual_height;
1028 int width, height, virtual_width, virtual_height;
1031 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
1032 const int size = 2, bigger_size = 3;
1033 float data[size * size] = {
1037 float out_data[size * size];
1039 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1041 SizeStoringEffect *size_store = new SizeStoringEffect();
1043 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
1044 tester.get_chain()->add_effect(size_store);
1045 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
1046 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1048 EXPECT_EQ(bigger_size, size_store->input_width);
1049 EXPECT_EQ(bigger_size, size_store->input_height);
1051 // If the resize is implemented as non-virtual, we'll fail here,
1052 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
1053 expect_equal(data, out_data, size, size);
1056 // An effect that is like VirtualResizeEffect, but always has virtual and real
1057 // sizes the same (and promises this).
1058 class NonVirtualResizeEffect : public VirtualResizeEffect {
1060 NonVirtualResizeEffect(int width, int height)
1061 : VirtualResizeEffect(width, height, width, height) {}
1062 virtual string effect_type_id() const { return "NonVirtualResizeEffect"; }
1063 virtual bool sets_virtual_output_size() const { return false; }
1066 // An effect that promises one-to-one sampling (unlike IdentityEffect).
1067 class OneToOneEffect : public Effect {
1070 virtual string effect_type_id() const { return "OneToOneEffect"; }
1071 string output_fragment_shader() { return read_file("identity.frag"); }
1072 virtual bool one_to_one_sampling() const { return true; }
1075 TEST(EffectChainTest, NoBounceWithOneToOneSampling) {
1077 float data[size * size] = {
1081 float out_data[size * size];
1083 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1085 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1086 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1088 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1089 tester.get_chain()->add_effect(effect1);
1090 tester.get_chain()->add_effect(effect2);
1091 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1093 expect_equal(data, out_data, size, size);
1095 // The first OneToOneEffect should be in the same phase as its input.
1096 ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
1097 EXPECT_EQ(effect1->replaced_node->incoming_links[0]->containing_phase,
1098 effect1->replaced_node->containing_phase);
1100 // The second OneToOneEffect, too.
1101 EXPECT_EQ(effect1->replaced_node->containing_phase,
1102 effect2->replaced_node->containing_phase);
1105 TEST(EffectChainTest, BounceWhenOneToOneIsBroken) {
1107 float data[size * size] = {
1111 float out_data[size * size];
1113 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1115 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1116 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1117 RewritingEffect<IdentityEffect> *effect3 = new RewritingEffect<IdentityEffect>();
1118 RewritingEffect<OneToOneEffect> *effect4 = new RewritingEffect<OneToOneEffect>();
1120 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1121 tester.get_chain()->add_effect(effect1);
1122 tester.get_chain()->add_effect(effect2);
1123 tester.get_chain()->add_effect(effect3);
1124 tester.get_chain()->add_effect(effect4);
1125 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1127 expect_equal(data, out_data, size, size);
1129 // The NonVirtualResizeEffect should be in a different phase from
1130 // the IdentityEffect (since the latter is not one-to-one),
1131 // ie., the chain should be broken somewhere between them, but exactly
1132 // where doesn't matter.
1133 ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
1134 EXPECT_NE(effect1->replaced_node->incoming_links[0]->containing_phase,
1135 effect3->replaced_node->containing_phase);
1137 // The last OneToOneEffect should also be in the same phase as the
1138 // IdentityEffect (the phase was already broken).
1139 EXPECT_EQ(effect3->replaced_node->containing_phase,
1140 effect4->replaced_node->containing_phase);
1143 // Does not use EffectChainTest, so that it can construct an EffectChain without
1144 // a shared ResourcePool (which is also properly destroyed afterwards).
1145 // Also turns on debugging to test that code path.
1146 TEST(EffectChainTest, IdentityWithOwnPool) {
1147 const int width = 3, height = 2;
1152 const float expected_data[] = {
1156 float out_data[6], temp[6 * 4];
1158 EffectChain chain(width, height);
1159 movit_debug_level = MOVIT_DEBUG_ON;
1162 format.color_space = COLORSPACE_sRGB;
1163 format.gamma_curve = GAMMA_LINEAR;
1165 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
1166 input->set_pixel_data(data);
1167 chain.add_input(input);
1168 chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1171 glGenTextures(1, &texnum);
1173 glBindTexture(GL_TEXTURE_2D, texnum);
1175 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
1178 glGenFramebuffers(1, &fbo);
1180 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1182 glFramebufferTexture2D(
1184 GL_COLOR_ATTACHMENT0,
1189 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1194 chain.render_to_fbo(fbo, width, height);
1196 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1198 glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp);
1200 for (unsigned i = 0; i < 6; ++i) {
1201 out_data[i] = temp[i * 4];
1204 expect_equal(expected_data, out_data, width, height);
1206 // Reset the debug status again.
1207 movit_debug_level = MOVIT_DEBUG_OFF;
1210 // A dummy effect whose only purpose is to test sprintf decimal behavior.
1211 class PrintfingBlueEffect : public Effect {
1213 PrintfingBlueEffect() {}
1214 virtual string effect_type_id() const { return "PrintfingBlueEffect"; }
1215 string output_fragment_shader() {
1217 ss.imbue(locale("C"));
1219 ss << "vec4 FUNCNAME(vec2 tc) { return vec4("
1220 << 0.0f << ", " << 0.0f << ", "
1221 << 0.5f << ", " << 1.0f << "); }\n";
1226 TEST(EffectChainTest, StringStreamLocalesWork) {
1227 // An example of a locale with comma instead of period as decimal separator.
1228 // Obviously, if you run on a machine without this locale available,
1229 // the test will always succeed. Note that the OpenGL driver might call
1230 // setlocale() behind-the-scenes, and that might corrupt the returned
1231 // pointer, so we need to take our own copy of it here.
1232 char *saved_locale = strdup(setlocale(LC_ALL, "nb_NO.UTF_8"));
1234 0.0f, 0.0f, 0.0f, 0.0f,
1236 float expected_data[] = {
1237 0.0f, 0.0f, 0.5f, 1.0f,
1240 EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1241 tester.get_chain()->add_effect(new PrintfingBlueEffect());
1242 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1244 expect_equal(expected_data, out_data, 4, 1);
1246 setlocale(LC_ALL, saved_locale);
1251 } // namespace movit