+ 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_FORMAT_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);
+}
+
+// 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<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
+ 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<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
+ 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 string effect_type_id() const { return "MipmapNeedingEffect"; }
+ string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
+ void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
+ {
+ glActiveTexture(GL_TEXTURE0);
+ 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();
+ }
+};
+
+TEST(EffectChainTest, MipmapGenerationWorks) {
+ float data[] = { // In 4x4 blocks.
+ 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, 1.0f,
+
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.5f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f,
+
+ 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f,
+
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ };
+ float expected_data[] = { // Repeated four times each way.
+ 0.125f, 0.125f, 0.125f, 0.125f,
+ 0.09375f, 0.09375f, 0.09375f, 0.09375f,
+ 1.0f, 1.0f, 1.0f, 1.0f,
+ 0.25f, 0.25f, 0.25f, 0.25f,
+
+ 0.125f, 0.125f, 0.125f, 0.125f,
+ 0.09375f, 0.09375f, 0.09375f, 0.09375f,
+ 1.0f, 1.0f, 1.0f, 1.0f,
+ 0.25f, 0.25f, 0.25f, 0.25f,
+
+ 0.125f, 0.125f, 0.125f, 0.125f,
+ 0.09375f, 0.09375f, 0.09375f, 0.09375f,
+ 1.0f, 1.0f, 1.0f, 1.0f,
+ 0.25f, 0.25f, 0.25f, 0.25f,
+
+ 0.125f, 0.125f, 0.125f, 0.125f,
+ 0.09375f, 0.09375f, 0.09375f, 0.09375f,
+ 1.0f, 1.0f, 1.0f, 1.0f,
+ 0.25f, 0.25f, 0.25f, 0.25f,
+ };
+ float out_data[4 * 16];
+ EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
+ tester.get_chain()->add_effect(new MipmapNeedingEffect());
+ tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
+
+ expect_equal(expected_data, out_data, 4, 16);
+}
+
+TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
+ float data[] = { // In 4x4 blocks.
+ 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, 1.0f,
+
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.5f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f,
+
+ 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f,
+
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ };
+ float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
+ 0.1250f, 0.1250f, 0.1250f, 0.1250f,
+ 0.1250f, 0.1250f, 0.1250f, 0.1250f,
+ 0.1211f, 0.1211f, 0.1211f, 0.1211f,
+ 0.1133f, 0.1133f, 0.1133f, 0.1133f,
+ 0.1055f, 0.1055f, 0.1055f, 0.1055f,
+ 0.0977f, 0.0977f, 0.0977f, 0.0977f,
+ 0.2070f, 0.2070f, 0.2070f, 0.2070f,
+ 0.4336f, 0.4336f, 0.4336f, 0.4336f,
+ 0.6602f, 0.6602f, 0.6602f, 0.6602f,
+ 0.8867f, 0.8867f, 0.8867f, 0.8867f,
+ 0.9062f, 0.9062f, 0.9062f, 0.9062f,
+ 0.7188f, 0.7188f, 0.7188f, 0.7188f,
+ 0.5312f, 0.5312f, 0.5312f, 0.5312f,
+ 0.3438f, 0.3438f, 0.3438f, 0.3438f,
+ 0.2500f, 0.2500f, 0.2500f, 0.2500f,
+ 0.2500f, 0.2500f, 0.2500f, 0.2500f,
+ };
+ float out_data[4 * 16];
+
+ ResizeEffect *downscale = new ResizeEffect();
+ ASSERT_TRUE(downscale->set_int("width", 1));
+ ASSERT_TRUE(downscale->set_int("height", 4));
+
+ ResizeEffect *upscale = new ResizeEffect();
+ ASSERT_TRUE(upscale->set_int("width", 4));
+ ASSERT_TRUE(upscale->set_int("height", 16));
+
+ EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
+ tester.get_chain()->add_effect(downscale);
+ tester.get_chain()->add_effect(upscale);
+ tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
+
+ expect_equal(expected_data, out_data, 4, 16);
+}
+
+// An effect that adds its two inputs together. Used below.
+class AddEffect : public Effect {
+public:
+ AddEffect() {}
+ 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; }