+ // OK, no inputs. Just use the global width/height.
+ // TODO: We probably want to use the texture's size eventually.
+ phase->output_width = width;
+ phase->output_height = height;
+}
+
+void EffectChain::finalize()
+{
+ // Find the output effect. This is, simply, one that has no outgoing links.
+ // If there are multiple ones, the graph is malformed (we do not support
+ // multiple outputs right now).
+ std::vector<Node *> output_nodes;
+ for (unsigned i = 0; i < nodes.size(); ++i) {
+ Node *node = nodes[i];
+ if (node->outgoing_links.empty()) {
+ output_nodes.push_back(node);
+ }
+ }
+ assert(output_nodes.size() == 1);
+ Node *output_node = output_nodes[0];
+
+ // Add normalizers to get the output format right.
+ if (output_node->output_color_space != output_format.color_space) {
+ ColorSpaceConversionEffect *colorspace_conversion = new ColorSpaceConversionEffect();
+ colorspace_conversion->set_int("source_space", output_node->output_color_space);
+ colorspace_conversion->set_int("destination_space", output_format.color_space);
+ std::vector<Effect *> inputs;
+ inputs.push_back(output_node->effect);
+ colorspace_conversion->add_self_to_effect_chain(this, inputs);
+
+ assert(node_map.count(colorspace_conversion) != 0);
+ output_node = node_map[colorspace_conversion];
+ output_node->output_color_space = output_format.color_space;
+ }
+ if (output_node->output_gamma_curve != output_format.gamma_curve) {
+ if (output_node->output_gamma_curve != GAMMA_LINEAR) {
+ output_node = normalize_to_linear_gamma(output_node);
+ }
+ GammaCompressionEffect *gamma_conversion = new GammaCompressionEffect();
+ gamma_conversion->set_int("destination_curve", output_format.gamma_curve);
+ std::vector<Effect *> inputs;
+ inputs.push_back(output_node->effect);
+ gamma_conversion->add_self_to_effect_chain(this, inputs);
+
+ assert(node_map.count(gamma_conversion) != 0);
+ output_node = node_map[gamma_conversion];
+ output_node->output_gamma_curve = output_format.gamma_curve;
+ }
+
+ // Construct all needed GLSL programs, starting at the output.
+ construct_glsl_programs(output_node);
+
+ // 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) {
+ 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;
+ }
+ }
+
+ for (unsigned i = 0; i < inputs.size(); ++i) {
+ inputs[i]->finalize();