]> git.sesse.net Git - movit/commitdiff
Merge branch 'master' into epoxy
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 10 Mar 2014 23:40:00 +0000 (00:40 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 10 Mar 2014 23:43:58 +0000 (00:43 +0100)
13 files changed:
1  2 
Makefile.in
complex_modulate_effect.cpp
complex_modulate_effect.h
complex_modulate_effect_test.cpp
effect_chain.cpp
effect_chain.h
effect_chain_test.cpp
fft_pass_effect.cpp
fft_pass_effect.h
resample_effect.cpp
resample_effect.h
slice_effect.cpp
slice_effect.h

diff --combined Makefile.in
index 660bf7aba58c807be087161f3bcebd11aa7f95ac,3a1674b5e2ec6beae582eaebf2a1addbf2734952..ea764650fe938a790f24ca18069cdebb5e30cae2
@@@ -8,16 -8,12 +8,16 @@@ datarootdir = @datarootdir
  datadir = @datadir@
  top_builddir = @top_builddir@
  with_demo_app = @with_demo_app@
 +with_SDL2 = @with_SDL2@
  
  CC=@CC@
  CXX=@CXX@
 -CXXFLAGS=-Wall @CXXFLAGS@ -I$(GTEST_DIR)/include @Eigen3_CFLAGS@ @GLEW_CFLAGS@
 -LDFLAGS=@GLEW_LIBS@ @SDL_LIBS@ -lpthread
 -DEMO_LDLIBS=@SDL_image_LIBS@ -lrt -lpthread @libpng_LIBS@
 +CXXFLAGS=-Wall @CXXFLAGS@ -I$(GTEST_DIR)/include @SDL2_CFLAGS@ @SDL_CFLAGS@ @Eigen3_CFLAGS@ @epoxy_CFLAGS@
 +ifeq ($(with_SDL2),yes)
 +CXXFLAGS += -DHAVE_SDL2
 +endif
 +LDFLAGS=@epoxy_LIBS@ @SDL2_LIBS@ @SDL_LIBS@ -lpthread
 +DEMO_LDLIBS=@SDL2_image_LIBS@ @SDL_image_LIBS@ -lrt -lpthread @libpng_LIBS@
  SHELL=@SHELL@
  LIBTOOL=@LIBTOOL@ --tag=CXX
  RANLIB=ranlib
@@@ -60,6 -56,7 +60,7 @@@ TESTED_EFFECTS += deconvolution_sharpen
  TESTED_EFFECTS += fft_pass_effect
  TESTED_EFFECTS += vignette_effect
  TESTED_EFFECTS += slice_effect
+ TESTED_EFFECTS += complex_modulate_effect
  
  UNTESTED_EFFECTS = sandbox_effect
  UNTESTED_EFFECTS += mirror_effect
index 0000000000000000000000000000000000000000,70486082387012993ce726ee6e3b762e382910ba..656de3a3728ef0fb6006f5ba31811fac677f0fa5
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,60 +1,60 @@@
 -#include <GL/glew.h>
++#include <epoxy/gl.h>
+ #include "complex_modulate_effect.h"
+ #include "effect_chain.h"
+ #include "effect_util.h"
+ #include "util.h"
+ using namespace std;
+ namespace movit {
+ ComplexModulateEffect::ComplexModulateEffect()
+       : num_repeats_x(1), num_repeats_y(1)
+ {
+       register_int("num_repeats_x", &num_repeats_x);
+       register_int("num_repeats_y", &num_repeats_y);
+ }
+ string ComplexModulateEffect::output_fragment_shader()
+ {
+       return read_file("complex_modulate_effect.frag");
+ }
+ void ComplexModulateEffect::set_gl_state(GLuint glsl_program_num, const string &prefix, unsigned *sampler_num)
+ {
+       Effect::set_gl_state(glsl_program_num, prefix, sampler_num);
+       float num_repeats[] = { num_repeats_x, num_repeats_y };
+       set_uniform_vec2(glsl_program_num, prefix, "num_repeats", num_repeats);
+       // Set the secondary input to repeat (and nearest while we're at it).
+       Node *self = chain->find_node_for_effect(this);
+       glActiveTexture(chain->get_input_sampler(self, 1));
+       check_error();
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+       check_error();
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+       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();
+ }
+ void ComplexModulateEffect::inform_input_size(unsigned input_num, unsigned width, unsigned height)
+ {
+       if (input_num == 0) {
+               primary_input_width = width;
+               primary_input_height = height;
+       }
+ }
+ void ComplexModulateEffect::get_output_size(unsigned *width, unsigned *height,
+                                             unsigned *virtual_width, unsigned *virtual_height) const
+ {
+       *width = *virtual_width = primary_input_width;
+       *height = *virtual_height = primary_input_height;
+ }
+ }  // namespace movit
index 0000000000000000000000000000000000000000,52de229b915eadeb153f177630ea554076f257f3..fd4eb8b2a385adb7db6bc706cb356fdcaa96a3e4
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,61 +1,61 @@@
 -#include <GL/glew.h>
+ #ifndef _MOVIT_COMPLEX_MODULATE_EFFECT_H
+ #define _MOVIT_COMPLEX_MODULATE_EFFECT_H 1
+ // An effect that treats each pixel as two complex numbers (xy and zw),
+ // and multiplies it with some other complex number (xy and xy, so the
+ // same in both cases). The latter can be repeated both horizontally and
+ // vertically if desired.
+ //
+ // The typical use is to implement convolution by way of FFT; since
+ // FFT(A ⊙ B) = FFT(A) * FFT(B), you can FFT both inputs (where B
+ // would often even be a constant, so you'd only need to do FFT once),
+ // multiply them together and then IFFT the result to get a convolution.
+ //
+ // It is in a sense “wrong” to do this directly on pixels, since the color
+ // channels are independent and real-valued (ie., not complex numbers), but
+ // since convolution is a linear operation, it's unproblematic to treat R + Gi
+ // as a single complex number and B + Ai and another one; barring numerical
+ // errors, there should be no leakage between the channels as long as you're
+ // convolving with a real quantity. (There are more sophisticated ways of doing
+ // two real FFTs with a single complex one, but we won't need them, as we
+ // don't care about the actual FFT result, just that the convolution property
+ // holds.)
++#include <epoxy/gl.h>
+ #include <string>
+ #include "effect.h"
+ namespace movit {
+ class EffectChain;
+ class ComplexModulateEffect : public Effect {
+ public:
+       ComplexModulateEffect();
+       virtual std::string effect_type_id() const { return "ComplexModulateEffect"; }
+       std::string output_fragment_shader();
+       // Technically we only need texture bounce for the second input
+       // (to be allowed to mess with its sampler state), but there's
+       // no way of expressing that currently.
+       virtual bool needs_texture_bounce() const { return true; }
+       virtual bool changes_output_size() const { return true; }
+       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;
+       virtual unsigned num_inputs() const { return 2; }
+       virtual void inform_added(EffectChain *chain) { this->chain = chain; }
+       void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
+ private:
+       EffectChain *chain;
+       int primary_input_width, primary_input_height;
+       int num_repeats_x, num_repeats_y;
+ };
+ }  // namespace movit
+ #endif // !defined(_MOVIT_COMPLEX_MODULATE_EFFECT_H)
index 0000000000000000000000000000000000000000,8c49d8ae63be202ff53525d13791ca5850e965b5..b505d861bb7b246babd2ae595b60b9d74d3d60f3
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,99 +1,99 @@@
 -#include <GL/glew.h>
+ // Unit tests for ComplexModulateEffect.
++#include <epoxy/gl.h>
+ #include "effect_chain.h"
+ #include "gtest/gtest.h"
+ #include "complex_modulate_effect.h"
+ #include "image_format.h"
+ #include "input.h"
+ #include "test_util.h"
+ namespace movit {
+ TEST(ComplexModulateEffectTest, Identity) {
+       const int size = 3;
+       float data_a[size * 4] = {
+               0.0f, 0.1f, 0.2f, 0.1f,
+               0.4f, 0.3f, 0.8f, 2.0f,
+               0.5f, 0.2f, 0.1f, 0.0f,
+       };
+       float data_b[size * 2] = {
+               1.0f, 0.0f,
+               1.0f, 0.0f,
+               1.0f, 0.0f,
+       };
+       float out_data[size * 4];
+       EffectChainTester tester(data_a, 1, size, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
+       Effect *input1 = tester.get_chain()->last_added_effect();
+       Effect *input2 = tester.add_input(data_b, FORMAT_RG, COLORSPACE_sRGB, GAMMA_LINEAR);
+       tester.get_chain()->add_effect(new ComplexModulateEffect(), input1, input2);
+       tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
+       expect_equal(data_a, out_data, 4, size);
+ }
+ TEST(ComplexModulateEffectTest, ComplexMultiplication) {
+       const int size = 2;
+       float data_a[size * 4] = {
+               0.0f, 0.1f, 0.2f, 0.1f,
+               0.4f, 0.3f, 0.8f, 2.0f,
+       };
+       float data_b[size * 2] = {
+               0.0f,  1.0f,
+               0.5f, -0.8f,
+       };
+       float expected_data[size * 4] = {
+               -0.1f,   0.0f,  -0.1f, 0.2f,
+                0.44f, -0.17f,  2.0f, 0.36f,
+       };
+       float out_data[size * 4];
+       EffectChainTester tester(data_a, 1, size, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
+       Effect *input1 = tester.get_chain()->last_added_effect();
+       Effect *input2 = tester.add_input(data_b, FORMAT_RG, COLORSPACE_sRGB, GAMMA_LINEAR);
+       tester.get_chain()->add_effect(new ComplexModulateEffect(), input1, input2);
+       tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
+       expect_equal(expected_data, out_data, 4, size);
+ }
+ TEST(ComplexModulateEffectTest, Repeat) {
+       const int size = 2, repeats = 3;
+       float data_a[size * repeats * 4] = {
+               0.0f, 0.1f, 0.2f, 0.3f,
+               1.0f, 1.1f, 1.2f, 1.3f,
+               2.0f, 2.1f, 2.2f, 2.3f,
+               3.0f, 3.1f, 3.2f, 3.3f,
+               4.0f, 4.1f, 4.2f, 4.3f,
+               5.0f, 5.1f, 5.2f, 5.3f,
+       };
+       float data_b[size * 2] = {
+               1.0f,  0.0f,
+               0.0f, -1.0f,
+       };
+       float expected_data[size * repeats * 4] = {
+               0.0f,  0.1f, 0.2f,  0.3f,
+               1.1f, -1.0f, 1.3f, -1.2f,
+               2.0f,  2.1f, 2.2f,  2.3f,
+               3.1f, -3.0f, 3.3f, -3.2f,
+               4.0f,  4.1f, 4.2f,  4.3f,
+               5.1f, -5.0f, 5.3f, -5.2f,
+       };
+       float out_data[size * repeats * 4];
+       EffectChainTester tester(data_a, 1, repeats * size, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
+       Effect *input1 = tester.get_chain()->last_added_effect();
+       Effect *input2 = tester.add_input(data_b, FORMAT_RG, COLORSPACE_sRGB, GAMMA_LINEAR, 1, size);
+       Effect *effect = tester.get_chain()->add_effect(new ComplexModulateEffect(), input1, input2);
+       ASSERT_TRUE(effect->set_int("num_repeats_y", repeats));
+       tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED);
+       expect_equal(expected_data, out_data, 4, size * repeats);
+ }
+ }  // namespace movit
diff --combined effect_chain.cpp
index 0cd480287e5672eb0f09812535153ac500697d02,c886155a19dc96324fbbca76ec1cc7d5e571b53d..ef4402491ab2c3c23b613336235f7f884a5874b6
@@@ -1,6 -1,6 +1,6 @@@
  #define GL_GLEXT_PROTOTYPES 1
  
 -#include <GL/glew.h>
 +#include <epoxy/gl.h>
  #include <assert.h>
  #include <locale.h>
  #include <math.h>
@@@ -149,6 -149,15 +149,15 @@@ void EffectChain::insert_node_between(N
        assert(middle->incoming_links.size() == middle->effect->num_inputs());
  }
  
+ GLenum EffectChain::get_input_sampler(Node *node, unsigned input_num) const
+ {
+       assert(node->effect->needs_texture_bounce());
+       assert(input_num < node->incoming_links.size());
+       assert(node->incoming_links[input_num]->bound_sampler_num >= 0);
+       assert(node->incoming_links[input_num]->bound_sampler_num < 8);
+       return GL_TEXTURE0 + node->incoming_links[input_num]->bound_sampler_num;
+ }
  void EffectChain::find_all_nonlinear_inputs(Node *node, vector<Node *> *nonlinear_inputs)
  {
        if (node->output_gamma_curve == GAMMA_LINEAR &&
@@@ -1480,6 -1489,7 +1489,7 @@@ void EffectChain::render_to_fbo(GLuint 
                for (unsigned sampler = 0; sampler < phases[phase]->inputs.size(); ++sampler) {
                        glActiveTexture(GL_TEXTURE0 + sampler);
                        Node *input = phases[phase]->inputs[sampler];
+                       input->bound_sampler_num = sampler;
                        glBindTexture(GL_TEXTURE_2D, output_textures[input->phase]);
                        check_error();
                        if (phases[phase]->input_needs_mipmaps) {
                unsigned sampler_num = phases[phase]->inputs.size();
                for (unsigned i = 0; i < phases[phase]->effects.size(); ++i) {
                        Node *node = phases[phase]->effects[i];
+                       unsigned old_sampler_num = sampler_num;
                        node->effect->set_gl_state(glsl_program_num, phases[phase]->effect_ids[node], &sampler_num);
                        check_error();
+                       if (node->effect->is_single_texture()) {
+                               assert(sampler_num - old_sampler_num == 1);
+                               node->bound_sampler_num = old_sampler_num;
+                       } else {
+                               node->bound_sampler_num = -1;
+                       }
                }
  
                // Now draw!
diff --combined effect_chain.h
index 4c3232b5f7dbca5a67f55a005775278c37d904d9,4417de912f9f979a8308f96e6291246c3c73841c..549fa603027c3476c9ce8fb9ab6dcae522400a02
@@@ -17,7 -17,7 +17,7 @@@
  // the EffectChain holds textures and other OpenGL objects that are tied to the
  // context.
  
 -#include <GL/glew.h>
 +#include <epoxy/gl.h>
  #include <stdio.h>
  #include <map>
  #include <set>
@@@ -71,6 -71,15 +71,15 @@@ private
        // phases as inputs, instead of Node.
        Phase *phase;
  
+       // If the effect has is_single_texture(), or if the output went to RTT
+       // and that texture has been bound to a sampler, the sampler number
+       // will be stored here.
+       //
+       // TODO: Can an RTT texture be used as inputs to multiple effects
+       // within the same phase? If so, we have a problem with modifying
+       // sampler state here.
+       int bound_sampler_num;
        // Used during the building of the effect chain.
        Colorspace output_color_space;
        GammaCurve output_gamma_curve;
@@@ -173,6 -182,16 +182,16 @@@ public
        void replace_receiver(Node *old_receiver, Node *new_receiver);
        void replace_sender(Node *new_sender, Node *receiver);
        void insert_node_between(Node *sender, Node *middle, Node *receiver);
+       Node *find_node_for_effect(Effect *effect) { return node_map[effect]; }
+       // Get the OpenGL sampler (GL_TEXTURE0, GL_TEXTURE1, etc.) for the
+       // input of the given node, so that one can modify the sampler state
+       // directly. Only valid to call during set_gl_state().
+       //
+       // Also, for this to be allowed, <node>'s effect must have
+       // needs_texture_bounce() set, so that it samples directly from a
+       // single-sampler input, or from an RTT texture.
+       GLenum get_input_sampler(Node *node, unsigned input_num) const;
  
        // Get the current resource pool assigned to this EffectChain.
        // Primarily to let effects allocate textures as needed.
diff --combined effect_chain_test.cpp
index 67b3dfc56aa7567678624884818242b40b1d5487,b1f0815cc62d80b604ca5a4fc0f1618d941ca913..aabcc33fe4dddf8b5c321f5f535bce8bd900beb2
@@@ -2,7 -2,7 +2,7 @@@
  //
  // Note that this also contains the tests for some of the simpler effects.
  
 -#include <GL/glew.h>
 +#include <epoxy/gl.h>
  #include <assert.h>
  
  #include "effect.h"
@@@ -522,17 -522,27 +522,27 @@@ class MipmapNeedingEffect : public Effe
  public:
        MipmapNeedingEffect() {}
        virtual bool needs_mipmaps() const { return true; }
+       // 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) {
diff --combined fft_pass_effect.cpp
index ec550b2846be60cfa1ae24398903496e9f9e66ec,9c8aeb058e71fa33435901d4f5a2588a38ccb4f3..0ffaa9e9918de2d5fdc4dcd271bf948cecbc5465
@@@ -1,6 -1,7 +1,7 @@@
 -#include <GL/glew.h>
 +#include <epoxy/gl.h>
  #include <math.h>
  
+ #include "effect_chain.h"
  #include "effect_util.h"
  #include "fp16.h"
  #include "fft_pass_effect.h"
@@@ -40,15 -41,13 +41,13 @@@ void FFTPassEffect::set_gl_state(GLuin
  
        int input_size = (direction == VERTICAL) ? input_height : input_width;
  
-       // See the comments on changes_output_size() in the .h file to see
-       // why this is legal. It is _needed_ because it counteracts the
-       // precision issues we get because we sample the input texture with
-       // normalized coordinates (especially when the repeat count along
-       // the axis is not a power of two); we very rapidly end up in narrowly
-       // missing a texel center, which causes precision loss to propagate
-       // throughout the FFT.
-       assert(*sampler_num == 1);
-       glActiveTexture(GL_TEXTURE0);
+       // This is needed because it counteracts the precision issues we get
+       // because we sample the input texture with normalized coordinates
+       // (especially when the repeat count along the axis is not a power of
+       // two); we very rapidly end up in narrowly missing a texel center,
+       // which causes precision loss to propagate throughout the FFT.
+       Node *self = chain->find_node_for_effect(this);
+       glActiveTexture(chain->get_input_sampler(self, 0));
        check_error();
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        check_error();
diff --combined fft_pass_effect.h
index 501c562700716ba3e0969595af950fdefb46d53f,460a946be4dcbdb24f8bddfcc011cb3cdb6af6a0..90e88bc1fddd94650c1deae08011e9c2d0e5395e
@@@ -50,7 -50,7 +50,7 @@@
  // scaling), and as fp16 has quite limited range at times, this can be relevant
  // on some GPUs for larger sizes.
  
 -#include <GL/glew.h>
 +#include <epoxy/gl.h>
  #include <assert.h>
  #include <stdio.h>
  #include <string>
@@@ -98,10 -98,13 +98,13 @@@ public
                *width = *virtual_width = input_width;
                *height = *virtual_height = input_height;
        }
+       virtual void inform_added(EffectChain *chain) { this->chain = chain; }
        
        enum Direction { HORIZONTAL = 0, VERTICAL = 1 };
  
  private:
+       EffectChain *chain;
        int input_width, input_height;
        GLuint tex;
        int fft_size;
diff --combined resample_effect.cpp
index b988e5af8eb656d2110352f7339e090d799c95c7,a6774873daa3b5b58be975300f97839cc5562a56..50b7c6bc5c4730fc6bb25cd18c96a446c8f02986
@@@ -1,7 -1,7 +1,7 @@@
  // Three-lobed Lanczos, the most common choice.
  #define LANCZOS_RADIUS 3.0
  
 -#include <GL/glew.h>
 +#include <epoxy/gl.h>
  #include <assert.h>
  #include <limits.h>
  #include <math.h>
@@@ -413,7 -413,8 +413,8 @@@ void SingleResamplePassEffect::set_gl_s
  
        // We specifically do not want mipmaps on the input texture;
        // they break minification.
-       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_MIN_FILTER, GL_LINEAR);
        check_error();
diff --combined resample_effect.h
index 24e637260ea97489d54080bef521572bc68d8b05,44587bf22f9200a1f79147c1ced2b9bf40e82dbb..f0112b3ca7c4abcec1ba73a6ce420f65b10162dd
@@@ -15,7 -15,7 +15,7 @@@
  // which is what the user is intended to use, instantiates two copies of
  // SingleResamplePassEffect behind the scenes).
  
 -#include <GL/glew.h>
 +#include <epoxy/gl.h>
  #include <assert.h>
  #include <stddef.h>
  #include <string>
@@@ -73,6 -73,7 +73,7 @@@ public
        virtual bool needs_srgb_primaries() const { return false; }
        virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
  
+       virtual void inform_added(EffectChain *chain) { this->chain = chain; }
        virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height) {
                if (parent != NULL) {
                        parent->inform_input_size(input_num, width, height);
@@@ -93,6 -94,7 +94,7 @@@ private
        void update_texture(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
  
        ResampleEffect *parent;
+       EffectChain *chain;
        Direction direction;
        GLuint texnum;
        int input_width, input_height, output_width, output_height;
diff --combined slice_effect.cpp
index 3cdd415251d880577b3d47456edb471c8a4798fb,15f539263af7be2da36a5fb9ba8da5b0351e5d1c..3a884656108154278d3f2bfcddb87d9c2b182b73
@@@ -1,5 -1,6 +1,6 @@@
 -#include <GL/glew.h>
 +#include <epoxy/gl.h>
  
+ #include "effect_chain.h"
  #include "slice_effect.h"
  #include "effect_util.h"
  #include "util.h"
@@@ -60,12 -61,10 +61,10 @@@ void SliceEffect::set_gl_state(GLuint g
                set_uniform_float(glsl_program_num, prefix, "slice_offset_to_input_coord", float(output_slice_size) / float(input_height));
        }
  
-       // Normalized coordinates could potentially cause blurring of the
-       // image; it's not critical, but we have set changes_output_size()
-       // and needs_texture_bounce(), so simply turning off the interpolation
-       // is allowed.
-       assert(*sampler_num == 1);
-       glActiveTexture(GL_TEXTURE0);
+       // Normalized coordinates could potentially cause blurring of the image.
+       // It isn't critical, but still good practice.
+       Node *self = chain->find_node_for_effect(this);
+       glActiveTexture(chain->get_input_sampler(self, 0));
        check_error();
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        check_error();
diff --combined slice_effect.h
index 3448941873aebbfef9067e682d906fafc0308805,17202d77c97577e720cf023426c8c28f095ab8c5..1c363ea7b104c19ccd31a6e82a74b50f95ec16c1
@@@ -10,7 -10,7 +10,7 @@@
  // Note that vertical slices happen from the bottom, not the top, due to the
  // OpenGL coordinate system.
  
 -#include <GL/glew.h>
 +#include <epoxy/gl.h>
  #include <string>
  
  #include "effect.h"
@@@ -29,10 -29,12 +29,12 @@@ public
                                     unsigned *virtual_width, unsigned *virtual_height) const;
  
        void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
+       virtual void inform_added(EffectChain *chain) { this->chain = chain; }
        
        enum Direction { HORIZONTAL = 0, VERTICAL = 1 };
  
  private:
+       EffectChain *chain;
        int input_width, input_height;
        int input_slice_size, output_slice_size;
        Direction direction;