+ // We should be consistently larger or smaller then the existing choice,
+ // since we have the same aspect.
+ assert(!(scaled_width < *output_width && scaled_height > *output_height));
+ assert(!(scaled_height < *output_height && scaled_width > *output_width));
+
+ if (scaled_width >= *output_width && scaled_height >= *output_height) {
+ *output_width = scaled_width;
+ *output_height = scaled_height;
+ }
+}
+
+// Propagate input texture sizes throughout, and inform effects downstream.
+// (Like a lot of other code, we depend on effects being in topological order.)
+void EffectChain::inform_input_sizes(Phase *phase)
+{
+ // All effects that have a defined size (inputs and RTT inputs)
+ // get that. Reset all others.
+ for (unsigned i = 0; i < phase->effects.size(); ++i) {
+ Node *node = phase->effects[i];
+ if (node->effect->num_inputs() == 0) {
+ Input *input = static_cast<Input *>(node->effect);
+ node->output_width = input->get_width();
+ node->output_height = input->get_height();
+ assert(node->output_width != 0);
+ assert(node->output_height != 0);
+ } else {
+ node->output_width = node->output_height = 0;
+ }
+ }
+ for (unsigned i = 0; i < phase->inputs.size(); ++i) {
+ 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.
+ // The rules are simple:
+ //
+ // 1. Don't touch effects that already have given sizes (ie., inputs).
+ // 2. If all of your inputs have the same size, that will be your output size.
+ // 3. Otherwise, your output size is 0x0.
+ for (unsigned i = 0; i < phase->effects.size(); ++i) {
+ Node *node = phase->effects[i];
+ if (node->effect->num_inputs() == 0) {
+ continue;
+ }
+ unsigned this_output_width = 0;
+ unsigned this_output_height = 0;
+ for (unsigned j = 0; j < node->incoming_links.size(); ++j) {
+ Node *input = node->incoming_links[j];
+ node->effect->inform_input_size(j, input->output_width, input->output_height);
+ if (j == 0) {
+ this_output_width = input->output_width;
+ this_output_height = input->output_height;
+ } else if (input->output_width != this_output_width || input->output_height != this_output_height) {
+ // Inputs disagree.
+ this_output_width = 0;
+ this_output_height = 0;
+ }
+ }
+ node->output_width = this_output_width;
+ node->output_height = this_output_height;
+ }
+}
+
+// Note: You should call inform_input_sizes() before this, as the last effect's
+// desired output size might change based on the inputs.
+void EffectChain::find_output_size(Phase *phase)
+{
+ Node *output_node = phase->effects.back();
+
+ // 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,
+ &phase->virtual_output_width, &phase->virtual_output_height);
+ return;
+ }
+
+ // If all effects have the same size, use that.
+ unsigned output_width = 0, output_height = 0;
+ bool all_inputs_same_size = true;
+
+ for (unsigned i = 0; i < phase->inputs.size(); ++i) {
+ 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->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;
+ }
+ }
+ for (unsigned i = 0; i < phase->effects.size(); ++i) {
+ Effect *effect = phase->effects[i]->effect;
+ if (effect->num_inputs() != 0) {
+ continue;
+ }
+
+ Input *input = static_cast<Input *>(effect);
+ if (output_width == 0 && output_height == 0) {
+ output_width = input->get_width();
+ output_height = input->get_height();
+ } else if (output_width != input->get_width() ||
+ output_height != input->get_height()) {
+ all_inputs_same_size = false;
+ }
+ }
+
+ if (all_inputs_same_size) {
+ assert(output_width != 0);
+ assert(output_height != 0);
+ phase->virtual_output_width = phase->output_width = output_width;
+ phase->virtual_output_height = phase->output_height = output_height;
+ return;
+ }
+
+ // If not, fit all the inputs into the current aspect, and select the largest one.
+ output_width = 0;
+ output_height = 0;
+ for (unsigned i = 0; i < phase->inputs.size(); ++i) {
+ 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;
+ if (effect->num_inputs() != 0) {
+ continue;
+ }
+
+ Input *input = static_cast<Input *>(effect);
+ size_rectangle_to_fit(input->get_width(), input->get_height(), &output_width, &output_height);
+ }
+ assert(output_width != 0);
+ assert(output_height != 0);
+ phase->virtual_output_width = phase->output_width = output_width;
+ phase->virtual_output_height = phase->output_height = output_height;
+}
+
+void EffectChain::sort_all_nodes_topologically()
+{
+ nodes = topological_sort(nodes);
+}
+
+vector<Node *> EffectChain::topological_sort(const vector<Node *> &nodes)
+{
+ 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);
+ }
+ reverse(sorted_list.begin(), sorted_list.end());
+ return 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;
+ }
+ nodes_left_to_visit->erase(node);
+ for (unsigned i = 0; i < node->outgoing_links.size(); ++i) {
+ topological_sort_visit_node(node->outgoing_links[i], nodes_left_to_visit, sorted_list);
+ }
+ sorted_list->push_back(node);
+}
+
+void EffectChain::find_color_spaces_for_inputs()
+{
+ for (unsigned i = 0; i < nodes.size(); ++i) {
+ Node *node = nodes[i];
+ if (node->disabled) {
+ continue;
+ }
+ if (node->incoming_links.size() == 0) {
+ Input *input = static_cast<Input *>(node->effect);
+ node->output_color_space = input->get_color_space();
+ node->output_gamma_curve = input->get_gamma_curve();
+
+ Effect::AlphaHandling alpha_handling = input->alpha_handling();
+ switch (alpha_handling) {
+ case Effect::OUTPUT_BLANK_ALPHA:
+ node->output_alpha_type = ALPHA_BLANK;
+ break;
+ case Effect::INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA:
+ node->output_alpha_type = ALPHA_PREMULTIPLIED;
+ break;
+ 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);
+ }
+
+ if (node->output_alpha_type == ALPHA_PREMULTIPLIED) {
+ assert(node->output_gamma_curve == GAMMA_LINEAR);
+ }
+ }
+ }
+}
+
+// Propagate gamma and color space information as far as we can in the graph.
+// The rules are simple: Anything where all the inputs agree, get that as
+// output as well. Anything else keeps having *_INVALID.
+void EffectChain::propagate_gamma_and_color_space()
+{
+ // We depend on going through the nodes in order.
+ sort_all_nodes_topologically();
+
+ for (unsigned i = 0; i < nodes.size(); ++i) {
+ Node *node = nodes[i];
+ if (node->disabled) {
+ continue;
+ }
+ assert(node->incoming_links.size() == node->effect->num_inputs());
+ if (node->incoming_links.size() == 0) {
+ assert(node->output_color_space != COLORSPACE_INVALID);
+ assert(node->output_gamma_curve != GAMMA_INVALID);
+ continue;
+ }
+
+ Colorspace color_space = node->incoming_links[0]->output_color_space;
+ GammaCurve gamma_curve = node->incoming_links[0]->output_gamma_curve;
+ for (unsigned j = 1; j < node->incoming_links.size(); ++j) {
+ if (node->incoming_links[j]->output_color_space != color_space) {
+ color_space = COLORSPACE_INVALID;
+ }
+ if (node->incoming_links[j]->output_gamma_curve != gamma_curve) {
+ gamma_curve = GAMMA_INVALID;
+ }