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 TEST(EffectChainTest, TopLeftOrigin) {
106 // Note that EffectChainTester assumes bottom-left origin, so by setting
107 // top-left, we will get flipped data back.
108 float expected_data[6] = {
113 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
114 tester.get_chain()->set_output_origin(OUTPUT_ORIGIN_TOP_LEFT);
115 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
117 expect_equal(expected_data, out_data, 3, 2);
120 // A dummy effect that inverts its input.
121 class InvertEffect : public Effect {
124 virtual string effect_type_id() const { return "InvertEffect"; }
125 string output_fragment_shader() { return read_file("invert_effect.frag"); }
127 // A real invert would actually care about its alpha,
128 // but in this unit test, it only complicates things.
129 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
132 // Like IdentityEffect, but rewrites itself out of the loop,
133 // splicing in a different effect instead. Also stores the new node,
134 // so we later can check whatever properties we'd like about the graph.
136 class RewritingEffect : public Effect {
138 RewritingEffect() : effect(new T()), replaced_node(NULL) {}
139 virtual string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
140 string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
141 virtual void rewrite_graph(EffectChain *graph, Node *self) {
142 replaced_node = graph->add_node(effect);
143 graph->replace_receiver(self, replaced_node);
144 graph->replace_sender(self, replaced_node);
145 self->disabled = true;
152 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
157 float expected_data[6] = {
158 1.0f, 0.9771f, 0.9673f,
162 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
163 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
164 tester.get_chain()->add_effect(effect);
165 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
167 Node *node = effect->replaced_node;
168 ASSERT_EQ(1, node->incoming_links.size());
169 ASSERT_EQ(1, node->outgoing_links.size());
170 EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
171 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
173 expect_equal(expected_data, out_data, 3, 2);
176 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
177 unsigned char data[] = {
183 float expected_data[] = {
184 1.0000f, 1.0000f, 1.0000f, 1.0000f,
185 0.9771f, 0.9771f, 0.9771f, 1.0000f,
186 0.8983f, 0.8983f, 0.8983f, 1.0000f,
187 0.0000f, 0.0000f, 0.0000f, 1.0000f
189 float out_data[4 * 4];
190 EffectChainTester tester(NULL, 1, 4);
191 tester.add_input(data, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_sRGB);
192 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
193 tester.get_chain()->add_effect(effect);
194 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
196 Node *node = effect->replaced_node;
197 ASSERT_EQ(1, node->incoming_links.size());
198 ASSERT_EQ(1, node->outgoing_links.size());
199 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
200 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
202 expect_equal(expected_data, out_data, 4, 4);
205 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
210 float expected_data[6] = {
215 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
216 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
217 tester.get_chain()->add_effect(effect);
218 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
220 Node *node = effect->replaced_node;
221 ASSERT_EQ(1, node->incoming_links.size());
222 ASSERT_EQ(1, node->outgoing_links.size());
223 EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
224 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
226 expect_equal(expected_data, out_data, 3, 2);
229 // A fake input that can change its output colorspace and gamma between instantiation
231 class UnknownColorspaceInput : public FlatInput {
233 UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
234 : FlatInput(format, pixel_format, type, width, height),
235 overridden_color_space(format.color_space),
236 overridden_gamma_curve(format.gamma_curve) {}
237 virtual string effect_type_id() const { return "UnknownColorspaceInput"; }
239 void set_color_space(Colorspace colorspace) {
240 overridden_color_space = colorspace;
242 void set_gamma_curve(GammaCurve gamma_curve) {
243 overridden_gamma_curve = gamma_curve;
245 Colorspace get_color_space() const { return overridden_color_space; }
246 GammaCurve get_gamma_curve() const { return overridden_gamma_curve; }
249 Colorspace overridden_color_space;
250 GammaCurve overridden_gamma_curve;
253 TEST(EffectChainTest, HandlesInputChangingColorspace) {
262 float out_data[size];
264 EffectChainTester tester(NULL, 4, 1, FORMAT_GRAYSCALE);
266 // First say that we have sRGB, linear input.
268 format.color_space = COLORSPACE_sRGB;
269 format.gamma_curve = GAMMA_LINEAR;
271 UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
272 input->set_pixel_data(data);
273 tester.get_chain()->add_input(input);
275 // Now we change to Rec. 601 input.
276 input->set_color_space(COLORSPACE_REC_601_625);
277 input->set_gamma_curve(GAMMA_REC_601);
279 // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
280 tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
281 expect_equal(data, out_data, 4, 1);
284 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
289 float expected_data[6] = {
294 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
295 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
296 tester.get_chain()->add_effect(effect);
297 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
299 Node *node = effect->replaced_node;
300 ASSERT_EQ(1, node->incoming_links.size());
301 EXPECT_EQ(0, node->outgoing_links.size());
302 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
304 expect_equal(expected_data, out_data, 3, 2);
307 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
312 float expected_data[6] = {
317 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
318 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
319 tester.get_chain()->add_effect(effect);
320 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
322 Node *node = effect->replaced_node;
323 ASSERT_EQ(1, node->incoming_links.size());
324 EXPECT_EQ(0, node->outgoing_links.size());
325 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
327 expect_equal(expected_data, out_data, 3, 2);
330 // The identity effect needs linear light, and thus will get conversions on both sides.
331 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
332 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
334 for (unsigned i = 0; i < 256; ++i) {
338 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
339 tester.get_chain()->add_effect(new IdentityEffect());
340 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
342 expect_equal(data, out_data, 256, 1);
345 // Same, but uses the forward sRGB table from the GPU.
346 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
347 unsigned char data[256];
348 float expected_data[256];
349 for (unsigned i = 0; i < 256; ++i) {
351 expected_data[i] = i / 255.0;
354 EffectChainTester tester(NULL, 256, 1);
355 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
356 tester.get_chain()->add_effect(new IdentityEffect());
357 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
359 expect_equal(expected_data, out_data, 256, 1);
362 // Same, for the Rec. 601/709 gamma curve.
363 TEST(EffectChainTest, IdentityThroughRec709) {
365 for (unsigned i = 0; i < 256; ++i) {
369 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
370 tester.get_chain()->add_effect(new IdentityEffect());
371 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
373 expect_equal(data, out_data, 256, 1);
376 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
377 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
379 float data[4 * size] = {
380 0.8f, 0.0f, 0.0f, 0.5f,
381 0.0f, 0.2f, 0.2f, 0.3f,
382 0.1f, 0.0f, 1.0f, 1.0f,
384 float out_data[4 * size];
385 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
386 tester.get_chain()->add_effect(new IdentityEffect());
387 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
389 expect_equal(data, out_data, 4, size);
392 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
394 float data[4 * size] = {
395 0.8f, 0.0f, 0.0f, 0.5f,
396 0.0f, 0.2f, 0.2f, 0.3f,
397 0.1f, 0.0f, 1.0f, 1.0f,
399 float expected_data[4 * size] = {
400 0.1f, 0.0f, 1.0f, 1.0f,
401 0.0f, 0.2f, 0.2f, 0.3f,
402 0.8f, 0.0f, 0.0f, 0.5f,
404 float out_data[4 * size];
405 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
406 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
407 tester.get_chain()->add_effect(effect);
408 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
410 Node *node = effect->replaced_node;
411 ASSERT_EQ(1, node->incoming_links.size());
412 EXPECT_EQ(0, node->outgoing_links.size());
413 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
415 expect_equal(expected_data, out_data, 4, size);
418 // An input that outputs only blue, which has blank alpha.
419 class BlueInput : public Input {
421 BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
422 virtual string effect_type_id() const { return "IdentityEffect"; }
423 string output_fragment_shader() { return read_file("blue.frag"); }
424 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
425 virtual void finalize() {}
426 virtual bool can_output_linear_gamma() const { return true; }
427 virtual unsigned get_width() const { return 1; }
428 virtual unsigned get_height() const { return 1; }
429 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
430 virtual GammaCurve get_gamma_curve() const { 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(NULL) { register_int("needs_mipmaps", &needs_mipmaps); }
441 virtual string effect_type_id() const { return "RewritingToBlueInput"; }
442 string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
443 virtual void rewrite_graph(EffectChain *graph, Node *self) {
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 virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; }
455 virtual void finalize() {}
456 virtual bool can_output_linear_gamma() const { return true; }
457 virtual unsigned get_width() const { return 1; }
458 virtual unsigned get_height() const { return 1; }
459 virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; }
460 virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; }
468 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
470 float data[4 * size] = {
471 0.0f, 0.0f, 1.0f, 1.0f,
472 0.0f, 0.0f, 1.0f, 1.0f,
473 0.0f, 0.0f, 1.0f, 1.0f,
475 float out_data[4 * size];
476 EffectChainTester tester(NULL, size, 1);
477 RewritingToBlueInput *input = new RewritingToBlueInput();
478 tester.get_chain()->add_input(input);
479 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
481 Node *node = input->blue_node;
482 EXPECT_EQ(0, node->incoming_links.size());
483 EXPECT_EQ(0, node->outgoing_links.size());
485 expect_equal(data, out_data, 4, size);
488 // An effect that does nothing, and specifies that it preserves blank alpha.
489 class BlankAlphaPreservingEffect : public Effect {
491 BlankAlphaPreservingEffect() {}
492 virtual string effect_type_id() const { return "BlankAlphaPreservingEffect"; }
493 string output_fragment_shader() { return read_file("identity.frag"); }
494 virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
497 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
499 float data[4 * size] = {
500 0.0f, 0.0f, 1.0f, 1.0f,
501 0.0f, 0.0f, 1.0f, 1.0f,
502 0.0f, 0.0f, 1.0f, 1.0f,
504 float out_data[4 * size];
505 EffectChainTester tester(NULL, size, 1);
506 tester.get_chain()->add_input(new BlueInput());
507 tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
508 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
509 tester.get_chain()->add_effect(effect);
510 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
512 Node *node = effect->replaced_node;
513 EXPECT_EQ(1, node->incoming_links.size());
514 EXPECT_EQ(0, node->outgoing_links.size());
516 expect_equal(data, out_data, 4, size);
519 // This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
520 // just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
521 // an alpha conversion _should_ be inserted at the very end. (There is some overlap
522 // with other tests.)
523 TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
525 float data[4 * size] = {
526 0.0f, 0.0f, 1.0f, 1.0f,
527 0.0f, 0.0f, 1.0f, 1.0f,
528 0.0f, 0.0f, 1.0f, 1.0f,
530 float out_data[4 * size];
531 EffectChainTester tester(NULL, size, 1);
532 tester.get_chain()->add_input(new BlueInput());
533 tester.get_chain()->add_effect(new IdentityEffect()); // Not BlankAlphaPreservingEffect.
534 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
535 tester.get_chain()->add_effect(effect);
536 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
538 Node *node = effect->replaced_node;
539 EXPECT_EQ(1, node->incoming_links.size());
540 EXPECT_EQ(1, node->outgoing_links.size());
541 EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
543 expect_equal(data, out_data, 4, size);
546 // Effectively scales down its input linearly by 4x (and repeating it),
547 // which is not attainable without mipmaps.
548 class MipmapNeedingEffect : public Effect {
550 MipmapNeedingEffect() {}
551 virtual bool needs_mipmaps() const { return true; }
553 // To be allowed to mess with the sampler state.
554 virtual bool needs_texture_bounce() const { return true; }
556 virtual string effect_type_id() const { return "MipmapNeedingEffect"; }
557 string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
558 virtual void inform_added(EffectChain *chain) { this->chain = chain; }
560 void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
562 Node *self = chain->find_node_for_effect(this);
563 glActiveTexture(chain->get_input_sampler(self, 0));
565 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
567 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
575 TEST(EffectChainTest, MipmapGenerationWorks) {
576 float data[] = { // In 4x4 blocks.
577 1.0f, 0.0f, 0.0f, 0.0f,
578 0.0f, 0.0f, 0.0f, 0.0f,
579 0.0f, 0.0f, 0.0f, 0.0f,
580 0.0f, 0.0f, 0.0f, 1.0f,
582 0.0f, 0.0f, 0.0f, 0.0f,
583 0.0f, 0.5f, 0.0f, 0.0f,
584 0.0f, 0.0f, 1.0f, 0.0f,
585 0.0f, 0.0f, 0.0f, 0.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,
590 1.0f, 1.0f, 1.0f, 1.0f,
592 0.0f, 0.0f, 0.0f, 0.0f,
593 0.0f, 1.0f, 1.0f, 0.0f,
594 0.0f, 1.0f, 1.0f, 0.0f,
595 0.0f, 0.0f, 0.0f, 0.0f,
597 float expected_data[] = { // Repeated four times each way.
598 0.125f, 0.125f, 0.125f, 0.125f,
599 0.09375f, 0.09375f, 0.09375f, 0.09375f,
600 1.0f, 1.0f, 1.0f, 1.0f,
601 0.25f, 0.25f, 0.25f, 0.25f,
603 0.125f, 0.125f, 0.125f, 0.125f,
604 0.09375f, 0.09375f, 0.09375f, 0.09375f,
605 1.0f, 1.0f, 1.0f, 1.0f,
606 0.25f, 0.25f, 0.25f, 0.25f,
608 0.125f, 0.125f, 0.125f, 0.125f,
609 0.09375f, 0.09375f, 0.09375f, 0.09375f,
610 1.0f, 1.0f, 1.0f, 1.0f,
611 0.25f, 0.25f, 0.25f, 0.25f,
613 0.125f, 0.125f, 0.125f, 0.125f,
614 0.09375f, 0.09375f, 0.09375f, 0.09375f,
615 1.0f, 1.0f, 1.0f, 1.0f,
616 0.25f, 0.25f, 0.25f, 0.25f,
618 float out_data[4 * 16];
619 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
620 tester.get_chain()->add_effect(new MipmapNeedingEffect());
621 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
623 expect_equal(expected_data, out_data, 4, 16);
626 class NonMipmapCapableInput : public FlatInput {
628 NonMipmapCapableInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
629 : FlatInput(format, pixel_format, type, width, height) {}
631 virtual bool can_supply_mipmaps() const { return false; }
632 bool set_int(const std::string& key, int value) {
633 if (key == "needs_mipmaps") {
636 return FlatInput::set_int(key, value);
640 // The same test as MipmapGenerationWorks, but with an input that refuses
641 // to supply mipmaps.
642 TEST(EffectChainTest, MipmapsWithNonMipmapCapableInput) {
643 float data[] = { // In 4x4 blocks.
644 1.0f, 0.0f, 0.0f, 0.0f,
645 0.0f, 0.0f, 0.0f, 0.0f,
646 0.0f, 0.0f, 0.0f, 0.0f,
647 0.0f, 0.0f, 0.0f, 1.0f,
649 0.0f, 0.0f, 0.0f, 0.0f,
650 0.0f, 0.5f, 0.0f, 0.0f,
651 0.0f, 0.0f, 1.0f, 0.0f,
652 0.0f, 0.0f, 0.0f, 0.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,
657 1.0f, 1.0f, 1.0f, 1.0f,
659 0.0f, 0.0f, 0.0f, 0.0f,
660 0.0f, 1.0f, 1.0f, 0.0f,
661 0.0f, 1.0f, 1.0f, 0.0f,
662 0.0f, 0.0f, 0.0f, 0.0f,
664 float expected_data[] = { // Repeated four times each way.
665 0.125f, 0.125f, 0.125f, 0.125f,
666 0.09375f, 0.09375f, 0.09375f, 0.09375f,
667 1.0f, 1.0f, 1.0f, 1.0f,
668 0.25f, 0.25f, 0.25f, 0.25f,
670 0.125f, 0.125f, 0.125f, 0.125f,
671 0.09375f, 0.09375f, 0.09375f, 0.09375f,
672 1.0f, 1.0f, 1.0f, 1.0f,
673 0.25f, 0.25f, 0.25f, 0.25f,
675 0.125f, 0.125f, 0.125f, 0.125f,
676 0.09375f, 0.09375f, 0.09375f, 0.09375f,
677 1.0f, 1.0f, 1.0f, 1.0f,
678 0.25f, 0.25f, 0.25f, 0.25f,
680 0.125f, 0.125f, 0.125f, 0.125f,
681 0.09375f, 0.09375f, 0.09375f, 0.09375f,
682 1.0f, 1.0f, 1.0f, 1.0f,
683 0.25f, 0.25f, 0.25f, 0.25f,
685 float out_data[4 * 16];
686 EffectChainTester tester(NULL, 4, 16, FORMAT_GRAYSCALE);
689 format.color_space = COLORSPACE_sRGB;
690 format.gamma_curve = GAMMA_LINEAR;
692 NonMipmapCapableInput *input = new NonMipmapCapableInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 16);
693 input->set_pixel_data(data);
694 tester.get_chain()->add_input(input);
695 tester.get_chain()->add_effect(new MipmapNeedingEffect());
696 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
698 expect_equal(expected_data, out_data, 4, 16);
701 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
702 float data[] = { // In 4x4 blocks.
703 1.0f, 0.0f, 0.0f, 0.0f,
704 0.0f, 0.0f, 0.0f, 0.0f,
705 0.0f, 0.0f, 0.0f, 0.0f,
706 0.0f, 0.0f, 0.0f, 1.0f,
708 0.0f, 0.0f, 0.0f, 0.0f,
709 0.0f, 0.5f, 0.0f, 0.0f,
710 0.0f, 0.0f, 1.0f, 0.0f,
711 0.0f, 0.0f, 0.0f, 0.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,
716 1.0f, 1.0f, 1.0f, 1.0f,
718 0.0f, 0.0f, 0.0f, 0.0f,
719 0.0f, 1.0f, 1.0f, 0.0f,
720 0.0f, 1.0f, 1.0f, 0.0f,
721 0.0f, 0.0f, 0.0f, 0.0f,
723 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
724 0.1250f, 0.1250f, 0.1250f, 0.1250f,
725 0.1250f, 0.1250f, 0.1250f, 0.1250f,
726 0.1211f, 0.1211f, 0.1211f, 0.1211f,
727 0.1133f, 0.1133f, 0.1133f, 0.1133f,
728 0.1055f, 0.1055f, 0.1055f, 0.1055f,
729 0.0977f, 0.0977f, 0.0977f, 0.0977f,
730 0.2070f, 0.2070f, 0.2070f, 0.2070f,
731 0.4336f, 0.4336f, 0.4336f, 0.4336f,
732 0.6602f, 0.6602f, 0.6602f, 0.6602f,
733 0.8867f, 0.8867f, 0.8867f, 0.8867f,
734 0.9062f, 0.9062f, 0.9062f, 0.9062f,
735 0.7188f, 0.7188f, 0.7188f, 0.7188f,
736 0.5312f, 0.5312f, 0.5312f, 0.5312f,
737 0.3438f, 0.3438f, 0.3438f, 0.3438f,
738 0.2500f, 0.2500f, 0.2500f, 0.2500f,
739 0.2500f, 0.2500f, 0.2500f, 0.2500f,
741 float out_data[4 * 16];
743 ResizeEffect *downscale = new ResizeEffect();
744 ASSERT_TRUE(downscale->set_int("width", 1));
745 ASSERT_TRUE(downscale->set_int("height", 4));
747 ResizeEffect *upscale = new ResizeEffect();
748 ASSERT_TRUE(upscale->set_int("width", 4));
749 ASSERT_TRUE(upscale->set_int("height", 16));
751 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
752 tester.get_chain()->add_effect(downscale);
753 tester.get_chain()->add_effect(upscale);
754 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
756 expect_equal(expected_data, out_data, 4, 16);
759 // An effect that adds its two inputs together. Used below.
760 class AddEffect : public Effect {
763 virtual string effect_type_id() const { return "AddEffect"; }
764 string output_fragment_shader() { return read_file("add.frag"); }
765 virtual unsigned num_inputs() const { return 2; }
766 virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
769 // Constructs the graph
773 // MultiplyEffect MultiplyEffect |
777 // and verifies that it gives the correct output.
778 TEST(EffectChainTest, DiamondGraph) {
783 float expected_data[] = {
787 float out_data[2 * 2];
789 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
790 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
792 MultiplyEffect *mul_half = new MultiplyEffect();
793 ASSERT_TRUE(mul_half->set_vec4("factor", half));
795 MultiplyEffect *mul_two = new MultiplyEffect();
796 ASSERT_TRUE(mul_two->set_vec4("factor", two));
798 EffectChainTester tester(NULL, 2, 2);
801 format.color_space = COLORSPACE_sRGB;
802 format.gamma_curve = GAMMA_LINEAR;
804 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
805 input->set_pixel_data(data);
807 tester.get_chain()->add_input(input);
808 tester.get_chain()->add_effect(mul_half, input);
809 tester.get_chain()->add_effect(mul_two, input);
810 tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
811 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
813 expect_equal(expected_data, out_data, 2, 2);
816 // Constructs the graph
820 // MultiplyEffect MultiplyEffect |
822 // \ BouncingIdentityEffect |
826 // and verifies that it gives the correct output.
827 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
832 float expected_data[] = {
836 float out_data[2 * 2];
838 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
839 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
841 MultiplyEffect *mul_half = new MultiplyEffect();
842 ASSERT_TRUE(mul_half->set_vec4("factor", half));
844 MultiplyEffect *mul_two = new MultiplyEffect();
845 ASSERT_TRUE(mul_two->set_vec4("factor", two));
847 BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
849 EffectChainTester tester(NULL, 2, 2);
852 format.color_space = COLORSPACE_sRGB;
853 format.gamma_curve = GAMMA_LINEAR;
855 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
856 input->set_pixel_data(data);
858 tester.get_chain()->add_input(input);
859 tester.get_chain()->add_effect(mul_half, input);
860 tester.get_chain()->add_effect(mul_two, input);
861 tester.get_chain()->add_effect(bounce, mul_two);
862 tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
863 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
865 expect_equal(expected_data, out_data, 2, 2);
868 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
873 float expected_data[] = {
874 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
877 float out_data[2 * 2];
879 EffectChainTester tester(NULL, 2, 2);
880 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
882 // MirrorEffect does not get linear light, so the conversions will be
883 // inserted after it, not before.
884 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
885 tester.get_chain()->add_effect(effect);
887 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
888 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
889 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
890 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
892 expect_equal(expected_data, out_data, 2, 2);
894 Node *node = effect->replaced_node;
895 ASSERT_EQ(1, node->incoming_links.size());
896 ASSERT_EQ(1, node->outgoing_links.size());
897 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
898 EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
901 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
906 float expected_data[] = {
907 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
910 float out_data[2 * 2];
912 EffectChainTester tester(NULL, 2, 2);
913 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
915 // MirrorEffect does not get linear light, so the conversions will be
916 // inserted after it, not before.
917 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
918 tester.get_chain()->add_effect(effect);
920 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
921 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
922 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
923 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
925 expect_equal(expected_data, out_data, 2, 2);
927 Node *node = effect->replaced_node;
928 ASSERT_EQ(1, node->incoming_links.size());
929 ASSERT_EQ(1, node->outgoing_links.size());
930 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
931 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
934 // An effect that does nothing, but requests texture bounce and stores
936 class SizeStoringEffect : public BouncingIdentityEffect {
938 SizeStoringEffect() : input_width(-1), input_height(-1) {}
939 virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) {
940 assert(input_num == 0);
942 input_height = height;
944 virtual string effect_type_id() const { return "SizeStoringEffect"; }
946 int input_width, input_height;
949 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
950 float data[2 * 2] = {
954 float out_data[2 * 2];
956 EffectChainTester tester(NULL, 4, 3); // Note non-square aspect.
959 format.color_space = COLORSPACE_sRGB;
960 format.gamma_curve = GAMMA_LINEAR;
962 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
963 input1->set_pixel_data(data);
965 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
966 input2->set_pixel_data(data);
968 SizeStoringEffect *input_store = new SizeStoringEffect();
970 tester.get_chain()->add_input(input1);
971 tester.get_chain()->add_input(input2);
972 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
973 tester.get_chain()->add_effect(input_store);
974 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
976 EXPECT_EQ(2, input_store->input_width);
977 EXPECT_EQ(2, input_store->input_height);
980 TEST(EffectChainTest, AspectRatioConversion) {
981 float data1[4 * 3] = {
982 0.0f, 0.0f, 0.0f, 0.0f,
983 0.0f, 0.0f, 0.0f, 0.0f,
984 0.0f, 0.0f, 0.0f, 0.0f,
986 float data2[7 * 7] = {
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, 0.0f, 0.0f, 0.0f, 0.0f,
990 0.0f, 0.0f, 0.0f, 1.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,
993 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
996 // The right conversion here is that the 7x7 image decides the size,
997 // since it is the biggest, so everything is scaled up to 9x7
998 // (keep the height, round the width 9.333 to 9).
999 float out_data[9 * 7];
1001 EffectChainTester tester(NULL, 4, 3);
1004 format.color_space = COLORSPACE_sRGB;
1005 format.gamma_curve = GAMMA_LINEAR;
1007 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
1008 input1->set_pixel_data(data1);
1010 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
1011 input2->set_pixel_data(data2);
1013 SizeStoringEffect *input_store = new SizeStoringEffect();
1015 tester.get_chain()->add_input(input1);
1016 tester.get_chain()->add_input(input2);
1017 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
1018 tester.get_chain()->add_effect(input_store);
1019 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1021 EXPECT_EQ(9, input_store->input_width);
1022 EXPECT_EQ(7, input_store->input_height);
1025 // An effect that does nothing except changing its output sizes.
1026 class VirtualResizeEffect : public Effect {
1028 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
1031 virtual_width(virtual_width),
1032 virtual_height(virtual_height) {}
1033 virtual string effect_type_id() const { return "VirtualResizeEffect"; }
1034 string output_fragment_shader() { return read_file("identity.frag"); }
1036 virtual bool changes_output_size() const { return true; }
1038 virtual void get_output_size(unsigned *width, unsigned *height,
1039 unsigned *virtual_width, unsigned *virtual_height) const {
1040 *width = this->width;
1041 *height = this->height;
1042 *virtual_width = this->virtual_width;
1043 *virtual_height = this->virtual_height;
1047 int width, height, virtual_width, virtual_height;
1050 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
1051 const int size = 2, bigger_size = 3;
1052 float data[size * size] = {
1056 float out_data[size * size];
1058 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1060 SizeStoringEffect *size_store = new SizeStoringEffect();
1062 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
1063 tester.get_chain()->add_effect(size_store);
1064 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
1065 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1067 EXPECT_EQ(bigger_size, size_store->input_width);
1068 EXPECT_EQ(bigger_size, size_store->input_height);
1070 // If the resize is implemented as non-virtual, we'll fail here,
1071 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
1072 expect_equal(data, out_data, size, size);
1075 // An effect that is like VirtualResizeEffect, but always has virtual and real
1076 // sizes the same (and promises this).
1077 class NonVirtualResizeEffect : public VirtualResizeEffect {
1079 NonVirtualResizeEffect(int width, int height)
1080 : VirtualResizeEffect(width, height, width, height) {}
1081 virtual string effect_type_id() const { return "NonVirtualResizeEffect"; }
1082 virtual bool sets_virtual_output_size() const { return false; }
1085 // An effect that promises one-to-one sampling (unlike IdentityEffect).
1086 class OneToOneEffect : public Effect {
1089 virtual string effect_type_id() const { return "OneToOneEffect"; }
1090 string output_fragment_shader() { return read_file("identity.frag"); }
1091 virtual bool one_to_one_sampling() const { return true; }
1094 TEST(EffectChainTest, NoBounceWithOneToOneSampling) {
1096 float data[size * size] = {
1100 float out_data[size * size];
1102 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1104 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1105 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1107 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1108 tester.get_chain()->add_effect(effect1);
1109 tester.get_chain()->add_effect(effect2);
1110 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1112 expect_equal(data, out_data, size, size);
1114 // The first OneToOneEffect should be in the same phase as its input.
1115 ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
1116 EXPECT_EQ(effect1->replaced_node->incoming_links[0]->containing_phase,
1117 effect1->replaced_node->containing_phase);
1119 // The second OneToOneEffect, too.
1120 EXPECT_EQ(effect1->replaced_node->containing_phase,
1121 effect2->replaced_node->containing_phase);
1124 TEST(EffectChainTest, BounceWhenOneToOneIsBroken) {
1126 float data[size * size] = {
1130 float out_data[size * size];
1132 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1134 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1135 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1136 RewritingEffect<IdentityEffect> *effect3 = new RewritingEffect<IdentityEffect>();
1137 RewritingEffect<OneToOneEffect> *effect4 = new RewritingEffect<OneToOneEffect>();
1139 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1140 tester.get_chain()->add_effect(effect1);
1141 tester.get_chain()->add_effect(effect2);
1142 tester.get_chain()->add_effect(effect3);
1143 tester.get_chain()->add_effect(effect4);
1144 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1146 expect_equal(data, out_data, size, size);
1148 // The NonVirtualResizeEffect should be in a different phase from
1149 // the IdentityEffect (since the latter is not one-to-one),
1150 // ie., the chain should be broken somewhere between them, but exactly
1151 // where doesn't matter.
1152 ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
1153 EXPECT_NE(effect1->replaced_node->incoming_links[0]->containing_phase,
1154 effect3->replaced_node->containing_phase);
1156 // The last OneToOneEffect should also be in the same phase as the
1157 // IdentityEffect (the phase was already broken).
1158 EXPECT_EQ(effect3->replaced_node->containing_phase,
1159 effect4->replaced_node->containing_phase);
1162 // Does not use EffectChainTest, so that it can construct an EffectChain without
1163 // a shared ResourcePool (which is also properly destroyed afterwards).
1164 // Also turns on debugging to test that code path.
1165 TEST(EffectChainTest, IdentityWithOwnPool) {
1166 const int width = 3, height = 2;
1171 const float expected_data[] = {
1175 float out_data[6], temp[6 * 4];
1177 EffectChain chain(width, height);
1178 movit_debug_level = MOVIT_DEBUG_ON;
1181 format.color_space = COLORSPACE_sRGB;
1182 format.gamma_curve = GAMMA_LINEAR;
1184 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
1185 input->set_pixel_data(data);
1186 chain.add_input(input);
1187 chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1190 glGenTextures(1, &texnum);
1192 glBindTexture(GL_TEXTURE_2D, texnum);
1194 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
1197 glGenFramebuffers(1, &fbo);
1199 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1201 glFramebufferTexture2D(
1203 GL_COLOR_ATTACHMENT0,
1208 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1213 chain.render_to_fbo(fbo, width, height);
1215 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1217 glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp);
1219 for (unsigned i = 0; i < 6; ++i) {
1220 out_data[i] = temp[i * 4];
1223 expect_equal(expected_data, out_data, width, height);
1225 // Reset the debug status again.
1226 movit_debug_level = MOVIT_DEBUG_OFF;
1229 // A dummy effect whose only purpose is to test sprintf decimal behavior.
1230 class PrintfingBlueEffect : public Effect {
1232 PrintfingBlueEffect() {}
1233 virtual string effect_type_id() const { return "PrintfingBlueEffect"; }
1234 string output_fragment_shader() {
1236 ss.imbue(locale("C"));
1238 ss << "vec4 FUNCNAME(vec2 tc) { return vec4("
1239 << 0.0f << ", " << 0.0f << ", "
1240 << 0.5f << ", " << 1.0f << "); }\n";
1245 TEST(EffectChainTest, StringStreamLocalesWork) {
1246 // An example of a locale with comma instead of period as decimal separator.
1247 // Obviously, if you run on a machine without this locale available,
1248 // the test will always succeed. Note that the OpenGL driver might call
1249 // setlocale() behind-the-scenes, and that might corrupt the returned
1250 // pointer, so we need to take our own copy of it here.
1251 char *saved_locale = strdup(setlocale(LC_ALL, "nb_NO.UTF_8"));
1253 0.0f, 0.0f, 0.0f, 0.0f,
1255 float expected_data[] = {
1256 0.0f, 0.0f, 0.5f, 1.0f,
1259 EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1260 tester.get_chain()->add_effect(new PrintfingBlueEffect());
1261 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1263 expect_equal(expected_data, out_data, 4, 1);
1265 setlocale(LC_ALL, saved_locale);
1270 } // namespace movit