Move to 'using namespace std;' in all .cpp files.
[movit] / effect_chain.cpp
index 9aaa748..d80c4f8 100644 (file)
@@ -1,58 +1,66 @@
 #define GL_GLEXT_PROTOTYPES 1
 
-#include <stdio.h>
+#include <GL/glew.h>
+#include <assert.h>
+#include <locale.h>
 #include <math.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
-#include <locale.h>
-#include <assert.h>
-#include <GL/glew.h>
-
 #include <algorithm>
 #include <set>
 #include <stack>
 #include <vector>
 
-#include "util.h"
-#include "effect_chain.h"
-#include "gamma_expansion_effect.h"
-#include "gamma_compression_effect.h"
-#include "colorspace_conversion_effect.h"
-#include "alpha_multiplication_effect.h"
 #include "alpha_division_effect.h"
+#include "alpha_multiplication_effect.h"
+#include "colorspace_conversion_effect.h"
 #include "dither_effect.h"
-#include "input.h"
+#include "effect.h"
+#include "effect_chain.h"
+#include "gamma_compression_effect.h"
+#include "gamma_expansion_effect.h"
 #include "init.h"
+#include "input.h"
+#include "resource_pool.h"
+#include "util.h"
+
+using namespace std;
 
-EffectChain::EffectChain(float aspect_nom, float aspect_denom)
+EffectChain::EffectChain(float aspect_nom, float aspect_denom, ResourcePool *resource_pool)
        : aspect_nom(aspect_nom),
          aspect_denom(aspect_denom),
          dither_effect(NULL),
-         fbo(0),
          num_dither_bits(0),
-         finalized(false) {}
+         finalized(false),
+         resource_pool(resource_pool) {
+       if (resource_pool == NULL) {
+               this->resource_pool = new ResourcePool();
+               owns_resource_pool = true;
+       } else {
+               owns_resource_pool = false;
+       }
+}
 
 EffectChain::~EffectChain()
 {
        for (unsigned i = 0; i < nodes.size(); ++i) {
-               if (nodes[i]->output_texture != 0) {
-                       glDeleteTextures(1, &nodes[i]->output_texture);
-               }
                delete nodes[i]->effect;
                delete nodes[i];
        }
        for (unsigned i = 0; i < phases.size(); ++i) {
-               glDeleteProgram(phases[i]->glsl_program_num);
-               glDeleteShader(phases[i]->vertex_shader);
-               glDeleteShader(phases[i]->fragment_shader);
+               resource_pool->release_glsl_program(phases[i]->glsl_program_num);
                delete phases[i];
        }
-       if (fbo != 0) {
-               glDeleteFramebuffers(1, &fbo);
+       if (owns_resource_pool) {
+               delete resource_pool;
        }
 }
 
 Input *EffectChain::add_input(Input *input)
 {
+       assert(!finalized);
        inputs.push_back(input);
        add_node(input);
        return input;
@@ -60,26 +68,27 @@ Input *EffectChain::add_input(Input *input)
 
 void EffectChain::add_output(const ImageFormat &format, OutputAlphaFormat alpha_format)
 {
+       assert(!finalized);
        output_format = format;
        output_alpha_format = alpha_format;
 }
 
 Node *EffectChain::add_node(Effect *effect)
 {
-       char effect_id[256];
-       sprintf(effect_id, "eff%u", (unsigned)nodes.size());
+       for (unsigned i = 0; i < nodes.size(); ++i) {
+               assert(nodes[i]->effect != effect);
+       }
 
        Node *node = new Node;
        node->effect = effect;
        node->disabled = false;
-       node->effect_id = effect_id;
        node->output_color_space = COLORSPACE_INVALID;
        node->output_gamma_curve = GAMMA_INVALID;
        node->output_alpha_type = ALPHA_INVALID;
-       node->output_texture = 0;
 
        nodes.push_back(node);
        node_map[effect] = node;
+       effect->inform_added(this);
        return node;
 }
 
@@ -137,7 +146,7 @@ void EffectChain::insert_node_between(Node *sender, Node *middle, Node *receiver
        assert(middle->incoming_links.size() == middle->effect->num_inputs());
 }
 
-void EffectChain::find_all_nonlinear_inputs(Node *node, std::vector<Node *> *nonlinear_inputs)
+void EffectChain::find_all_nonlinear_inputs(Node *node, vector<Node *> *nonlinear_inputs)
 {
        if (node->output_gamma_curve == GAMMA_LINEAR &&
            node->effect->effect_type_id() != "GammaCompressionEffect") {
@@ -153,8 +162,9 @@ void EffectChain::find_all_nonlinear_inputs(Node *node, std::vector<Node *> *non
        }
 }
 
-Effect *EffectChain::add_effect(Effect *effect, const std::vector<Effect *> &inputs)
+Effect *EffectChain::add_effect(Effect *effect, const vector<Effect *> &inputs)
 {
+       assert(!finalized);
        assert(inputs.size() == effect->num_inputs());
        Node *node = add_node(effect);
        for (unsigned i = 0; i < inputs.size(); ++i) {
@@ -165,15 +175,15 @@ Effect *EffectChain::add_effect(Effect *effect, const std::vector<Effect *> &inp
 }
 
 // GLSL pre-1.30 doesn't support token pasting. Replace PREFIX(x) with <effect_id>_x.
-std::string replace_prefix(const std::string &text, const std::string &prefix)
+string replace_prefix(const string &text, const string &prefix)
 {
-       std::string output;
+       string output;
        size_t start = 0;
 
        while (start < text.size()) {
                size_t pos = text.find("PREFIX(", start);
-               if (pos == std::string::npos) {
-                       output.append(text.substr(start, std::string::npos));
+               if (pos == string::npos) {
+                       output.append(text.substr(start, string::npos));
                        break;
                }
 
@@ -206,49 +216,56 @@ std::string replace_prefix(const std::string &text, const std::string &prefix)
 }
 
 Phase *EffectChain::compile_glsl_program(
-       const std::vector<Node *> &inputs,
-       const std::vector<Node *> &effects)
+       const vector<Node *> &inputs,
+       const vector<Node *> &effects)
 {
+       Phase *phase = new Phase;
        assert(!effects.empty());
 
        // Deduplicate the inputs.
-       std::vector<Node *> true_inputs = inputs;
-       std::sort(true_inputs.begin(), true_inputs.end());
-       true_inputs.erase(std::unique(true_inputs.begin(), true_inputs.end()), true_inputs.end());
+       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;
-       std::string frag_shader = read_file("header.frag");
+       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];
+               char effect_id[256];
+               sprintf(effect_id, "in%u", i);
+               phase->effect_ids.insert(make_pair(input, effect_id));
        
-               frag_shader += std::string("uniform sampler2D tex_") + input->effect_id + ";\n";
-               frag_shader += std::string("vec4 ") + input->effect_id + "(vec2 tc) {\n";
-               frag_shader += "\treturn texture2D(tex_" + input->effect_id + ", tc);\n";
+               frag_shader += string("uniform sampler2D tex_") + effect_id + ";\n";
+               frag_shader += string("vec4 ") + effect_id + "(vec2 tc) {\n";
+               frag_shader += "\treturn texture2D(tex_" + string(effect_id) + ", tc);\n";
                frag_shader += "}\n";
                frag_shader += "\n";
        }
 
-       std::vector<Node *> sorted_effects = topological_sort(effects);
+       vector<Node *> sorted_effects = topological_sort(effects);
 
        for (unsigned i = 0; i < sorted_effects.size(); ++i) {
                Node *node = sorted_effects[i];
+               char effect_id[256];
+               sprintf(effect_id, "eff%u", i);
+               phase->effect_ids.insert(make_pair(node, effect_id));
 
                if (node->incoming_links.size() == 1) {
-                       frag_shader += std::string("#define INPUT ") + node->incoming_links[0]->effect_id + "\n";
+                       frag_shader += string("#define INPUT ") + phase->effect_ids[node->incoming_links[0]] + "\n";
                } else {
                        for (unsigned j = 0; j < node->incoming_links.size(); ++j) {
                                char buf[256];
-                               sprintf(buf, "#define INPUT%d %s\n", j + 1, node->incoming_links[j]->effect_id.c_str());
+                               sprintf(buf, "#define INPUT%d %s\n", j + 1, phase->effect_ids[node->incoming_links[j]].c_str());
                                frag_shader += buf;
                        }
                }
        
                frag_shader += "\n";
-               frag_shader += std::string("#define FUNCNAME ") + node->effect_id + "\n";
-               frag_shader += replace_prefix(node->effect->output_convenience_uniforms(), node->effect_id);
-               frag_shader += replace_prefix(node->effect->output_fragment_shader(), node->effect_id);
+               frag_shader += string("#define FUNCNAME ") + effect_id + "\n";
+               frag_shader += replace_prefix(node->effect->output_convenience_uniforms(), effect_id);
+               frag_shader += replace_prefix(node->effect->output_fragment_shader(), effect_id);
                frag_shader += "#undef PREFIX\n";
                frag_shader += "#undef FUNCNAME\n";
                if (node->incoming_links.size() == 1) {
@@ -270,37 +287,10 @@ Phase *EffectChain::compile_glsl_program(
                        CHECK(node->effect->set_int("needs_mipmaps", input_needs_mipmaps));
                }
        }
-       frag_shader += std::string("#define INPUT ") + sorted_effects.back()->effect_id + "\n";
+       frag_shader += string("#define INPUT ") + phase->effect_ids[sorted_effects.back()] + "\n";
        frag_shader.append(read_file("footer.frag"));
 
-       if (movit_debug_level == MOVIT_DEBUG_ON) {
-               // Output shader to a temporary file, for easier debugging.
-               static int compiled_shader_num = 0;
-               char filename[256];
-               sprintf(filename, "chain-%03d.frag", compiled_shader_num++);
-               FILE *fp = fopen(filename, "w");
-               if (fp == NULL) {
-                       perror(filename);
-                       exit(1);
-               }
-               fprintf(fp, "%s\n", frag_shader.c_str());
-               fclose(fp);
-       }
-       
-       GLuint glsl_program_num = glCreateProgram();
-       GLuint vs_obj = compile_shader(read_file("vs.vert"), GL_VERTEX_SHADER);
-       GLuint fs_obj = compile_shader(frag_shader, GL_FRAGMENT_SHADER);
-       glAttachShader(glsl_program_num, vs_obj);
-       check_error();
-       glAttachShader(glsl_program_num, fs_obj);
-       check_error();
-       glLinkProgram(glsl_program_num);
-       check_error();
-
-       Phase *phase = new Phase;
-       phase->glsl_program_num = glsl_program_num;
-       phase->vertex_shader = vs_obj;
-       phase->fragment_shader = fs_obj;
+       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;
@@ -321,22 +311,22 @@ void EffectChain::construct_glsl_programs(Node *output)
        // 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.
-       std::set<Node *> completed_effects;
+       set<Node *> completed_effects;
 
        // 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.
-       std::vector<Node *> this_phase_inputs;
-       std::vector<Node *> this_phase_effects;
+       vector<Node *> this_phase_inputs;
+       vector<Node *> this_phase_effects;
 
        // Effects that we have yet to calculate, but that we know should
        // be in the current phase.
-       std::stack<Node *> effects_todo_this_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.
-       std::stack<Node *> effects_todo_other_phases;
+       stack<Node *> effects_todo_other_phases;
 
        effects_todo_this_phase.push(output);
 
@@ -361,7 +351,7 @@ void EffectChain::construct_glsl_programs(Node *output)
                        completed_effects.insert(node);
 
                        // Find all the dependencies of this effect, and add them to the stack.
-                       std::vector<Node *> deps = node->incoming_links;
+                       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;
@@ -436,7 +426,7 @@ void EffectChain::construct_glsl_programs(Node *output)
 
        // Finally, since the phases are found from the output but must be executed
        // from the input(s), reverse them, too.
-       std::reverse(phases.begin(), phases.end());
+       reverse(phases.begin(), phases.end());
 }
 
 void EffectChain::output_dot(const char *filename)
@@ -455,10 +445,10 @@ void EffectChain::output_dot(const char *filename)
        fprintf(fp, "  output [shape=box label=\"(output)\"];\n");
        for (unsigned i = 0; i < nodes.size(); ++i) {
                // Find out which phase this event belongs to.
-               std::vector<int> in_phases;
+               vector<int> in_phases;
                for (unsigned j = 0; j < phases.size(); ++j) {
                        const Phase* p = phases[j];
-                       if (std::find(p->effects.begin(), p->effects.end(), nodes[i]) != p->effects.end()) {
+                       if (find(p->effects.begin(), p->effects.end(), nodes[i]) != p->effects.end()) {
                                in_phases.push_back(j);
                        }
                }
@@ -484,13 +474,13 @@ void EffectChain::output_dot(const char *filename)
                        char to_node_id[256];
                        snprintf(to_node_id, 256, "n%ld", (long)nodes[i]->outgoing_links[j]);
 
-                       std::vector<std::string> labels = get_labels_for_edge(nodes[i], nodes[i]->outgoing_links[j]);
+                       vector<string> labels = get_labels_for_edge(nodes[i], nodes[i]->outgoing_links[j]);
                        output_dot_edge(fp, from_node_id, to_node_id, labels);
                }
 
                if (nodes[i]->outgoing_links.empty() && !nodes[i]->disabled) {
                        // Output node.
-                       std::vector<std::string> labels = get_labels_for_edge(nodes[i], NULL);
+                       vector<string> labels = get_labels_for_edge(nodes[i], NULL);
                        output_dot_edge(fp, from_node_id, "output", labels);
                }
        }
@@ -499,9 +489,9 @@ void EffectChain::output_dot(const char *filename)
        fclose(fp);
 }
 
-std::vector<std::string> EffectChain::get_labels_for_edge(const Node *from, const Node *to)
+vector<string> EffectChain::get_labels_for_edge(const Node *from, const Node *to)
 {
-       std::vector<std::string> labels;
+       vector<string> labels;
 
        if (to != NULL && to->effect->needs_texture_bounce()) {
                labels.push_back("needs_bounce");
@@ -556,14 +546,14 @@ std::vector<std::string> EffectChain::get_labels_for_edge(const Node *from, cons
 }
 
 void EffectChain::output_dot_edge(FILE *fp,
-                                  const std::string &from_node_id,
-                                  const std::string &to_node_id,
-                                  const std::vector<std::string> &labels)
+                                  const string &from_node_id,
+                                  const string &to_node_id,
+                                  const vector<string> &labels)
 {
        if (labels.empty()) {
                fprintf(fp, "  %s -> %s;\n", from_node_id.c_str(), to_node_id.c_str());
        } else {
-               std::string label = labels[0];
+               string label = labels[0];
                for (unsigned k = 1; k < labels.size(); ++k) {
                        label += ", " + labels[k];
                }
@@ -618,8 +608,8 @@ void EffectChain::inform_input_sizes(Phase *phase)
        }
        for (unsigned i = 0; i < phase->inputs.size(); ++i) {
                Node *input = phase->inputs[i];
-               input->output_width = input->phase->output_width;
-               input->output_height = input->phase->output_height;
+               input->output_width = input->phase->virtual_output_width;
+               input->output_height = input->phase->virtual_output_height;
                assert(input->output_width != 0);
                assert(input->output_height != 0);
        }
@@ -662,7 +652,8 @@ void EffectChain::find_output_size(Phase *phase)
 
        // If the last effect explicitly sets an output size, use that.
        if (output_node->effect->changes_output_size()) {
-               output_node->effect->get_output_size(&phase->output_width, &phase->output_height);
+               output_node->effect->get_output_size(&phase->output_width, &phase->output_height,
+                                                    &phase->virtual_output_width, &phase->virtual_output_height);
                return;
        }
 
@@ -675,10 +666,10 @@ void EffectChain::find_output_size(Phase *phase)
                assert(input->phase->output_width != 0);
                assert(input->phase->output_height != 0);
                if (output_width == 0 && output_height == 0) {
-                       output_width = input->phase->output_width;
-                       output_height = input->phase->output_height;
-               } else if (output_width != input->phase->output_width ||
-                          output_height != input->phase->output_height) {
+                       output_width = input->phase->virtual_output_width;
+                       output_height = input->phase->virtual_output_height;
+               } else if (output_width != input->phase->virtual_output_width ||
+                          output_height != input->phase->virtual_output_height) {
                        all_inputs_same_size = false;
                }
        }
@@ -701,8 +692,8 @@ void EffectChain::find_output_size(Phase *phase)
        if (all_inputs_same_size) {
                assert(output_width != 0);
                assert(output_height != 0);
-               phase->output_width = output_width;
-               phase->output_height = output_height;
+               phase->virtual_output_width = phase->output_width = output_width;
+               phase->virtual_output_height = phase->output_height = output_height;
                return;
        }
 
@@ -726,8 +717,8 @@ void EffectChain::find_output_size(Phase *phase)
        }
        assert(output_width != 0);
        assert(output_height != 0);
-       phase->output_width = output_width;
-       phase->output_height = output_height;
+       phase->virtual_output_width = phase->output_width = output_width;
+       phase->virtual_output_height = phase->output_height = output_height;
 }
 
 void EffectChain::sort_all_nodes_topologically()
@@ -735,10 +726,10 @@ void EffectChain::sort_all_nodes_topologically()
        nodes = topological_sort(nodes);
 }
 
-std::vector<Node *> EffectChain::topological_sort(const std::vector<Node *> &nodes)
+vector<Node *> EffectChain::topological_sort(const vector<Node *> &nodes)
 {
-       std::set<Node *> nodes_left_to_visit(nodes.begin(), nodes.end());
-       std::vector<Node *> sorted_list;
+       set<Node *> nodes_left_to_visit(nodes.begin(), nodes.end());
+       vector<Node *> sorted_list;
        for (unsigned i = 0; i < nodes.size(); ++i) {
                topological_sort_visit_node(nodes[i], &nodes_left_to_visit, &sorted_list);
        }
@@ -746,7 +737,7 @@ std::vector<Node *> EffectChain::topological_sort(const std::vector<Node *> &nod
        return sorted_list;
 }
 
-void EffectChain::topological_sort_visit_node(Node *node, std::set<Node *> *nodes_left_to_visit, std::vector<Node *> *sorted_list)
+void EffectChain::topological_sort_visit_node(Node *node, set<Node *> *nodes_left_to_visit, vector<Node *> *sorted_list)
 {
        if (nodes_left_to_visit->count(node) == 0) {
                return;
@@ -775,12 +766,13 @@ void EffectChain::find_color_spaces_for_inputs()
                        case Effect::OUTPUT_BLANK_ALPHA:
                                node->output_alpha_type = ALPHA_BLANK;
                                break;
-                       case Effect::INPUT_AND_OUTPUT_ALPHA_PREMULTIPLIED:
+                       case Effect::INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA:
                                node->output_alpha_type = ALPHA_PREMULTIPLIED;
                                break;
-                       case Effect::OUTPUT_ALPHA_POSTMULTIPLIED:
+                       case Effect::OUTPUT_POSTMULTIPLIED_ALPHA:
                                node->output_alpha_type = ALPHA_POSTMULTIPLIED;
                                break;
+                       case Effect::INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK:
                        case Effect::DONT_CARE_ALPHA_TYPE:
                        default:
                                assert(false);
@@ -884,7 +876,7 @@ void EffectChain::propagate_alpha()
                }
 
                // Only inputs can have unconditional alpha output (OUTPUT_BLANK_ALPHA
-               // or OUTPUT_ALPHA_POSTMULTIPLIED), and they have already been
+               // or OUTPUT_POSTMULTIPLIED_ALPHA), and they have already been
                // taken care of above. Rationale: Even if you could imagine
                // e.g. an effect that took in an image and set alpha=1.0
                // unconditionally, it wouldn't make any sense to have it as
@@ -892,7 +884,8 @@ void EffectChain::propagate_alpha()
                // got its input pre- or postmultiplied, so it wouldn't know
                // whether to divide away the old alpha or not.
                Effect::AlphaHandling alpha_handling = node->effect->alpha_handling();
-               assert(alpha_handling == Effect::INPUT_AND_OUTPUT_ALPHA_PREMULTIPLIED ||
+               assert(alpha_handling == Effect::INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA ||
+                      alpha_handling == Effect::INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK ||
                       alpha_handling == Effect::DONT_CARE_ALPHA_TYPE);
 
                // If the node has multiple inputs, check that they are all valid and
@@ -932,16 +925,16 @@ void EffectChain::propagate_alpha()
                        continue;
                }
 
-               if (alpha_handling == Effect::INPUT_AND_OUTPUT_ALPHA_PREMULTIPLIED) {
+               if (alpha_handling == Effect::INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA ||
+                   alpha_handling == Effect::INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK) {
                        // If the effect has asked for premultiplied alpha, check that it has got it.
                        if (any_postmultiplied) {
                                node->output_alpha_type = ALPHA_INVALID;
+                       } else if (!any_premultiplied &&
+                                  alpha_handling == Effect::INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK) {
+                               // Blank input alpha, and the effect preserves blank alpha.
+                               node->output_alpha_type = ALPHA_BLANK;
                        } else {
-                               // In some rare cases, it might be advantageous to say
-                               // that blank input alpha yields blank output alpha.
-                               // However, this would cause a more complex Effect interface
-                               // an effect would need to guarantee that it doesn't mess with
-                               // blank alpha), so this is the simplest.
                                node->output_alpha_type = ALPHA_PREMULTIPLIED;
                        }
                } else {
@@ -1140,14 +1133,14 @@ void EffectChain::fix_output_alpha()
                return;
        }
        if (output->output_alpha_type == ALPHA_PREMULTIPLIED &&
-           output_alpha_format == OUTPUT_ALPHA_POSTMULTIPLIED) {
+           output_alpha_format == OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED) {
                Node *conversion = add_node(new AlphaDivisionEffect());
                connect_nodes(output, conversion);
                propagate_alpha();
                propagate_gamma_and_color_space();
        }
        if (output->output_alpha_type == ALPHA_POSTMULTIPLIED &&
-           output_alpha_format == OUTPUT_ALPHA_PREMULTIPLIED) {
+           output_alpha_format == OUTPUT_ALPHA_FORMAT_PREMULTIPLIED) {
                Node *conversion = add_node(new AlphaMultiplicationEffect());
                connect_nodes(output, conversion);
                propagate_alpha();
@@ -1213,7 +1206,7 @@ void EffectChain::fix_internal_gamma_by_asking_inputs(unsigned step)
                        }
 
                        // See if all inputs can give us linear gamma. If not, leave it.
-                       std::vector<Node *> nonlinear_inputs;
+                       vector<Node *> nonlinear_inputs;
                        find_all_nonlinear_inputs(node, &nonlinear_inputs);
                        assert(!nonlinear_inputs.empty());
 
@@ -1344,7 +1337,7 @@ void EffectChain::add_dither_if_needed()
 // multiple outputs right now).
 Node *EffectChain::find_output_node()
 {
-       std::vector<Node *> output_nodes;
+       vector<Node *> output_nodes;
        for (unsigned i = 0; i < nodes.size(); ++i) {
                Node *node = nodes[i];
                if (node->disabled) {
@@ -1419,36 +1412,6 @@ void EffectChain::finalize()
 
        output_dot("step19-split-to-phases.dot");
 
-       // If we have more than one phase, we need intermediate render-to-texture.
-       // Construct an FBO, and then as many textures as we need.
-       // We choose the simplest option of having one texture per output,
-       // since otherwise this turns into an (albeit simple)
-       // register allocation problem.
-       if (phases.size() > 1) {
-               glGenFramebuffers(1, &fbo);
-
-               for (unsigned i = 0; i < phases.size() - 1; ++i) {
-                       inform_input_sizes(phases[i]);
-                       find_output_size(phases[i]);
-
-                       Node *output_node = phases[i]->effects.back();
-                       glGenTextures(1, &output_node->output_texture);
-                       check_error();
-                       glBindTexture(GL_TEXTURE_2D, output_node->output_texture);
-                       check_error();
-                       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-                       check_error();
-                       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-                       check_error();
-                       glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, phases[i]->output_width, phases[i]->output_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
-                       check_error();
-
-                       output_node->output_texture_width = phases[i]->output_width;
-                       output_node->output_texture_height = phases[i]->output_height;
-               }
-               inform_input_sizes(phases.back());
-       }
-               
        for (unsigned i = 0; i < inputs.size(); ++i) {
                inputs[i]->finalize();
        }
@@ -1465,6 +1428,7 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
 
        // Save original viewport.
        GLuint x = 0, y = 0;
+       GLuint fbo = 0;
 
        if (width == 0 && height == 0) {
                GLint viewport[4];
@@ -1491,35 +1455,26 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
        glLoadIdentity();
 
        if (phases.size() > 1) {
+               glGenFramebuffers(1, &fbo);
+               check_error();
                glBindFramebuffer(GL_FRAMEBUFFER, fbo);
                check_error();
        }
 
-       std::set<Node *> generated_mipmaps;
+       set<Node *> generated_mipmaps;
+
+       // We choose the simplest option of having one texture per output,
+       // since otherwise this turns into an (albeit simple) register allocation problem.
+       map<Phase *, GLuint> output_textures;
 
        for (unsigned phase = 0; phase < phases.size(); ++phase) {
-               // See if the requested output size has changed. If so, we need to recreate
-               // the texture (and before we start setting up inputs).
+               // Find a texture for this phase.
                inform_input_sizes(phases[phase]);
                if (phase != phases.size() - 1) {
                        find_output_size(phases[phase]);
 
-                       Node *output_node = phases[phase]->effects.back();
-
-                       if (output_node->output_texture_width != phases[phase]->output_width ||
-                           output_node->output_texture_height != phases[phase]->output_height) {
-                               glActiveTexture(GL_TEXTURE0);
-                               check_error();
-                               glBindTexture(GL_TEXTURE_2D, output_node->output_texture);
-                               check_error();
-                               glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, phases[phase]->output_width, phases[phase]->output_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
-                               check_error();
-                               glBindTexture(GL_TEXTURE_2D, 0);
-                               check_error();
-
-                               output_node->output_texture_width = phases[phase]->output_width;
-                               output_node->output_texture_height = phases[phase]->output_height;
-                       }
+                       GLuint tex_num = resource_pool->create_2d_texture(GL_RGBA16F_ARB, phases[phase]->output_width, phases[phase]->output_height);
+                       output_textures.insert(make_pair(phases[phase], tex_num));
                }
 
                glUseProgram(phases[phase]->glsl_program_num);
@@ -1529,7 +1484,7 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
                for (unsigned sampler = 0; sampler < phases[phase]->inputs.size(); ++sampler) {
                        glActiveTexture(GL_TEXTURE0 + sampler);
                        Node *input = phases[phase]->inputs[sampler];
-                       glBindTexture(GL_TEXTURE_2D, input->output_texture);
+                       glBindTexture(GL_TEXTURE_2D, output_textures[input->phase]);
                        check_error();
                        if (phases[phase]->input_needs_mipmaps) {
                                if (generated_mipmaps.count(input) == 0) {
@@ -1543,8 +1498,12 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
                                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                                check_error();
                        }
+                       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+                       check_error();
+                       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+                       check_error();
 
-                       std::string texture_name = std::string("tex_") + input->effect_id;
+                       string texture_name = string("tex_") + phases[phase]->effect_ids[input];
                        glUniform1i(glGetUniformLocation(phases[phase]->glsl_program_num, texture_name.c_str()), sampler);
                        check_error();
                }
@@ -1554,20 +1513,23 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
                        // Last phase goes to the output the user specified.
                        glBindFramebuffer(GL_FRAMEBUFFER, dest_fbo);
                        check_error();
+                       GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+                       assert(status == GL_FRAMEBUFFER_COMPLETE);
                        glViewport(x, y, width, height);
                        if (dither_effect != NULL) {
                                CHECK(dither_effect->set_int("output_width", width));
                                CHECK(dither_effect->set_int("output_height", height));
                        }
                } else {
-                       Node *output_node = phases[phase]->effects.back();
                        glFramebufferTexture2D(
                                GL_FRAMEBUFFER,
                                GL_COLOR_ATTACHMENT0,
                                GL_TEXTURE_2D,
-                               output_node->output_texture,
+                               output_textures[phases[phase]],
                                0);
                        check_error();
+                       GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+                       assert(status == GL_FRAMEBUFFER_COMPLETE);
                        glViewport(0, 0, phases[phase]->output_width, phases[phase]->output_height);
                }
 
@@ -1575,7 +1537,7 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
                unsigned sampler_num = phases[phase]->inputs.size();
                for (unsigned i = 0; i < phases[phase]->effects.size(); ++i) {
                        Node *node = phases[phase]->effects[i];
-                       node->effect->set_gl_state(phases[phase]->glsl_program_num, node->effect_id, &sampler_num);
+                       node->effect->set_gl_state(phases[phase]->glsl_program_num, phases[phase]->effect_ids[node], &sampler_num);
                        check_error();
                }
 
@@ -1602,4 +1564,18 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
                        node->effect->clear_gl_state();
                }
        }
+
+       for (map<Phase *, GLuint>::const_iterator texture_it = output_textures.begin();
+            texture_it != output_textures.end();
+            ++texture_it) {
+               resource_pool->release_2d_texture(texture_it->second);
+       }
+
+       glBindFramebuffer(GL_FRAMEBUFFER, 0);
+       check_error();
+
+       if (fbo != 0) {
+               glDeleteFramebuffers(1, &fbo);
+               check_error();
+       }
 }