X-Git-Url: https://git.sesse.net/?p=movit;a=blobdiff_plain;f=effect_chain_test.cpp;h=e7f25e53861045ac8b0b49581ed91c173709b6b6;hp=ce3870a5b166caca74ccfa589d013805f7e28f8b;hb=572e7aaa57028d7eda4bc445a6249637134a2b02;hpb=f6ccf310656ba0c218574654fbf8e794041efeaf diff --git a/effect_chain_test.cpp b/effect_chain_test.cpp index ce3870a..e7f25e5 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) { @@ -101,7 +102,7 @@ public: template class RewritingEffect : public Effect { public: - RewritingEffect() : effect(new T()) {} + 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) { @@ -148,7 +149,7 @@ TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) { 1.0f, 0.9771f, 0.8983f, 0.0f, }; - float out_data[2]; + float out_data[4]; EffectChainTester tester(NULL, 2, 2); tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB); RewritingEffect *effect = new RewritingEffect(); @@ -343,7 +344,7 @@ TEST(EffectChainTest, IdentityThroughAlphaConversions) { 0.0f, 0.2f, 0.2f, 0.3f, 0.1f, 0.0f, 1.0f, 1.0f, }; - float out_data[6]; + float out_data[4 * size]; 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); @@ -399,7 +400,7 @@ private: // which outputs blank alpha. class RewritingToBlueInput : public Input { public: - RewritingToBlueInput() { register_int("needs_mipmaps", &needs_mipmaps); } + RewritingToBlueInput() : blue_node(NULL) { 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) { @@ -438,7 +439,7 @@ TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) { 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); + tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED); Node *node = input->blue_node; EXPECT_EQ(0, node->incoming_links.size()); @@ -640,3 +641,259 @@ TEST(EffectChainTest, DiamondGraph) { 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); +} + +TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) { + float data[] = { + 0.735f, 0.0f, + 0.735f, 0.0f, + }; + float expected_data[] = { + 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly. + 0.0f, 0.5f, + }; + float out_data[2 * 2]; + + EffectChainTester tester(NULL, 2, 2); + tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB); + + // MirrorEffect does not get linear light, so the conversions will be + // inserted after it, not before. + RewritingEffect *effect = new RewritingEffect(); + tester.get_chain()->add_effect(effect); + + Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect); + Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect); + tester.get_chain()->add_effect(new AddEffect(), identity1, identity2); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(expected_data, out_data, 2, 2); + + 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()); + EXPECT_EQ("GammaExpansionEffect", node->outgoing_links[0]->effect->effect_type_id()); +} + +TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) { + float data[] = { + 0.5f, 0.0f, + 0.5f, 0.0f, + }; + float expected_data[] = { + 0.0f, 0.5f, // 0.5 and not 1.0, since AddEffect doesn't clamp alpha properly. + 0.0f, 0.5f, + }; + float out_data[2 * 2]; + + EffectChainTester tester(NULL, 2, 2); + tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR); + + // MirrorEffect does not get linear light, so the conversions will be + // inserted after it, not before. + RewritingEffect *effect = new RewritingEffect(); + tester.get_chain()->add_effect(effect); + + Effect *identity1 = tester.get_chain()->add_effect(new IdentityEffect(), effect); + Effect *identity2 = tester.get_chain()->add_effect(new IdentityEffect(), effect); + tester.get_chain()->add_effect(new AddEffect(), identity1, identity2); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(expected_data, out_data, 2, 2); + + 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()); + EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id()); +} + +// An effect that does nothing, but requests texture bounce and stores +// its input size. +class SizeStoringEffect : public BouncingIdentityEffect { +public: + SizeStoringEffect() : input_width(-1), input_height(-1) {} + virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) { + assert(input_num == 0); + input_width = width; + input_height = height; + } + virtual std::string effect_type_id() const { return "SizeStoringEffect"; } + + int input_width, input_height; +}; + +TEST(EffectChainTest, SameInputsGiveSameOutputs) { + float data[2 * 2] = { + 0.0f, 0.0f, + 0.0f, 0.0f, + }; + float out_data[2 * 2]; + + EffectChainTester tester(NULL, 4, 3); // Note non-square aspect. + + ImageFormat format; + format.color_space = COLORSPACE_sRGB; + format.gamma_curve = GAMMA_LINEAR; + + FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2); + input1->set_pixel_data(data); + + FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 2, 2); + input2->set_pixel_data(data); + + SizeStoringEffect *input_store = new SizeStoringEffect(); + + tester.get_chain()->add_input(input1); + tester.get_chain()->add_input(input2); + tester.get_chain()->add_effect(new AddEffect(), input1, input2); + tester.get_chain()->add_effect(input_store); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + EXPECT_EQ(2, input_store->input_width); + EXPECT_EQ(2, input_store->input_height); +} + +TEST(EffectChainTest, AspectRatioConversion) { + float data1[4 * 3] = { + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + }; + float data2[7 * 7] = { + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + }; + + // The right conversion here is that the 7x7 image decides the size, + // since it is the biggest, so everything is scaled up to 9x7 + // (keep the height, round the width 9.333 to 9). + float out_data[9 * 7]; + + EffectChainTester tester(NULL, 4, 3); + + ImageFormat format; + format.color_space = COLORSPACE_sRGB; + format.gamma_curve = GAMMA_LINEAR; + + FlatInput *input1 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 4, 3); + input1->set_pixel_data(data1); + + FlatInput *input2 = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 7, 7); + input2->set_pixel_data(data2); + + SizeStoringEffect *input_store = new SizeStoringEffect(); + + tester.get_chain()->add_input(input1); + tester.get_chain()->add_input(input2); + tester.get_chain()->add_effect(new AddEffect(), input1, input2); + tester.get_chain()->add_effect(input_store); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + EXPECT_EQ(9, input_store->input_width); + EXPECT_EQ(7, input_store->input_height); +} + +// An effect that does nothing except changing its output sizes. +class VirtualResizeEffect : public Effect { +public: + VirtualResizeEffect(int width, int height, int virtual_width, int virtual_height) + : width(width), + height(height), + virtual_width(virtual_width), + virtual_height(virtual_height) {} + virtual std::string effect_type_id() const { return "VirtualResizeEffect"; } + std::string output_fragment_shader() { return read_file("identity.frag"); } + + virtual bool changes_output_size() const { return true; } + + virtual void get_output_size(unsigned *width, unsigned *height, + unsigned *virtual_width, unsigned *virtual_height) const { + *width = this->width; + *height = this->height; + *virtual_width = this->virtual_width; + *virtual_height = this->virtual_height; + } + +private: + int width, height, virtual_width, virtual_height; +}; + +TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) { + const int size = 2, bigger_size = 3; + float data[size * size] = { + 1.0f, 0.0f, + 0.0f, 1.0f, + }; + float out_data[size * size]; + + EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR); + + SizeStoringEffect *size_store = new SizeStoringEffect(); + + tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, bigger_size, bigger_size)); + tester.get_chain()->add_effect(size_store); + tester.get_chain()->add_effect(new VirtualResizeEffect(size, size, size, size)); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + EXPECT_EQ(bigger_size, size_store->input_width); + EXPECT_EQ(bigger_size, size_store->input_height); + + // If the resize is implemented as non-virtual, we'll fail here, + // since bilinear scaling from 2x2 → 3x3 → 2x2 is not very exact. + expect_equal(data, out_data, size, size); +}