1 #define GL_GLEXT_PROTOTYPES 1
13 #include "effect_chain.h"
14 #include "gamma_expansion_effect.h"
15 #include "gamma_compression_effect.h"
16 #include "colorspace_conversion_effect.h"
20 EffectChain::EffectChain(unsigned width, unsigned height)
25 Input *EffectChain::add_input(Input *input)
27 inputs.push_back(input);
29 Node *node = add_node(input);
30 node->output_color_space = input->get_color_space();
31 node->output_gamma_curve = input->get_gamma_curve();
35 void EffectChain::add_output(const ImageFormat &format)
37 output_format = format;
40 Node *EffectChain::add_node(Effect *effect)
43 sprintf(effect_id, "eff%u", (unsigned)nodes.size());
45 Node *node = new Node;
46 node->effect = effect;
47 node->disabled = false;
48 node->effect_id = effect_id;
49 node->output_color_space = COLORSPACE_INVALID;
50 node->output_gamma_curve = GAMMA_INVALID;
52 nodes.push_back(node);
53 node_map[effect] = node;
57 void EffectChain::connect_nodes(Node *sender, Node *receiver)
59 sender->outgoing_links.push_back(receiver);
60 receiver->incoming_links.push_back(sender);
63 void EffectChain::replace_receiver(Node *old_receiver, Node *new_receiver)
65 new_receiver->incoming_links = old_receiver->incoming_links;
66 old_receiver->incoming_links.clear();
68 for (unsigned i = 0; i < new_receiver->incoming_links.size(); ++i) {
69 Node *sender = new_receiver->incoming_links[i];
70 for (unsigned j = 0; j < sender->outgoing_links.size(); ++j) {
71 if (sender->outgoing_links[j] == old_receiver) {
72 sender->outgoing_links[j] = new_receiver;
78 void EffectChain::replace_sender(Node *old_sender, Node *new_sender)
80 new_sender->outgoing_links = old_sender->outgoing_links;
81 old_sender->outgoing_links.clear();
83 for (unsigned i = 0; i < new_sender->outgoing_links.size(); ++i) {
84 Node *receiver = new_sender->outgoing_links[i];
85 for (unsigned j = 0; j < receiver->incoming_links.size(); ++j) {
86 if (receiver->incoming_links[j] == old_sender) {
87 receiver->incoming_links[j] = new_sender;
93 void EffectChain::insert_node_between(Node *sender, Node *middle, Node *receiver)
95 for (unsigned i = 0; i < sender->outgoing_links.size(); ++i) {
96 if (sender->outgoing_links[i] == receiver) {
97 sender->outgoing_links[i] = middle;
98 middle->incoming_links.push_back(sender);
101 for (unsigned i = 0; i < receiver->incoming_links.size(); ++i) {
102 if (receiver->incoming_links[i] == sender) {
103 receiver->incoming_links[i] = middle;
104 middle->outgoing_links.push_back(receiver);
108 assert(middle->incoming_links.size() == middle->effect->num_inputs());
111 void EffectChain::find_all_nonlinear_inputs(Node *node, std::vector<Node *> *nonlinear_inputs)
113 if (node->output_gamma_curve == GAMMA_LINEAR) {
116 if (node->effect->num_inputs() == 0) {
117 nonlinear_inputs->push_back(node);
119 assert(node->effect->num_inputs() == node->incoming_links.size());
120 for (unsigned i = 0; i < node->incoming_links.size(); ++i) {
121 find_all_nonlinear_inputs(node->incoming_links[i], nonlinear_inputs);
126 Effect *EffectChain::add_effect(Effect *effect, const std::vector<Effect *> &inputs)
128 assert(inputs.size() == effect->num_inputs());
129 Node *node = add_node(effect);
130 for (unsigned i = 0; i < inputs.size(); ++i) {
131 assert(node_map.count(inputs[i]) != 0);
132 connect_nodes(node_map[inputs[i]], node);
137 // GLSL pre-1.30 doesn't support token pasting. Replace PREFIX(x) with <effect_id>_x.
138 std::string replace_prefix(const std::string &text, const std::string &prefix)
143 while (start < text.size()) {
144 size_t pos = text.find("PREFIX(", start);
145 if (pos == std::string::npos) {
146 output.append(text.substr(start, std::string::npos));
150 output.append(text.substr(start, pos - start));
151 output.append(prefix);
154 pos += strlen("PREFIX(");
156 // Output stuff until we find the matching ), which we then eat.
158 size_t end_arg_pos = pos;
159 while (end_arg_pos < text.size()) {
160 if (text[end_arg_pos] == '(') {
162 } else if (text[end_arg_pos] == ')') {
170 output.append(text.substr(pos, end_arg_pos - pos));
178 Phase *EffectChain::compile_glsl_program(
179 const std::vector<Node *> &inputs,
180 const std::vector<Node *> &effects)
182 assert(!effects.empty());
184 // Deduplicate the inputs.
185 std::vector<Node *> true_inputs = inputs;
186 std::sort(true_inputs.begin(), true_inputs.end());
187 true_inputs.erase(std::unique(true_inputs.begin(), true_inputs.end()), true_inputs.end());
189 bool input_needs_mipmaps = false;
190 std::string frag_shader = read_file("header.frag");
192 // Create functions for all the texture inputs that we need.
193 for (unsigned i = 0; i < true_inputs.size(); ++i) {
194 Node *input = true_inputs[i];
196 frag_shader += std::string("uniform sampler2D tex_") + input->effect_id + ";\n";
197 frag_shader += std::string("vec4 ") + input->effect_id + "(vec2 tc) {\n";
198 frag_shader += "\treturn texture2D(tex_" + input->effect_id + ", tc);\n";
199 frag_shader += "}\n";
203 for (unsigned i = 0; i < effects.size(); ++i) {
204 Node *node = effects[i];
206 if (node->incoming_links.size() == 1) {
207 frag_shader += std::string("#define INPUT ") + node->incoming_links[0]->effect_id + "\n";
209 for (unsigned j = 0; j < node->incoming_links.size(); ++j) {
211 sprintf(buf, "#define INPUT%d %s\n", j + 1, node->incoming_links[j]->effect_id.c_str());
217 frag_shader += std::string("#define FUNCNAME ") + node->effect_id + "\n";
218 frag_shader += replace_prefix(node->effect->output_convenience_uniforms(), node->effect_id);
219 frag_shader += replace_prefix(node->effect->output_fragment_shader(), node->effect_id);
220 frag_shader += "#undef PREFIX\n";
221 frag_shader += "#undef FUNCNAME\n";
222 if (node->incoming_links.size() == 1) {
223 frag_shader += "#undef INPUT\n";
225 for (unsigned j = 0; j < node->incoming_links.size(); ++j) {
227 sprintf(buf, "#undef INPUT%d\n", j + 1);
233 input_needs_mipmaps |= node->effect->needs_mipmaps();
235 for (unsigned i = 0; i < effects.size(); ++i) {
236 Node *node = effects[i];
237 if (node->effect->num_inputs() == 0) {
238 node->effect->set_int("needs_mipmaps", input_needs_mipmaps);
241 frag_shader += std::string("#define INPUT ") + effects.back()->effect_id + "\n";
242 frag_shader.append(read_file("footer.frag"));
243 printf("%s\n", frag_shader.c_str());
245 GLuint glsl_program_num = glCreateProgram();
246 GLuint vs_obj = compile_shader(read_file("vs.vert"), GL_VERTEX_SHADER);
247 GLuint fs_obj = compile_shader(frag_shader, GL_FRAGMENT_SHADER);
248 glAttachShader(glsl_program_num, vs_obj);
250 glAttachShader(glsl_program_num, fs_obj);
252 glLinkProgram(glsl_program_num);
255 Phase *phase = new Phase;
256 phase->glsl_program_num = glsl_program_num;
257 phase->input_needs_mipmaps = input_needs_mipmaps;
258 phase->inputs = true_inputs;
259 phase->effects = effects;
264 // Construct GLSL programs, starting at the given effect and following
265 // the chain from there. We end a program every time we come to an effect
266 // marked as "needs texture bounce", one that is used by multiple other
267 // effects, every time an effect wants to change the output size,
268 // and of course at the end.
270 // We follow a quite simple depth-first search from the output, although
271 // without any explicit recursion.
272 void EffectChain::construct_glsl_programs(Node *output)
274 // Which effects have already been completed in this phase?
275 // We need to keep track of it, as an effect with multiple outputs
276 // could otherwise be calculate multiple times.
277 std::set<Node *> completed_effects;
279 // Effects in the current phase, as well as inputs (outputs from other phases
280 // that we depend on). Note that since we start iterating from the end,
281 // the effect list will be in the reverse order.
282 std::vector<Node *> this_phase_inputs;
283 std::vector<Node *> this_phase_effects;
285 // Effects that we have yet to calculate, but that we know should
286 // be in the current phase.
287 std::stack<Node *> effects_todo_this_phase;
289 // Effects that we have yet to calculate, but that come from other phases.
290 // We delay these until we have this phase done in its entirety,
291 // at which point we pick any of them and start a new phase from that.
292 std::stack<Node *> effects_todo_other_phases;
294 effects_todo_this_phase.push(output);
296 for ( ;; ) { // Termination condition within loop.
297 if (!effects_todo_this_phase.empty()) {
298 // OK, we have more to do this phase.
299 Node *node = effects_todo_this_phase.top();
300 effects_todo_this_phase.pop();
302 // This should currently only happen for effects that are phase outputs,
303 // and we throw those out separately below.
304 assert(completed_effects.count(node) == 0);
306 this_phase_effects.push_back(node);
307 completed_effects.insert(node);
309 // Find all the dependencies of this effect, and add them to the stack.
310 std::vector<Node *> deps = node->incoming_links;
311 assert(node->effect->num_inputs() == deps.size());
312 for (unsigned i = 0; i < deps.size(); ++i) {
313 bool start_new_phase = false;
315 // FIXME: If we sample directly from a texture, we won't need this.
316 if (node->effect->needs_texture_bounce()) {
317 start_new_phase = true;
320 if (deps[i]->outgoing_links.size() > 1 && deps[i]->effect->num_inputs() > 0) {
321 // More than one effect uses this as the input,
322 // and it is not a texture itself.
323 // The easiest thing to do (and probably also the safest
324 // performance-wise in most cases) is to bounce it to a texture
325 // and then let the next passes read from that.
326 start_new_phase = true;
329 if (deps[i]->effect->changes_output_size()) {
330 start_new_phase = true;
333 if (start_new_phase) {
334 effects_todo_other_phases.push(deps[i]);
335 this_phase_inputs.push_back(deps[i]);
337 effects_todo_this_phase.push(deps[i]);
343 // No more effects to do this phase. Take all the ones we have,
344 // and create a GLSL program for it.
345 if (!this_phase_effects.empty()) {
346 reverse(this_phase_effects.begin(), this_phase_effects.end());
347 phases.push_back(compile_glsl_program(this_phase_inputs, this_phase_effects));
348 this_phase_effects.back()->phase = phases.back();
349 this_phase_inputs.clear();
350 this_phase_effects.clear();
352 assert(this_phase_inputs.empty());
353 assert(this_phase_effects.empty());
355 // If we have no effects left, exit.
356 if (effects_todo_other_phases.empty()) {
360 Node *node = effects_todo_other_phases.top();
361 effects_todo_other_phases.pop();
363 if (completed_effects.count(node) == 0) {
364 // Start a new phase, calculating from this effect.
365 effects_todo_this_phase.push(node);
369 // Finally, since the phases are found from the output but must be executed
370 // from the input(s), reverse them, too.
371 std::reverse(phases.begin(), phases.end());
374 void EffectChain::output_dot(const char *filename)
376 FILE *fp = fopen(filename, "w");
382 fprintf(fp, "digraph G {\n");
383 for (unsigned i = 0; i < nodes.size(); ++i) {
384 fprintf(fp, " n%ld [label=\"%s\"];\n", (long)nodes[i], nodes[i]->effect->effect_type_id().c_str());
385 for (unsigned j = 0; j < nodes[i]->outgoing_links.size(); ++j) {
386 std::vector<std::string> labels;
388 if (nodes[i]->outgoing_links[j]->effect->needs_texture_bounce()) {
389 labels.push_back("needs_bounce");
391 if (nodes[i]->effect->changes_output_size()) {
392 labels.push_back("resize");
395 switch (nodes[i]->output_color_space) {
396 case COLORSPACE_INVALID:
397 labels.push_back("spc[invalid]");
399 case COLORSPACE_REC_601_525:
400 labels.push_back("spc[rec601-525]");
402 case COLORSPACE_REC_601_625:
403 labels.push_back("spc[rec601-625]");
409 switch (nodes[i]->output_gamma_curve) {
411 labels.push_back("gamma[invalid]");
414 labels.push_back("gamma[sRGB]");
416 case GAMMA_REC_601: // and GAMMA_REC_709
417 labels.push_back("gamma[rec601/709]");
423 if (labels.empty()) {
424 fprintf(fp, " n%ld -> n%ld;\n", (long)nodes[i], (long)nodes[i]->outgoing_links[j]);
426 std::string label = labels[0];
427 for (unsigned k = 1; k < labels.size(); ++k) {
428 label += ", " + labels[k];
430 fprintf(fp, " n%ld -> n%ld [label=\"%s\"];\n", (long)nodes[i], (long)nodes[i]->outgoing_links[j], label.c_str());
439 void EffectChain::find_output_size(Phase *phase)
441 Node *output_node = phase->effects.back();
443 // If the last effect explicitly sets an output size,
445 if (output_node->effect->changes_output_size()) {
446 output_node->effect->get_output_size(&phase->output_width, &phase->output_height);
450 // If not, look at the input phases, if any. We select the largest one
451 // (really assuming they all have the same aspect currently), by pixel count.
452 if (!phase->inputs.empty()) {
453 unsigned best_width = 0, best_height = 0;
454 for (unsigned i = 0; i < phase->inputs.size(); ++i) {
455 Node *input = phase->inputs[i];
456 assert(input->phase->output_width != 0);
457 assert(input->phase->output_height != 0);
458 if (input->phase->output_width * input->phase->output_height > best_width * best_height) {
459 best_width = input->phase->output_width;
460 best_height = input->phase->output_height;
463 assert(best_width != 0);
464 assert(best_height != 0);
465 phase->output_width = best_width;
466 phase->output_height = best_height;
470 // OK, no inputs. Just use the global width/height.
471 // TODO: We probably want to use the texture's size eventually.
472 phase->output_width = width;
473 phase->output_height = height;
476 void EffectChain::sort_nodes_topologically()
478 std::set<Node *> visited_nodes;
479 std::vector<Node *> sorted_list;
480 for (unsigned i = 0; i < nodes.size(); ++i) {
481 if (nodes[i]->incoming_links.size() == 0) {
482 topological_sort_visit_node(nodes[i], &visited_nodes, &sorted_list);
485 reverse(sorted_list.begin(), sorted_list.end());
489 void EffectChain::topological_sort_visit_node(Node *node, std::set<Node *> *visited_nodes, std::vector<Node *> *sorted_list)
491 if (visited_nodes->count(node) != 0) {
494 visited_nodes->insert(node);
495 for (unsigned i = 0; i < node->outgoing_links.size(); ++i) {
496 topological_sort_visit_node(node->outgoing_links[i], visited_nodes, sorted_list);
498 sorted_list->push_back(node);
501 // Propagate gamma and color space information as far as we can in the graph.
502 // The rules are simple: Anything where all the inputs agree, get that as
503 // output as well. Anything else keeps having *_INVALID.
504 void EffectChain::propagate_gamma_and_color_space()
506 // We depend on going through the nodes in order.
507 sort_nodes_topologically();
509 for (unsigned i = 0; i < nodes.size(); ++i) {
510 Node *node = nodes[i];
511 if (node->disabled) {
514 assert(node->incoming_links.size() == node->effect->num_inputs());
515 if (node->incoming_links.size() == 0) {
516 assert(node->output_color_space != COLORSPACE_INVALID);
517 assert(node->output_gamma_curve != GAMMA_INVALID);
521 ColorSpace color_space = node->incoming_links[0]->output_color_space;
522 GammaCurve gamma_curve = node->incoming_links[0]->output_gamma_curve;
523 for (unsigned j = 1; j < node->incoming_links.size(); ++j) {
524 if (node->incoming_links[j]->output_color_space != color_space) {
525 color_space = COLORSPACE_INVALID;
527 if (node->incoming_links[j]->output_gamma_curve != gamma_curve) {
528 gamma_curve = GAMMA_INVALID;
532 // The conversion effects already have their outputs set correctly,
533 // so leave them alone.
534 if (node->effect->effect_type_id() != "ColorSpaceConversionEffect") {
535 node->output_color_space = color_space;
537 if (node->effect->effect_type_id() != "GammaCompressionEffect" &&
538 node->effect->effect_type_id() != "GammaExpansionEffect") {
539 node->output_gamma_curve = gamma_curve;
544 bool EffectChain::node_needs_colorspace_fix(Node *node)
546 if (node->disabled) {
549 if (node->effect->num_inputs() == 0) {
553 // propagate_gamma_and_color_space() has already set our output
554 // to COLORSPACE_INVALID if the inputs differ, so we can rely on that.
555 if (node->output_color_space == COLORSPACE_INVALID) {
558 return (node->effect->needs_srgb_primaries() && node->output_color_space != COLORSPACE_sRGB);
561 // Fix up color spaces so that there are no COLORSPACE_INVALID nodes left in
562 // the graph. Our strategy is not always optimal, but quite simple:
563 // Find an effect that's as early as possible where the inputs are of
564 // unacceptable colorspaces (that is, either different, or, if the effect only
565 // wants sRGB, not sRGB.) Add appropriate conversions on all its inputs,
566 // propagate the information anew, and repeat until there are no more such
568 void EffectChain::fix_internal_color_spaces()
570 unsigned colorspace_propagation_pass = 0;
574 for (unsigned i = 0; i < nodes.size(); ++i) {
575 Node *node = nodes[i];
576 if (!node_needs_colorspace_fix(node)) {
580 // Go through each input that is not sRGB, and insert
581 // a colorspace conversion before it.
582 for (unsigned j = 0; j < node->incoming_links.size(); ++j) {
583 Node *input = node->incoming_links[j];
584 assert(input->output_color_space != COLORSPACE_INVALID);
585 if (input->output_color_space == COLORSPACE_sRGB) {
588 Node *conversion = add_node(new ColorSpaceConversionEffect());
589 conversion->effect->set_int("source_space", input->output_color_space);
590 conversion->effect->set_int("destination_space", COLORSPACE_sRGB);
591 conversion->output_color_space = COLORSPACE_sRGB;
592 insert_node_between(input, conversion, node);
595 // Re-sort topologically, and propagate the new information.
596 propagate_gamma_and_color_space();
603 sprintf(filename, "step3-colorspacefix-iter%u.dot", ++colorspace_propagation_pass);
604 output_dot(filename);
605 assert(colorspace_propagation_pass < 100);
608 for (unsigned i = 0; i < nodes.size(); ++i) {
609 Node *node = nodes[i];
610 if (node->disabled) {
613 assert(node->output_color_space != COLORSPACE_INVALID);
617 // Make so that the output is in the desired color space.
618 void EffectChain::fix_output_color_space()
620 Node *output = find_output_node();
621 if (output->output_color_space != output_format.color_space) {
622 Node *conversion = add_node(new ColorSpaceConversionEffect());
623 conversion->effect->set_int("source_space", output->output_color_space);
624 conversion->effect->set_int("destination_space", output_format.color_space);
625 conversion->output_color_space = output_format.color_space;
626 connect_nodes(output, conversion);
630 bool EffectChain::node_needs_gamma_fix(Node *node)
632 if (node->disabled) {
635 if (node->effect->num_inputs() == 0) {
639 // propagate_gamma_and_color_space() has already set our output
640 // to GAMMA_INVALID if the inputs differ, so we can rely on that,
641 // except for GammaCompressionEffect.
642 if (node->output_gamma_curve == GAMMA_INVALID) {
645 if (node->effect->effect_type_id() == "GammaCompressionEffect") {
646 assert(node->incoming_links.size() == 1);
647 return node->incoming_links[0]->output_gamma_curve != GAMMA_LINEAR;
649 return (node->effect->needs_linear_light() && node->output_gamma_curve != GAMMA_LINEAR);
652 // Very similar to fix_internal_color_spaces(), but for gamma.
653 // There is one difference, though; before we start adding conversion nodes,
654 // we see if we can get anything out of asking the sources to deliver
655 // linear gamma directly. fix_internal_gamma_by_asking_inputs()
656 // does that part, while fix_internal_gamma_by_inserting_nodes()
657 // inserts nodes as needed afterwards.
658 void EffectChain::fix_internal_gamma_by_asking_inputs(unsigned step)
660 unsigned gamma_propagation_pass = 0;
664 for (unsigned i = 0; i < nodes.size(); ++i) {
665 Node *node = nodes[i];
666 if (!node_needs_gamma_fix(node)) {
670 // See if all inputs can give us linear gamma. If not, leave it.
671 std::vector<Node *> nonlinear_inputs;
672 find_all_nonlinear_inputs(node, &nonlinear_inputs);
675 for (unsigned i = 0; i < nonlinear_inputs.size(); ++i) {
676 Input *input = static_cast<Input *>(nonlinear_inputs[i]->effect);
677 all_ok &= input->can_output_linear_gamma();
684 for (unsigned i = 0; i < nonlinear_inputs.size(); ++i) {
685 nonlinear_inputs[i]->effect->set_int("output_linear_gamma", 1);
686 nonlinear_inputs[i]->output_gamma_curve = GAMMA_LINEAR;
689 // Re-sort topologically, and propagate the new information.
690 propagate_gamma_and_color_space();
697 sprintf(filename, "step%u-gammafix-iter%u.dot", step, ++gamma_propagation_pass);
698 output_dot(filename);
699 assert(gamma_propagation_pass < 100);
703 void EffectChain::fix_internal_gamma_by_inserting_nodes(unsigned step)
705 unsigned gamma_propagation_pass = 0;
709 for (unsigned i = 0; i < nodes.size(); ++i) {
710 Node *node = nodes[i];
711 if (!node_needs_gamma_fix(node)) {
715 // Go through each input that is not linear gamma, and insert
716 // a gamma conversion before it.
717 for (unsigned j = 0; j < node->incoming_links.size(); ++j) {
718 Node *input = node->incoming_links[j];
719 assert(input->output_gamma_curve != GAMMA_INVALID);
720 if (input->output_gamma_curve == GAMMA_LINEAR) {
723 Node *conversion = add_node(new GammaExpansionEffect());
724 conversion->effect->set_int("destination_curve", GAMMA_LINEAR);
725 conversion->output_gamma_curve = GAMMA_LINEAR;
726 insert_node_between(input, conversion, node);
729 // Re-sort topologically, and propagate the new information.
730 propagate_gamma_and_color_space();
737 sprintf(filename, "step%u-gammafix-iter%u.dot", step, ++gamma_propagation_pass);
738 output_dot(filename);
739 assert(gamma_propagation_pass < 100);
742 for (unsigned i = 0; i < nodes.size(); ++i) {
743 Node *node = nodes[i];
744 if (node->disabled) {
747 assert(node->output_gamma_curve != GAMMA_INVALID);
751 // Make so that the output is in the desired gamma.
752 // Note that this assumes linear input gamma, so it might create the need
753 // for another pass of fix_internal_gamma().
754 void EffectChain::fix_output_gamma()
756 Node *output = find_output_node();
757 if (output->output_gamma_curve != output_format.gamma_curve) {
758 Node *conversion = add_node(new GammaCompressionEffect());
759 conversion->effect->set_int("destination_curve", output_format.gamma_curve);
760 conversion->output_gamma_curve = output_format.gamma_curve;
761 connect_nodes(output, conversion);
765 // Find the output node. This is, simply, one that has no outgoing links.
766 // If there are multiple ones, the graph is malformed (we do not support
767 // multiple outputs right now).
768 Node *EffectChain::find_output_node()
770 std::vector<Node *> output_nodes;
771 for (unsigned i = 0; i < nodes.size(); ++i) {
772 Node *node = nodes[i];
773 if (node->disabled) {
776 if (node->outgoing_links.empty()) {
777 output_nodes.push_back(node);
780 assert(output_nodes.size() == 1);
781 return output_nodes[0];
784 void EffectChain::finalize()
786 // Output the graph as it is before we do any conversions on it.
787 output_dot("step0-start.dot");
789 // Give each effect in turn a chance to rewrite its own part of the graph.
790 // Note that if more effects are added as part of this, they will be
791 // picked up as part of the same for loop, since they are added at the end.
792 for (unsigned i = 0; i < nodes.size(); ++i) {
793 nodes[i]->effect->rewrite_graph(this, nodes[i]);
795 output_dot("step1-rewritten.dot");
797 propagate_gamma_and_color_space();
798 output_dot("step2-propagated.dot");
800 fix_internal_color_spaces();
801 fix_output_color_space();
802 output_dot("step4-output-colorspacefix.dot");
804 // Note that we need to fix gamma after colorspace conversion,
805 // because colorspace conversions might create needs for gamma conversions.
806 // Also, we need to run an extra pass of fix_internal_gamma() after
807 // fixing the output gamma, as we only have conversions to/from linear.
808 fix_internal_gamma_by_asking_inputs(5);
809 fix_internal_gamma_by_inserting_nodes(6);
811 output_dot("step8-output-gammafix.dot");
812 fix_internal_gamma_by_asking_inputs(9);
813 fix_internal_gamma_by_inserting_nodes(10);
815 output_dot("step11-final.dot");
817 // Construct all needed GLSL programs, starting at the output.
818 construct_glsl_programs(find_output_node());
820 // If we have more than one phase, we need intermediate render-to-texture.
821 // Construct an FBO, and then as many textures as we need.
822 // We choose the simplest option of having one texture per output,
823 // since otherwise this turns into an (albeit simple)
824 // register allocation problem.
825 if (phases.size() > 1) {
826 glGenFramebuffers(1, &fbo);
828 for (unsigned i = 0; i < phases.size() - 1; ++i) {
829 find_output_size(phases[i]);
831 Node *output_node = phases[i]->effects.back();
832 glGenTextures(1, &output_node->output_texture);
834 glBindTexture(GL_TEXTURE_2D, output_node->output_texture);
836 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
838 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
840 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, phases[i]->output_width, phases[i]->output_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
843 output_node->output_texture_width = phases[i]->output_width;
844 output_node->output_texture_height = phases[i]->output_height;
848 for (unsigned i = 0; i < inputs.size(); ++i) {
849 inputs[i]->finalize();
852 assert(phases[0]->inputs.empty());
857 void EffectChain::render_to_screen()
864 glDisable(GL_DEPTH_TEST);
866 glDepthMask(GL_FALSE);
869 glMatrixMode(GL_PROJECTION);
871 glOrtho(0.0, 1.0, 0.0, 1.0, 0.0, 1.0);
873 glMatrixMode(GL_MODELVIEW);
876 if (phases.size() > 1) {
877 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
881 std::set<Node *> generated_mipmaps;
883 for (unsigned phase = 0; phase < phases.size(); ++phase) {
884 // See if the requested output size has changed. If so, we need to recreate
885 // the texture (and before we start setting up inputs).
886 if (phase != phases.size() - 1) {
887 find_output_size(phases[phase]);
889 Node *output_node = phases[phase]->effects.back();
891 if (output_node->output_texture_width != phases[phase]->output_width ||
892 output_node->output_texture_height != phases[phase]->output_height) {
893 glActiveTexture(GL_TEXTURE0);
895 glBindTexture(GL_TEXTURE_2D, output_node->output_texture);
897 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, phases[phase]->output_width, phases[phase]->output_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
899 glBindTexture(GL_TEXTURE_2D, 0);
902 output_node->output_texture_width = phases[phase]->output_width;
903 output_node->output_texture_height = phases[phase]->output_height;
907 glUseProgram(phases[phase]->glsl_program_num);
910 // Set up RTT inputs for this phase.
911 for (unsigned sampler = 0; sampler < phases[phase]->inputs.size(); ++sampler) {
912 glActiveTexture(GL_TEXTURE0 + sampler);
913 Node *input = phases[phase]->inputs[sampler];
914 glBindTexture(GL_TEXTURE_2D, input->output_texture);
916 if (phases[phase]->input_needs_mipmaps) {
917 if (generated_mipmaps.count(input) == 0) {
918 glGenerateMipmap(GL_TEXTURE_2D);
920 generated_mipmaps.insert(input);
922 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
925 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
929 std::string texture_name = std::string("tex_") + input->effect_id;
930 glUniform1i(glGetUniformLocation(phases[phase]->glsl_program_num, texture_name.c_str()), sampler);
934 // And now the output.
935 if (phase == phases.size() - 1) {
936 // Last phase goes directly to the screen.
937 glBindFramebuffer(GL_FRAMEBUFFER, 0);
939 glViewport(0, 0, width, height);
941 Node *output_node = phases[phase]->effects.back();
942 glFramebufferTexture2D(
944 GL_COLOR_ATTACHMENT0,
946 output_node->output_texture,
949 glViewport(0, 0, phases[phase]->output_width, phases[phase]->output_height);
952 // Give the required parameters to all the effects.
953 unsigned sampler_num = phases[phase]->inputs.size();
954 for (unsigned i = 0; i < phases[phase]->effects.size(); ++i) {
955 Node *node = phases[phase]->effects[i];
956 node->effect->set_gl_state(phases[phase]->glsl_program_num, node->effect_id, &sampler_num);
963 glTexCoord2f(0.0f, 0.0f);
964 glVertex2f(0.0f, 0.0f);
966 glTexCoord2f(1.0f, 0.0f);
967 glVertex2f(1.0f, 0.0f);
969 glTexCoord2f(1.0f, 1.0f);
970 glVertex2f(1.0f, 1.0f);
972 glTexCoord2f(0.0f, 1.0f);
973 glVertex2f(0.0f, 1.0f);
978 for (unsigned i = 0; i < phases[phase]->effects.size(); ++i) {
979 Node *node = phases[phase]->effects[i];
980 node->effect->clear_gl_state();