X-Git-Url: https://git.sesse.net/?p=movit;a=blobdiff_plain;f=effect_chain_test.cpp;h=75491030b05b66f27fef22a17ca8e5f7ccf66dcd;hp=ace160f1a015a11464eb586e1fcd5c44e714f331;hb=8e37939a9bc1c902d0929dfae70e8d515de6e684;hpb=a616ded3842994840ce0cfa365d259f602493779 diff --git a/effect_chain_test.cpp b/effect_chain_test.cpp index ace160f..7549103 100644 --- a/effect_chain_test.cpp +++ b/effect_chain_test.cpp @@ -51,6 +51,7 @@ public: virtual std::string effect_type_id() const { return "IdentityEffect"; } std::string output_fragment_shader() { return read_file("identity.frag"); } bool needs_texture_bounce() const { return true; } + AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; } }; TEST(EffectChainTest, TextureBouncePreservesIdentity) { @@ -89,27 +90,30 @@ public: InvertEffect() {} virtual std::string effect_type_id() const { return "InvertEffect"; } std::string output_fragment_shader() { return read_file("invert_effect.frag"); } + + // A real invert would actually care about its alpha, + // but in this unit test, it only complicates things. + virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; } }; // Like IdentityEffect, but rewrites itself out of the loop, -// splicing in a InvertEffect instead. Also stores the new node, -// so we later can check that there are gamma conversion effects -// on both sides. -class RewritingToInvertEffect : public Effect { +// splicing in a different effect instead. Also stores the new node, +// so we later can check whatever properties we'd like about the graph. +template +class RewritingEffect : public Effect { public: - RewritingToInvertEffect() {} - virtual std::string effect_type_id() const { return "RewritingToInvertEffect"; } + RewritingEffect() : effect(new T()), replaced_node(NULL) {} + virtual std::string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; } std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); } virtual void rewrite_graph(EffectChain *graph, Node *self) { - Node *invert_node = graph->add_node(new InvertEffect()); - graph->replace_receiver(self, invert_node); - graph->replace_sender(self, invert_node); - + replaced_node = graph->add_node(effect); + graph->replace_receiver(self, replaced_node); + graph->replace_sender(self, replaced_node); self->disabled = true; - this->invert_node = invert_node; } - Node *invert_node; + T *effect; + Node *replaced_node; }; TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) { @@ -123,11 +127,11 @@ TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) { }; float out_data[6]; EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB); - RewritingToInvertEffect *effect = new RewritingToInvertEffect(); + RewritingEffect *effect = new RewritingEffect(); tester.get_chain()->add_effect(effect); tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB); - Node *node = effect->invert_node; + Node *node = effect->replaced_node; ASSERT_EQ(1, node->incoming_links.size()); ASSERT_EQ(1, node->outgoing_links.size()); EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id()); @@ -148,11 +152,11 @@ TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) { float out_data[2]; EffectChainTester tester(NULL, 2, 2); tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB); - RewritingToInvertEffect *effect = new RewritingToInvertEffect(); + RewritingEffect *effect = new RewritingEffect(); tester.get_chain()->add_effect(effect); tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB); - Node *node = effect->invert_node; + Node *node = effect->replaced_node; ASSERT_EQ(1, node->incoming_links.size()); ASSERT_EQ(1, node->outgoing_links.size()); EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id()); @@ -172,11 +176,11 @@ TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) { }; float out_data[6]; EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR); - RewritingToInvertEffect *effect = new RewritingToInvertEffect(); + RewritingEffect *effect = new RewritingEffect(); tester.get_chain()->add_effect(effect); tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR); - Node *node = effect->invert_node; + Node *node = effect->replaced_node; ASSERT_EQ(1, node->incoming_links.size()); ASSERT_EQ(1, node->outgoing_links.size()); EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id()); @@ -240,25 +244,6 @@ TEST(EffectChainTester, HandlesInputChangingColorspace) { expect_equal(data, out_data, 4, 1); } -// Like RewritingToInvertEffect, but splicing in a MirrorEffect instead, -// which does not need linear light or sRGB primaries. -class RewritingToMirrorEffect : public Effect { -public: - RewritingToMirrorEffect() {} - virtual std::string effect_type_id() const { return "RewritingToMirrorEffect"; } - std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); } - virtual void rewrite_graph(EffectChain *graph, Node *self) { - Node *mirror_node = graph->add_node(new MirrorEffect()); - graph->replace_receiver(self, mirror_node); - graph->replace_sender(self, mirror_node); - - self->disabled = true; - this->mirror_node = mirror_node; - } - - Node *mirror_node; -}; - TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) { float data[] = { 0.0f, 0.25f, 0.3f, @@ -270,11 +255,11 @@ TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) { }; float out_data[6]; EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB); - RewritingToMirrorEffect *effect = new RewritingToMirrorEffect(); + RewritingEffect *effect = new RewritingEffect(); tester.get_chain()->add_effect(effect); tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB); - Node *node = effect->mirror_node; + Node *node = effect->replaced_node; ASSERT_EQ(1, node->incoming_links.size()); EXPECT_EQ(0, node->outgoing_links.size()); EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id()); @@ -293,11 +278,11 @@ TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) { }; float out_data[6]; EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR); - RewritingToMirrorEffect *effect = new RewritingToMirrorEffect(); + RewritingEffect *effect = new RewritingEffect(); tester.get_chain()->add_effect(effect); tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR); - Node *node = effect->mirror_node; + Node *node = effect->replaced_node; ASSERT_EQ(1, node->incoming_links.size()); EXPECT_EQ(0, node->outgoing_links.size()); EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id()); @@ -351,6 +336,118 @@ TEST(EffectChainTest, IdentityThroughRec709) { expect_equal(data, out_data, 256, 1); } +// The identity effect needs premultiplied alpha, and thus will get conversions on both sides. +TEST(EffectChainTest, IdentityThroughAlphaConversions) { + const int size = 3; + float data[4 * size] = { + 0.8f, 0.0f, 0.0f, 0.5f, + 0.0f, 0.2f, 0.2f, 0.3f, + 0.1f, 0.0f, 1.0f, 1.0f, + }; + float out_data[6]; + EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR); + tester.get_chain()->add_effect(new IdentityEffect()); + tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(data, out_data, 4, size); +} + +TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) { + const int size = 3; + float data[4 * size] = { + 0.8f, 0.0f, 0.0f, 0.5f, + 0.0f, 0.2f, 0.2f, 0.3f, + 0.1f, 0.0f, 1.0f, 1.0f, + }; + float expected_data[4 * size] = { + 0.1f, 0.0f, 1.0f, 1.0f, + 0.0f, 0.2f, 0.2f, 0.3f, + 0.8f, 0.0f, 0.0f, 0.5f, + }; + float out_data[4 * size]; + EffectChainTester tester(data, size, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR); + RewritingEffect *effect = new RewritingEffect(); + tester.get_chain()->add_effect(effect); + tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR); + + Node *node = effect->replaced_node; + ASSERT_EQ(1, node->incoming_links.size()); + EXPECT_EQ(0, node->outgoing_links.size()); + EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id()); + + expect_equal(expected_data, out_data, 4, size); +} + +// An input that outputs only blue, which has blank alpha. +class BlueInput : public Input { +public: + BlueInput() { register_int("needs_mipmaps", &needs_mipmaps); } + virtual std::string effect_type_id() const { return "IdentityEffect"; } + std::string output_fragment_shader() { return read_file("blue.frag"); } + virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; } + virtual void finalize() {} + virtual bool can_output_linear_gamma() const { return true; } + virtual unsigned get_width() const { return 1; } + virtual unsigned get_height() const { return 1; } + virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; } + virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; } + +private: + int needs_mipmaps; +}; + +// Like RewritingEffect, but splicing in a BlueInput instead, +// which outputs blank alpha. +class RewritingToBlueInput : public Input { +public: + RewritingToBlueInput() { register_int("needs_mipmaps", &needs_mipmaps); } + virtual std::string effect_type_id() const { return "RewritingToBlueInput"; } + std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); } + virtual void rewrite_graph(EffectChain *graph, Node *self) { + Node *blue_node = graph->add_node(new BlueInput()); + graph->replace_receiver(self, blue_node); + graph->replace_sender(self, blue_node); + + self->disabled = true; + this->blue_node = blue_node; + } + + // Dummy values that we need to implement because we inherit from Input. + // Same as BlueInput. + virtual AlphaHandling alpha_handling() const { return OUTPUT_BLANK_ALPHA; } + virtual void finalize() {} + virtual bool can_output_linear_gamma() const { return true; } + virtual unsigned get_width() const { return 1; } + virtual unsigned get_height() const { return 1; } + virtual Colorspace get_color_space() const { return COLORSPACE_sRGB; } + virtual GammaCurve get_gamma_curve() const { return GAMMA_LINEAR; } + + Node *blue_node; + +private: + int needs_mipmaps; +}; + +TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) { + const int size = 3; + float data[4 * size] = { + 0.0f, 0.0f, 1.0f, 1.0f, + 0.0f, 0.0f, 1.0f, 1.0f, + 0.0f, 0.0f, 1.0f, 1.0f, + }; + float out_data[4 * size]; + EffectChainTester tester(NULL, size, 1); + RewritingToBlueInput *input = new RewritingToBlueInput(); + tester.get_chain()->add_input(input); + tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_PREMULTIPLIED); + + Node *node = input->blue_node; + EXPECT_EQ(0, node->incoming_links.size()); + EXPECT_EQ(0, node->outgoing_links.size()); + + expect_equal(data, out_data, 4, size); +} + // Effectively scales down its input linearly by 4x (and repeating it), // which is not attainable without mipmaps. class MipmapNeedingEffect : public Effect { @@ -478,3 +575,118 @@ TEST(EffectChainTest, ResizeDownByFourThenUpByFour) { expect_equal(expected_data, out_data, 4, 16); } + +// An effect that multiplies with a constant. Used below. +class MultiplyEffect : public Effect { +public: + MultiplyEffect() { register_float("factor", &factor); } + virtual std::string effect_type_id() const { return "MultiplyEffect"; } + std::string output_fragment_shader() { return read_file("multiply.frag"); } + virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; } + +private: + float factor; +}; + +// An effect that adds its two inputs together. Used below. +class AddEffect : public Effect { +public: + AddEffect() {} + virtual std::string effect_type_id() const { return "AddEffect"; } + std::string output_fragment_shader() { return read_file("add.frag"); } + virtual unsigned num_inputs() const { return 2; } + virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; } +}; + +// Constructs the graph +// +// FlatInput | +// / \ | +// MultiplyEffect MultiplyEffect | +// \ / | +// AddEffect | +// +// and verifies that it gives the correct output. +TEST(EffectChainTest, DiamondGraph) { + float data[] = { + 1.0f, 1.0f, + 1.0f, 0.0f, + }; + float expected_data[] = { + 2.5f, 2.5f, + 2.5f, 0.0f, + }; + float out_data[2 * 2]; + + MultiplyEffect *mul_half = new MultiplyEffect(); + ASSERT_TRUE(mul_half->set_float("factor", 0.5f)); + + MultiplyEffect *mul_two = new MultiplyEffect(); + ASSERT_TRUE(mul_two->set_float("factor", 2.0f)); + + EffectChainTester tester(NULL, 2, 2); + + ImageFormat format; + format.color_space = COLORSPACE_sRGB; + format.gamma_curve = GAMMA_LINEAR; + + FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2); + input->set_pixel_data(data); + + tester.get_chain()->add_input(input); + tester.get_chain()->add_effect(mul_half, input); + tester.get_chain()->add_effect(mul_two, input); + tester.get_chain()->add_effect(new AddEffect(), mul_half, mul_two); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(expected_data, out_data, 2, 2); +} + +// Constructs the graph +// +// FlatInput | +// / \ | +// MultiplyEffect MultiplyEffect | +// \ | | +// \ BouncingIdentityEffect | +// \ / | +// AddEffect | +// +// and verifies that it gives the correct output. +TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) { + float data[] = { + 1.0f, 1.0f, + 1.0f, 0.0f, + }; + float expected_data[] = { + 2.5f, 2.5f, + 2.5f, 0.0f, + }; + float out_data[2 * 2]; + + MultiplyEffect *mul_half = new MultiplyEffect(); + ASSERT_TRUE(mul_half->set_float("factor", 0.5f)); + + MultiplyEffect *mul_two = new MultiplyEffect(); + ASSERT_TRUE(mul_two->set_float("factor", 2.0f)); + + BouncingIdentityEffect *bounce = new BouncingIdentityEffect(); + + EffectChainTester tester(NULL, 2, 2); + + ImageFormat format; + format.color_space = COLORSPACE_sRGB; + format.gamma_curve = GAMMA_LINEAR; + + FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2); + input->set_pixel_data(data); + + tester.get_chain()->add_input(input); + tester.get_chain()->add_effect(mul_half, input); + tester.get_chain()->add_effect(mul_two, input); + tester.get_chain()->add_effect(bounce, mul_two); + tester.get_chain()->add_effect(new AddEffect(), mul_half, bounce); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(expected_data, out_data, 2, 2); +}