#define GL_GLEXT_PROTOTYPES 1
#include <stdio.h>
+#include <math.h>
#include <string.h>
#include <assert.h>
#include "input.h"
#include "opengl.h"
-EffectChain::EffectChain(unsigned width, unsigned height)
- : width(width),
- height(height),
+EffectChain::EffectChain(float aspect_nom, float aspect_denom)
+ : aspect_nom(aspect_nom),
+ aspect_denom(aspect_denom),
finalized(false) {}
Input *EffectChain::add_input(Input *input)
fclose(fp);
}
+unsigned EffectChain::fit_rectangle_to_aspect(unsigned width, unsigned height)
+{
+ if (float(width) * aspect_denom >= float(height) * aspect_nom) {
+ // Same aspect, or W/H > aspect (image is wider than the frame).
+ // In either case, keep width.
+ return width;
+ } else {
+ // W/H < aspect (image is taller than the frame), so keep height,
+ // and adjust width correspondingly.
+ return lrintf(height * aspect_nom / aspect_denom);
+ }
+}
+
+// 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) {
+ Node *input = phase->inputs[i];
+ input->output_width = input->phase->output_width;
+ input->output_height = input->phase->output_height;
+ assert(input->output_width != 0);
+ assert(input->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 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);
return;
}
- // If not, look at the input phases, if any. We select the largest one
- // (really assuming they all have the same aspect currently), by pixel count.
- if (!phase->inputs.empty()) {
- unsigned best_width = 0, best_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);
- if (input->phase->output_width * input->phase->output_height > best_width * best_height) {
- best_width = input->phase->output_width;
- best_height = input->phase->output_height;
- }
+ // If not, look at the input phases and textures.
+ // We select the largest one (by fit into the current aspect).
+ unsigned best_width = 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);
+ unsigned width = fit_rectangle_to_aspect(input->phase->output_width, input->phase->output_height);
+ if (width > best_width) {
+ best_width = width;
}
- assert(best_width != 0);
- assert(best_height != 0);
- phase->output_width = best_width;
- phase->output_height = best_height;
- return;
}
+ for (unsigned i = 0; i < phase->effects.size(); ++i) {
+ Effect *effect = phase->effects[i]->effect;
+ if (effect->num_inputs() != 0) {
+ continue;
+ }
- // 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;
+ Input *input = static_cast<Input *>(effect);
+ unsigned width = fit_rectangle_to_aspect(input->get_width(), input->get_height());
+ if (width > best_width) {
+ best_width = width;
+ }
+ }
+ assert(best_width != 0);
+ phase->output_width = best_width;
+ phase->output_height = best_width * aspect_denom / aspect_nom;
}
void EffectChain::sort_nodes_topologically()
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();
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) {
{
assert(finalized);
+ // Save original viewport.
+ GLint viewport[4];
+ glGetIntegerv(GL_VIEWPORT, viewport);
+
// Basic state.
glDisable(GL_BLEND);
check_error();
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).
+ inform_input_sizes(phases[phase]);
if (phase != phases.size() - 1) {
find_output_size(phases[phase]);
// Last phase goes directly to the screen.
glBindFramebuffer(GL_FRAMEBUFFER, 0);
check_error();
- glViewport(0, 0, width, height);
+ glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
} else {
Node *output_node = phases[phase]->effects.back();
glFramebufferTexture2D(