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[4 * 3];
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 // Tests that putting a BlueInput (constant color) into its own pass,
1026 // which creates a phase that doesn't need texture coordinates,
1027 // doesn't mess up a second phase that actually does.
1028 TEST(EffectChainTest, FirstPhaseWithNoTextureCoordinates) {
1034 float expected_data[] = {
1035 1.0f, 1.0f, 2.0f, 2.0f,
1036 0.0f, 0.0f, 1.0f, 2.0f,
1038 float out_data[size * 4];
1039 // First say that we have sRGB, linear input.
1041 format.color_space = COLORSPACE_sRGB;
1042 format.gamma_curve = GAMMA_LINEAR;
1043 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 1, size);
1045 input->set_pixel_data(data);
1046 EffectChainTester tester(NULL, 1, size);
1047 tester.get_chain()->add_input(new BlueInput());
1048 Effect *phase1_end = tester.get_chain()->add_effect(new BouncingIdentityEffect());
1049 tester.get_chain()->add_input(input);
1050 tester.get_chain()->add_effect(new AddEffect(), phase1_end, input);
1052 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1054 expect_equal(expected_data, out_data, 4, size);
1057 // An effect that does nothing except changing its output sizes.
1058 class VirtualResizeEffect : public Effect {
1060 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
1063 virtual_width(virtual_width),
1064 virtual_height(virtual_height) {}
1065 virtual string effect_type_id() const { return "VirtualResizeEffect"; }
1066 string output_fragment_shader() { return read_file("identity.frag"); }
1068 virtual bool changes_output_size() const { return true; }
1070 virtual void get_output_size(unsigned *width, unsigned *height,
1071 unsigned *virtual_width, unsigned *virtual_height) const {
1072 *width = this->width;
1073 *height = this->height;
1074 *virtual_width = this->virtual_width;
1075 *virtual_height = this->virtual_height;
1079 int width, height, virtual_width, virtual_height;
1082 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
1083 const int size = 2, bigger_size = 3;
1084 float data[size * size] = {
1088 float out_data[size * size];
1090 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1092 SizeStoringEffect *size_store = new SizeStoringEffect();
1094 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
1095 tester.get_chain()->add_effect(size_store);
1096 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
1097 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1099 EXPECT_EQ(bigger_size, size_store->input_width);
1100 EXPECT_EQ(bigger_size, size_store->input_height);
1102 // If the resize is implemented as non-virtual, we'll fail here,
1103 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
1104 expect_equal(data, out_data, size, size);
1107 // An effect that is like VirtualResizeEffect, but always has virtual and real
1108 // sizes the same (and promises this).
1109 class NonVirtualResizeEffect : public VirtualResizeEffect {
1111 NonVirtualResizeEffect(int width, int height)
1112 : VirtualResizeEffect(width, height, width, height) {}
1113 virtual string effect_type_id() const { return "NonVirtualResizeEffect"; }
1114 virtual bool sets_virtual_output_size() const { return false; }
1117 // An effect that promises one-to-one sampling (unlike IdentityEffect).
1118 class OneToOneEffect : public Effect {
1121 virtual string effect_type_id() const { return "OneToOneEffect"; }
1122 string output_fragment_shader() { return read_file("identity.frag"); }
1123 virtual bool one_to_one_sampling() const { return true; }
1126 TEST(EffectChainTest, NoBounceWithOneToOneSampling) {
1128 float data[size * size] = {
1132 float out_data[size * size];
1134 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1136 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1137 RewritingEffect<OneToOneEffect> *effect2 = 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.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1144 expect_equal(data, out_data, size, size);
1146 // The first OneToOneEffect should be in the same phase as its input.
1147 ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
1148 EXPECT_EQ(effect1->replaced_node->incoming_links[0]->containing_phase,
1149 effect1->replaced_node->containing_phase);
1151 // The second OneToOneEffect, too.
1152 EXPECT_EQ(effect1->replaced_node->containing_phase,
1153 effect2->replaced_node->containing_phase);
1156 TEST(EffectChainTest, BounceWhenOneToOneIsBroken) {
1158 float data[size * size] = {
1162 float out_data[size * size];
1164 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1166 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1167 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1168 RewritingEffect<IdentityEffect> *effect3 = new RewritingEffect<IdentityEffect>();
1169 RewritingEffect<OneToOneEffect> *effect4 = new RewritingEffect<OneToOneEffect>();
1171 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1172 tester.get_chain()->add_effect(effect1);
1173 tester.get_chain()->add_effect(effect2);
1174 tester.get_chain()->add_effect(effect3);
1175 tester.get_chain()->add_effect(effect4);
1176 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1178 expect_equal(data, out_data, size, size);
1180 // The NonVirtualResizeEffect should be in a different phase from
1181 // the IdentityEffect (since the latter is not one-to-one),
1182 // ie., the chain should be broken somewhere between them, but exactly
1183 // where doesn't matter.
1184 ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
1185 EXPECT_NE(effect1->replaced_node->incoming_links[0]->containing_phase,
1186 effect3->replaced_node->containing_phase);
1188 // The last OneToOneEffect should also be in the same phase as the
1189 // IdentityEffect (the phase was already broken).
1190 EXPECT_EQ(effect3->replaced_node->containing_phase,
1191 effect4->replaced_node->containing_phase);
1194 // Does not use EffectChainTest, so that it can construct an EffectChain without
1195 // a shared ResourcePool (which is also properly destroyed afterwards).
1196 // Also turns on debugging to test that code path.
1197 TEST(EffectChainTest, IdentityWithOwnPool) {
1198 const int width = 3, height = 2;
1203 const float expected_data[] = {
1207 float out_data[6], temp[6 * 4];
1209 EffectChain chain(width, height);
1210 movit_debug_level = MOVIT_DEBUG_ON;
1213 format.color_space = COLORSPACE_sRGB;
1214 format.gamma_curve = GAMMA_LINEAR;
1216 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
1217 input->set_pixel_data(data);
1218 chain.add_input(input);
1219 chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1222 glGenTextures(1, &texnum);
1224 glBindTexture(GL_TEXTURE_2D, texnum);
1226 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
1229 glGenFramebuffers(1, &fbo);
1231 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1233 glFramebufferTexture2D(
1235 GL_COLOR_ATTACHMENT0,
1240 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1245 chain.render_to_fbo(fbo, width, height);
1247 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1249 glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp);
1251 for (unsigned i = 0; i < 6; ++i) {
1252 out_data[i] = temp[i * 4];
1255 expect_equal(expected_data, out_data, width, height);
1257 // Reset the debug status again.
1258 movit_debug_level = MOVIT_DEBUG_OFF;
1261 // A dummy effect whose only purpose is to test sprintf decimal behavior.
1262 class PrintfingBlueEffect : public Effect {
1264 PrintfingBlueEffect() {}
1265 virtual string effect_type_id() const { return "PrintfingBlueEffect"; }
1266 string output_fragment_shader() {
1268 ss.imbue(locale("C"));
1270 ss << "vec4 FUNCNAME(vec2 tc) { return vec4("
1271 << 0.0f << ", " << 0.0f << ", "
1272 << 0.5f << ", " << 1.0f << "); }\n";
1277 TEST(EffectChainTest, StringStreamLocalesWork) {
1278 // An example of a locale with comma instead of period as decimal separator.
1279 // Obviously, if you run on a machine without this locale available,
1280 // the test will always succeed. Note that the OpenGL driver might call
1281 // setlocale() behind-the-scenes, and that might corrupt the returned
1282 // pointer, so we need to take our own copy of it here.
1283 char *saved_locale = setlocale(LC_ALL, "nb_NO.UTF_8");
1284 if (saved_locale == NULL) {
1285 // The locale wasn't available.
1288 saved_locale = strdup(saved_locale);
1290 0.0f, 0.0f, 0.0f, 0.0f,
1292 float expected_data[] = {
1293 0.0f, 0.0f, 0.5f, 1.0f,
1296 EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1297 tester.get_chain()->add_effect(new PrintfingBlueEffect());
1298 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1300 expect_equal(expected_data, out_data, 4, 1);
1302 setlocale(LC_ALL, saved_locale);
1306 TEST(EffectChainTest, sRGBIntermediate) {
1308 0.0f, 0.5f, 0.0f, 1.0f,
1311 EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA16F_ARB, GL_SRGB8);
1312 tester.get_chain()->add_effect(new IdentityEffect());
1313 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1314 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1316 EXPECT_GE(fabs(out_data[1] - data[1]), 1e-3)
1317 << "Expected sRGB not to be able to represent 0.5 exactly (got " << out_data[1] << ")";
1318 EXPECT_LT(fabs(out_data[1] - data[1]), 0.1f)
1319 << "Expected sRGB to be able to represent 0.5 approximately (got " << out_data[1] << ")";
1322 } // namespace movit