1 // Unit tests for EffectChain.
3 // Note that this also contains the tests for some of the simpler effects.
13 #include "effect_chain.h"
14 #include "flat_input.h"
15 #include "gtest/gtest.h"
18 #include "mirror_effect.h"
19 #include "multiply_effect.h"
20 #include "resize_effect.h"
21 #include "resource_pool.h"
22 #include "test_util.h"
29 TEST(EffectChainTest, EmptyChain) {
35 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
36 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
38 expect_equal(data, out_data, 3, 2);
41 // An effect that does nothing.
42 class IdentityEffect : public Effect {
45 string effect_type_id() const override { return "IdentityEffect"; }
46 string output_fragment_shader() override { return read_file("identity.frag"); }
49 TEST(EffectChainTest, Identity) {
55 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
56 tester.get_chain()->add_effect(new IdentityEffect());
57 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
59 expect_equal(data, out_data, 3, 2);
62 // An effect that does nothing, but requests texture bounce.
63 class BouncingIdentityEffect : public Effect {
65 BouncingIdentityEffect() {}
66 string effect_type_id() const override { return "IdentityEffect"; }
67 string output_fragment_shader() override { return read_file("identity.frag"); }
68 bool needs_texture_bounce() const override { return true; }
69 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
72 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
78 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
79 tester.get_chain()->add_effect(new BouncingIdentityEffect());
80 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
82 expect_equal(data, out_data, 3, 2);
85 TEST(MirrorTest, BasicTest) {
90 float expected_data[6] = {
95 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
96 tester.get_chain()->add_effect(new MirrorEffect());
97 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
99 expect_equal(expected_data, out_data, 3, 2);
102 class WithAndWithoutComputeShaderTest : public testing::TestWithParam<string> {
104 INSTANTIATE_TEST_CASE_P(WithAndWithoutComputeShaderTest,
105 WithAndWithoutComputeShaderTest,
106 testing::Values("fragment", "compute"));
108 // An effect that does nothing, but as a compute shader.
109 class IdentityComputeEffect : public Effect {
111 IdentityComputeEffect() {}
112 virtual string effect_type_id() const { return "IdentityComputeEffect"; }
113 virtual bool is_compute_shader() const { return true; }
114 string output_fragment_shader() { return read_file("identity.comp"); }
117 TEST_P(WithAndWithoutComputeShaderTest, TopLeftOrigin) {
122 // Note that EffectChainTester assumes bottom-left origin, so by setting
123 // top-left, we will get flipped data back.
124 float expected_data[6] = {
129 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
130 tester.get_chain()->set_output_origin(OUTPUT_ORIGIN_TOP_LEFT);
131 if (GetParam() == "compute") {
132 tester.get_chain()->add_effect(new IdentityComputeEffect());
134 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
136 expect_equal(expected_data, out_data, 3, 2);
139 // A dummy effect that inverts its input.
140 class InvertEffect : public Effect {
143 string effect_type_id() const override { return "InvertEffect"; }
144 string output_fragment_shader() override { return read_file("invert_effect.frag"); }
146 // A real invert would actually care about its alpha,
147 // but in this unit test, it only complicates things.
148 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
151 // Like IdentityEffect, but rewrites itself out of the loop,
152 // splicing in a different effect instead. Also stores the new node,
153 // so we later can check whatever properties we'd like about the graph.
155 class RewritingEffect : public Effect {
157 template<class... Args>
158 RewritingEffect(Args &&... args) : effect(new T(std::forward<Args>(args)...)), replaced_node(nullptr) {}
159 string effect_type_id() const override { return "RewritingEffect[" + effect->effect_type_id() + "]"; }
160 string output_fragment_shader() override { EXPECT_TRUE(false); return read_file("identity.frag"); }
161 void rewrite_graph(EffectChain *graph, Node *self) override {
162 replaced_node = graph->add_node(effect);
163 graph->replace_receiver(self, replaced_node);
164 graph->replace_sender(self, replaced_node);
165 self->disabled = true;
172 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
177 float expected_data[6] = {
178 1.0f, 0.9771f, 0.9673f,
182 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
183 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
184 tester.get_chain()->add_effect(effect);
185 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
187 Node *node = effect->replaced_node;
188 ASSERT_EQ(1u, node->incoming_links.size());
189 ASSERT_EQ(1u, node->outgoing_links.size());
190 EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
191 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
193 expect_equal(expected_data, out_data, 3, 2);
196 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
197 unsigned char data[] = {
203 float expected_data[] = {
204 1.0000f, 1.0000f, 1.0000f, 1.0000f,
205 0.9771f, 0.9771f, 0.9771f, 1.0000f,
206 0.8983f, 0.8983f, 0.8983f, 1.0000f,
207 0.0000f, 0.0000f, 0.0000f, 1.0000f
209 float out_data[4 * 4];
210 EffectChainTester tester(nullptr, 1, 4);
211 tester.add_input(data, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_sRGB);
212 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
213 tester.get_chain()->add_effect(effect);
214 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
216 Node *node = effect->replaced_node;
217 ASSERT_EQ(1u, node->incoming_links.size());
218 ASSERT_EQ(1u, node->outgoing_links.size());
219 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
220 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
222 expect_equal(expected_data, out_data, 4, 4);
225 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
230 float expected_data[6] = {
235 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
236 RewritingEffect<InvertEffect> *effect = new RewritingEffect<InvertEffect>();
237 tester.get_chain()->add_effect(effect);
238 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
240 Node *node = effect->replaced_node;
241 ASSERT_EQ(1u, node->incoming_links.size());
242 ASSERT_EQ(1u, node->outgoing_links.size());
243 EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
244 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
246 expect_equal(expected_data, out_data, 3, 2);
249 // A fake input that can change its output colorspace and gamma between instantiation
251 class UnknownColorspaceInput : public FlatInput {
253 UnknownColorspaceInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
254 : FlatInput(format, pixel_format, type, width, height),
255 overridden_color_space(format.color_space),
256 overridden_gamma_curve(format.gamma_curve) {}
257 string effect_type_id() const override { return "UnknownColorspaceInput"; }
259 void set_color_space(Colorspace colorspace) {
260 overridden_color_space = colorspace;
262 void set_gamma_curve(GammaCurve gamma_curve) {
263 overridden_gamma_curve = gamma_curve;
265 Colorspace get_color_space() const override { return overridden_color_space; }
266 GammaCurve get_gamma_curve() const override { return overridden_gamma_curve; }
269 Colorspace overridden_color_space;
270 GammaCurve overridden_gamma_curve;
273 TEST(EffectChainTest, HandlesInputChangingColorspace) {
282 float out_data[size];
284 EffectChainTester tester(nullptr, 4, 1, FORMAT_GRAYSCALE);
286 // First say that we have sRGB, linear input.
288 format.color_space = COLORSPACE_sRGB;
289 format.gamma_curve = GAMMA_LINEAR;
291 UnknownColorspaceInput *input = new UnknownColorspaceInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 1);
292 input->set_pixel_data(data);
293 tester.get_chain()->add_input(input);
295 // Now we change to Rec. 601 input.
296 input->set_color_space(COLORSPACE_REC_601_625);
297 input->set_gamma_curve(GAMMA_REC_601);
299 // Now ask for Rec. 601 output. Thus, our chain should now be a no-op.
300 tester.run(out_data, GL_RED, COLORSPACE_REC_601_625, GAMMA_REC_601);
301 expect_equal(data, out_data, 4, 1);
304 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
309 float expected_data[6] = {
314 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
315 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
316 tester.get_chain()->add_effect(effect);
317 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
319 Node *node = effect->replaced_node;
320 ASSERT_EQ(1u, node->incoming_links.size());
321 EXPECT_EQ(0u, node->outgoing_links.size());
322 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
324 expect_equal(expected_data, out_data, 3, 2);
327 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
332 float expected_data[6] = {
337 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
338 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
339 tester.get_chain()->add_effect(effect);
340 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
342 Node *node = effect->replaced_node;
343 ASSERT_EQ(1u, node->incoming_links.size());
344 EXPECT_EQ(0u, node->outgoing_links.size());
345 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
347 expect_equal(expected_data, out_data, 3, 2);
350 // The identity effect needs linear light, and thus will get conversions on both sides.
351 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
352 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
354 for (unsigned i = 0; i < 256; ++i) {
358 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
359 tester.get_chain()->add_effect(new IdentityEffect());
360 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
362 expect_equal(data, out_data, 256, 1);
365 // Same, but uses the forward sRGB table from the GPU.
366 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
367 unsigned char data[256];
368 float expected_data[256];
369 for (unsigned i = 0; i < 256; ++i) {
371 expected_data[i] = i / 255.0;
374 EffectChainTester tester(nullptr, 256, 1);
375 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
376 tester.get_chain()->add_effect(new IdentityEffect());
377 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
379 expect_equal(expected_data, out_data, 256, 1);
382 // Same, for the Rec. 601/709 gamma curve.
383 TEST(EffectChainTest, IdentityThroughRec709) {
385 for (unsigned i = 0; i < 256; ++i) {
389 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
390 tester.get_chain()->add_effect(new IdentityEffect());
391 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
393 expect_equal(data, out_data, 256, 1);
396 // The identity effect needs premultiplied alpha, and thus will get conversions on both sides.
397 TEST(EffectChainTest, IdentityThroughAlphaConversions) {
399 float data[4 * size] = {
400 0.8f, 0.0f, 0.0f, 0.5f,
401 0.0f, 0.2f, 0.2f, 0.3f,
402 0.1f, 0.0f, 1.0f, 1.0f,
404 float out_data[4 * size];
405 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
406 tester.get_chain()->add_effect(new IdentityEffect());
407 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
409 expect_equal(data, out_data, 4, size);
412 TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) {
414 float data[4 * size] = {
415 0.8f, 0.0f, 0.0f, 0.5f,
416 0.0f, 0.2f, 0.2f, 0.3f,
417 0.1f, 0.0f, 1.0f, 1.0f,
419 float expected_data[4 * size] = {
420 0.1f, 0.0f, 1.0f, 1.0f,
421 0.0f, 0.2f, 0.2f, 0.3f,
422 0.8f, 0.0f, 0.0f, 0.5f,
424 float out_data[4 * size];
425 EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
426 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
427 tester.get_chain()->add_effect(effect);
428 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
430 Node *node = effect->replaced_node;
431 ASSERT_EQ(1u, node->incoming_links.size());
432 EXPECT_EQ(0u, node->outgoing_links.size());
433 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
435 expect_equal(expected_data, out_data, 4, size);
438 // An input that outputs only blue, which has blank alpha.
439 class BlueInput : public Input {
441 BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); }
442 string effect_type_id() const override { return "IdentityEffect"; }
443 string output_fragment_shader() override { return read_file("blue.frag"); }
444 AlphaHandling alpha_handling() const override { return OUTPUT_BLANK_ALPHA; }
445 bool can_output_linear_gamma() const override { return true; }
446 unsigned get_width() const override { return 1; }
447 unsigned get_height() const override { return 1; }
448 Colorspace get_color_space() const override { return COLORSPACE_sRGB; }
449 GammaCurve get_gamma_curve() const override { return GAMMA_LINEAR; }
455 // Like RewritingEffect<InvertEffect>, but splicing in a BlueInput instead,
456 // which outputs blank alpha.
457 class RewritingToBlueInput : public Input {
459 RewritingToBlueInput() : blue_node(nullptr) { register_int("needs_mipmaps", &needs_mipmaps); }
460 string effect_type_id() const override { return "RewritingToBlueInput"; }
461 string output_fragment_shader() override { EXPECT_TRUE(false); return read_file("identity.frag"); }
462 void rewrite_graph(EffectChain *graph, Node *self) override {
463 Node *blue_node = graph->add_node(new BlueInput());
464 graph->replace_receiver(self, blue_node);
465 graph->replace_sender(self, blue_node);
467 self->disabled = true;
468 this->blue_node = blue_node;
471 // Dummy values that we need to implement because we inherit from Input.
472 // Same as BlueInput.
473 AlphaHandling alpha_handling() const override { return OUTPUT_BLANK_ALPHA; }
474 bool can_output_linear_gamma() const override { return true; }
475 unsigned get_width() const override { return 1; }
476 unsigned get_height() const override { return 1; }
477 Colorspace get_color_space() const override { return COLORSPACE_sRGB; }
478 GammaCurve get_gamma_curve() const override { return GAMMA_LINEAR; }
486 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
488 float data[4 * size] = {
489 0.0f, 0.0f, 1.0f, 1.0f,
490 0.0f, 0.0f, 1.0f, 1.0f,
491 0.0f, 0.0f, 1.0f, 1.0f,
493 float out_data[4 * size];
494 EffectChainTester tester(nullptr, size, 1);
495 RewritingToBlueInput *input = new RewritingToBlueInput();
496 tester.get_chain()->add_input(input);
497 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
499 Node *node = input->blue_node;
500 EXPECT_EQ(0u, node->incoming_links.size());
501 EXPECT_EQ(0u, node->outgoing_links.size());
503 expect_equal(data, out_data, 4, size);
506 // An effect that does nothing, and specifies that it preserves blank alpha.
507 class BlankAlphaPreservingEffect : public Effect {
509 BlankAlphaPreservingEffect() {}
510 string effect_type_id() const override { return "BlankAlphaPreservingEffect"; }
511 string output_fragment_shader() override { return read_file("identity.frag"); }
512 AlphaHandling alpha_handling() const override { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
515 TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
517 float data[4 * size] = {
518 0.0f, 0.0f, 1.0f, 1.0f,
519 0.0f, 0.0f, 1.0f, 1.0f,
520 0.0f, 0.0f, 1.0f, 1.0f,
522 float out_data[4 * size];
523 EffectChainTester tester(nullptr, size, 1);
524 tester.get_chain()->add_input(new BlueInput());
525 tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
526 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
527 tester.get_chain()->add_effect(effect);
528 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
530 Node *node = effect->replaced_node;
531 EXPECT_EQ(1u, node->incoming_links.size());
532 EXPECT_EQ(0u, node->outgoing_links.size());
534 expect_equal(data, out_data, 4, size);
537 // This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
538 // just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
539 // an alpha conversion _should_ be inserted at the very end. (There is some overlap
540 // with other tests.)
541 TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
543 float data[4 * size] = {
544 0.0f, 0.0f, 1.0f, 1.0f,
545 0.0f, 0.0f, 1.0f, 1.0f,
546 0.0f, 0.0f, 1.0f, 1.0f,
548 float out_data[4 * size];
549 EffectChainTester tester(nullptr, size, 1);
550 tester.get_chain()->add_input(new BlueInput());
551 tester.get_chain()->add_effect(new IdentityEffect()); // Not BlankAlphaPreservingEffect.
552 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
553 tester.get_chain()->add_effect(effect);
554 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
556 Node *node = effect->replaced_node;
557 EXPECT_EQ(1u, node->incoming_links.size());
558 EXPECT_EQ(1u, node->outgoing_links.size());
559 EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
561 expect_equal(data, out_data, 4, size);
564 // Effectively scales down its input linearly by 4x (and repeating it),
565 // which is not attainable without mipmaps.
566 class MipmapNeedingEffect : public Effect {
568 MipmapNeedingEffect() {}
569 MipmapRequirements needs_mipmaps() const override { return NEEDS_MIPMAPS; }
571 // To be allowed to mess with the sampler state.
572 bool needs_texture_bounce() const override { return true; }
574 string effect_type_id() const override { return "MipmapNeedingEffect"; }
575 string output_fragment_shader() override { return read_file("mipmap_needing_effect.frag"); }
576 void inform_added(EffectChain *chain) override { this->chain = chain; }
578 void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num) override
580 Node *self = chain->find_node_for_effect(this);
581 glActiveTexture(chain->get_input_sampler(self, 0));
583 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
585 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
593 TEST(EffectChainTest, MipmapGenerationWorks) {
594 float data[] = { // In 4x4 blocks.
595 1.0f, 0.0f, 0.0f, 0.0f,
596 0.0f, 0.0f, 0.0f, 0.0f,
597 0.0f, 0.0f, 0.0f, 0.0f,
598 0.0f, 0.0f, 0.0f, 1.0f,
600 0.0f, 0.0f, 0.0f, 0.0f,
601 0.0f, 0.5f, 0.0f, 0.0f,
602 0.0f, 0.0f, 1.0f, 0.0f,
603 0.0f, 0.0f, 0.0f, 0.0f,
605 1.0f, 1.0f, 1.0f, 1.0f,
606 1.0f, 1.0f, 1.0f, 1.0f,
607 1.0f, 1.0f, 1.0f, 1.0f,
608 1.0f, 1.0f, 1.0f, 1.0f,
610 0.0f, 0.0f, 0.0f, 0.0f,
611 0.0f, 1.0f, 1.0f, 0.0f,
612 0.0f, 1.0f, 1.0f, 0.0f,
613 0.0f, 0.0f, 0.0f, 0.0f,
615 float expected_data[] = { // Repeated four times each way.
616 0.125f, 0.125f, 0.125f, 0.125f,
617 0.09375f, 0.09375f, 0.09375f, 0.09375f,
618 1.0f, 1.0f, 1.0f, 1.0f,
619 0.25f, 0.25f, 0.25f, 0.25f,
621 0.125f, 0.125f, 0.125f, 0.125f,
622 0.09375f, 0.09375f, 0.09375f, 0.09375f,
623 1.0f, 1.0f, 1.0f, 1.0f,
624 0.25f, 0.25f, 0.25f, 0.25f,
626 0.125f, 0.125f, 0.125f, 0.125f,
627 0.09375f, 0.09375f, 0.09375f, 0.09375f,
628 1.0f, 1.0f, 1.0f, 1.0f,
629 0.25f, 0.25f, 0.25f, 0.25f,
631 0.125f, 0.125f, 0.125f, 0.125f,
632 0.09375f, 0.09375f, 0.09375f, 0.09375f,
633 1.0f, 1.0f, 1.0f, 1.0f,
634 0.25f, 0.25f, 0.25f, 0.25f,
636 float out_data[4 * 16];
637 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
638 tester.get_chain()->add_effect(new MipmapNeedingEffect());
639 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
641 expect_equal(expected_data, out_data, 4, 16);
644 class NonMipmapCapableInput : public FlatInput {
646 NonMipmapCapableInput(ImageFormat format, MovitPixelFormat pixel_format, GLenum type, unsigned width, unsigned height)
647 : FlatInput(format, pixel_format, type, width, height) {}
649 bool can_supply_mipmaps() const override { return false; }
650 bool set_int(const std::string& key, int value) override {
651 if (key == "needs_mipmaps") {
654 return FlatInput::set_int(key, value);
658 // The same test as MipmapGenerationWorks, but with an input that refuses
659 // to supply mipmaps.
660 TEST(EffectChainTest, MipmapsWithNonMipmapCapableInput) {
661 float data[] = { // In 4x4 blocks.
662 1.0f, 0.0f, 0.0f, 0.0f,
663 0.0f, 0.0f, 0.0f, 0.0f,
664 0.0f, 0.0f, 0.0f, 0.0f,
665 0.0f, 0.0f, 0.0f, 1.0f,
667 0.0f, 0.0f, 0.0f, 0.0f,
668 0.0f, 0.5f, 0.0f, 0.0f,
669 0.0f, 0.0f, 1.0f, 0.0f,
670 0.0f, 0.0f, 0.0f, 0.0f,
672 1.0f, 1.0f, 1.0f, 1.0f,
673 1.0f, 1.0f, 1.0f, 1.0f,
674 1.0f, 1.0f, 1.0f, 1.0f,
675 1.0f, 1.0f, 1.0f, 1.0f,
677 0.0f, 0.0f, 0.0f, 0.0f,
678 0.0f, 1.0f, 1.0f, 0.0f,
679 0.0f, 1.0f, 1.0f, 0.0f,
680 0.0f, 0.0f, 0.0f, 0.0f,
682 float expected_data[] = { // Repeated four times each way.
683 0.125f, 0.125f, 0.125f, 0.125f,
684 0.09375f, 0.09375f, 0.09375f, 0.09375f,
685 1.0f, 1.0f, 1.0f, 1.0f,
686 0.25f, 0.25f, 0.25f, 0.25f,
688 0.125f, 0.125f, 0.125f, 0.125f,
689 0.09375f, 0.09375f, 0.09375f, 0.09375f,
690 1.0f, 1.0f, 1.0f, 1.0f,
691 0.25f, 0.25f, 0.25f, 0.25f,
693 0.125f, 0.125f, 0.125f, 0.125f,
694 0.09375f, 0.09375f, 0.09375f, 0.09375f,
695 1.0f, 1.0f, 1.0f, 1.0f,
696 0.25f, 0.25f, 0.25f, 0.25f,
698 0.125f, 0.125f, 0.125f, 0.125f,
699 0.09375f, 0.09375f, 0.09375f, 0.09375f,
700 1.0f, 1.0f, 1.0f, 1.0f,
701 0.25f, 0.25f, 0.25f, 0.25f,
703 float out_data[4 * 16];
704 EffectChainTester tester(nullptr, 4, 16, FORMAT_GRAYSCALE);
707 format.color_space = COLORSPACE_sRGB;
708 format.gamma_curve = GAMMA_LINEAR;
710 NonMipmapCapableInput *input = new NonMipmapCapableInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 16);
711 input->set_pixel_data(data);
712 tester.get_chain()->add_input(input);
713 tester.get_chain()->add_effect(new MipmapNeedingEffect());
714 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
716 expect_equal(expected_data, out_data, 4, 16);
719 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
720 float data[] = { // In 4x4 blocks.
721 1.0f, 0.0f, 0.0f, 0.0f,
722 0.0f, 0.0f, 0.0f, 0.0f,
723 0.0f, 0.0f, 0.0f, 0.0f,
724 0.0f, 0.0f, 0.0f, 1.0f,
726 0.0f, 0.0f, 0.0f, 0.0f,
727 0.0f, 0.5f, 0.0f, 0.0f,
728 0.0f, 0.0f, 1.0f, 0.0f,
729 0.0f, 0.0f, 0.0f, 0.0f,
731 1.0f, 1.0f, 1.0f, 1.0f,
732 1.0f, 1.0f, 1.0f, 1.0f,
733 1.0f, 1.0f, 1.0f, 1.0f,
734 1.0f, 1.0f, 1.0f, 1.0f,
736 0.0f, 0.0f, 0.0f, 0.0f,
737 0.0f, 1.0f, 1.0f, 0.0f,
738 0.0f, 1.0f, 1.0f, 0.0f,
739 0.0f, 0.0f, 0.0f, 0.0f,
741 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
742 0.1250f, 0.1250f, 0.1250f, 0.1250f,
743 0.1250f, 0.1250f, 0.1250f, 0.1250f,
744 0.1211f, 0.1211f, 0.1211f, 0.1211f,
745 0.1133f, 0.1133f, 0.1133f, 0.1133f,
746 0.1055f, 0.1055f, 0.1055f, 0.1055f,
747 0.0977f, 0.0977f, 0.0977f, 0.0977f,
748 0.2070f, 0.2070f, 0.2070f, 0.2070f,
749 0.4336f, 0.4336f, 0.4336f, 0.4336f,
750 0.6602f, 0.6602f, 0.6602f, 0.6602f,
751 0.8867f, 0.8867f, 0.8867f, 0.8867f,
752 0.9062f, 0.9062f, 0.9062f, 0.9062f,
753 0.7188f, 0.7188f, 0.7188f, 0.7188f,
754 0.5312f, 0.5312f, 0.5312f, 0.5312f,
755 0.3438f, 0.3438f, 0.3438f, 0.3438f,
756 0.2500f, 0.2500f, 0.2500f, 0.2500f,
757 0.2500f, 0.2500f, 0.2500f, 0.2500f,
759 float out_data[4 * 16];
761 ResizeEffect *downscale = new ResizeEffect();
762 ASSERT_TRUE(downscale->set_int("width", 1));
763 ASSERT_TRUE(downscale->set_int("height", 4));
765 ResizeEffect *upscale = new ResizeEffect();
766 ASSERT_TRUE(upscale->set_int("width", 4));
767 ASSERT_TRUE(upscale->set_int("height", 16));
769 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
770 tester.get_chain()->add_effect(downscale);
771 tester.get_chain()->add_effect(upscale);
772 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
774 expect_equal(expected_data, out_data, 4, 16);
777 // An effect to verify that you can turn off mipmaps; it downscales by two,
778 // which gives blur with mipmaps and aliasing (picks out every other pixel)
780 class Downscale2xEffect : public Effect {
782 explicit Downscale2xEffect(MipmapRequirements mipmap_requirements)
783 : mipmap_requirements(mipmap_requirements)
785 register_vec2("offset", offset);
787 MipmapRequirements needs_mipmaps() const override { return mipmap_requirements; }
789 string effect_type_id() const override { return "Downscale2xEffect"; }
790 string output_fragment_shader() override { return read_file("downscale2x.frag"); }
793 const MipmapRequirements mipmap_requirements;
794 float offset[2] { 0.0f, 0.0f };
797 TEST(EffectChainTest, MipmapChainGetsSplit) {
799 0.0f, 0.0f, 0.0f, 0.0f,
800 1.0f, 0.0f, 1.0f, 0.0f,
801 0.0f, 0.0f, 0.0f, 0.0f,
802 1.0f, 0.0f, 1.0f, 0.0f,
805 // The intermediate result after the first step looks like this,
806 // assuming there are no mipmaps (the zeros are due to border behavior):
813 // so another 2x downscale towards the bottom left will give
818 // with yet more zeros coming in on the top and the right from the border.
819 float expected_data[] = {
820 0.0f, 0.0f, 0.0f, 0.0f,
821 0.0f, 0.0f, 0.0f, 0.0f,
822 0.0f, 0.0f, 0.0f, 0.0f,
823 1.0f, 0.0f, 0.0f, 0.0f,
825 float out_data[4 * 4];
827 float offset[] = { -0.5f / 4.0f, -0.5f / 4.0f };
828 RewritingEffect<Downscale2xEffect> *pick_out_bottom_left = new RewritingEffect<Downscale2xEffect>(Effect::CANNOT_ACCEPT_MIPMAPS);
829 ASSERT_TRUE(pick_out_bottom_left->effect->set_vec2("offset", offset));
831 RewritingEffect<Downscale2xEffect> *downscale2x = new RewritingEffect<Downscale2xEffect>(Effect::NEEDS_MIPMAPS);
833 EffectChainTester tester(data, 4, 4, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
834 tester.get_chain()->add_effect(pick_out_bottom_left);
835 tester.get_chain()->add_effect(downscale2x);
836 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
838 EXPECT_NE(pick_out_bottom_left->replaced_node->containing_phase,
839 downscale2x->replaced_node->containing_phase);
841 expect_equal(expected_data, out_data, 4, 4);
844 // An effect that adds its two inputs together. Used below.
845 class AddEffect : public Effect {
848 string effect_type_id() const override { return "AddEffect"; }
849 string output_fragment_shader() override { return read_file("add.frag"); }
850 unsigned num_inputs() const override { return 2; }
851 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
854 // Constructs the graph
858 // MultiplyEffect MultiplyEffect |
862 // and verifies that it gives the correct output.
863 TEST(EffectChainTest, DiamondGraph) {
868 float expected_data[] = {
872 float out_data[2 * 2];
874 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
875 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
877 MultiplyEffect *mul_half = new MultiplyEffect();
878 ASSERT_TRUE(mul_half->set_vec4("factor", half));
880 MultiplyEffect *mul_two = new MultiplyEffect();
881 ASSERT_TRUE(mul_two->set_vec4("factor", two));
883 EffectChainTester tester(nullptr, 2, 2);
886 format.color_space = COLORSPACE_sRGB;
887 format.gamma_curve = GAMMA_LINEAR;
889 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
890 input->set_pixel_data(data);
892 tester.get_chain()->add_input(input);
893 tester.get_chain()->add_effect(mul_half, input);
894 tester.get_chain()->add_effect(mul_two, input);
895 tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
896 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
898 expect_equal(expected_data, out_data, 2, 2);
901 // Constructs the graph
905 // MultiplyEffect MultiplyEffect |
907 // \ BouncingIdentityEffect |
911 // and verifies that it gives the correct output.
912 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
917 float expected_data[] = {
921 float out_data[2 * 2];
923 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
924 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
926 MultiplyEffect *mul_half = new MultiplyEffect();
927 ASSERT_TRUE(mul_half->set_vec4("factor", half));
929 MultiplyEffect *mul_two = new MultiplyEffect();
930 ASSERT_TRUE(mul_two->set_vec4("factor", two));
932 BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
934 EffectChainTester tester(nullptr, 2, 2);
937 format.color_space = COLORSPACE_sRGB;
938 format.gamma_curve = GAMMA_LINEAR;
940 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
941 input->set_pixel_data(data);
943 tester.get_chain()->add_input(input);
944 tester.get_chain()->add_effect(mul_half, input);
945 tester.get_chain()->add_effect(mul_two, input);
946 tester.get_chain()->add_effect(bounce, mul_two);
947 tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
948 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
950 expect_equal(expected_data, out_data, 2, 2);
953 // Constructs the graph
957 // Downscale2xEffect (mipmaps) Downscale2xEffect (no mipmaps) |
959 // Downscale2xEffect (mipmaps) Downscale2xEffect (no mipmaps) |
963 // and verifies that it gives the correct output. Due to the conflicting
964 // mipmap demands, EffectChain needs to make two phases; exactly where it's
965 // split is less important, though (this is a fairly obscure situation that
966 // is unlikely to happen in practice).
967 TEST(EffectChainTest, DiamondGraphWithConflictingMipmaps) {
969 0.0f, 0.0f, 0.0f, 0.0f,
970 1.0f, 0.0f, 1.0f, 0.0f,
971 0.0f, 0.0f, 0.0f, 0.0f,
972 1.0f, 0.0f, 1.0f, 0.0f,
975 // Same situation as MipmapChainGetsSplit. The output of the two
976 // downscales with no mipmaps looks like this:
983 // and the one with mipmaps is 0.25 everywhere. Due to postmultiplied
984 // alpha, we get the average even though we are using AddEffect.
985 float expected_data[] = {
986 0.125f, 0.125f, 0.125f, 0.125f,
987 0.125f, 0.125f, 0.125f, 0.125f,
988 0.125f, 0.125f, 0.125f, 0.125f,
989 0.625f, 0.125f, 0.125f, 0.125f,
991 float out_data[4 * 4];
993 float offset[] = { -0.5f / 4.0f, -0.5f / 4.0f };
994 Downscale2xEffect *nomipmap1 = new Downscale2xEffect(Effect::CANNOT_ACCEPT_MIPMAPS);
995 Downscale2xEffect *nomipmap2 = new Downscale2xEffect(Effect::CANNOT_ACCEPT_MIPMAPS);
996 ASSERT_TRUE(nomipmap1->set_vec2("offset", offset));
997 ASSERT_TRUE(nomipmap2->set_vec2("offset", offset));
999 Downscale2xEffect *mipmap1 = new Downscale2xEffect(Effect::NEEDS_MIPMAPS);
1000 Downscale2xEffect *mipmap2 = new Downscale2xEffect(Effect::NEEDS_MIPMAPS);
1002 EffectChainTester tester(nullptr, 4, 4);
1005 format.color_space = COLORSPACE_sRGB;
1006 format.gamma_curve = GAMMA_LINEAR;
1008 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 4);
1009 input->set_pixel_data(data);
1011 tester.get_chain()->add_input(input);
1013 tester.get_chain()->add_effect(nomipmap1, input);
1014 tester.get_chain()->add_effect(nomipmap2, nomipmap1);
1016 tester.get_chain()->add_effect(mipmap1, input);
1017 tester.get_chain()->add_effect(mipmap2, mipmap1);
1019 tester.get_chain()->add_effect(new AddEffect(), nomipmap2, mipmap2);
1020 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1022 expect_equal(expected_data, out_data, 4, 4);
1025 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
1030 float expected_data[] = {
1031 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
1034 float out_data[2 * 2];
1036 EffectChainTester tester(nullptr, 2, 2);
1037 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
1039 // MirrorEffect does not get linear light, so the conversions will be
1040 // inserted after it, not before.
1041 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
1042 tester.get_chain()->add_effect(effect);
1044 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
1045 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
1046 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
1047 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1049 expect_equal(expected_data, out_data, 2, 2);
1051 Node *node = effect->replaced_node;
1052 ASSERT_EQ(1u, node->incoming_links.size());
1053 ASSERT_EQ(1u, node->outgoing_links.size());
1054 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
1055 EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
1058 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
1063 float expected_data[] = {
1064 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
1067 float out_data[2 * 2];
1069 EffectChainTester tester(nullptr, 2, 2);
1070 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
1072 // MirrorEffect does not get linear light, so the conversions will be
1073 // inserted after it, not before.
1074 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
1075 tester.get_chain()->add_effect(effect);
1077 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
1078 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
1079 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
1080 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1082 expect_equal(expected_data, out_data, 2, 2);
1084 Node *node = effect->replaced_node;
1085 ASSERT_EQ(1u, node->incoming_links.size());
1086 ASSERT_EQ(1u, node->outgoing_links.size());
1087 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
1088 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
1091 // An effect that does nothing, but requests texture bounce and stores
1093 class SizeStoringEffect : public BouncingIdentityEffect {
1095 SizeStoringEffect() : input_width(-1), input_height(-1) {}
1096 void inform_input_size(unsigned input_num, unsigned width, unsigned height) override {
1097 assert(input_num == 0);
1098 input_width = width;
1099 input_height = height;
1101 string effect_type_id() const override { return "SizeStoringEffect"; }
1103 int input_width, input_height;
1106 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
1107 float data[2 * 2] = {
1111 float out_data[4 * 3];
1113 EffectChainTester tester(nullptr, 4, 3); // Note non-square aspect.
1116 format.color_space = COLORSPACE_sRGB;
1117 format.gamma_curve = GAMMA_LINEAR;
1119 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
1120 input1->set_pixel_data(data);
1122 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
1123 input2->set_pixel_data(data);
1125 SizeStoringEffect *input_store = new SizeStoringEffect();
1127 tester.get_chain()->add_input(input1);
1128 tester.get_chain()->add_input(input2);
1129 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
1130 tester.get_chain()->add_effect(input_store);
1131 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1133 EXPECT_EQ(2, input_store->input_width);
1134 EXPECT_EQ(2, input_store->input_height);
1137 TEST(EffectChainTest, AspectRatioConversion) {
1138 float data1[4 * 3] = {
1139 0.0f, 0.0f, 0.0f, 0.0f,
1140 0.0f, 0.0f, 0.0f, 0.0f,
1141 0.0f, 0.0f, 0.0f, 0.0f,
1143 float data2[7 * 7] = {
1144 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
1145 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
1146 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
1147 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
1148 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
1149 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
1150 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
1153 // The right conversion here is that the 7x7 image decides the size,
1154 // since it is the biggest, so everything is scaled up to 9x7
1155 // (keep the height, round the width 9.333 to 9).
1156 float out_data[9 * 7];
1158 EffectChainTester tester(nullptr, 4, 3);
1161 format.color_space = COLORSPACE_sRGB;
1162 format.gamma_curve = GAMMA_LINEAR;
1164 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
1165 input1->set_pixel_data(data1);
1167 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
1168 input2->set_pixel_data(data2);
1170 SizeStoringEffect *input_store = new SizeStoringEffect();
1172 tester.get_chain()->add_input(input1);
1173 tester.get_chain()->add_input(input2);
1174 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
1175 tester.get_chain()->add_effect(input_store);
1176 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1178 EXPECT_EQ(9, input_store->input_width);
1179 EXPECT_EQ(7, input_store->input_height);
1182 // Tests that putting a BlueInput (constant color) into its own pass,
1183 // which creates a phase that doesn't need texture coordinates,
1184 // doesn't mess up a second phase that actually does.
1185 TEST(EffectChainTest, FirstPhaseWithNoTextureCoordinates) {
1191 float expected_data[] = {
1192 1.0f, 1.0f, 2.0f, 2.0f,
1193 0.0f, 0.0f, 1.0f, 2.0f,
1195 float out_data[size * 4];
1196 // First say that we have sRGB, linear input.
1198 format.color_space = COLORSPACE_sRGB;
1199 format.gamma_curve = GAMMA_LINEAR;
1200 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 1, size);
1202 input->set_pixel_data(data);
1203 EffectChainTester tester(nullptr, 1, size);
1204 tester.get_chain()->add_input(new BlueInput());
1205 Effect *phase1_end = tester.get_chain()->add_effect(new BouncingIdentityEffect());
1206 tester.get_chain()->add_input(input);
1207 tester.get_chain()->add_effect(new AddEffect(), phase1_end, input);
1209 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1211 expect_equal(expected_data, out_data, 4, size);
1214 // An effect that does nothing except changing its output sizes.
1215 class VirtualResizeEffect : public Effect {
1217 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
1220 virtual_width(virtual_width),
1221 virtual_height(virtual_height) {}
1222 string effect_type_id() const override { return "VirtualResizeEffect"; }
1223 string output_fragment_shader() override { return read_file("identity.frag"); }
1225 bool changes_output_size() const override { return true; }
1227 void get_output_size(unsigned *width, unsigned *height,
1228 unsigned *virtual_width, unsigned *virtual_height) const override {
1229 *width = this->width;
1230 *height = this->height;
1231 *virtual_width = this->virtual_width;
1232 *virtual_height = this->virtual_height;
1236 int width, height, virtual_width, virtual_height;
1239 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
1240 const int size = 2, bigger_size = 3;
1241 float data[size * size] = {
1245 float out_data[size * size];
1247 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1249 SizeStoringEffect *size_store = new SizeStoringEffect();
1251 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
1252 tester.get_chain()->add_effect(size_store);
1253 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
1254 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1256 EXPECT_EQ(bigger_size, size_store->input_width);
1257 EXPECT_EQ(bigger_size, size_store->input_height);
1259 // If the resize is implemented as non-virtual, we'll fail here,
1260 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
1261 expect_equal(data, out_data, size, size);
1264 // An effect that is like VirtualResizeEffect, but always has virtual and real
1265 // sizes the same (and promises this).
1266 class NonVirtualResizeEffect : public VirtualResizeEffect {
1268 NonVirtualResizeEffect(int width, int height)
1269 : VirtualResizeEffect(width, height, width, height) {}
1270 string effect_type_id() const override { return "NonVirtualResizeEffect"; }
1271 bool sets_virtual_output_size() const override { return false; }
1274 // An effect that promises one-to-one sampling (unlike IdentityEffect).
1275 class OneToOneEffect : public Effect {
1278 string effect_type_id() const override { return "OneToOneEffect"; }
1279 string output_fragment_shader() override { return read_file("identity.frag"); }
1280 bool strong_one_to_one_sampling() const override { return true; }
1283 TEST_P(WithAndWithoutComputeShaderTest, NoBounceWithOneToOneSampling) {
1285 float data[size * size] = {
1289 float out_data[size * size];
1291 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1293 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1294 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1296 if (GetParam() == "compute") {
1297 tester.get_chain()->add_effect(new IdentityComputeEffect());
1299 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1301 tester.get_chain()->add_effect(effect1);
1302 tester.get_chain()->add_effect(effect2);
1303 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1305 expect_equal(data, out_data, size, size);
1307 // The first OneToOneEffect should be in the same phase as its input.
1308 ASSERT_EQ(1u, effect1->replaced_node->incoming_links.size());
1309 EXPECT_EQ(effect1->replaced_node->incoming_links[0]->containing_phase,
1310 effect1->replaced_node->containing_phase);
1312 // The second OneToOneEffect, too.
1313 EXPECT_EQ(effect1->replaced_node->containing_phase,
1314 effect2->replaced_node->containing_phase);
1317 TEST(EffectChainTest, BounceWhenOneToOneIsBroken) {
1319 float data[size * size] = {
1323 float out_data[size * size];
1325 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1327 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1328 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1329 RewritingEffect<IdentityEffect> *effect3 = new RewritingEffect<IdentityEffect>();
1330 RewritingEffect<OneToOneEffect> *effect4 = new RewritingEffect<OneToOneEffect>();
1332 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1333 tester.get_chain()->add_effect(effect1);
1334 tester.get_chain()->add_effect(effect2);
1335 tester.get_chain()->add_effect(effect3);
1336 tester.get_chain()->add_effect(effect4);
1337 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1339 expect_equal(data, out_data, size, size);
1341 // The NonVirtualResizeEffect should be in a different phase from
1342 // the IdentityEffect (since the latter is not one-to-one),
1343 // ie., the chain should be broken somewhere between them, but exactly
1344 // where doesn't matter.
1345 ASSERT_EQ(1u, effect1->replaced_node->incoming_links.size());
1346 EXPECT_NE(effect1->replaced_node->incoming_links[0]->containing_phase,
1347 effect3->replaced_node->containing_phase);
1349 // The last OneToOneEffect should also be in the same phase as the
1350 // IdentityEffect (the phase was already broken).
1351 EXPECT_EQ(effect3->replaced_node->containing_phase,
1352 effect4->replaced_node->containing_phase);
1355 // Does not use EffectChainTest, so that it can construct an EffectChain without
1356 // a shared ResourcePool (which is also properly destroyed afterwards).
1357 // Also turns on debugging to test that code path.
1358 TEST(EffectChainTest, IdentityWithOwnPool) {
1359 const int width = 3, height = 2;
1364 const float expected_data[] = {
1368 float out_data[6], temp[6 * 4];
1370 EffectChain chain(width, height);
1371 MovitDebugLevel old_movit_debug_level = movit_debug_level;
1372 movit_debug_level = MOVIT_DEBUG_ON;
1375 format.color_space = COLORSPACE_sRGB;
1376 format.gamma_curve = GAMMA_LINEAR;
1378 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
1379 input->set_pixel_data(data);
1380 chain.add_input(input);
1381 chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1384 glGenTextures(1, &texnum);
1386 glBindTexture(GL_TEXTURE_2D, texnum);
1388 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
1391 glGenFramebuffers(1, &fbo);
1393 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1395 glFramebufferTexture2D(
1397 GL_COLOR_ATTACHMENT0,
1402 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1407 chain.render_to_fbo(fbo, width, height);
1409 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1411 glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp);
1413 for (unsigned i = 0; i < 6; ++i) {
1414 out_data[i] = temp[i * 4];
1417 expect_equal(expected_data, out_data, width, height);
1419 // Reset the debug status again.
1420 movit_debug_level = old_movit_debug_level;
1423 // A dummy effect whose only purpose is to test sprintf decimal behavior.
1424 class PrintfingBlueEffect : public Effect {
1426 PrintfingBlueEffect() {}
1427 string effect_type_id() const override { return "PrintfingBlueEffect"; }
1428 string output_fragment_shader() override {
1430 ss.imbue(locale("C"));
1432 ss << "vec4 FUNCNAME(vec2 tc) { return vec4("
1433 << 0.0f << ", " << 0.0f << ", "
1434 << 0.5f << ", " << 1.0f << "); }\n";
1439 TEST(EffectChainTest, StringStreamLocalesWork) {
1440 // An example of a locale with comma instead of period as decimal separator.
1441 // Obviously, if you run on a machine without this locale available,
1442 // the test will always succeed. Note that the OpenGL driver might call
1443 // setlocale() behind-the-scenes, and that might corrupt the returned
1444 // pointer, so we need to take our own copy of it here.
1445 char *saved_locale = setlocale(LC_ALL, "nb_NO.UTF_8");
1446 if (saved_locale == nullptr) {
1447 // The locale wasn't available.
1450 saved_locale = strdup(saved_locale);
1452 0.0f, 0.0f, 0.0f, 0.0f,
1454 float expected_data[] = {
1455 0.0f, 0.0f, 0.5f, 1.0f,
1458 EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1459 tester.get_chain()->add_effect(new PrintfingBlueEffect());
1460 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1462 expect_equal(expected_data, out_data, 4, 1);
1464 setlocale(LC_ALL, saved_locale);
1468 TEST(EffectChainTest, sRGBIntermediate) {
1470 0.0f, 0.5f, 0.0f, 1.0f,
1473 EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1474 tester.get_chain()->set_intermediate_format(GL_SRGB8);
1475 tester.get_chain()->add_effect(new IdentityEffect());
1476 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1477 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1479 EXPECT_GE(fabs(out_data[1] - data[1]), 1e-3)
1480 << "Expected sRGB not to be able to represent 0.5 exactly (got " << out_data[1] << ")";
1481 EXPECT_LT(fabs(out_data[1] - data[1]), 0.1f)
1482 << "Expected sRGB to be able to represent 0.5 approximately (got " << out_data[1] << ")";
1484 // This state should have been preserved.
1485 EXPECT_FALSE(glIsEnabled(GL_FRAMEBUFFER_SRGB));
1488 // An effect that is like IdentityEffect, but also does not require linear light.
1489 class PassThroughEffect : public IdentityEffect {
1491 PassThroughEffect() {}
1492 string effect_type_id() const override { return "PassThroughEffect"; }
1493 bool needs_linear_light() const override { return false; }
1494 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
1497 // Same, just also bouncing.
1498 class BouncingPassThroughEffect : public BouncingIdentityEffect {
1500 BouncingPassThroughEffect() {}
1501 string effect_type_id() const override { return "BouncingPassThroughEffect"; }
1502 bool needs_linear_light() const override { return false; }
1503 bool needs_texture_bounce() const override { return true; }
1504 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
1507 TEST(EffectChainTest, Linear10bitIntermediateAccuracy) {
1508 // Note that we do the comparison in sRGB space, which is what we
1509 // typically would want; however, we do the sRGB conversion ourself
1510 // to avoid compounding errors from shader conversions into the
1512 const int size = 4096; // 12-bit.
1513 float linear_data[size], data[size], out_data[size];
1515 for (int i = 0; i < size; ++i) {
1516 linear_data[i] = i / double(size - 1);
1517 data[i] = srgb_to_linear(linear_data[i]);
1520 EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
1521 tester.get_chain()->set_intermediate_format(GL_RGB10_A2);
1522 tester.get_chain()->add_effect(new IdentityEffect());
1523 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1524 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1526 for (int i = 0; i < size; ++i) {
1527 out_data[i] = linear_to_srgb(out_data[i]);
1530 // This maximum error is pretty bad; about 6.5 levels of a 10-bit sRGB
1531 // framebuffer. (Slightly more on NVIDIA cards.)
1532 expect_equal(linear_data, out_data, size, 1, 7.5e-3, 2e-5);
1535 TEST_P(WithAndWithoutComputeShaderTest, SquareRoot10bitIntermediateAccuracy) {
1536 // Note that we do the comparison in sRGB space, which is what we
1537 // typically would want; however, we do the sRGB conversion ourself
1538 // to avoid compounding errors from shader conversions into the
1540 const int size = 4096; // 12-bit.
1541 float linear_data[size], data[size], out_data[size];
1543 for (int i = 0; i < size; ++i) {
1544 linear_data[i] = i / double(size - 1);
1545 data[i] = srgb_to_linear(linear_data[i]);
1548 EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
1549 tester.get_chain()->set_intermediate_format(GL_RGB10_A2, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION);
1550 if (GetParam() == "compute") {
1551 tester.get_chain()->add_effect(new IdentityComputeEffect());
1553 tester.get_chain()->add_effect(new IdentityEffect());
1555 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1556 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1558 for (int i = 0; i < size; ++i) {
1559 out_data[i] = linear_to_srgb(out_data[i]);
1562 // This maximum error is much better; about 0.7 levels of a 10-bit sRGB
1563 // framebuffer (ideal would be 0.5). That is an order of magnitude better
1564 // than in the linear test above. The RMS error is much better, too.
1565 expect_equal(linear_data, out_data, size, 1, 7.5e-4, 5e-6);
1568 TEST(EffectChainTest, SquareRootIntermediateIsTurnedOffForNonLinearData) {
1569 const int size = 256; // 8-bit.
1570 float data[size], out_data[size];
1572 for (int i = 0; i < size; ++i) {
1573 data[i] = i / double(size - 1);
1576 EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_601, GL_RGBA32F);
1577 tester.get_chain()->set_intermediate_format(GL_RGB8, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION);
1578 tester.get_chain()->add_effect(new PassThroughEffect());
1579 tester.get_chain()->add_effect(new BouncingPassThroughEffect());
1580 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_601);
1582 // The data should be passed through nearly exactly, since there is no effect
1583 // on the path that requires linear light. (Actually, it _is_ exact modulo
1584 // fp32 errors, but the error bounds is strictly _less than_, not zero.)
1585 expect_equal(data, out_data, size, 1, 1e-6, 1e-6);
1588 // An effect that stores which program number was last run under.
1589 class RecordingIdentityEffect : public Effect {
1591 RecordingIdentityEffect() {}
1592 string effect_type_id() const override { return "RecordingIdentityEffect"; }
1593 string output_fragment_shader() override { return read_file("identity.frag"); }
1595 GLuint last_glsl_program_num;
1596 void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num) override
1598 last_glsl_program_num = glsl_program_num;
1602 TEST(EffectChainTest, ProgramsAreClonedForMultipleThreads) {
1608 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1609 RecordingIdentityEffect *effect = new RecordingIdentityEffect();
1610 tester.get_chain()->add_effect(effect);
1611 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1613 expect_equal(data, out_data, 3, 2);
1615 ASSERT_NE(0u, effect->last_glsl_program_num);
1617 // Now pretend some other effect is using this program number;
1618 // ResourcePool will then need to clone it.
1619 ResourcePool *resource_pool = tester.get_chain()->get_resource_pool();
1620 GLuint master_program_num = resource_pool->use_glsl_program(effect->last_glsl_program_num);
1621 EXPECT_EQ(effect->last_glsl_program_num, master_program_num);
1623 // Re-run should still give the correct data, but it should have run
1624 // with a different program.
1625 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1626 expect_equal(data, out_data, 3, 2);
1627 EXPECT_NE(effect->last_glsl_program_num, master_program_num);
1629 // Release the program, and check one final time.
1630 resource_pool->unuse_glsl_program(master_program_num);
1631 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1632 expect_equal(data, out_data, 3, 2);
1635 TEST(ComputeShaderTest, Identity) {
1641 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1642 if (!movit_compute_shaders_supported) {
1643 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1646 tester.get_chain()->add_effect(new IdentityComputeEffect());
1647 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1649 expect_equal(data, out_data, 3, 2);
1652 // Like IdentityComputeEffect, but due to the alpha handling, this will be
1653 // the very last effect in the chain, which means we can't output it directly
1655 class IdentityAlphaComputeEffect : public IdentityComputeEffect {
1656 AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
1659 TEST(ComputeShaderTest, LastEffectInChain) {
1665 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1666 if (!movit_compute_shaders_supported) {
1667 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1670 tester.get_chain()->add_effect(new IdentityAlphaComputeEffect());
1671 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1673 expect_equal(data, out_data, 3, 2);
1676 TEST(ComputeShaderTest, Render8BitTo8Bit) {
1681 uint8_t out_data[6];
1682 EffectChainTester tester(nullptr, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
1683 if (!movit_compute_shaders_supported) {
1684 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1687 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 3, 2);
1688 tester.get_chain()->add_effect(new IdentityAlphaComputeEffect());
1689 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1691 expect_equal(data, out_data, 3, 2);
1694 // A compute shader to mirror the inputs, in 2x2 blocks.
1695 class MirrorComputeEffect : public Effect {
1697 MirrorComputeEffect() {}
1698 string effect_type_id() const override { return "MirrorComputeEffect"; }
1699 bool is_compute_shader() const override { return true; }
1700 string output_fragment_shader() override { return read_file("mirror.comp"); }
1701 void get_compute_dimensions(unsigned output_width, unsigned output_height,
1702 unsigned *x, unsigned *y, unsigned *z) const override {
1703 *x = output_width / 2;
1704 *y = output_height / 2;
1709 TEST(ComputeShaderTest, ComputeThenOneToOne) {
1711 0.0f, 0.25f, 0.3f, 0.8f,
1712 0.75f, 1.0f, 1.0f, 0.2f,
1714 float expected_data[] = {
1715 0.8f, 0.3f, 0.25f, 0.0f,
1716 0.2f, 1.0f, 1.0f, 0.75f,
1719 EffectChainTester tester(data, 4, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1720 tester.get_chain()->add_effect(new MirrorComputeEffect());
1721 tester.get_chain()->add_effect(new OneToOneEffect());
1722 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1724 expect_equal(expected_data, out_data, 4, 2);
1727 // A compute shader that also resizes its input, taking the upper-left pixel
1728 // of every 2x2 group. (The shader is hard-coded to 4x2 input for simplicity.)
1729 class Downscale2xComputeEffect : public Effect {
1731 Downscale2xComputeEffect() {}
1732 string effect_type_id() const override { return "Downscale2xComputeEffect"; }
1733 bool is_compute_shader() const override { return true; }
1734 string output_fragment_shader() override { return read_file("downscale2x.comp"); }
1735 bool changes_output_size() const override { return true; }
1736 void inform_input_size(unsigned input_num, unsigned width, unsigned height) override
1738 this->width = width;
1739 this->height = height;
1741 void get_output_size(unsigned *width, unsigned *height,
1742 unsigned *virtual_width, unsigned *virtual_height) const override {
1743 *width = *virtual_width = this->width / 2;
1744 *height = *virtual_height = this->height / 2;
1748 unsigned width, height;
1751 // Even if the compute shader is not the last effect, it's the one that should decide
1752 // the output size of the phase.
1753 TEST(ComputeShaderTest, ResizingComputeThenOneToOne) {
1755 0.0f, 0.25f, 0.3f, 0.8f,
1756 0.75f, 1.0f, 1.0f, 0.2f,
1758 float expected_data[] = {
1762 EffectChainTester tester(nullptr, 2, 1);
1763 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 4, 2);
1765 RewritingEffect<Downscale2xComputeEffect> *downscale_effect = new RewritingEffect<Downscale2xComputeEffect>();
1766 tester.get_chain()->add_effect(downscale_effect);
1767 tester.get_chain()->add_effect(new OneToOneEffect());
1768 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1769 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1771 expect_equal(expected_data, out_data, 2, 1);
1773 Phase *phase = downscale_effect->replaced_node->containing_phase;
1774 EXPECT_EQ(2u, phase->output_width);
1775 EXPECT_EQ(1u, phase->output_height);
1778 class StrongOneToOneAddEffect : public AddEffect {
1780 StrongOneToOneAddEffect() {}
1781 string effect_type_id() const override { return "StrongOneToOneAddEffect"; }
1782 bool strong_one_to_one_sampling() const override { return true; }
1785 TEST(ComputeShaderTest, NoTwoComputeInSamePhase) {
1787 0.0f, 0.25f, 0.3f, 0.8f,
1788 0.75f, 1.0f, 1.0f, 0.2f,
1790 float expected_data[] = {
1796 format.color_space = COLORSPACE_sRGB;
1797 format.gamma_curve = GAMMA_LINEAR;
1799 EffectChainTester tester(nullptr, 2, 1);
1801 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1802 input1->set_pixel_data(data);
1803 tester.get_chain()->add_input(input1);
1804 Effect *downscale1 = tester.get_chain()->add_effect(new Downscale2xComputeEffect());
1806 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1807 input2->set_pixel_data(data);
1808 tester.get_chain()->add_input(input2);
1809 Effect *downscale2 = tester.get_chain()->add_effect(new Downscale2xComputeEffect());
1811 tester.get_chain()->add_effect(new StrongOneToOneAddEffect(), downscale1, downscale2);
1812 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1813 expect_equal(expected_data, out_data, 2, 1);
1816 // Like the previous test, but the adder effect is not directly connected
1817 // to the compute shaders (so the status has to be propagated through those effects).
1818 TEST(ComputeShaderTest, NoTwoComputeInSamePhaseIndirect) {
1820 0.0f, 0.25f, 0.3f, 0.8f,
1821 0.75f, 1.0f, 1.0f, 0.2f,
1823 float expected_data[] = {
1829 format.color_space = COLORSPACE_sRGB;
1830 format.gamma_curve = GAMMA_LINEAR;
1832 EffectChainTester tester(nullptr, 2, 1);
1834 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1835 input1->set_pixel_data(data);
1836 tester.get_chain()->add_input(input1);
1837 tester.get_chain()->add_effect(new Downscale2xComputeEffect());
1838 Effect *identity1 = tester.get_chain()->add_effect(new OneToOneEffect());
1840 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1841 input2->set_pixel_data(data);
1842 tester.get_chain()->add_input(input2);
1843 tester.get_chain()->add_effect(new Downscale2xComputeEffect());
1844 Effect *identity2 = tester.get_chain()->add_effect(new OneToOneEffect());
1846 tester.get_chain()->add_effect(new StrongOneToOneAddEffect(), identity1, identity2);
1847 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1848 expect_equal(expected_data, out_data, 2, 1);
1851 // Like the previous test, but the adder is not strong one-to-one
1852 // (so there are two different compute shader inputs, but none of them
1853 // are in the same phase).
1854 TEST(ComputeShaderTest, BounceTextureFromTwoComputeShaders) {
1856 0.0f, 0.25f, 0.3f, 0.8f,
1857 0.75f, 1.0f, 1.0f, 0.2f,
1859 float expected_data[] = {
1865 format.color_space = COLORSPACE_sRGB;
1866 format.gamma_curve = GAMMA_LINEAR;
1868 EffectChainTester tester(nullptr, 2, 1);
1870 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1871 input1->set_pixel_data(data);
1872 tester.get_chain()->add_input(input1);
1873 tester.get_chain()->add_effect(new Downscale2xComputeEffect());
1874 Effect *identity1 = tester.get_chain()->add_effect(new OneToOneEffect());
1876 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1877 input2->set_pixel_data(data);
1878 tester.get_chain()->add_input(input2);
1879 tester.get_chain()->add_effect(new Downscale2xComputeEffect());
1880 Effect *identity2 = tester.get_chain()->add_effect(new OneToOneEffect());
1882 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
1883 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1884 expect_equal(expected_data, out_data, 2, 1);
1887 // Requires mipmaps, but is otherwise like IdentityEffect.
1888 class MipmapNeedingIdentityEffect : public IdentityEffect {
1890 MipmapNeedingIdentityEffect() {}
1891 MipmapRequirements needs_mipmaps() const override { return NEEDS_MIPMAPS; }
1892 string effect_type_id() const override { return "MipmapNeedingIdentityEffect"; }
1893 bool strong_one_to_one_sampling() const override { return true; }
1896 TEST(ComputeShaderTest, StrongOneToOneButStillNotChained) {
1898 0.0f, 0.25f, 0.3f, 0.8f,
1899 0.75f, 1.0f, 1.0f, 0.2f,
1904 format.color_space = COLORSPACE_sRGB;
1905 format.gamma_curve = GAMMA_LINEAR;
1907 EffectChainTester tester(nullptr, 4, 2);
1909 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1910 input1->set_pixel_data(data);
1911 tester.get_chain()->add_input(input1);
1912 Effect *compute_effect = tester.get_chain()->add_effect(new IdentityComputeEffect());
1914 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1915 input2->set_pixel_data(data);
1916 tester.get_chain()->add_input(input2);
1918 // Not chained with the compute shader because MipmapNeedingIdentityEffect comes in
1919 // the same phase, and compute shaders cannot supply mipmaps.
1920 tester.get_chain()->add_effect(new StrongOneToOneAddEffect(), compute_effect, input2);
1921 tester.get_chain()->add_effect(new MipmapNeedingIdentityEffect());
1923 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1924 expect_equal(data, out_data, 4, 2);
1927 } // namespace movit