X-Git-Url: https://git.sesse.net/?p=movit;a=blobdiff_plain;f=effect_chain.cpp;h=cfc6d06431f46631302b22f9d7693bcf79dab15b;hp=b04727bf1bb8c973bc3c9eff7f935578c12d9f18;hb=89da3437c862c15acb870fbe3175b9e4a0a8244a;hpb=e655afd53f2e56938bd4e7f72640eff56ef4a1ee diff --git a/effect_chain.cpp b/effect_chain.cpp index b04727b..cfc6d06 100644 --- a/effect_chain.cpp +++ b/effect_chain.cpp @@ -1,6 +1,7 @@ #define GL_GLEXT_PROTOTYPES 1 #include +#include #include #include @@ -17,9 +18,9 @@ #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) @@ -436,41 +437,114 @@ void EffectChain::output_dot(const char *filename) 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(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(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() @@ -721,7 +795,7 @@ void EffectChain::fix_internal_gamma_by_inserting_nodes(unsigned step) continue; } Node *conversion = add_node(new GammaExpansionEffect()); - conversion->effect->set_int("destination_curve", GAMMA_LINEAR); + conversion->effect->set_int("source_curve", input->output_gamma_curve); conversion->output_gamma_curve = GAMMA_LINEAR; insert_node_between(input, conversion, node); } @@ -826,6 +900,7 @@ void EffectChain::finalize() 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(); @@ -843,6 +918,7 @@ void EffectChain::finalize() 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) { @@ -858,6 +934,10 @@ void EffectChain::render_to_screen() { assert(finalized); + // Save original viewport. + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + // Basic state. glDisable(GL_BLEND); check_error(); @@ -883,6 +963,7 @@ void EffectChain::render_to_screen() 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]); @@ -936,7 +1017,7 @@ void EffectChain::render_to_screen() // 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(