]> git.sesse.net Git - movit/commitdiff
Merge branch 'master' into epoxy
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 18 Mar 2014 23:20:55 +0000 (00:20 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 18 Mar 2014 23:20:55 +0000 (00:20 +0100)
Conflicts:
Makefile.in
README
movit.pc.in

15 files changed:
Makefile.in
NEWS [new file with mode: 0644]
README
blur_effect.cpp
blur_effect.frag
complex_modulate_effect.cpp
configure.ac
effect_chain.cpp
effect_chain.h
effect_util.cpp
effect_util.h
effectlist.txt [deleted file]
movit.pc.in
util.cpp
util.h

index ee3626b53eeb12d303e1c0021a2590d5dfd286d2..77ced6c3d048af72ef3799602394615c323cc11b 100644 (file)
@@ -1,5 +1,14 @@
 GTEST_DIR ?= /usr/src/gtest
 
+# This will be upgraded for each release, although not necessarily for every git commit.
+# See http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
+# for the rules as of how this is changed. This does not really mean that Movit will
+# strive towards having a rock-stable ABI, but at least the soversion will increase
+# whenever it breaks, so that you will not have silent failures, and distribution package
+# management can run its course.
+movit_ltversion = 1:3:0
+movit_version = 1.0.3
+
 prefix = @prefix@
 exec_prefix = @exec_prefix@
 includedir = @includedir@
@@ -13,11 +22,13 @@ with_coverage = @with_coverage@
 
 CC=@CC@
 CXX=@CXX@
-CXXFLAGS=-Wall @CXXFLAGS@ -I$(GTEST_DIR)/include @SDL2_CFLAGS@ @SDL_CFLAGS@ @Eigen3_CFLAGS@ @epoxy_CFLAGS@ @FFTW3_CFLAGS@
+CXXFLAGS=-Wall @CXXFLAGS@ -I$(GTEST_DIR)/include @Eigen3_CFLAGS@ @epoxy_CFLAGS@ @FFTW3_CFLAGS@
 ifeq ($(with_SDL2),yes)
 CXXFLAGS += -DHAVE_SDL2
 endif
-LDFLAGS=@epoxy_LIBS@ @SDL2_LIBS@ @SDL_LIBS@ @FFTW3_LIBS@ -lpthread
+LDFLAGS=@LDFLAGS@
+LDLIBS=@epoxy_LIBS@ @FFTW3_LIBS@ -lpthread
+TEST_LDLIBS=@epoxy_LIBS@ @SDL2_LIBS@ @SDL_LIBS@ -lpthread
 DEMO_LDLIBS=@SDL2_image_LIBS@ @SDL_image_LIBS@ -lrt -lpthread @libpng_LIBS@ @FFTW3_LIBS@
 SHELL=@SHELL@
 LIBTOOL=@LIBTOOL@ --tag=CXX
@@ -86,7 +97,8 @@ all: demo
 endif
 
 # Google Test and other test library functions.
-TEST_OBJS = gtest-all.o gtest_sdl_main.o test_util.o
+OWN_TEST_OBJS = gtest_sdl_main.o test_util.o
+TEST_OBJS = gtest-all.o $(OWN_TEST_OBJS)
 
 gtest-all.o: $(GTEST_DIR)/src/gtest-all.cc
        $(CXX) -MMD $(CPPFLAGS) -I$(GTEST_DIR) $(CXXFLAGS) -c $< -o $@
@@ -95,8 +107,9 @@ gtest_sdl_main.o: gtest_sdl_main.cpp
 
 # Unit tests.
 $(TESTS): %: %.o $(TEST_OBJS) libmovit.la
-       $(LIBTOOL) --mode=link $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+       $(LIBTOOL) --mode=link $(CXX) $(LDFLAGS) -o $@ $^ $(TEST_LDLIBS)
 
+OWN_OBJS=$(DEMO_OBJS) $(LIB_OBJS) $(OWN_TEST_OBJS) $(TESTS:=.o)
 OBJS=$(DEMO_OBJS) $(LIB_OBJS) $(TEST_OBJS) $(TESTS:=.o)
 
 # A small demo program.
@@ -105,7 +118,7 @@ demo: libmovit.la $(DEMO_OBJS)
 
 # The library itself.
 libmovit.la: $(LIB_OBJS:.o=.lo)
-       $(LIBTOOL) --mode=link $(CXX) $(LDFLAGS) -rpath $(libdir) -o $@ $^ $(LDLIBS)
+       $(LIBTOOL) --mode=link $(CXX) $(LDFLAGS) -rpath $(libdir) -version-info $(movit_ltversion) -o $@ $^ $(LDLIBS)
 
 %.lo: %.cpp
        $(LIBTOOL) --mode=compile $(CXX) -MMD -MP $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $<
@@ -159,15 +172,30 @@ SHADERS += texture1d.frag
 
 # These purposefully do not exist.
 MISSING_SHADERS = diffusion_effect.frag glow_effect.frag unsharp_mask_effect.frag resize_effect.frag
+MISSING_SHADERS += fft_convolution_effect.frag fft_input.frag
 SHADERS := $(filter-out $(MISSING_SHADERS),$(SHADERS))
 
 install: libmovit.la
-       $(LIBTOOL) --mode=install $(INSTALL) -m 0644 libmovit.la $(libdir)/
-       $(MKDIR) -p $(includedir)/movit/
-       $(INSTALL) -m 0644 $(HDRS) $(includedir)/movit/
-       $(MKDIR) -p $(datadir)/movit/
-       $(INSTALL) -m 0644 $(SHADERS) $(datadir)/movit/
-       $(MKDIR) -p $(libdir)/pkgconfig/
-       $(INSTALL) -m 644 movit.pc $(libdir)/pkgconfig/
-
-.PHONY: coverage clean distclean check all install
+       $(MKDIR) -p $(DESTDIR)$(libdir)/
+       $(LIBTOOL) --mode=install $(INSTALL) -m 0644 libmovit.la $(DESTDIR)$(libdir)/
+       $(MKDIR) -p $(DESTDIR)$(includedir)/movit/
+       $(INSTALL) -m 0644 $(HDRS) $(DESTDIR)$(includedir)/movit/
+       $(MKDIR) -p $(DESTDIR)$(datadir)/movit/
+       $(INSTALL) -m 0644 $(SHADERS) $(DESTDIR)$(datadir)/movit/
+       $(MKDIR) -p $(DESTDIR)$(libdir)/pkgconfig/
+       $(INSTALL) -m 644 movit.pc $(DESTDIR)$(libdir)/pkgconfig/
+
+DISTDIR=movit-$(movit_version)
+OTHER_DIST_FILES=add.frag autogen.sh blue.frag configure.ac d65.h identity.frag invert_effect.frag Makefile.in mipmap_needing_effect.frag movit.pc.in README NEWS test_util.h widgets.h
+
+dist:
+       $(MKDIR) $(DISTDIR)
+       cp $(OWN_OBJS:.o=.cpp) $(DISTDIR)/
+       cp $(HDRS) $(DISTDIR)/
+       cp $(SHADERS) $(DISTDIR)/
+       cp $(OTHER_DIST_FILES) $(DISTDIR)/
+       ( cd $(DISTDIR) && aclocal && libtoolize --install --copy && autoconf && $(RM) -r autom4te.cache/ )
+       tar zcvvf ../$(DISTDIR).tar.gz $(DISTDIR)
+       $(RM) -r $(DISTDIR)
+
+.PHONY: coverage clean distclean check all install dist
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..8688e99
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,18 @@
+Movit 1.0.3, March 16th, 2014
+
+ - Yet more build system tweaks mainly related to distribution packaging.
+
+
+Movit 1.0.2, March 16th, 2014
+
+ - Make a few tweaks to “make install”, to make distributions' lives easier.
+
+
+Movit 1.0.1, March 16th, 2014
+
+ - Fix so that shared libraries are built.
+
+
+Movit 1.0, March 16th, 2014
+
+ - Initial release.
diff --git a/README b/README
index 0537f687e9b0b07fb340ae554970849ef44fe0eb..74be5e5f17bdb8af1d65ba1ded439a35e273ba90 100644 (file)
--- a/README
+++ b/README
@@ -9,7 +9,7 @@ Movit is the Modern Video Toolkit, notwithstanding that anything that's
 called “modern” usually isn't, and it's really not a toolkit.
 
 Movit aims to be a _high-quality_, _high-performance_, _open-source_
-library for video filters. It is currently in alpha stage.
+library for video filters.
 
 
 TL;DR, please give me download link and system demands
@@ -25,8 +25,8 @@ OK, you need
   part of OpenGL 3.0 or newer, although most OpenGL 2.0 cards also
   have what's needed through extensions). If your machine is less than five
   years old _and you have the appropriate drivers_, you're home free.
-* The [Eigen 3] and [Google Test] libraries. (The library itself
-  depends only on the former, but you probably want to run the unit tests.)
+* The [Eigen 3], [FFTW3] and [Google Test] libraries. (The library itself
+  does not depend on the latter, but you probably want to run the unit tests.)
 * The [epoxy] library, for dealing with OpenGL extensions on various
   platforms.
 
index b0327e2948307c0f5a92bd7de1dd537425fd93b5..903737a2e01127421a35aea355802c2a7180110d 100644 (file)
@@ -108,7 +108,9 @@ SingleBlurPassEffect::SingleBlurPassEffect(BlurEffect *parent)
 
 string SingleBlurPassEffect::output_fragment_shader()
 {
-       return read_file("blur_effect.frag");
+       char buf[256];
+       sprintf(buf, "#define DIRECTION_VERTICAL %d\n", (direction == VERTICAL));
+       return buf + read_file("blur_effect.frag");
 }
 
 void SingleBlurPassEffect::set_gl_state(GLuint glsl_program_num, const string &prefix, unsigned *sampler_num)
@@ -159,13 +161,11 @@ void SingleBlurPassEffect::set_gl_state(GLuint glsl_program_num, const string &p
        //
        // We pack the parameters into a float4: The relative sample coordinates
        // in (x,y), and the weight in z. w is unused.
-       float samples[4 * (NUM_TAPS / 2 + 1)];
+       float samples[2 * (NUM_TAPS / 2 + 1)];
 
        // Center sample.
-       samples[4 * 0 + 0] = 0.0f;
-       samples[4 * 0 + 1] = 0.0f;
-       samples[4 * 0 + 2] = weight[0];
-       samples[4 * 0 + 3] = 0.0f;
+       samples[2 * 0 + 0] = 0.0f;
+       samples[2 * 0 + 1] = weight[0];
 
        // All other samples.
        for (unsigned i = 1; i < NUM_TAPS / 2 + 1; ++i) {
@@ -176,23 +176,18 @@ void SingleBlurPassEffect::set_gl_state(GLuint glsl_program_num, const string &p
                float offset, total_weight;
                combine_two_samples(w1, w2, &offset, &total_weight, NULL);
 
-               float x = 0.0f, y = 0.0f;
-
                if (direction == HORIZONTAL) {
-                       x = (base_pos + offset) / (float)width;
+                       samples[2 * i + 0] = (base_pos + offset) / (float)width;
                } else if (direction == VERTICAL) {
-                       y = (base_pos + offset) / (float)height;
+                       samples[2 * i + 0] = (base_pos + offset) / (float)height;
                } else {
                        assert(false);
                }
 
-               samples[4 * i + 0] = x;
-               samples[4 * i + 1] = y;
-               samples[4 * i + 2] = total_weight;
-               samples[4 * i + 3] = 0.0f;
+               samples[2 * i + 1] = total_weight;
        }
 
-       set_uniform_vec4_array(glsl_program_num, prefix, "samples", samples, NUM_TAPS / 2 + 1);
+       set_uniform_vec2_array(glsl_program_num, prefix, "samples", samples, NUM_TAPS / 2 + 1);
 }
 
 void SingleBlurPassEffect::clear_gl_state()
index c6e4cf5465e7cbf62178edd8e231d8c962b8c1d2..8853854229038c067b0ab8f677008a24d67e64b7 100644 (file)
@@ -1,14 +1,24 @@
-// A simple unidirectional blur.
+// A simple un.directional blur.
+// DIRECTION_VERTICAL will be #defined to 1 if we are doing a vertical blur,
+// 0 otherwise.
 
 #define NUM_TAPS 16
 
-uniform vec4 PREFIX(samples)[NUM_TAPS + 1];
+uniform vec2 PREFIX(samples)[NUM_TAPS + 1];
 
 vec4 FUNCNAME(vec2 tc) {
-       vec4 sum = vec4(PREFIX(samples)[0].z) * INPUT(tc);
+       vec4 sum = vec4(PREFIX(samples)[0].y) * INPUT(tc);
        for (int i = 1; i < NUM_TAPS + 1; ++i) {
-               vec4 sample = PREFIX(samples)[i];
-               sum += vec4(sample.z) * (INPUT(tc - sample.xy) + INPUT(tc + sample.xy));
+               vec2 sample = PREFIX(samples)[i];
+               vec2 sample1_tc = tc, sample2_tc = tc;
+#if DIRECTION_VERTICAL
+               sample1_tc.y -= sample.x;
+               sample2_tc.y += sample.x;
+#else
+               sample1_tc.x -= sample.x;
+               sample2_tc.x += sample.x;
+#endif
+               sum += vec4(sample.y) * (INPUT(sample1_tc) + INPUT(sample2_tc));
        }
        return sum;
 }
index 656de3a3728ef0fb6006f5ba31811fac677f0fa5..9483f6b1f0f09202b54d6c8d8e54cf48f67e274c 100644 (file)
@@ -25,7 +25,7 @@ void ComplexModulateEffect::set_gl_state(GLuint glsl_program_num, const string &
 {
        Effect::set_gl_state(glsl_program_num, prefix, sampler_num);
 
-       float num_repeats[] = { num_repeats_x, num_repeats_y };
+       float num_repeats[] = { float(num_repeats_x), float(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).
index 1007ce28668de7bf4f969e8892a5e9b8f9def7a7..823740b9e610c39b69a898f68fe248d012ae9d6c 100644 (file)
@@ -1,8 +1,10 @@
 AC_CONFIG_MACRO_DIR([m4])
 AC_INIT(movit, git)
 LT_INIT
+PKG_PROG_PKG_CONFIG
 
 AC_CONFIG_SRCDIR(effect.cpp)
+AC_CONFIG_AUX_DIR(.)
 
 AC_PROG_CC
 AC_PROG_CXX
index ef4402491ab2c3c23b613336235f7f884a5874b6..7f6c9430b00fc780a72b26f60a36cf7a1c46808c 100644 (file)
@@ -53,12 +53,26 @@ EffectChain::~EffectChain()
                delete nodes[i];
        }
        for (unsigned i = 0; i < phases.size(); ++i) {
+               glBindVertexArray(phases[i]->vao);
+               check_error();
+
+               cleanup_vertex_attribute(phases[i]->glsl_program_num, "position", phases[i]->position_vbo);
+               cleanup_vertex_attribute(phases[i]->glsl_program_num, "texcoord", phases[i]->texcoord_vbo);
+
+               glBindVertexArray(0);
+               check_error();
+
                resource_pool->release_glsl_program(phases[i]->glsl_program_num);
                delete phases[i];
        }
        if (owns_resource_pool) {
                delete resource_pool;
        }
+       for (map<void *, GLuint>::const_iterator fbo_it = fbos.begin();
+            fbo_it != fbos.end(); ++fbo_it) {
+               glDeleteFramebuffers(1, &fbo_it->second);
+               check_error();
+       }
 }
 
 Input *EffectChain::add_input(Input *input)
@@ -227,24 +241,13 @@ string replace_prefix(const string &text, const string &prefix)
        return output;
 }
 
-Phase *EffectChain::compile_glsl_program(
-       const vector<Node *> &inputs,
-       const vector<Node *> &effects)
+void EffectChain::compile_glsl_program(Phase *phase)
 {
-       Phase *phase = new Phase;
-       assert(!effects.empty());
-
-       // Deduplicate the inputs.
-       vector<Node *> true_inputs = inputs;
-       sort(true_inputs.begin(), true_inputs.end());
-       true_inputs.erase(unique(true_inputs.begin(), true_inputs.end()), true_inputs.end());
-
-       bool input_needs_mipmaps = false;
        string frag_shader = read_file("header.frag");
 
        // Create functions for all the texture inputs that we need.
-       for (unsigned i = 0; i < true_inputs.size(); ++i) {
-               Node *input = true_inputs[i];
+       for (unsigned i = 0; i < phase->inputs.size(); ++i) {
+               Node *input = phase->inputs[i]->output_node;
                char effect_id[256];
                sprintf(effect_id, "in%u", i);
                phase->effect_ids.insert(make_pair(input, effect_id));
@@ -256,10 +259,8 @@ Phase *EffectChain::compile_glsl_program(
                frag_shader += "\n";
        }
 
-       vector<Node *> sorted_effects = topological_sort(effects);
-
-       for (unsigned i = 0; i < sorted_effects.size(); ++i) {
-               Node *node = sorted_effects[i];
+       for (unsigned i = 0; i < phase->effects.size(); ++i) {
+               Node *node = phase->effects[i];
                char effect_id[256];
                sprintf(effect_id, "eff%u", i);
                phase->effect_ids.insert(make_pair(node, effect_id));
@@ -290,24 +291,32 @@ Phase *EffectChain::compile_glsl_program(
                        }
                }
                frag_shader += "\n";
-
-               input_needs_mipmaps |= node->effect->needs_mipmaps();
        }
-       for (unsigned i = 0; i < sorted_effects.size(); ++i) {
-               Node *node = sorted_effects[i];
-               if (node->effect->num_inputs() == 0) {
-                       CHECK(node->effect->set_int("needs_mipmaps", input_needs_mipmaps));
-               }
-       }
-       frag_shader += string("#define INPUT ") + phase->effect_ids[sorted_effects.back()] + "\n";
+       frag_shader += string("#define INPUT ") + phase->effect_ids[phase->effects.back()] + "\n";
        frag_shader.append(read_file("footer.frag"));
 
        phase->glsl_program_num = resource_pool->compile_glsl_program(read_file("vs.vert"), frag_shader);
-       phase->input_needs_mipmaps = input_needs_mipmaps;
-       phase->inputs = true_inputs;
-       phase->effects = sorted_effects;
 
-       return phase;
+       // Prepare the geometry for the fullscreen quad used in this phase.
+       // (We have separate VAOs per shader, since the bindings can in theory
+       // be different.)
+       float vertices[] = {
+               0.0f, 1.0f,
+               0.0f, 0.0f,
+               1.0f, 1.0f,
+               1.0f, 0.0f
+       };
+
+       glGenVertexArrays(1, &phase->vao);
+       check_error();
+       glBindVertexArray(phase->vao);
+       check_error();
+
+       phase->position_vbo = fill_vertex_attribute(phase->glsl_program_num, "position", 2, GL_FLOAT, sizeof(vertices), vertices);
+       phase->texcoord_vbo = fill_vertex_attribute(phase->glsl_program_num, "texcoord", 2, GL_FLOAT, sizeof(vertices), vertices);  // Same as vertices.
+
+       glBindVertexArray(0);
+       check_error();
 }
 
 // Construct GLSL programs, starting at the given effect and following
@@ -317,130 +326,118 @@ Phase *EffectChain::compile_glsl_program(
 // and of course at the end.
 //
 // We follow a quite simple depth-first search from the output, although
-// without any explicit recursion.
-void EffectChain::construct_glsl_programs(Node *output)
+// without recursing explicitly within each phase.
+Phase *EffectChain::construct_phase(Node *output, map<Node *, Phase *> *completed_effects)
 {
-       // Which effects have already been completed?
-       // We need to keep track of it, as an effect with multiple outputs
-       // could otherwise be calculated multiple times.
-       set<Node *> completed_effects;
+       if (completed_effects->count(output)) {
+               return (*completed_effects)[output];
+       }
 
-       // Effects in the current phase, as well as inputs (outputs from other phases
-       // that we depend on). Note that since we start iterating from the end,
-       // the effect list will be in the reverse order.
-       vector<Node *> this_phase_inputs;
-       vector<Node *> this_phase_effects;
+       Phase *phase = new Phase;
+       phase->output_node = output;
 
        // Effects that we have yet to calculate, but that we know should
        // be in the current phase.
        stack<Node *> effects_todo_this_phase;
-
-       // Effects that we have yet to calculate, but that come from other phases.
-       // We delay these until we have this phase done in its entirety,
-       // at which point we pick any of them and start a new phase from that.
-       stack<Node *> effects_todo_other_phases;
-
        effects_todo_this_phase.push(output);
 
-       for ( ;; ) {  // Termination condition within loop.
-               if (!effects_todo_this_phase.empty()) {
-                       // OK, we have more to do this phase.
-                       Node *node = effects_todo_this_phase.top();
-                       effects_todo_this_phase.pop();
-
-                       // This should currently only happen for effects that are inputs
-                       // (either true inputs or phase outputs). We special-case inputs,
-                       // and then deduplicate phase outputs in compile_glsl_program().
-                       if (node->effect->num_inputs() == 0) {
-                               if (find(this_phase_effects.begin(), this_phase_effects.end(), node) != this_phase_effects.end()) {
-                                       continue;
-                               }
-                       } else {
-                               assert(completed_effects.count(node) == 0);
+       while (!effects_todo_this_phase.empty()) {
+               Node *node = effects_todo_this_phase.top();
+               effects_todo_this_phase.pop();
+
+               // This should currently only happen for effects that are inputs
+               // (either true inputs or phase outputs). We special-case inputs,
+               // and then deduplicate phase outputs below.
+               if (node->effect->num_inputs() == 0) {
+                       if (find(phase->effects.begin(), phase->effects.end(), node) != phase->effects.end()) {
+                               continue;
                        }
+               } else {
+                       assert(completed_effects->count(node) == 0);
+               }
 
-                       this_phase_effects.push_back(node);
-                       completed_effects.insert(node);
+               phase->effects.push_back(node);
 
-                       // Find all the dependencies of this effect, and add them to the stack.
-                       vector<Node *> deps = node->incoming_links;
-                       assert(node->effect->num_inputs() == deps.size());
-                       for (unsigned i = 0; i < deps.size(); ++i) {
-                               bool start_new_phase = false;
+               // Find all the dependencies of this effect, and add them to the stack.
+               vector<Node *> deps = node->incoming_links;
+               assert(node->effect->num_inputs() == deps.size());
+               for (unsigned i = 0; i < deps.size(); ++i) {
+                       bool start_new_phase = false;
 
-                               if (node->effect->needs_texture_bounce() &&
-                                   !deps[i]->effect->is_single_texture()) {
-                                       start_new_phase = true;
-                               }
+                       if (node->effect->needs_texture_bounce() &&
+                           !deps[i]->effect->is_single_texture()) {
+                               start_new_phase = true;
+                       }
 
-                               if (deps[i]->outgoing_links.size() > 1) {
-                                       if (!deps[i]->effect->is_single_texture()) {
-                                               // More than one effect uses this as the input,
-                                               // and it is not a texture itself.
-                                               // The easiest thing to do (and probably also the safest
-                                               // performance-wise in most cases) is to bounce it to a texture
-                                               // and then let the next passes read from that.
-                                               start_new_phase = true;
-                                       } else {
-                                               assert(deps[i]->effect->num_inputs() == 0);
-
-                                               // For textures, we try to be slightly more clever;
-                                               // if none of our outputs need a bounce, we don't bounce
-                                               // but instead simply use the effect many times.
-                                               //
-                                               // Strictly speaking, we could bounce it for some outputs
-                                               // and use it directly for others, but the processing becomes
-                                               // somewhat simpler if the effect is only used in one such way.
-                                               for (unsigned j = 0; j < deps[i]->outgoing_links.size(); ++j) {
-                                                       Node *rdep = deps[i]->outgoing_links[j];
-                                                       start_new_phase |= rdep->effect->needs_texture_bounce();
-                                               }
+                       if (deps[i]->outgoing_links.size() > 1) {
+                               if (!deps[i]->effect->is_single_texture()) {
+                                       // More than one effect uses this as the input,
+                                       // and it is not a texture itself.
+                                       // The easiest thing to do (and probably also the safest
+                                       // performance-wise in most cases) is to bounce it to a texture
+                                       // and then let the next passes read from that.
+                                       start_new_phase = true;
+                               } else {
+                                       assert(deps[i]->effect->num_inputs() == 0);
+
+                                       // For textures, we try to be slightly more clever;
+                                       // if none of our outputs need a bounce, we don't bounce
+                                       // but instead simply use the effect many times.
+                                       //
+                                       // Strictly speaking, we could bounce it for some outputs
+                                       // and use it directly for others, but the processing becomes
+                                       // somewhat simpler if the effect is only used in one such way.
+                                       for (unsigned j = 0; j < deps[i]->outgoing_links.size(); ++j) {
+                                               Node *rdep = deps[i]->outgoing_links[j];
+                                               start_new_phase |= rdep->effect->needs_texture_bounce();
                                        }
                                }
+                       }
 
-                               if (deps[i]->effect->changes_output_size()) {
-                                       start_new_phase = true;
-                               }
+                       if (deps[i]->effect->changes_output_size()) {
+                               start_new_phase = true;
+                       }
 
-                               if (start_new_phase) {
-                                       effects_todo_other_phases.push(deps[i]);
-                                       this_phase_inputs.push_back(deps[i]);
-                               } else {
-                                       effects_todo_this_phase.push(deps[i]);
-                               }
+                       if (start_new_phase) {
+                               phase->inputs.push_back(construct_phase(deps[i], completed_effects));
+                       } else {
+                               effects_todo_this_phase.push(deps[i]);
                        }
-                       continue;
                }
+       }
 
-               // No more effects to do this phase. Take all the ones we have,
-               // and create a GLSL program for it.
-               if (!this_phase_effects.empty()) {
-                       reverse(this_phase_effects.begin(), this_phase_effects.end());
-                       phases.push_back(compile_glsl_program(this_phase_inputs, this_phase_effects));
-                       this_phase_effects.back()->phase = phases.back();
-                       this_phase_inputs.clear();
-                       this_phase_effects.clear();
-               }
-               assert(this_phase_inputs.empty());
-               assert(this_phase_effects.empty());
+       // No more effects to do this phase. Take all the ones we have,
+       // and create a GLSL program for it.
+       assert(!phase->effects.empty());
 
-               // If we have no effects left, exit.
-               if (effects_todo_other_phases.empty()) {
-                       break;
-               }
+       // Deduplicate the inputs.
+       sort(phase->inputs.begin(), phase->inputs.end());
+       phase->inputs.erase(unique(phase->inputs.begin(), phase->inputs.end()), phase->inputs.end());
 
-               Node *node = effects_todo_other_phases.top();
-               effects_todo_other_phases.pop();
+       // We added the effects from the output and back, but we need to output
+       // them in topological sort order in the shader.
+       phase->effects = topological_sort(phase->effects);
 
-               if (completed_effects.count(node) == 0) {
-                       // Start a new phase, calculating from this effect.
-                       effects_todo_this_phase.push(node);
+       // Figure out if we need mipmaps or not, and if so, tell the inputs that.
+       phase->input_needs_mipmaps = false;
+       for (unsigned i = 0; i < phase->effects.size(); ++i) {
+               Node *node = phase->effects[i];
+               phase->input_needs_mipmaps |= node->effect->needs_mipmaps();
+       }
+       for (unsigned i = 0; i < phase->effects.size(); ++i) {
+               Node *node = phase->effects[i];
+               if (node->effect->num_inputs() == 0) {
+                       CHECK(node->effect->set_int("needs_mipmaps", phase->input_needs_mipmaps));
                }
        }
 
-       // Finally, since the phases are found from the output but must be executed
-       // from the input(s), reverse them, too.
-       reverse(phases.begin(), phases.end());
+       // Actually make the shader for this phase.
+       compile_glsl_program(phase);
+
+       assert(completed_effects->count(output) == 0);
+       completed_effects->insert(make_pair(output, phase));
+       phases.push_back(phase);
+       return phase;
 }
 
 void EffectChain::output_dot(const char *filename)
@@ -621,11 +618,11 @@ 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->virtual_output_width;
-               input->output_height = input->phase->virtual_output_height;
-               assert(input->output_width != 0);
-               assert(input->output_height != 0);
+               Phase *input = phase->inputs[i];
+               input->output_node->output_width = input->virtual_output_width;
+               input->output_node->output_height = input->virtual_output_height;
+               assert(input->output_node->output_width != 0);
+               assert(input->output_node->output_height != 0);
        }
 
        // Now propagate from the inputs towards the end, and inform as we go.
@@ -676,14 +673,14 @@ void EffectChain::find_output_size(Phase *phase)
        bool all_inputs_same_size = true;
 
        for (unsigned i = 0; i < phase->inputs.size(); ++i) {
-               Node *input = phase->inputs[i];
-               assert(input->phase->output_width != 0);
-               assert(input->phase->output_height != 0);
+               Phase *input = phase->inputs[i];
+               assert(input->output_width != 0);
+               assert(input->output_height != 0);
                if (output_width == 0 && output_height == 0) {
-                       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) {
+                       output_width = input->virtual_output_width;
+                       output_height = input->virtual_output_height;
+               } else if (output_width != input->virtual_output_width ||
+                          output_height != input->virtual_output_height) {
                        all_inputs_same_size = false;
                }
        }
@@ -715,10 +712,10 @@ void EffectChain::find_output_size(Phase *phase)
        output_width = 0;
        output_height = 0;
        for (unsigned i = 0; i < phase->inputs.size(); ++i) {
-               Node *input = phase->inputs[i];
-               assert(input->phase->output_width != 0);
-               assert(input->phase->output_height != 0);
-               size_rectangle_to_fit(input->phase->output_width, input->phase->output_height, &output_width, &output_height);
+               Phase *input = phase->inputs[i];
+               assert(input->output_width != 0);
+               assert(input->output_height != 0);
+               size_rectangle_to_fit(input->output_width, input->output_height, &output_width, &output_height);
        }
        for (unsigned i = 0; i < phase->effects.size(); ++i) {
                Effect *effect = phase->effects[i]->effect;
@@ -1422,7 +1419,11 @@ void EffectChain::finalize()
        output_dot("step18-final.dot");
        
        // Construct all needed GLSL programs, starting at the output.
-       construct_glsl_programs(find_output_node());
+       // We need to keep track of which effects have already been computed,
+       // as an effect with multiple users could otherwise be calculated
+       // multiple times.
+       map<Node *, Phase *> completed_effects;
+       construct_phase(find_output_node(), &completed_effects);
 
        output_dot("step19-split-to-phases.dot");
 
@@ -1439,6 +1440,7 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
        // Save original viewport.
        GLuint x = 0, y = 0;
        GLuint fbo = 0;
+       void *context = get_gl_context_identifier();
 
        if (width == 0 && height == 0) {
                GLint viewport[4];
@@ -1458,13 +1460,18 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
        check_error();
 
        if (phases.size() > 1) {
-               glGenFramebuffers(1, &fbo);
-               check_error();
+               if (fbos.count(context) == 0) {
+                       glGenFramebuffers(1, &fbo);
+                       check_error();
+                       fbos.insert(make_pair(context, fbo));
+               } else {
+                       fbo = fbos[context];
+               }
                glBindFramebuffer(GL_FRAMEBUFFER, fbo);
                check_error();
        }
 
-       set<Node *> generated_mipmaps;
+       set<Phase *> generated_mipmaps;
 
        // We choose the simplest option of having one texture per output,
        // since otherwise this turns into an (albeit simple) register allocation problem.
@@ -1488,9 +1495,9 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
                // Set up RTT inputs for this phase.
                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]);
+                       Phase *input = phases[phase]->inputs[sampler];
+                       input->output_node->bound_sampler_num = sampler;
+                       glBindTexture(GL_TEXTURE_2D, output_textures[input]);
                        check_error();
                        if (phases[phase]->input_needs_mipmaps) {
                                if (generated_mipmaps.count(input) == 0) {
@@ -1509,7 +1516,7 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
                        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
                        check_error();
 
-                       string texture_name = string("tex_") + phases[phase]->effect_ids[input];
+                       string texture_name = string("tex_") + phases[phase]->effect_ids[input->output_node];
                        glUniform1i(glGetUniformLocation(glsl_program_num, texture_name.c_str()), sampler);
                        check_error();
                }
@@ -1555,39 +1562,15 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
                        }
                }
 
-               // Now draw!
-               float vertices[] = {
-                       0.0f, 1.0f,
-                       0.0f, 0.0f,
-                       1.0f, 1.0f,
-                       1.0f, 0.0f
-               };
-
-               GLuint vao;
-               glGenVertexArrays(1, &vao);
-               check_error();
-               glBindVertexArray(vao);
+               glBindVertexArray(phases[phase]->vao);
                check_error();
-
-               GLuint position_vbo = fill_vertex_attribute(glsl_program_num, "position", 2, GL_FLOAT, sizeof(vertices), vertices);
-               GLuint texcoord_vbo = fill_vertex_attribute(glsl_program_num, "texcoord", 2, GL_FLOAT, sizeof(vertices), vertices);  // Same as vertices.
-
                glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
                check_error();
 
-               cleanup_vertex_attribute(glsl_program_num, "position", position_vbo);
-               cleanup_vertex_attribute(glsl_program_num, "texcoord", texcoord_vbo);
-
-               glUseProgram(0);
-               check_error();
-
                for (unsigned i = 0; i < phases[phase]->effects.size(); ++i) {
                        Node *node = phases[phase]->effects[i];
                        node->effect->clear_gl_state();
                }
-
-               glDeleteVertexArrays(1, &vao);
-               check_error();
        }
 
        for (map<Phase *, GLuint>::const_iterator texture_it = output_textures.begin();
@@ -1598,11 +1581,10 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
 
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        check_error();
-
-       if (fbo != 0) {
-               glDeleteFramebuffers(1, &fbo);
-               check_error();
-       }
+       glBindVertexArray(0);
+       check_error();
+       glUseProgram(0);
+       check_error();
 }
 
 }  // namespace movit
index 02906fe0b9885b0263db412a16b3b4d0fd540031..1ecee63d660cbac4d2565e846c908cc26efad0b0 100644 (file)
@@ -66,11 +66,6 @@ private:
        // they will be equal.
        unsigned output_width, output_height;
 
-       // If output goes to RTT, which phase it is in (otherwise unset).
-       // This is a bit ugly; we should probably fix so that Phase takes other
-       // 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.
@@ -90,19 +85,27 @@ private:
 
 // A rendering phase; a single GLSL program rendering a single quad.
 struct Phase {
+       Node *output_node;
+
        GLuint glsl_program_num;  // Owned by the resource_pool.
        bool input_needs_mipmaps;
 
        // Inputs are only inputs from other phases (ie., those that come from RTT);
-       // input textures are not counted here.
-       std::vector<Node *> inputs;
-
+       // input textures are counted as part of <effects>.
+       std::vector<Phase *> inputs;
        std::vector<Node *> effects;  // In order.
        unsigned output_width, output_height, virtual_output_width, virtual_output_height;
 
        // Identifier used to create unique variables in GLSL.
        // Unique per-phase to increase cacheability of compiled shaders.
        std::map<Node *, std::string> effect_ids;
+
+       // The geometry needed to draw this quad, bound to the vertex array
+       // object. (Seemingly it's actually a win not to upload geometry every
+       // frame, even for something as small as a quad, due to fewer state
+       // changes.)
+       GLuint vao;
+       GLuint position_vbo, texcoord_vbo;
 };
 
 class EffectChain {
@@ -224,13 +227,13 @@ private:
        // output gamma different from GAMMA_LINEAR.
        void find_all_nonlinear_inputs(Node *effect, std::vector<Node *> *nonlinear_inputs);
 
-       // Create a GLSL program computing the given effects in order.
-       Phase *compile_glsl_program(const std::vector<Node *> &inputs,
-                                   const std::vector<Node *> &effects);
+       // Create a GLSL program computing the effects for this phase in order.
+       void compile_glsl_program(Phase *phase);
 
        // Create all GLSL programs needed to compute the given effect, and all outputs
-       // that depends on it (whenever possible).
-       void construct_glsl_programs(Node *output);
+       // that depend on it (whenever possible). Returns the phase that has <output>
+       // as the last effect. Also pushes all phases in order onto <phases>.
+       Phase *construct_phase(Node *output, std::map<Node *, Phase *> *completed_effects);
 
        // Output the current graph to the given file in a Graphviz-compatible format;
        // only useful for debugging.
@@ -284,6 +287,7 @@ private:
        std::map<Effect *, Node *> node_map;
        Effect *dither_effect;
 
+       std::map<void *, GLuint> fbos;  // One for each OpenGL context.
        std::vector<Input *> inputs;  // Also contained in nodes.
        std::vector<Phase *> phases;
 
index b671620388719b4e7943698124f3fe175c40ab01..dbeb48b0381a12bd682c018c799199c5869f39cb 100644 (file)
@@ -69,6 +69,17 @@ void set_uniform_vec4(GLuint glsl_program_num, const string &prefix, const strin
        check_error();
 }
 
+void set_uniform_vec2_array(GLuint glsl_program_num, const string &prefix, const string &key, const float *values, size_t num_values)
+{
+       GLint location = get_uniform_location(glsl_program_num, prefix, key);
+       if (location == -1) {
+               return;
+       }
+       check_error();
+       glUniform2fv(location, num_values, values);
+       check_error();
+}
+
 void set_uniform_vec4_array(GLuint glsl_program_num, const string &prefix, const string &key, const float *values, size_t num_values)
 {
        GLint location = get_uniform_location(glsl_program_num, prefix, key);
index da0f95a4d66a492bc3f24e41f7eb9e836a28ed07..a1588efd71da6203a3cb137e1e1c9c38b62409b6 100644 (file)
@@ -26,6 +26,7 @@ void set_uniform_float(GLuint glsl_program_num, const std::string &prefix, const
 void set_uniform_vec2(GLuint glsl_program_num, const std::string &prefix, const std::string &key, const float *values);
 void set_uniform_vec3(GLuint glsl_program_num, const std::string &prefix, const std::string &key, const float *values);
 void set_uniform_vec4(GLuint glsl_program_num, const std::string &prefix, const std::string &key, const float *values);
+void set_uniform_vec2_array(GLuint glsl_program_num, const std::string &prefix, const std::string &key, const float *values, size_t num_values);
 void set_uniform_vec4_array(GLuint glsl_program_num, const std::string &prefix, const std::string &key, const float *values, size_t num_values);
 void set_uniform_mat3(GLuint glsl_program_num, const std::string &prefix, const std::string &key, const Eigen::Matrix3d &matrix);
 
diff --git a/effectlist.txt b/effectlist.txt
deleted file mode 100644 (file)
index 21fc509..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-                                  linear light?
-
-geometrical (5)
- * crop                                0
- * rotate                             +1
- * lens correction                    +1
- * mirror/flip                         0
- * aspect ratio change                 0
-time (2)
- * slowmo/speedup                     ??
- * stabilize                          ??
-spatial (6)
- * blur                               +2
- * sharpen                            +2
- * denoise                            +2
- * glow                               +2 
- * vignette                           +1
- * diffuse                            +2
-color and exposure (5)
- * brightness/contrast                +2
- * hue/saturation/value               +2
- * levels/curves                      -1
- * lift/gamma/gain                    +2
- * white balance                      +2
-misc (1)
- * chroma key                         ??
-
-
-linear light key:
-
- * -1: actively harmful
- *  0: don't care
- *  1: would be nice 
- *  2: important
index 6069a06e4d72f0b468f97ef979b1f629b4cd8e60..c57c031e5dba7e884a40471d5b21dc4822b52fc1 100644 (file)
@@ -13,5 +13,6 @@ Description: Movit is a library for high-quality, high-performance video filters
 Version: git
 Requires:
 Conflicts:
-Libs: -lmovit @epoxy_LIBS@
-Cflags: -I${includedir}/movit @Eigen3_CFLAGS@ @epoxy_CFLAGS@
+Libs: -lmovit
+Libs.private: @epoxy_LIBS@ @FFTW3_LIBS@
+Cflags: -I${includedir}/movit @Eigen3_CFLAGS@ @GLEW_CFLAGS@ @epoxy_CFLAGS@
index ac6a218098873953d25036605d728ed98257ec70..cce3d54a09492956b828cd65c092d15650fab033 100644 (file)
--- a/util.cpp
+++ b/util.cpp
@@ -9,6 +9,14 @@
 #include "init.h"
 #include "util.h"
 
+#if defined(__DARWIN__)
+#include <OpenGL/OpenGL.h>
+#elif defined(WIN32)
+#include <epoxy/wgl.h>
+#else
+#include <epoxy/glx.h>
+#endif
+
 using namespace std;
 
 namespace movit {
@@ -233,4 +241,15 @@ unsigned next_power_of_two(unsigned v)
        return v;
 }
 
+void *get_gl_context_identifier()
+{
+#if defined(__DARWIN__)
+       return (void *)CGLGetCurrentContext();
+#elif defined(WIN32)
+       return (void *)wglGetCurrentContext();
+#else
+       return (void *)glXGetCurrentContext();
+#endif
+}
+
 }  // namespace movit
diff --git a/util.h b/util.h
index a3e262fa59709469034f33c0c6aa4be1c5cb605b..6def56d87614a68454a45d45182e577cf612246f 100644 (file)
--- a/util.h
+++ b/util.h
@@ -56,6 +56,12 @@ void cleanup_vertex_attribute(GLuint glsl_program_num, const std::string &attrib
 // If v is not already a power of two, return the first higher power of two.
 unsigned next_power_of_two(unsigned v);
 
+// Get a pointer that represents the current OpenGL context, in a cross-platform way.
+// This is not intended for anything but identification (ie., so you can associate
+// different FBOs with different contexts); you should probably not try to cast it
+// back into anything you intend to pass into OpenGL.
+void *get_gl_context_identifier();
+
 }  // namespace movit
 
 #ifdef NDEBUG