In resizing effects, add the notion of a “virtual output size”.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Fri, 1 Feb 2013 00:03:00 +0000 (01:03 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Fri, 1 Feb 2013 00:03:00 +0000 (01:03 +0100)
This is the size that the effect wants to be perceived as for the next
effect in the chain, which is useful if you e.g. have a blur and then
padding. Even though the blur might rescale the image down from e.g.
512x512 to 128x128 before blurring (in case of a large blur), and this
size is what actually comes out in the texture at the other end,
it still wants to be treated as a 512x512 image when adding padding.

Reported by Christophe Thommeret.

blur_effect.cpp
blur_effect.h
effect.h
effect_chain.cpp
effect_chain.h
effect_chain_test.cpp
padding_effect.cpp
padding_effect.h
resample_effect.h
resize_effect.cpp
resize_effect.h

index 7723853..d62b056 100644 (file)
@@ -64,10 +64,14 @@ void BlurEffect::update_radius()
        bool ok = hpass->set_float("radius", adjusted_radius);
        ok |= hpass->set_int("width", mipmap_width);
        ok |= hpass->set_int("height", mipmap_height);
+       ok |= hpass->set_int("virtual_width", mipmap_width);
+       ok |= hpass->set_int("virtual_height", mipmap_height);
 
        ok |= vpass->set_float("radius", adjusted_radius);
        ok |= vpass->set_int("width", mipmap_width);
        ok |= vpass->set_int("height", mipmap_height);
+       ok |= vpass->set_int("virtual_width", input_width);
+       ok |= vpass->set_int("virtual_height", input_height);
 
        assert(ok);
 }
@@ -92,6 +96,8 @@ SingleBlurPassEffect::SingleBlurPassEffect(BlurEffect *parent)
        register_int("direction", (int *)&direction);
        register_int("width", &width);
        register_int("height", &height);
+       register_int("virtual_width", &virtual_width);
+       register_int("virtual_height", &virtual_height);
 }
 
 std::string SingleBlurPassEffect::output_fragment_shader()
index b680947..792014b 100644 (file)
@@ -66,9 +66,11 @@ public:
        }
        virtual bool changes_output_size() const { return true; }
 
-       virtual void get_output_size(unsigned *width, unsigned *height) const {
+       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;
        }
 
        void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
@@ -80,7 +82,7 @@ private:
        BlurEffect *parent;
        float radius;
        Direction direction;
-       int width, height;
+       int width, height, virtual_width, virtual_height;
 };
 
 #endif // !defined(_BLUR_EFFECT_H)
index d7db299..c911f27 100644 (file)
--- a/effect.h
+++ b/effect.h
@@ -168,11 +168,16 @@ public:
        virtual bool changes_output_size() const { return false; }
 
        // If changes_output_size() is true, you must implement this to tell
-       // the framework what output size you want.
+       // the framework what output size you want. Also, you can set a
+       // virtual width/height, which is the size the next effect (if any)
+       // will _think_ your data is in. This is primarily useful if you are
+       // relying on getting OpenGL's bilinear resizing for free; otherwise,
+       // your virtual_width/virtual_height should be the same as width/height.
        //
        // Note that it is explicitly allowed to change width and height
        // from frame to frame; EffectChain will reallocate textures as needed.
-       virtual void get_output_size(unsigned *width, unsigned *height) const {
+       virtual void get_output_size(unsigned *width, unsigned *height,
+                                    unsigned *virtual_width, unsigned *virtual_height) const {
                assert(false);
        }
 
index 9aaa748..8a7ea9a 100644 (file)
@@ -618,8 +618,8 @@ void EffectChain::inform_input_sizes(Phase *phase)
        }
        for (unsigned i = 0; i < phase->inputs.size(); ++i) {
                Node *input = phase->inputs[i];
-               input->output_width = input->phase->output_width;
-               input->output_height = input->phase->output_height;
+               input->output_width = input->phase->virtual_output_width;
+               input->output_height = input->phase->virtual_output_height;
                assert(input->output_width != 0);
                assert(input->output_height != 0);
        }
@@ -662,7 +662,8 @@ void EffectChain::find_output_size(Phase *phase)
 
        // If the last effect explicitly sets an output size, use that.
        if (output_node->effect->changes_output_size()) {
-               output_node->effect->get_output_size(&phase->output_width, &phase->output_height);
+               output_node->effect->get_output_size(&phase->output_width, &phase->output_height,
+                                                    &phase->virtual_output_width, &phase->virtual_output_height);
                return;
        }
 
@@ -675,10 +676,10 @@ void EffectChain::find_output_size(Phase *phase)
                assert(input->phase->output_width != 0);
                assert(input->phase->output_height != 0);
                if (output_width == 0 && output_height == 0) {
-                       output_width = input->phase->output_width;
-                       output_height = input->phase->output_height;
-               } else if (output_width != input->phase->output_width ||
-                          output_height != input->phase->output_height) {
+                       output_width = input->phase->virtual_output_width;
+                       output_height = input->phase->virtual_output_height;
+               } else if (output_width != input->phase->virtual_output_width ||
+                          output_height != input->phase->virtual_output_height) {
                        all_inputs_same_size = false;
                }
        }
@@ -701,8 +702,8 @@ void EffectChain::find_output_size(Phase *phase)
        if (all_inputs_same_size) {
                assert(output_width != 0);
                assert(output_height != 0);
-               phase->output_width = output_width;
-               phase->output_height = output_height;
+               phase->virtual_output_width = phase->output_width = output_width;
+               phase->virtual_output_height = phase->output_height = output_height;
                return;
        }
 
@@ -726,8 +727,8 @@ void EffectChain::find_output_size(Phase *phase)
        }
        assert(output_width != 0);
        assert(output_height != 0);
-       phase->output_width = output_width;
-       phase->output_height = output_height;
+       phase->virtual_output_width = phase->output_width = output_width;
+       phase->virtual_output_height = phase->output_height = output_height;
 }
 
 void EffectChain::sort_all_nodes_topologically()
index 88d5d32..bfe52c0 100644 (file)
@@ -72,7 +72,7 @@ struct Phase {
        std::vector<Node *> inputs;
 
        std::vector<Node *> effects;  // In order.
-       unsigned output_width, output_height;
+       unsigned output_width, output_height, virtual_output_width, virtual_output_height;
 };
 
 class EffectChain {
index 51412c0..5fa81d3 100644 (file)
@@ -847,3 +847,53 @@ 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 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);
+}
index 2e428c4..c06a1ac 100644 (file)
@@ -99,10 +99,10 @@ Effect::AlphaHandling PaddingEffect::alpha_handling() const
        return INPUT_AND_OUTPUT_ALPHA_PREMULTIPLIED;
 }
        
-void PaddingEffect::get_output_size(unsigned *width, unsigned *height) const
+void PaddingEffect::get_output_size(unsigned *width, unsigned *height, unsigned *virtual_width, unsigned *virtual_height) const
 {
-       *width = output_width;
-       *height = output_height;
+       *virtual_width = *width = output_width;
+       *virtual_height = *height = output_height;
 }
        
 void PaddingEffect::inform_input_size(unsigned input_num, unsigned width, unsigned height)
index e9d4ce7..9e206ce 100644 (file)
@@ -26,7 +26,7 @@ public:
        virtual AlphaHandling alpha_handling() const;
        
        virtual bool changes_output_size() const { return true; }
-       virtual void get_output_size(unsigned *width, unsigned *height) const;
+       virtual void get_output_size(unsigned *width, unsigned *height, unsigned *virtual_width, unsigned *virtual_height) const;
        virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height);
 
 private:
index 5867f25..a0ae1a8 100644 (file)
@@ -70,9 +70,9 @@ public:
        }
        virtual bool changes_output_size() const { return true; }
 
-       virtual void get_output_size(unsigned *width, unsigned *height) const {
-               *width = this->output_width;
-               *height = this->output_height;
+       virtual void get_output_size(unsigned *width, unsigned *height, unsigned *virtual_width, unsigned *virtual_height) const {
+               *virtual_width = *width = this->output_width;
+               *virtual_height = *height = this->output_height;
        }
 
        void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
index e701d71..1141fba 100644 (file)
@@ -13,8 +13,8 @@ std::string ResizeEffect::output_fragment_shader()
        return read_file("identity.frag");
 }
 
-void ResizeEffect::get_output_size(unsigned *width, unsigned *height) const
+void ResizeEffect::get_output_size(unsigned *width, unsigned *height, unsigned *virtual_width, unsigned *virtual_height) const
 {
-       *width = this->width;
-       *height = this->height;
+       *virtual_width = *width = this->width;
+       *virtual_height = *height = this->height;
 }
index 4c3fe86..c11f4bc 100644 (file)
@@ -19,7 +19,7 @@ public:
        virtual bool needs_mipmaps() const { return true; }
 
        virtual bool changes_output_size() const { return true; }
-       virtual void get_output_size(unsigned *width, unsigned *height) const;
+       virtual void get_output_size(unsigned *width, unsigned *height, unsigned *virtual_width, unsigned *virtual_height) const;
 
 private:
        int width, height;