]> git.sesse.net Git - movit/commitdiff
Collapse passes more aggressively in the face of size changes.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 2 Sep 2015 23:46:58 +0000 (01:46 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 2 Sep 2015 23:46:58 +0000 (01:46 +0200)
The motivating chain for this change was a case where we had
a SinglePassResampleEffect (the second half of a ResampleEffect)
feeding into a PaddingEffect, feeding into an OverlayEffect.
Currently, since the two former change output size, we'd bounce
to a temporary texture twice (output size changes would always
cause bounces).

However, this is needlessly conservative. The reason for bouncing
when changing output size is really if you want to get rid of
data by downscaling and then later upsampling, e.g. for a blur.
(It could also be useful for cropping, but we don't really use
that right now; PaddingEffect, which does crop, explicitly checks
the borders anyway to set the border color manually.) But in this case,
we are not downscaling at all, so we could just drop the bounce,
saving tons of texture bandwidth.

Thus, we add yet more parameters that effects can specify; first,
that an effect uses _one-to-one_ sampling; that is, that it
 will only use its input as-is without sampling
between texels or outside the border (so the different
interpolation and border behavior will be irrelevant).
(Actually, almost all of our effects fall into this category.)
Second, a flag saying that even if an effect changes size,
it doesn't use virtual sizes (otherwise even a one-to-one effect
would de-facto be sampling between texels). If these flags
are set on the input and the output respectively, we can avoid
the bounce, at least unless there's an effect that's _not_
one-to-one further up the chain.

For my motivating case, this folded eight phases into four,
changing ~16.0 ms into ~10.6 ms rendering time. Seemingly
memory bandwidth is a really precious resource on my laptop's
GPU.

28 files changed:
alpha_division_effect.h
alpha_multiplication_effect.h
blur_effect.h
colorspace_conversion_effect.h
complex_modulate_effect.h
diffusion_effect.h
dither_effect.h
effect.h
effect_chain.cpp
effect_chain.h
effect_chain_test.cpp
fft_pass_effect.h
gamma_compression_effect.h
gamma_expansion_effect.h
glow_effect.h
lift_gamma_gain_effect.h
luma_mix_effect.h
mirror_effect.h
mix_effect.h
multiply_effect.h
overlay_effect.h
padding_effect.h
resample_effect.h
resize_effect.h
saturation_effect.h
slice_effect.h
vignette_effect.h
white_balance_effect.h

index ae8f786e3075040c147f844da3ff8afae2326667..2fd90820615269edad7a7655030c69d57ea01398 100644 (file)
@@ -14,6 +14,7 @@ public:
        AlphaDivisionEffect() {}
        virtual std::string effect_type_id() const { return "AlphaDivisionEffect"; }
        std::string output_fragment_shader();
+       virtual bool one_to_one_sampling() const { return true; }
 };
 
 }  // namespace movit
index bc66d72214d8f612bf3f8425cb2ed39f4a3d837e..f342719c5987b0e3304e5b0782811829b3680faa 100644 (file)
@@ -14,6 +14,7 @@ public:
        AlphaMultiplicationEffect() {}
        virtual std::string effect_type_id() const { return "AlphaMultiplicationEffect"; }
        std::string output_fragment_shader();
+       virtual bool one_to_one_sampling() const { return true; }
 };
 
 }  // namespace movit
index f531eb6e85aecb4a3305800e01051f510bb0a48b..eb35790573c0bac7b6f37030a68c73b39276f06e 100644 (file)
@@ -81,6 +81,8 @@ public:
                }
        }
        virtual bool changes_output_size() const { return true; }
+       virtual bool sets_virtual_output_size() const { return true; }
+       virtual bool one_to_one_sampling() const { return false; }  // Can sample outside the border.
 
        virtual void get_output_size(unsigned *width, unsigned *height, unsigned *virtual_width, unsigned *virtual_height) const {
                *width = this->width;
index 76246d6519e4b6c668d38d3b6d3595541091578d..92bad1f7d7eb1ec5f7c65633d8eda34856d2e212 100644 (file)
@@ -28,6 +28,7 @@ public:
 
        virtual bool needs_srgb_primaries() const { return false; }
        virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
+       virtual bool one_to_one_sampling() const { return true; }
 
        // Get a conversion matrix from the given color space to XYZ.
        static Eigen::Matrix3d get_xyz_matrix(Colorspace space);
index fd4eb8b2a385adb7db6bc706cb356fdcaa96a3e4..e2bf50d5851fb0b64f5c71a8f13bfc9fe3e8df79 100644 (file)
@@ -41,6 +41,7 @@ public:
        // no way of expressing that currently.
        virtual bool needs_texture_bounce() const { return true; }
        virtual bool changes_output_size() const { return true; }
+       virtual bool sets_virtual_output_size() const { return false; }
 
        virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height);
        virtual void get_output_size(unsigned *width, unsigned *height,
index 64b94dd8dad11df184e3fb082ec622ab13186644..77625ae439568b8fb6f043b5da796ddad067490d 100644 (file)
@@ -57,6 +57,7 @@ public:
        virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
 
        unsigned num_inputs() const { return 2; }
+       virtual bool one_to_one_sampling() const { return true; }
 
 private:
        float blurred_mix_amount;
index 9818603c2a46f4a659628b68c03be5fed3f868ac..e5805862011c94b36c608fe804cb0f2a6a121bca 100644 (file)
@@ -66,6 +66,7 @@ public:
        // premultiplied error. However, we need to do dithering in the same
        // space as quantization, whether that be pre- or postmultiply.
        virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
+       virtual bool one_to_one_sampling() const { return true; }
 
        void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
 
index 9d95705f6a66ce344a54bd18eb83154018d85c35..fbe39d7274a421fd9f906ae809fdace3df7a8bc2 100644 (file)
--- a/effect.h
+++ b/effect.h
@@ -161,12 +161,38 @@ public:
        // needs mipmaps, you will also get them).
        virtual bool needs_mipmaps() const { return false; }
 
+       // Whether there is a direct correspondence between input and output
+       // texels. Specifically, the effect must not:
+       //
+       //   1. Try to sample in the border (ie., outside the 0.0 to 1.0 area).
+       //   2. Try to sample between texels.
+       //   3. Sample with an x- or y-derivative different from -1 or 1.
+       //      (This also means needs_mipmaps() and one_to_one_sampling()
+       //      together would make no sense.)
+       //
+       // The most common case for this would be an effect that has an exact
+       // 1:1-correspondence between input and output texels, e.g. SaturationEffect.
+       // However, more creative things, like mirroring/flipping or padding,
+       // would also be allowed.
+       //
+       // The primary gain from setting this is that you can sample directly
+       // from an effect that changes output size (see changes_output_size() below),
+       // without going through a bounce texture. It won't work for effects that
+       // set sets_virtual_output_size(), though.
+       //
+       // Does not make a lot of sense together with needs_texture_bounce().
+       virtual bool one_to_one_sampling() const { return false; }
+
        // Whether this effect wants to output to a different size than
-       // its input(s) (see inform_input_size(), below). If you set this to
-       // true, the output will be bounced to a texture (similarly to if the
-       // next effect set needs_texture_bounce()).
+       // its input(s) (see inform_input_size(), below). See also
+       // sets_virtual_output_size() below.
        virtual bool changes_output_size() const { return false; }
 
+       // Whether your get_output_size() function (see below) intends to ever set
+       // virtual_width different from width, or similar for height.
+       // It does not make sense to set this to true if changes_output_size() is false.
+       virtual bool sets_virtual_output_size() const { return changes_output_size(); }
+
        // Whether this effect is effectively sampling from a a single texture.
        // If so, it will override needs_texture_bounce(); however, there are also
        // two demands it needs to fulfill:
index 048cc1e0dbe878b881449af0366aa19feb2217c3..519cb2c6b9ea1c5816ead123b05260904519f96e 100644 (file)
@@ -89,6 +89,7 @@ Node *EffectChain::add_node(Effect *effect)
        node->output_gamma_curve = GAMMA_INVALID;
        node->output_alpha_type = ALPHA_INVALID;
        node->needs_mipmaps = false;
+       node->one_to_one_sampling = false;
 
        nodes.push_back(node);
        node_map[effect] = node;
@@ -289,8 +290,8 @@ void EffectChain::compile_glsl_program(Phase *phase)
 // Construct GLSL programs, starting at the given effect and following
 // the chain from there. We end a program every time we come to an effect
 // marked as "needs texture bounce", one that is used by multiple other
-// effects, every time an effect wants to change the output size,
-// and of course at the end.
+// effects, every time we need to bounce due to output size change
+// (not all size changes require ending), and of course at the end.
 //
 // We follow a quite simple depth-first search from the output, although
 // without recursing explicitly within each phase.
@@ -303,6 +304,13 @@ Phase *EffectChain::construct_phase(Node *output, map<Node *, Phase *> *complete
        Phase *phase = new Phase;
        phase->output_node = output;
 
+       // If the output effect has one-to-one sampling, we try to trace this
+       // status down through the dependency chain. This is important in case
+       // we hit an effect that changes output size (and not sets a virtual
+       // output size); if we have one-to-one sampling, we don't have to break
+       // the phase.
+       output->one_to_one_sampling = output->effect->one_to_one_sampling();
+
        // Effects that we have yet to calculate, but that we know should
        // be in the current phase.
        stack<Node *> effects_todo_this_phase;
@@ -380,7 +388,14 @@ Phase *EffectChain::construct_phase(Node *output, map<Node *, Phase *> *complete
                                }
                        }
 
-                       if (deps[i]->effect->changes_output_size()) {
+                       if (deps[i]->effect->sets_virtual_output_size()) {
+                               assert(deps[i]->effect->changes_output_size());
+                               // If the next effect sets a virtual size to rely on OpenGL's
+                               // bilinear sampling, we'll really need to break the phase here.
+                               start_new_phase = true;
+                       } else if (deps[i]->effect->changes_output_size() && !node->one_to_one_sampling) {
+                               // If the next effect changes size and we don't have one-to-one sampling,
+                               // we also need to break here.
                                start_new_phase = true;
                        }
 
@@ -388,6 +403,10 @@ Phase *EffectChain::construct_phase(Node *output, map<Node *, Phase *> *complete
                                phase->inputs.push_back(construct_phase(deps[i], completed_effects));
                        } else {
                                effects_todo_this_phase.push(deps[i]);
+
+                               // Propagate the one-to-one status down through the dependency.
+                               deps[i]->one_to_one_sampling = node->one_to_one_sampling &&
+                                       deps[i]->effect->one_to_one_sampling();
                        }
                }
        }
@@ -419,6 +438,14 @@ Phase *EffectChain::construct_phase(Node *output, map<Node *, Phase *> *complete
                }
        }
 
+       // Tell each node which phase it ended up in, so that the unit test
+       // can check that the phases were split in the right place.
+       // Note that this ignores that effects may be part of multiple phases;
+       // if the unit tests need to test such cases, we'll reconsider.
+       for (unsigned i = 0; i < phase->effects.size(); ++i) {
+               phase->effects[i]->containing_phase = phase;
+       }
+
        // Actually make the shader for this phase.
        compile_glsl_program(phase);
 
@@ -649,9 +676,12 @@ void EffectChain::inform_input_sizes(Phase *phase)
                if (node->effect->changes_output_size()) {
                        // We cannot call get_output_size() before we've done inform_input_size()
                        // on all inputs.
-                       unsigned real_width_ignored, real_height_ignored;
-                       node->effect->get_output_size(&real_width_ignored, &real_height_ignored,
+                       unsigned real_width, real_height;
+                       node->effect->get_output_size(&real_width, &real_height,
                                                      &node->output_width, &node->output_height);
+                       assert(node->effect->sets_virtual_output_size() ||
+                              (real_width == node->output_width &&
+                               real_height == node->output_height));
                } else {
                        node->output_width = this_output_width;
                        node->output_height = this_output_height;
@@ -669,6 +699,9 @@ void EffectChain::find_output_size(Phase *phase)
        if (output_node->effect->changes_output_size()) {
                output_node->effect->get_output_size(&phase->output_width, &phase->output_height,
                                                     &phase->virtual_output_width, &phase->virtual_output_height);
+               assert(output_node->effect->sets_virtual_output_size() ||
+                      (phase->output_width == phase->virtual_output_width &&
+                       phase->output_height == phase->virtual_output_height));
                return;
        }
 
index b87fce7e5bf651d811bd424d6386ec848c764a7a..2f088b607feeec92fd3bb98c69be5cafde2329dd 100644 (file)
@@ -62,6 +62,10 @@ public:
        std::vector<Node *> outgoing_links;
        std::vector<Node *> incoming_links;
 
+       // For unit tests only. Do not use from other code.
+       // Will contain an arbitrary choice if the node is in multiple phases.
+       Phase *containing_phase;
+
 private:
        // Logical size of the output of this effect, ie. the resolution
        // you would get if you sampled it as a texture. If it is undefined
@@ -85,6 +89,10 @@ private:
        AlphaType output_alpha_type;
        bool needs_mipmaps;  // Directly or indirectly.
 
+       // Set if this effect, and all effects consuming output from this node
+       // (in the same phase) have one_to_one_sampling() set.
+       bool one_to_one_sampling;
+
        friend class EffectChain;
 };
 
index 516dd54ea00e84eaeaac7e2afd2e58b1f545343e..28ccc70b0bde6c136705d7bbba685a93e390763d 100644 (file)
@@ -1053,6 +1053,93 @@ TEST(EffectChainTest, VirtualSizeIsSentOnToInputs) {
        expect_equal(data, out_data, size, size);
 }
 
+// An effect that is like VirtualResizeEffect, but always has virtual and real
+// sizes the same (and promises this).
+class NonVirtualResizeEffect : public VirtualResizeEffect {
+public:
+       NonVirtualResizeEffect(int width, int height)
+               : VirtualResizeEffect(width, height, width, height) {}
+       virtual string effect_type_id() const { return "NonVirtualResizeEffect"; }
+       virtual bool sets_virtual_output_size() const { return false; }
+};
+
+// An effect that promises one-to-one sampling (unlike IdentityEffect).
+class OneToOneEffect : public Effect {
+public:
+       OneToOneEffect() {}
+       virtual string effect_type_id() const { return "OneToOneEffect"; }
+       string output_fragment_shader() { return read_file("identity.frag"); }
+       virtual bool one_to_one_sampling() const { return true; }
+};
+
+TEST(EffectChainTest, NoBounceWithOneToOneSampling) {
+       const int size = 2;
+       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);
+
+       RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
+       RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
+
+       tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
+       tester.get_chain()->add_effect(effect1);
+       tester.get_chain()->add_effect(effect2);
+       tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
+
+       expect_equal(data, out_data, size, size);
+
+       // The first OneToOneEffect should be in the same phase as its input.
+       ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
+       EXPECT_EQ(effect1->replaced_node->incoming_links[0]->containing_phase,
+                 effect1->replaced_node->containing_phase);
+
+       // The second OneToOneEffect, too.
+       EXPECT_EQ(effect1->replaced_node->containing_phase,
+                 effect2->replaced_node->containing_phase);
+}
+
+TEST(EffectChainTest, BounceWhenOneToOneIsBroken) {
+       const int size = 2;
+       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);
+
+       RewritingEffect<OneToOneEffect> *effect1 = new RewritingEffect<OneToOneEffect>();
+       RewritingEffect<OneToOneEffect> *effect2 = new RewritingEffect<OneToOneEffect>();
+       RewritingEffect<IdentityEffect> *effect3 = new RewritingEffect<IdentityEffect>();
+       RewritingEffect<OneToOneEffect> *effect4 = new RewritingEffect<OneToOneEffect>();
+
+       tester.get_chain()->add_effect(new NonVirtualResizeEffect(size, size));
+       tester.get_chain()->add_effect(effect1);
+       tester.get_chain()->add_effect(effect2);
+       tester.get_chain()->add_effect(effect3);
+       tester.get_chain()->add_effect(effect4);
+       tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
+
+       expect_equal(data, out_data, size, size);
+
+       // The NonVirtualResizeEffect should be in a different phase from
+       // the IdentityEffect (since the latter is not one-to-one),
+       // ie., the chain should be broken somewhere between them, but exactly
+       // where doesn't matter.
+       ASSERT_EQ(1, effect1->replaced_node->incoming_links.size());
+       EXPECT_NE(effect1->replaced_node->incoming_links[0]->containing_phase,
+                 effect3->replaced_node->containing_phase);
+
+       // The last OneToOneEffect should also be in the same phase as the
+       // IdentityEffect (the phase was already broken).
+       EXPECT_EQ(effect3->replaced_node->containing_phase,
+                 effect4->replaced_node->containing_phase);
+}
+
 // 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.
index fbf4511e222cd6a725a5ffa2e597ae5c5927f292..561bc6dd20788a6605a5d55230171cba9c5a40b8 100644 (file)
@@ -85,6 +85,7 @@ public:
        // in our own phase, which is exactly what we want.
        virtual bool needs_texture_bounce() const { return true; }
        virtual bool changes_output_size() const { return true; }
+       virtual bool sets_virtual_output_size() const { return false; }
 
        virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height)
        {
index ee3985f9fa03c807b2928dac5176e9b0baf58eff..52a8654a63015652204c0b9fb82d506f3eb04db7 100644 (file)
@@ -29,6 +29,7 @@ public:
        virtual void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
 
        virtual bool needs_srgb_primaries() const { return false; }
+       virtual bool one_to_one_sampling() const { return true; }
 
        // Actually needs postmultiplied input as well as outputting it.
        // EffectChain will take care of that.
index 81f42d137392cf7e0c08ba557f3a3d090f708f7f..1025695d88cdb60d192b4aeefcc1278f2f6c0483 100644 (file)
@@ -30,6 +30,7 @@ public:
 
        virtual bool needs_linear_light() const { return false; }
        virtual bool needs_srgb_primaries() const { return false; }
+       virtual bool one_to_one_sampling() const { return true; }
 
        // Actually processes its input in a nonlinear fashion,
        // but does not touch alpha, and we are a special case anyway.
index 2a2af16dd3abbd7d1bded97de0aaad88475b63bf..02520d7bd9ad8f87f38dd9c4365f63be9e85851f 100644 (file)
@@ -52,6 +52,7 @@ public:
        std::string output_fragment_shader();
        
        virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
+       virtual bool one_to_one_sampling() const { return true; }
 
 private:
        float cutoff;
index c93724d8b8fe2ae10150feb4cad8096ebfbce5dc..9183c580e080b7294cbb3f94bf4e20c7c4de3d6e 100644 (file)
@@ -32,6 +32,7 @@ public:
        LiftGammaGainEffect();
        virtual std::string effect_type_id() const { return "LiftGammaGainEffect"; }
        virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
+       virtual bool one_to_one_sampling() const { return true; }
        std::string output_fragment_shader();
 
        void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
index ce890dfb557e887e6059d4178738fc5d1acad17c..c4b2476a6f45a3689ea1cb4d7baa30e50a6177a7 100644 (file)
@@ -26,6 +26,7 @@ public:
 
        virtual bool needs_srgb_primaries() const { return false; }
        virtual unsigned num_inputs() const { return 3; }
+       virtual bool one_to_one_sampling() const { return true; }
        virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
 
 private:
index c4b46dbb6ff61cff94554bf845bbc8238cec8340..d9c3f717f9c776b75c83fe53b59653e29038863f 100644 (file)
@@ -18,6 +18,7 @@ public:
        virtual bool needs_linear_light() const { return false; }
        virtual bool needs_srgb_primaries() const { return false; }
        virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
+       virtual bool one_to_one_sampling() const { return true; }
 };
 
 }  // namespace movit
index 9abe6010a86d1aabb4640759c99fef537cb4caae..d891eb5212f7470d013553bc48a2b742d5a40f69 100644 (file)
@@ -18,6 +18,7 @@ public:
 
        virtual bool needs_srgb_primaries() const { return false; }
        virtual unsigned num_inputs() const { return 2; }
+       virtual bool one_to_one_sampling() const { return true; }
 
        // TODO: In the common case where a+b=1, it would be useful to be able to set
        // alpha_handling() to INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK. However, right now
index 92e98e30944ec61eedca44e75e27276cef26a609..c017e06c10d46444fb9bdbefc27a08ecdf4d8b9f 100644 (file)
@@ -18,6 +18,7 @@ public:
        MultiplyEffect();
        virtual std::string effect_type_id() const { return "MultiplyEffect"; }
        std::string output_fragment_shader();
+       virtual bool one_to_one_sampling() const { return true; }
 
 private:
        RGBATuple factor;
index 489d8d78168c449ccf8bf6e35c86182732f1a04f..c11569f4eec6c34e0eea4e169ee51b6142fef637 100644 (file)
@@ -24,6 +24,7 @@ public:
 
        virtual bool needs_srgb_primaries() const { return false; }
        virtual unsigned num_inputs() const { return 2; }
+       virtual bool one_to_one_sampling() const { return true; }
 
        // Actually, if _either_ image has blank alpha, our output will have
        // blank alpha, too (this only tells the framework that having _both_
index 901f892b9dd14f999776f823bb897370f32d25d5..13dc5c00ab511e34166450c5f768206c91016cb7 100644 (file)
@@ -31,6 +31,8 @@ public:
        virtual AlphaHandling alpha_handling() const;
        
        virtual bool changes_output_size() const { return true; }
+       virtual bool sets_virtual_output_size() const { return false; }
+       virtual bool one_to_one_sampling() const { return true; }
        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);
 
index 05ef3656fd94d4dc4f8d6091927200eb7108b082..669d15b94569626d5b1a2bfc058b98162dd6f6be 100644 (file)
@@ -85,6 +85,7 @@ public:
                }
        }
        virtual bool changes_output_size() const { return true; }
+       virtual bool sets_virtual_output_size() const { return false; }
 
        virtual void get_output_size(unsigned *width, unsigned *height, unsigned *virtual_width, unsigned *virtual_height) const {
                *virtual_width = *width = this->output_width;
index dff6dccdc3c02b0bc6e35103a553dcd0e6eff6fc..77dfdd48e1932c63bdb99110045e8b4263b6a290 100644 (file)
@@ -24,6 +24,7 @@ public:
        virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
 
        virtual bool changes_output_size() const { return true; }
+       virtual bool sets_virtual_output_size() const { return false; }
        virtual void get_output_size(unsigned *width, unsigned *height, unsigned *virtual_width, unsigned *virtual_height) const;
 
 private:
index a80740fefc0946508d23776f4b7c63ff1d33ef70..bf975725f2c740404a7300b152fbe4b59a1a7061 100644 (file)
@@ -18,6 +18,7 @@ public:
        SaturationEffect();
        virtual std::string effect_type_id() const { return "SaturationEffect"; }
        virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
+       virtual bool one_to_one_sampling() const { return true; }
        std::string output_fragment_shader();
 
 private:
index 89aeb0e4297eaac7eebd0985833c6549adc5829d..ccca527922d314d40e895b15e5d79b04d909164f 100644 (file)
@@ -24,6 +24,7 @@ public:
        std::string output_fragment_shader();
        virtual bool needs_texture_bounce() const { return true; }
        virtual bool changes_output_size() const { return true; }
+       virtual bool sets_virtual_output_size() const { return false; }
        virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height);
        virtual void get_output_size(unsigned *width, unsigned *height,
                                     unsigned *virtual_width, unsigned *virtual_height) const;
index fdf1a116502b8be220b74cacca1432539daec206..582e67687ef42bf67699980337ad71d430f5259a 100644 (file)
@@ -19,6 +19,7 @@ public:
 
        virtual bool needs_srgb_primaries() const { return false; }
        virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
+       virtual bool one_to_one_sampling() const { return true; }
 
        virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height);
        void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
index f438b91507a00b579d9eac697a3f619ac4527096..342426beee9e514ae1b16debe2a6181c37d09079 100644 (file)
@@ -15,6 +15,7 @@ public:
        WhiteBalanceEffect();
        virtual std::string effect_type_id() const { return "WhiteBalanceEffect"; }
        virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
+       virtual bool one_to_one_sampling() const { return true; }
        std::string output_fragment_shader();
 
        void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);