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;
795 float offset[2] { 0.0f, 0.0f };
798 TEST(EffectChainTest, MipmapChainGetsSplit) {
800 0.0f, 0.0f, 0.0f, 0.0f,
801 1.0f, 0.0f, 1.0f, 0.0f,
802 0.0f, 0.0f, 0.0f, 0.0f,
803 1.0f, 0.0f, 1.0f, 0.0f,
806 // The intermediate result after the first step looks like this,
807 // assuming there are no mipmaps (the zeros are due to border behavior):
814 // so another 2x downscale towards the bottom left will give
819 // with yet more zeros coming in on the top and the right from the border.
820 float expected_data[] = {
821 0.0f, 0.0f, 0.0f, 0.0f,
822 0.0f, 0.0f, 0.0f, 0.0f,
823 0.0f, 0.0f, 0.0f, 0.0f,
824 1.0f, 0.0f, 0.0f, 0.0f,
826 float out_data[4 * 4];
828 float offset[] = { -0.5f / 4.0f, -0.5f / 4.0f };
829 RewritingEffect<Downscale2xEffect> *pick_out_bottom_left = new RewritingEffect<Downscale2xEffect>(Effect::CANNOT_ACCEPT_MIPMAPS);
830 ASSERT_TRUE(pick_out_bottom_left->effect->set_vec2("offset", offset));
832 RewritingEffect<Downscale2xEffect> *downscale2x = new RewritingEffect<Downscale2xEffect>(Effect::NEEDS_MIPMAPS);
834 EffectChainTester tester(data, 4, 4, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
835 tester.get_chain()->add_effect(pick_out_bottom_left);
836 tester.get_chain()->add_effect(downscale2x);
837 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
839 EXPECT_NE(pick_out_bottom_left->replaced_node->containing_phase,
840 downscale2x->replaced_node->containing_phase);
842 expect_equal(expected_data, out_data, 4, 4);
845 // An effect that adds its two inputs together. Used below.
846 class AddEffect : public Effect {
849 string effect_type_id() const override { return "AddEffect"; }
850 string output_fragment_shader() override { return read_file("add.frag"); }
851 unsigned num_inputs() const override { return 2; }
852 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
855 // Constructs the graph
859 // MultiplyEffect MultiplyEffect |
863 // and verifies that it gives the correct output.
864 TEST(EffectChainTest, DiamondGraph) {
869 float expected_data[] = {
873 float out_data[2 * 2];
875 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
876 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
878 MultiplyEffect *mul_half = new MultiplyEffect();
879 ASSERT_TRUE(mul_half->set_vec4("factor", half));
881 MultiplyEffect *mul_two = new MultiplyEffect();
882 ASSERT_TRUE(mul_two->set_vec4("factor", two));
884 EffectChainTester tester(nullptr, 2, 2);
887 format.color_space = COLORSPACE_sRGB;
888 format.gamma_curve = GAMMA_LINEAR;
890 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
891 input->set_pixel_data(data);
893 tester.get_chain()->add_input(input);
894 tester.get_chain()->add_effect(mul_half, input);
895 tester.get_chain()->add_effect(mul_two, input);
896 tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two);
897 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
899 expect_equal(expected_data, out_data, 2, 2);
902 // Constructs the graph
906 // MultiplyEffect MultiplyEffect |
908 // \ BouncingIdentityEffect |
912 // and verifies that it gives the correct output.
913 TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) {
918 float expected_data[] = {
922 float out_data[2 * 2];
924 const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f };
925 const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f };
927 MultiplyEffect *mul_half = new MultiplyEffect();
928 ASSERT_TRUE(mul_half->set_vec4("factor", half));
930 MultiplyEffect *mul_two = new MultiplyEffect();
931 ASSERT_TRUE(mul_two->set_vec4("factor", two));
933 BouncingIdentityEffect *bounce = new BouncingIdentityEffect();
935 EffectChainTester tester(nullptr, 2, 2);
938 format.color_space = COLORSPACE_sRGB;
939 format.gamma_curve = GAMMA_LINEAR;
941 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
942 input->set_pixel_data(data);
944 tester.get_chain()->add_input(input);
945 tester.get_chain()->add_effect(mul_half, input);
946 tester.get_chain()->add_effect(mul_two, input);
947 tester.get_chain()->add_effect(bounce, mul_two);
948 tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce);
949 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
951 expect_equal(expected_data, out_data, 2, 2);
954 // Constructs the graph
958 // Downscale2xEffect (mipmaps) Downscale2xEffect (no mipmaps) |
960 // Downscale2xEffect (mipmaps) Downscale2xEffect (no mipmaps) |
964 // and verifies that it gives the correct output. Due to the conflicting
965 // mipmap demands, EffectChain needs to make two phases; exactly where it's
966 // split is less important, though (this is a fairly obscure situation that
967 // is unlikely to happen in practice).
968 TEST(EffectChainTest, DiamondGraphWithConflictingMipmaps) {
970 0.0f, 0.0f, 0.0f, 0.0f,
971 1.0f, 0.0f, 1.0f, 0.0f,
972 0.0f, 0.0f, 0.0f, 0.0f,
973 1.0f, 0.0f, 1.0f, 0.0f,
976 // Same situation as MipmapChainGetsSplit. The output of the two
977 // downscales with no mipmaps looks like this:
984 // and the one with mipmaps is 0.25 everywhere. Due to postmultiplied
985 // alpha, we get the average even though we are using AddEffect.
986 float expected_data[] = {
987 0.125f, 0.125f, 0.125f, 0.125f,
988 0.125f, 0.125f, 0.125f, 0.125f,
989 0.125f, 0.125f, 0.125f, 0.125f,
990 0.625f, 0.125f, 0.125f, 0.125f,
992 float out_data[4 * 4];
994 float offset[] = { -0.5f / 4.0f, -0.5f / 4.0f };
995 Downscale2xEffect *nomipmap1 = new Downscale2xEffect(Effect::CANNOT_ACCEPT_MIPMAPS);
996 Downscale2xEffect *nomipmap2 = new Downscale2xEffect(Effect::CANNOT_ACCEPT_MIPMAPS);
997 ASSERT_TRUE(nomipmap1->set_vec2("offset", offset));
998 ASSERT_TRUE(nomipmap2->set_vec2("offset", offset));
1000 Downscale2xEffect *mipmap1 = new Downscale2xEffect(Effect::NEEDS_MIPMAPS);
1001 Downscale2xEffect *mipmap2 = new Downscale2xEffect(Effect::NEEDS_MIPMAPS);
1003 EffectChainTester tester(nullptr, 4, 4);
1006 format.color_space = COLORSPACE_sRGB;
1007 format.gamma_curve = GAMMA_LINEAR;
1009 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 4);
1010 input->set_pixel_data(data);
1012 tester.get_chain()->add_input(input);
1014 tester.get_chain()->add_effect(nomipmap1, input);
1015 tester.get_chain()->add_effect(nomipmap2, nomipmap1);
1017 tester.get_chain()->add_effect(mipmap1, input);
1018 tester.get_chain()->add_effect(mipmap2, mipmap1);
1020 tester.get_chain()->add_effect(new AddEffect(), nomipmap2, mipmap2);
1021 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1023 expect_equal(expected_data, out_data, 4, 4);
1026 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) {
1031 float expected_data[] = {
1032 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
1035 float out_data[2 * 2];
1037 EffectChainTester tester(nullptr, 2, 2);
1038 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
1040 // MirrorEffect does not get linear light, so the conversions will be
1041 // inserted after it, not before.
1042 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
1043 tester.get_chain()->add_effect(effect);
1045 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
1046 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
1047 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
1048 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1050 expect_equal(expected_data, out_data, 2, 2);
1052 Node *node = effect->replaced_node;
1053 ASSERT_EQ(1u, node->incoming_links.size());
1054 ASSERT_EQ(1u, node->outgoing_links.size());
1055 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
1056 EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id());
1059 TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) {
1064 float expected_data[] = {
1065 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly.
1068 float out_data[2 * 2];
1070 EffectChainTester tester(nullptr, 2, 2);
1071 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR);
1073 // MirrorEffect does not get linear light, so the conversions will be
1074 // inserted after it, not before.
1075 RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
1076 tester.get_chain()->add_effect(effect);
1078 Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
1079 Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect);
1080 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
1081 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1083 expect_equal(expected_data, out_data, 2, 2);
1085 Node *node = effect->replaced_node;
1086 ASSERT_EQ(1u, node->incoming_links.size());
1087 ASSERT_EQ(1u, node->outgoing_links.size());
1088 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
1089 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
1092 // An effect that does nothing, but requests texture bounce and stores
1094 class SizeStoringEffect : public BouncingIdentityEffect {
1096 SizeStoringEffect() : input_width(-1), input_height(-1) {}
1097 void inform_input_size(unsigned input_num, unsigned width, unsigned height) override {
1098 assert(input_num == 0);
1099 input_width = width;
1100 input_height = height;
1102 string effect_type_id() const override { return "SizeStoringEffect"; }
1104 int input_width, input_height;
1107 TEST(EffectChainTest, SameInputsGiveSameOutputs) {
1108 float data[2 * 2] = {
1112 float out_data[4 * 3];
1114 EffectChainTester tester(nullptr, 4, 3); // Note non-square aspect.
1117 format.color_space = COLORSPACE_sRGB;
1118 format.gamma_curve = GAMMA_LINEAR;
1120 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
1121 input1->set_pixel_data(data);
1123 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2);
1124 input2->set_pixel_data(data);
1126 SizeStoringEffect *input_store = new SizeStoringEffect();
1128 tester.get_chain()->add_input(input1);
1129 tester.get_chain()->add_input(input2);
1130 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
1131 tester.get_chain()->add_effect(input_store);
1132 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1134 EXPECT_EQ(2, input_store->input_width);
1135 EXPECT_EQ(2, input_store->input_height);
1138 TEST(EffectChainTest, AspectRatioConversion) {
1139 float data1[4 * 3] = {
1140 0.0f, 0.0f, 0.0f, 0.0f,
1141 0.0f, 0.0f, 0.0f, 0.0f,
1142 0.0f, 0.0f, 0.0f, 0.0f,
1144 float data2[7 * 7] = {
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, 0.0f, 0.0f, 0.0f, 0.0f,
1148 0.0f, 0.0f, 0.0f, 1.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,
1151 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
1154 // The right conversion here is that the 7x7 image decides the size,
1155 // since it is the biggest, so everything is scaled up to 9x7
1156 // (keep the height, round the width 9.333 to 9).
1157 float out_data[9 * 7];
1159 EffectChainTester tester(nullptr, 4, 3);
1162 format.color_space = COLORSPACE_sRGB;
1163 format.gamma_curve = GAMMA_LINEAR;
1165 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3);
1166 input1->set_pixel_data(data1);
1168 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7);
1169 input2->set_pixel_data(data2);
1171 SizeStoringEffect *input_store = new SizeStoringEffect();
1173 tester.get_chain()->add_input(input1);
1174 tester.get_chain()->add_input(input2);
1175 tester.get_chain()->add_effect(new AddEffect(), input1, input2);
1176 tester.get_chain()->add_effect(input_store);
1177 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1179 EXPECT_EQ(9, input_store->input_width);
1180 EXPECT_EQ(7, input_store->input_height);
1183 // Tests that putting a BlueInput (constant color) into its own pass,
1184 // which creates a phase that doesn't need texture coordinates,
1185 // doesn't mess up a second phase that actually does.
1186 TEST(EffectChainTest, FirstPhaseWithNoTextureCoordinates) {
1192 float expected_data[] = {
1193 1.0f, 1.0f, 2.0f, 2.0f,
1194 0.0f, 0.0f, 1.0f, 2.0f,
1196 float out_data[size * 4];
1197 // First say that we have sRGB, linear input.
1199 format.color_space = COLORSPACE_sRGB;
1200 format.gamma_curve = GAMMA_LINEAR;
1201 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 1, size);
1203 input->set_pixel_data(data);
1204 EffectChainTester tester(nullptr, 1, size);
1205 tester.get_chain()->add_input(new BlueInput());
1206 Effect *phase1_end = tester.get_chain()->add_effect(new BouncingIdentityEffect());
1207 tester.get_chain()->add_input(input);
1208 tester.get_chain()->add_effect(new AddEffect(), phase1_end, input);
1210 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1212 expect_equal(expected_data, out_data, 4, size);
1215 // An effect that does nothing except changing its output sizes.
1216 class VirtualResizeEffect : public Effect {
1218 VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height)
1221 virtual_width(virtual_width),
1222 virtual_height(virtual_height) {}
1223 string effect_type_id() const override { return "VirtualResizeEffect"; }
1224 string output_fragment_shader() override { return read_file("identity.frag"); }
1226 bool changes_output_size() const override { return true; }
1228 void get_output_size(unsigned *width, unsigned *height,
1229 unsigned *virtual_width, unsigned *virtual_height) const override {
1230 *width = this->width;
1231 *height = this->height;
1232 *virtual_width = this->virtual_width;
1233 *virtual_height = this->virtual_height;
1237 int width, height, virtual_width, virtual_height;
1240 TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
1241 const int size = 2, bigger_size = 3;
1242 float data[size * size] = {
1246 float out_data[size * size];
1248 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1250 SizeStoringEffect *size_store = new SizeStoringEffect();
1252 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size));
1253 tester.get_chain()->add_effect(size_store);
1254 tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size));
1255 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1257 EXPECT_EQ(bigger_size, size_store->input_width);
1258 EXPECT_EQ(bigger_size, size_store->input_height);
1260 // If the resize is implemented as non-virtual, we'll fail here,
1261 // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact.
1262 expect_equal(data, out_data, size, size);
1265 // An effect that is like VirtualResizeEffect, but always has virtual and real
1266 // sizes the same (and promises this).
1267 class NonVirtualResizeEffect : public VirtualResizeEffect {
1269 NonVirtualResizeEffect(int width, int height)
1270 : VirtualResizeEffect(width, height, width, height) {}
1271 string effect_type_id() const override { return "NonVirtualResizeEffect"; }
1272 bool sets_virtual_output_size() const override { return false; }
1275 // An effect that promises one-to-one sampling (unlike IdentityEffect).
1276 class OneToOneEffect : public Effect {
1279 string effect_type_id() const override { return "OneToOneEffect"; }
1280 string output_fragment_shader() override { return read_file("identity.frag"); }
1281 bool strong_one_to_one_sampling() const override { return true; }
1284 TEST_P(WithAndWithoutComputeShaderTest, NoBounceWithOneToOneSampling) {
1286 float data[size * size] = {
1290 float out_data[size * size];
1292 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1294 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1295 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1297 if (GetParam() == "compute") {
1298 tester.get_chain()->add_effect(new IdentityComputeEffect());
1300 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1302 tester.get_chain()->add_effect(effect1);
1303 tester.get_chain()->add_effect(effect2);
1304 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1306 expect_equal(data, out_data, size, size);
1308 // The first OneToOneEffect should be in the same phase as its input.
1309 ASSERT_EQ(1u, effect1->replaced_node->incoming_links.size());
1310 EXPECT_EQ(effect1->replaced_node->incoming_links[0]->containing_phase,
1311 effect1->replaced_node->containing_phase);
1313 // The second OneToOneEffect, too.
1314 EXPECT_EQ(effect1->replaced_node->containing_phase,
1315 effect2->replaced_node->containing_phase);
1318 TEST(EffectChainTest, BounceWhenOneToOneIsBroken) {
1320 float data[size * size] = {
1324 float out_data[size * size];
1326 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1328 RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
1329 RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
1330 RewritingEffect<IdentityEffect> *effect3 = new RewritingEffect<IdentityEffect>();
1331 RewritingEffect<OneToOneEffect> *effect4 = new RewritingEffect<OneToOneEffect>();
1333 tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
1334 tester.get_chain()->add_effect(effect1);
1335 tester.get_chain()->add_effect(effect2);
1336 tester.get_chain()->add_effect(effect3);
1337 tester.get_chain()->add_effect(effect4);
1338 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1340 expect_equal(data, out_data, size, size);
1342 // The NonVirtualResizeEffect should be in a different phase from
1343 // the IdentityEffect (since the latter is not one-to-one),
1344 // ie., the chain should be broken somewhere between them, but exactly
1345 // where doesn't matter.
1346 ASSERT_EQ(1u, effect1->replaced_node->incoming_links.size());
1347 EXPECT_NE(effect1->replaced_node->incoming_links[0]->containing_phase,
1348 effect3->replaced_node->containing_phase);
1350 // The last OneToOneEffect should also be in the same phase as the
1351 // IdentityEffect (the phase was already broken).
1352 EXPECT_EQ(effect3->replaced_node->containing_phase,
1353 effect4->replaced_node->containing_phase);
1356 // Does not use EffectChainTest, so that it can construct an EffectChain without
1357 // a shared ResourcePool (which is also properly destroyed afterwards).
1358 // Also turns on debugging to test that code path.
1359 TEST(EffectChainTest, IdentityWithOwnPool) {
1360 const int width = 3, height = 2;
1365 const float expected_data[] = {
1369 float out_data[6], temp[6 * 4];
1371 EffectChain chain(width, height);
1372 MovitDebugLevel old_movit_debug_level = movit_debug_level;
1373 movit_debug_level = MOVIT_DEBUG_ON;
1376 format.color_space = COLORSPACE_sRGB;
1377 format.gamma_curve = GAMMA_LINEAR;
1379 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
1380 input->set_pixel_data(data);
1381 chain.add_input(input);
1382 chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
1385 glGenTextures(1, &texnum);
1387 glBindTexture(GL_TEXTURE_2D, texnum);
1389 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
1392 glGenFramebuffers(1, &fbo);
1394 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1396 glFramebufferTexture2D(
1398 GL_COLOR_ATTACHMENT0,
1403 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1408 chain.render_to_fbo(fbo, width, height);
1410 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1412 glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp);
1414 for (unsigned i = 0; i < 6; ++i) {
1415 out_data[i] = temp[i * 4];
1418 expect_equal(expected_data, out_data, width, height);
1420 // Reset the debug status again.
1421 movit_debug_level = old_movit_debug_level;
1424 // A dummy effect whose only purpose is to test sprintf decimal behavior.
1425 class PrintfingBlueEffect : public Effect {
1427 PrintfingBlueEffect() {}
1428 string effect_type_id() const override { return "PrintfingBlueEffect"; }
1429 string output_fragment_shader() override {
1431 ss.imbue(locale("C"));
1433 ss << "vec4 FUNCNAME(vec2 tc) { return vec4("
1434 << 0.0f << ", " << 0.0f << ", "
1435 << 0.5f << ", " << 1.0f << "); }\n";
1440 TEST(EffectChainTest, StringStreamLocalesWork) {
1441 // An example of a locale with comma instead of period as decimal separator.
1442 // Obviously, if you run on a machine without this locale available,
1443 // the test will always succeed. Note that the OpenGL driver might call
1444 // setlocale() behind-the-scenes, and that might corrupt the returned
1445 // pointer, so we need to take our own copy of it here.
1446 char *saved_locale = setlocale(LC_ALL, "nb_NO.UTF_8");
1447 if (saved_locale == nullptr) {
1448 // The locale wasn't available.
1451 saved_locale = strdup(saved_locale);
1453 0.0f, 0.0f, 0.0f, 0.0f,
1455 float expected_data[] = {
1456 0.0f, 0.0f, 0.5f, 1.0f,
1459 EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1460 tester.get_chain()->add_effect(new PrintfingBlueEffect());
1461 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1463 expect_equal(expected_data, out_data, 4, 1);
1465 setlocale(LC_ALL, saved_locale);
1469 TEST(EffectChainTest, sRGBIntermediate) {
1471 0.0f, 0.5f, 0.0f, 1.0f,
1474 EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
1475 tester.get_chain()->set_intermediate_format(GL_SRGB8);
1476 tester.get_chain()->add_effect(new IdentityEffect());
1477 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1478 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1480 EXPECT_GE(fabs(out_data[1] - data[1]), 1e-3)
1481 << "Expected sRGB not to be able to represent 0.5 exactly (got " << out_data[1] << ")";
1482 EXPECT_LT(fabs(out_data[1] - data[1]), 0.1f)
1483 << "Expected sRGB to be able to represent 0.5 approximately (got " << out_data[1] << ")";
1485 // This state should have been preserved.
1486 EXPECT_FALSE(glIsEnabled(GL_FRAMEBUFFER_SRGB));
1489 // An effect that is like IdentityEffect, but also does not require linear light.
1490 class PassThroughEffect : public IdentityEffect {
1492 PassThroughEffect() {}
1493 string effect_type_id() const override { return "PassThroughEffect"; }
1494 bool needs_linear_light() const override { return false; }
1495 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
1498 // Same, just also bouncing.
1499 class BouncingPassThroughEffect : public BouncingIdentityEffect {
1501 BouncingPassThroughEffect() {}
1502 string effect_type_id() const override { return "BouncingPassThroughEffect"; }
1503 bool needs_linear_light() const override { return false; }
1504 bool needs_texture_bounce() const override { return true; }
1505 AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
1508 TEST(EffectChainTest, Linear10bitIntermediateAccuracy) {
1509 // Note that we do the comparison in sRGB space, which is what we
1510 // typically would want; however, we do the sRGB conversion ourself
1511 // to avoid compounding errors from shader conversions into the
1513 const int size = 4096; // 12-bit.
1514 float linear_data[size], data[size], out_data[size];
1516 for (int i = 0; i < size; ++i) {
1517 linear_data[i] = i / double(size - 1);
1518 data[i] = srgb_to_linear(linear_data[i]);
1521 EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
1522 tester.get_chain()->set_intermediate_format(GL_RGB10_A2);
1523 tester.get_chain()->add_effect(new IdentityEffect());
1524 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1525 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1527 for (int i = 0; i < size; ++i) {
1528 out_data[i] = linear_to_srgb(out_data[i]);
1531 // This maximum error is pretty bad; about 6.5 levels of a 10-bit sRGB
1532 // framebuffer. (Slightly more on NVIDIA cards.)
1533 expect_equal(linear_data, out_data, size, 1, 7.5e-3, 2e-5);
1536 TEST_P(WithAndWithoutComputeShaderTest, SquareRoot10bitIntermediateAccuracy) {
1537 // Note that we do the comparison in sRGB space, which is what we
1538 // typically would want; however, we do the sRGB conversion ourself
1539 // to avoid compounding errors from shader conversions into the
1541 const int size = 4096; // 12-bit.
1542 float linear_data[size], data[size], out_data[size];
1544 for (int i = 0; i < size; ++i) {
1545 linear_data[i] = i / double(size - 1);
1546 data[i] = srgb_to_linear(linear_data[i]);
1549 EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
1550 tester.get_chain()->set_intermediate_format(GL_RGB10_A2, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION);
1551 if (GetParam() == "compute") {
1552 tester.get_chain()->add_effect(new IdentityComputeEffect());
1554 tester.get_chain()->add_effect(new IdentityEffect());
1556 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1557 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1559 for (int i = 0; i < size; ++i) {
1560 out_data[i] = linear_to_srgb(out_data[i]);
1563 // This maximum error is much better; about 0.7 levels of a 10-bit sRGB
1564 // framebuffer (ideal would be 0.5). That is an order of magnitude better
1565 // than in the linear test above. The RMS error is much better, too.
1566 expect_equal(linear_data, out_data, size, 1, 7.5e-4, 5e-6);
1569 TEST(EffectChainTest, SquareRootIntermediateIsTurnedOffForNonLinearData) {
1570 const int size = 256; // 8-bit.
1571 float data[size], out_data[size];
1573 for (int i = 0; i < size; ++i) {
1574 data[i] = i / double(size - 1);
1577 EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_601, GL_RGBA32F);
1578 tester.get_chain()->set_intermediate_format(GL_RGB8, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION);
1579 tester.get_chain()->add_effect(new PassThroughEffect());
1580 tester.get_chain()->add_effect(new BouncingPassThroughEffect());
1581 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_601);
1583 // The data should be passed through nearly exactly, since there is no effect
1584 // on the path that requires linear light. (Actually, it _is_ exact modulo
1585 // fp32 errors, but the error bounds is strictly _less than_, not zero.)
1586 expect_equal(data, out_data, size, 1, 1e-6, 1e-6);
1589 // An effect that stores which program number was last run under.
1590 class RecordingIdentityEffect : public Effect {
1592 RecordingIdentityEffect() {}
1593 string effect_type_id() const override { return "RecordingIdentityEffect"; }
1594 string output_fragment_shader() override { return read_file("identity.frag"); }
1596 GLuint last_glsl_program_num;
1597 void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num) override
1599 last_glsl_program_num = glsl_program_num;
1603 TEST(EffectChainTest, ProgramsAreClonedForMultipleThreads) {
1609 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1610 RecordingIdentityEffect *effect = new RecordingIdentityEffect();
1611 tester.get_chain()->add_effect(effect);
1612 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1614 expect_equal(data, out_data, 3, 2);
1616 ASSERT_NE(0u, effect->last_glsl_program_num);
1618 // Now pretend some other effect is using this program number;
1619 // ResourcePool will then need to clone it.
1620 ResourcePool *resource_pool = tester.get_chain()->get_resource_pool();
1621 GLuint master_program_num = resource_pool->use_glsl_program(effect->last_glsl_program_num);
1622 EXPECT_EQ(effect->last_glsl_program_num, master_program_num);
1624 // Re-run should still give the correct data, but it should have run
1625 // with a different program.
1626 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1627 expect_equal(data, out_data, 3, 2);
1628 EXPECT_NE(effect->last_glsl_program_num, master_program_num);
1630 // Release the program, and check one final time.
1631 resource_pool->unuse_glsl_program(master_program_num);
1632 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1633 expect_equal(data, out_data, 3, 2);
1636 TEST(ComputeShaderTest, Identity) {
1642 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1643 if (!movit_compute_shaders_supported) {
1644 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1647 tester.get_chain()->add_effect(new IdentityComputeEffect());
1648 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1650 expect_equal(data, out_data, 3, 2);
1653 // Like IdentityComputeEffect, but due to the alpha handling, this will be
1654 // the very last effect in the chain, which means we can't output it directly
1656 class IdentityAlphaComputeEffect : public IdentityComputeEffect {
1657 AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
1660 TEST(ComputeShaderTest, LastEffectInChain) {
1666 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1667 if (!movit_compute_shaders_supported) {
1668 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1671 tester.get_chain()->add_effect(new IdentityAlphaComputeEffect());
1672 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1674 expect_equal(data, out_data, 3, 2);
1677 TEST(ComputeShaderTest, Render8BitTo8Bit) {
1682 uint8_t out_data[6];
1683 EffectChainTester tester(nullptr, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
1684 if (!movit_compute_shaders_supported) {
1685 fprintf(stderr, "Skipping test; no support for compile shaders.\n");
1688 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 3, 2);
1689 tester.get_chain()->add_effect(new IdentityAlphaComputeEffect());
1690 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1692 expect_equal(data, out_data, 3, 2);
1695 // A compute shader to mirror the inputs, in 2x2 blocks.
1696 class MirrorComputeEffect : public Effect {
1698 MirrorComputeEffect() {}
1699 string effect_type_id() const override { return "MirrorComputeEffect"; }
1700 bool is_compute_shader() const override { return true; }
1701 string output_fragment_shader() override { return read_file("mirror.comp"); }
1702 void get_compute_dimensions(unsigned output_width, unsigned output_height,
1703 unsigned *x, unsigned *y, unsigned *z) const override {
1704 *x = output_width / 2;
1705 *y = output_height / 2;
1710 TEST(ComputeShaderTest, ComputeThenOneToOne) {
1712 0.0f, 0.25f, 0.3f, 0.8f,
1713 0.75f, 1.0f, 1.0f, 0.2f,
1715 float expected_data[] = {
1716 0.8f, 0.3f, 0.25f, 0.0f,
1717 0.2f, 1.0f, 1.0f, 0.75f,
1720 EffectChainTester tester(data, 4, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
1721 tester.get_chain()->add_effect(new MirrorComputeEffect());
1722 tester.get_chain()->add_effect(new OneToOneEffect());
1723 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1725 expect_equal(expected_data, out_data, 4, 2);
1728 // A compute shader that also resizes its input, taking the upper-left pixel
1729 // of every 2x2 group. (The shader is hard-coded to 4x2 input for simplicity.)
1730 class Downscale2xComputeEffect : public Effect {
1732 Downscale2xComputeEffect() {}
1733 string effect_type_id() const override { return "Downscale2xComputeEffect"; }
1734 bool is_compute_shader() const override { return true; }
1735 string output_fragment_shader() override { return read_file("downscale2x.comp"); }
1736 bool changes_output_size() const override { return true; }
1737 void inform_input_size(unsigned input_num, unsigned width, unsigned height) override
1739 this->width = width;
1740 this->height = height;
1742 void get_output_size(unsigned *width, unsigned *height,
1743 unsigned *virtual_width, unsigned *virtual_height) const override {
1744 *width = *virtual_width = this->width / 2;
1745 *height = *virtual_height = this->height / 2;
1749 unsigned width, height;
1752 // Even if the compute shader is not the last effect, it's the one that should decide
1753 // the output size of the phase.
1754 TEST(ComputeShaderTest, ResizingComputeThenOneToOne) {
1756 0.0f, 0.25f, 0.3f, 0.8f,
1757 0.75f, 1.0f, 1.0f, 0.2f,
1759 float expected_data[] = {
1763 EffectChainTester tester(nullptr, 2, 1);
1764 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 4, 2);
1766 RewritingEffect<Downscale2xComputeEffect> *downscale_effect = new RewritingEffect<Downscale2xComputeEffect>();
1767 tester.get_chain()->add_effect(downscale_effect);
1768 tester.get_chain()->add_effect(new OneToOneEffect());
1769 tester.get_chain()->add_effect(new BouncingIdentityEffect());
1770 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1772 expect_equal(expected_data, out_data, 2, 1);
1774 Phase *phase = downscale_effect->replaced_node->containing_phase;
1775 EXPECT_EQ(2u, phase->output_width);
1776 EXPECT_EQ(1u, phase->output_height);
1779 class StrongOneToOneAddEffect : public AddEffect {
1781 StrongOneToOneAddEffect() {}
1782 string effect_type_id() const override { return "StrongOneToOneAddEffect"; }
1783 bool strong_one_to_one_sampling() const override { return true; }
1786 TEST(ComputeShaderTest, NoTwoComputeInSamePhase) {
1788 0.0f, 0.25f, 0.3f, 0.8f,
1789 0.75f, 1.0f, 1.0f, 0.2f,
1791 float expected_data[] = {
1797 format.color_space = COLORSPACE_sRGB;
1798 format.gamma_curve = GAMMA_LINEAR;
1800 EffectChainTester tester(nullptr, 2, 1);
1802 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1803 input1->set_pixel_data(data);
1804 tester.get_chain()->add_input(input1);
1805 Effect *downscale1 = tester.get_chain()->add_effect(new Downscale2xComputeEffect());
1807 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1808 input2->set_pixel_data(data);
1809 tester.get_chain()->add_input(input2);
1810 Effect *downscale2 = tester.get_chain()->add_effect(new Downscale2xComputeEffect());
1812 tester.get_chain()->add_effect(new StrongOneToOneAddEffect(), downscale1, downscale2);
1813 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1814 expect_equal(expected_data, out_data, 2, 1);
1817 // Like the previous test, but the adder effect is not directly connected
1818 // to the compute shaders (so the status has to be propagated through those effects).
1819 TEST(ComputeShaderTest, NoTwoComputeInSamePhaseIndirect) {
1821 0.0f, 0.25f, 0.3f, 0.8f,
1822 0.75f, 1.0f, 1.0f, 0.2f,
1824 float expected_data[] = {
1830 format.color_space = COLORSPACE_sRGB;
1831 format.gamma_curve = GAMMA_LINEAR;
1833 EffectChainTester tester(nullptr, 2, 1);
1835 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1836 input1->set_pixel_data(data);
1837 tester.get_chain()->add_input(input1);
1838 tester.get_chain()->add_effect(new Downscale2xComputeEffect());
1839 Effect *identity1 = tester.get_chain()->add_effect(new OneToOneEffect());
1841 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1842 input2->set_pixel_data(data);
1843 tester.get_chain()->add_input(input2);
1844 tester.get_chain()->add_effect(new Downscale2xComputeEffect());
1845 Effect *identity2 = tester.get_chain()->add_effect(new OneToOneEffect());
1847 tester.get_chain()->add_effect(new StrongOneToOneAddEffect(), identity1, identity2);
1848 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1849 expect_equal(expected_data, out_data, 2, 1);
1852 // Like the previous test, but the adder is not strong one-to-one
1853 // (so there are two different compute shader inputs, but none of them
1854 // are in the same phase).
1855 TEST(ComputeShaderTest, BounceTextureFromTwoComputeShaders) {
1857 0.0f, 0.25f, 0.3f, 0.8f,
1858 0.75f, 1.0f, 1.0f, 0.2f,
1860 float expected_data[] = {
1866 format.color_space = COLORSPACE_sRGB;
1867 format.gamma_curve = GAMMA_LINEAR;
1869 EffectChainTester tester(nullptr, 2, 1);
1871 FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1872 input1->set_pixel_data(data);
1873 tester.get_chain()->add_input(input1);
1874 tester.get_chain()->add_effect(new Downscale2xComputeEffect());
1875 Effect *identity1 = tester.get_chain()->add_effect(new OneToOneEffect());
1877 FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 2);
1878 input2->set_pixel_data(data);
1879 tester.get_chain()->add_input(input2);
1880 tester.get_chain()->add_effect(new Downscale2xComputeEffect());
1881 Effect *identity2 = tester.get_chain()->add_effect(new OneToOneEffect());
1883 tester.get_chain()->add_effect(new AddEffect(), identity1, identity2);
1884 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
1885 expect_equal(expected_data, out_data, 2, 1);
1888 } // namespace movit