X-Git-Url: https://git.sesse.net/?p=movit;a=blobdiff_plain;f=effect_chain_test.cpp;h=673e9cc3665fd1acac09d9815eaa84947d416e22;hp=762447aad99018bcb885fc3651fb00a46df89a48;hb=131e7179b596f8268fd80c32312849da83581e55;hpb=fc55857d9ccf1edcc141fa0853a8bf2d6b40b4dc diff --git a/effect_chain_test.cpp b/effect_chain_test.cpp index 762447a..673e9cc 100644 --- a/effect_chain_test.cpp +++ b/effect_chain_test.cpp @@ -3,13 +3,23 @@ // Note that this also contains the tests for some of the simpler effects. #include +#include +#include "effect.h" #include "effect_chain.h" #include "flat_input.h" #include "gtest/gtest.h" +#include "init.h" +#include "input.h" #include "mirror_effect.h" +#include "multiply_effect.h" #include "resize_effect.h" #include "test_util.h" +#include "util.h" + +using namespace std; + +namespace movit { TEST(EffectChainTest, EmptyChain) { float data[] = { @@ -27,8 +37,8 @@ TEST(EffectChainTest, EmptyChain) { class IdentityEffect : public Effect { public: IdentityEffect() {} - virtual std::string effect_type_id() const { return "IdentityEffect"; } - std::string output_fragment_shader() { return read_file("identity.frag"); } + virtual string effect_type_id() const { return "IdentityEffect"; } + string output_fragment_shader() { return read_file("identity.frag"); } }; TEST(EffectChainTest, Identity) { @@ -48,8 +58,8 @@ TEST(EffectChainTest, Identity) { class BouncingIdentityEffect : public Effect { public: BouncingIdentityEffect() {} - virtual std::string effect_type_id() const { return "IdentityEffect"; } - std::string output_fragment_shader() { return read_file("identity.frag"); } + virtual string effect_type_id() const { return "IdentityEffect"; } + 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; } }; @@ -88,8 +98,8 @@ TEST(MirrorTest, BasicTest) { class InvertEffect : public Effect { public: InvertEffect() {} - virtual std::string effect_type_id() const { return "InvertEffect"; } - std::string output_fragment_shader() { return read_file("invert_effect.frag"); } + virtual string effect_type_id() const { return "InvertEffect"; } + 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. @@ -103,8 +113,8 @@ template class RewritingEffect : public Effect { public: 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 string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; } + string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); } virtual void rewrite_graph(EffectChain *graph, Node *self) { replaced_node = graph->add_node(effect); graph->replace_receiver(self, replaced_node); @@ -197,7 +207,7 @@ public: : FlatInput(format, pixel_format, type, width, height), overridden_color_space(format.color_space), overridden_gamma_curve(format.gamma_curve) {} - virtual std::string effect_type_id() const { return "UnknownColorspaceInput"; } + virtual string effect_type_id() const { return "UnknownColorspaceInput"; } void set_color_space(Colorspace colorspace) { overridden_color_space = colorspace; @@ -213,7 +223,7 @@ private: GammaCurve overridden_gamma_curve; }; -TEST(EffectChainTester, HandlesInputChangingColorspace) { +TEST(EffectChainTest, HandlesInputChangingColorspace) { const int size = 4; float data[size] = { @@ -382,8 +392,8 @@ TEST(EffectChainTest, NoAlphaConversionsWhenPremultipliedAlphaNotNeeded) { 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 string effect_type_id() const { return "IdentityEffect"; } + 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; } @@ -401,8 +411,8 @@ private: class RewritingToBlueInput : public Input { public: 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 string effect_type_id() const { return "RewritingToBlueInput"; } + 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); @@ -439,7 +449,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()); @@ -448,23 +458,91 @@ TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) { expect_equal(data, out_data, 4, size); } +// An effect that does nothing, and specifies that it preserves blank alpha. +class BlankAlphaPreservingEffect : public Effect { +public: + BlankAlphaPreservingEffect() {} + virtual string effect_type_id() const { return "BlankAlphaPreservingEffect"; } + string output_fragment_shader() { return read_file("identity.frag"); } + virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; } +}; + +TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) { + 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); + tester.get_chain()->add_input(new BlueInput()); + tester.get_chain()->add_effect(new BlankAlphaPreservingEffect()); + RewritingEffect *effect = new RewritingEffect(); + tester.get_chain()->add_effect(effect); + tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); + + Node *node = effect->replaced_node; + EXPECT_EQ(1, node->incoming_links.size()); + EXPECT_EQ(0, node->outgoing_links.size()); + + expect_equal(data, out_data, 4, size); +} + +// This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect; +// just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect, +// an alpha conversion _should_ be inserted at the very end. (There is some overlap +// with other tests.) +TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) { + 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); + tester.get_chain()->add_input(new BlueInput()); + tester.get_chain()->add_effect(new IdentityEffect()); // Not BlankAlphaPreservingEffect. + RewritingEffect *effect = new RewritingEffect(); + tester.get_chain()->add_effect(effect); + tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); + + Node *node = effect->replaced_node; + EXPECT_EQ(1, node->incoming_links.size()); + EXPECT_EQ(1, node->outgoing_links.size()); + EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id()); + + 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 { public: MipmapNeedingEffect() {} virtual bool needs_mipmaps() const { return true; } - virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; } - std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); } - void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num) + + // To be allowed to mess with the sampler state. + virtual bool needs_texture_bounce() const { return true; } + + virtual string effect_type_id() const { return "MipmapNeedingEffect"; } + string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); } + virtual void inform_added(EffectChain *chain) { this->chain = chain; } + + void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num) { - glActiveTexture(GL_TEXTURE0); + Node *self = chain->find_node_for_effect(this); + glActiveTexture(chain->get_input_sampler(self, 0)); check_error(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); check_error(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); check_error(); } + +private: + EffectChain *chain; }; TEST(EffectChainTest, MipmapGenerationWorks) { @@ -576,24 +654,12 @@ 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 string effect_type_id() const { return "AddEffect"; } + 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; } }; @@ -618,11 +684,14 @@ TEST(EffectChainTest, DiamondGraph) { }; float out_data[2 * 2]; + const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f }; + const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f }; + MultiplyEffect *mul_half = new MultiplyEffect(); - ASSERT_TRUE(mul_half->set_float("factor", 0.5f)); + ASSERT_TRUE(mul_half->set_vec4("factor", half)); MultiplyEffect *mul_two = new MultiplyEffect(); - ASSERT_TRUE(mul_two->set_float("factor", 2.0f)); + ASSERT_TRUE(mul_two->set_vec4("factor", two)); EffectChainTester tester(NULL, 2, 2); @@ -664,11 +733,14 @@ TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) { }; float out_data[2 * 2]; + const float half[] = { 0.5f, 0.5f, 0.5f, 0.5f }; + const float two[] = { 2.0f, 2.0f, 2.0f, 0.5f }; + MultiplyEffect *mul_half = new MultiplyEffect(); - ASSERT_TRUE(mul_half->set_float("factor", 0.5f)); + ASSERT_TRUE(mul_half->set_vec4("factor", half)); MultiplyEffect *mul_two = new MultiplyEffect(); - ASSERT_TRUE(mul_two->set_float("factor", 2.0f)); + ASSERT_TRUE(mul_two->set_vec4("factor", two)); BouncingIdentityEffect *bounce = new BouncingIdentityEffect(); @@ -767,10 +839,42 @@ public: input_width = width; input_height = height; } + virtual 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, @@ -815,3 +919,122 @@ TEST(EffectChainTest, AspectRatioConversion) { 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 string effect_type_id() const { return "VirtualResizeEffect"; } + 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); +} + +// Does not use EffectChainTest, so that it can construct an EffectChain without +// a shared ResourcePool (which is also properly destroyed afterwards). +// Also turns on debugging to test that code path. +TEST(EffectChainTest, IdentityWithOwnPool) { + const int width = 3, height = 2; + float data[] = { + 0.0f, 0.25f, 0.3f, + 0.75f, 1.0f, 1.0f, + }; + const float expected_data[] = { + 0.75f, 1.0f, 1.0f, + 0.0f, 0.25f, 0.3f, + }; + float out_data[6], temp[6 * 4]; + + EffectChain chain(width, height); + movit_debug_level = MOVIT_DEBUG_ON; + + ImageFormat format; + format.color_space = COLORSPACE_sRGB; + format.gamma_curve = GAMMA_LINEAR; + + FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height); + input->set_pixel_data(data); + chain.add_input(input); + chain.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); + + GLuint texnum, fbo; + glGenTextures(1, &texnum); + check_error(); + glBindTexture(GL_TEXTURE_2D, texnum); + check_error(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, NULL); + check_error(); + + glGenFramebuffers(1, &fbo); + check_error(); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + check_error(); + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + texnum, + 0); + check_error(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + check_error(); + + chain.finalize(); + + chain.render_to_fbo(fbo, width, height); + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + check_error(); + glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, temp); + check_error(); + for (unsigned i = 0; i < 6; ++i) { + out_data[i] = temp[i * 4]; + } + + expect_equal(expected_data, out_data, width, height); + + // Reset the debug status again. + movit_debug_level = MOVIT_DEBUG_OFF; +} + +} // namespace movit