From: Steinar H. Gunderson Date: Sun, 7 Oct 2012 18:17:38 +0000 (+0200) Subject: Support changing resolution in effects, and add a simple ResizeEffect that does that... X-Git-Tag: 1.0~353 X-Git-Url: https://git.sesse.net/?p=movit;a=commitdiff_plain;h=e9d9fc790abdf093176a0314f4588c6b8146e5a2 Support changing resolution in effects, and add a simple ResizeEffect that does that. It is still not possible to query the input size, though. --- diff --git a/Makefile b/Makefile index 93a1166..2ef5d1c 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ OBJS += blur_effect.o OBJS += diffusion_effect.o OBJS += glow_effect.o OBJS += mix_effect.o +OBJS += resize_effect.o OBJS += sandbox_effect.o test: $(OBJS) diff --git a/effect.h b/effect.h index a9734cc..c016a55 100644 --- a/effect.h +++ b/effect.h @@ -14,6 +14,8 @@ #include #include +#include + #include "opengl.h" class EffectChain; @@ -96,6 +98,21 @@ public: // needs mipmaps, you will also get them). virtual bool needs_mipmaps() const { return false; } + // Whether this effect wants to output to a different size than + // its input(s). If you set this to true, the output will be + // bounced to a texture (similarly to if the next effect set + // needs_texture_bounce()). + virtual bool changes_output_size() const { return false; } + + // If changes_output_size() is true, you must implement this to tell + // the framework what output size you want. + // + // Note that it is explicitly allowed to change width and height + // from frame to frame; EffectChain will reallocate textures as needed. + virtual void get_output_size(unsigned *width, unsigned *height) const { + assert(false); + } + // How many inputs this effect will take (a fixed number). // If you have only one input, it will be called INPUT() in GLSL; // if you have several, they will be INPUT1(), INPUT2(), and so on. diff --git a/effect_chain.cpp b/effect_chain.cpp index 312b27d..ced571e 100644 --- a/effect_chain.cpp +++ b/effect_chain.cpp @@ -200,7 +200,7 @@ std::string replace_prefix(const std::string &text, const std::string &prefix) return output; } -EffectChain::Phase EffectChain::compile_glsl_program(const std::vector &inputs, const std::vector &effects) +EffectChain::Phase *EffectChain::compile_glsl_program(const std::vector &inputs, const std::vector &effects) { assert(!effects.empty()); @@ -290,11 +290,11 @@ EffectChain::Phase EffectChain::compile_glsl_program(const std::vector glLinkProgram(glsl_program_num); check_error(); - Phase phase; - phase.glsl_program_num = glsl_program_num; - phase.input_needs_mipmaps = input_needs_mipmaps; - phase.inputs = true_inputs; - phase.effects = effects; + Phase *phase = new Phase; + phase->glsl_program_num = glsl_program_num; + phase->input_needs_mipmaps = input_needs_mipmaps; + phase->inputs = true_inputs; + phase->effects = effects; return phase; } @@ -302,7 +302,8 @@ EffectChain::Phase EffectChain::compile_glsl_program(const std::vector // Construct GLSL programs, starting at the given effect and following // the chain from there. We end a program every time we come to an effect // marked as "needs texture bounce", one that is used by multiple other -// effects, and of course at the end. +// effects, every time an effect wants to change the output size, +// and of course at the end. // // We follow a quite simple depth-first search from the output, although // without any explicit recursion. @@ -364,6 +365,10 @@ void EffectChain::construct_glsl_programs(Effect *output) start_new_phase = true; } + if (deps[i]->changes_output_size()) { + start_new_phase = true; + } + if (start_new_phase) { effects_todo_other_phases.push(deps[i]); this_phase_inputs.push_back(deps[i]); @@ -379,6 +384,7 @@ void EffectChain::construct_glsl_programs(Effect *output) if (!this_phase_effects.empty()) { reverse(this_phase_effects.begin(), this_phase_effects.end()); phases.push_back(compile_glsl_program(this_phase_inputs, this_phase_effects)); + output_effects_to_phase.insert(std::make_pair(this_phase_effects.back(), phases.back())); this_phase_inputs.clear(); this_phase_effects.clear(); } @@ -404,6 +410,45 @@ void EffectChain::construct_glsl_programs(Effect *output) std::reverse(phases.begin(), phases.end()); } +void EffectChain::find_output_size(EffectChain::Phase *phase) +{ + Effect *output_effect = phase->effects.back(); + + // If the last effect explicitly sets an output size, + // use that. + if (output_effect->changes_output_size()) { + output_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) { + Effect *input = phase->inputs[i]; + assert(output_effects_to_phase.count(input) != 0); + const Phase *input_phase = output_effects_to_phase[input]; + 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; + } + } + assert(best_width != 0); + assert(best_height != 0); + phase->output_width = best_width; + phase->output_height = best_height; + return; + } + + // 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. @@ -460,7 +505,9 @@ void EffectChain::finalize() glGenFramebuffers(1, &fbo); for (unsigned i = 0; i < phases.size() - 1; ++i) { - Effect *output_effect = phases[i].effects.back(); + find_output_size(phases[i]); + + Effect *output_effect = phases[i]->effects.back(); GLuint temp_texture; glGenTextures(1, &temp_texture); check_error(); @@ -470,9 +517,10 @@ void EffectChain::finalize() check_error(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); check_error(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + 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(); effect_output_textures.insert(std::make_pair(output_effect, temp_texture)); + effect_output_texture_sizes.insert(std::make_pair(output_effect, std::make_pair(phases[i]->output_width, phases[i]->output_height))); } } @@ -515,17 +563,41 @@ void EffectChain::render_to_screen() } for (unsigned phase = 0; phase < phases.size(); ++phase) { - glUseProgram(phases[phase].glsl_program_num); + // See if the requested output size has changed. If so, we need to recreate + // the texture (and before we start setting up inputs). + if (phase != phases.size() - 1) { + find_output_size(phases[phase]); + + Effect *output_effect = phases[phase]->effects.back(); + assert(effect_output_texture_sizes.count(output_effect) != 0); + std::pair old_size = effect_output_texture_sizes[output_effect]; + + if (old_size.first != phases[phase]->output_width || + old_size.second != phases[phase]->output_height) { + glActiveTexture(GL_TEXTURE0); + check_error(); + assert(effect_output_textures.count(output_effect) != 0); + glBindTexture(GL_TEXTURE_2D, effect_output_textures[output_effect]); + check_error(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, phases[phase]->output_width, phases[phase]->output_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + check_error(); + effect_output_texture_sizes[output_effect] = std::make_pair(phases[phase]->output_width, phases[phase]->output_height); + glBindTexture(GL_TEXTURE_2D, 0); + check_error(); + } + } + + glUseProgram(phases[phase]->glsl_program_num); check_error(); // Set up RTT inputs for this phase. - for (unsigned sampler = 0; sampler < phases[phase].inputs.size(); ++sampler) { + for (unsigned sampler = 0; sampler < phases[phase]->inputs.size(); ++sampler) { glActiveTexture(GL_TEXTURE0 + sampler); - Effect *input = phases[phase].inputs[sampler]; + Effect *input = phases[phase]->inputs[sampler]; assert(effect_output_textures.count(input) != 0); glBindTexture(GL_TEXTURE_2D, effect_output_textures[input]); check_error(); - if (phases[phase].input_needs_mipmaps) { + if (phases[phase]->input_needs_mipmaps) { if (generated_mipmaps.count(input) == 0) { glGenerateMipmap(GL_TEXTURE_2D); check_error(); @@ -540,7 +612,7 @@ void EffectChain::render_to_screen() assert(effect_ids.count(input)); std::string texture_name = std::string("tex_") + effect_ids[input]; - glUniform1i(glGetUniformLocation(phases[phase].glsl_program_num, texture_name.c_str()), sampler); + glUniform1i(glGetUniformLocation(phases[phase]->glsl_program_num, texture_name.c_str()), sampler); check_error(); } @@ -549,23 +621,26 @@ void EffectChain::render_to_screen() // Last phase goes directly to the screen. glBindFramebuffer(GL_FRAMEBUFFER, 0); check_error(); + glViewport(0, 0, width, height); } else { - Effect *last_effect = phases[phase].effects.back(); - assert(effect_output_textures.count(last_effect) != 0); + Effect *output_effect = phases[phase]->effects.back(); + assert(effect_output_textures.count(output_effect) != 0); glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - effect_output_textures[last_effect], + effect_output_textures[output_effect], 0); check_error(); + glViewport(0, 0, phases[phase]->output_width, phases[phase]->output_height); } // Give the required parameters to all the effects. - unsigned sampler_num = phases[phase].inputs.size(); - for (unsigned i = 0; i < phases[phase].effects.size(); ++i) { - Effect *effect = phases[phase].effects[i]; - effect->set_gl_state(phases[phase].glsl_program_num, effect_ids[effect], &sampler_num); + unsigned sampler_num = phases[phase]->inputs.size(); + for (unsigned i = 0; i < phases[phase]->effects.size(); ++i) { + Effect *effect = phases[phase]->effects[i]; + effect->set_gl_state(phases[phase]->glsl_program_num, effect_ids[effect], &sampler_num); + check_error(); } // Now draw! @@ -586,8 +661,8 @@ void EffectChain::render_to_screen() glEnd(); check_error(); - for (unsigned i = 0; i < phases[phase].effects.size(); ++i) { - Effect *effect = phases[phase].effects[i]; + for (unsigned i = 0; i < phases[phase]->effects.size(); ++i) { + Effect *effect = phases[phase]->effects[i]; effect->clear_gl_state(); } } diff --git a/effect_chain.h b/effect_chain.h index 6795578..dcfb7a1 100644 --- a/effect_chain.h +++ b/effect_chain.h @@ -67,8 +67,13 @@ private: bool input_needs_mipmaps; std::vector inputs; // Only from other phases; input textures are not counted here. std::vector effects; // In order. + unsigned output_width, output_height; }; + // Determine the preferred output size of a given phase. + // Requires that all input phases (if any) already have output sizes set. + void find_output_size(Phase *phase); + void find_all_nonlinear_inputs(Effect *effect, std::vector *nonlinear_inputs, std::vector *intermediates); @@ -78,7 +83,7 @@ private: void draw_vertex(float x, float y, const std::vector &inputs); // Create a GLSL program computing the given effects in order. - Phase compile_glsl_program(const std::vector &inputs, const std::vector &effects); + Phase *compile_glsl_program(const std::vector &inputs, const std::vector &effects); // Create all GLSL programs needed to compute the given effect, and all outputs // that depends on it (whenever possible). @@ -90,11 +95,16 @@ private: std::vector inputs; // Also contained in effects. std::map effect_ids; std::map effect_output_textures; + std::map > effect_output_texture_sizes; std::map > outgoing_links; std::map > incoming_links; GLuint fbo; - std::vector phases; + std::vector phases; + + // This is a bit ugly; we should probably fix so that Phase takes other phases + // as inputs, instead of Effect. + std::map output_effects_to_phase; GLenum format, bytes_per_pixel; bool finalized; diff --git a/resize_effect.cpp b/resize_effect.cpp new file mode 100644 index 0000000..e701d71 --- /dev/null +++ b/resize_effect.cpp @@ -0,0 +1,20 @@ +#include "resize_effect.h" +#include "util.h" + +ResizeEffect::ResizeEffect() + : width(1280), height(720) +{ + register_int("width", &width); + register_int("height", &height); +} + +std::string ResizeEffect::output_fragment_shader() +{ + return read_file("identity.frag"); +} + +void ResizeEffect::get_output_size(unsigned *width, unsigned *height) const +{ + *width = this->width; + *height = this->height; +} diff --git a/resize_effect.h b/resize_effect.h new file mode 100644 index 0000000..54c2b31 --- /dev/null +++ b/resize_effect.h @@ -0,0 +1,27 @@ +#ifndef _RESIZE_EFFECT_H +#define _RESIZE_EFFECT_H 1 + +// An effect that simply resizes the picture to a given output size +// (set by the two integer parameters "width" and "height"). +// Mostly useful as part of other algorithms. + +#include "effect.h" + +class ResizeEffect : public Effect { +public: + ResizeEffect(); + std::string output_fragment_shader(); + + // We want processing done pre-filtering and mipmapped, + // in case we need to scale down a lot. + virtual bool need_texture_bounce() const { return true; } + virtual bool needs_mipmaps() const { return true; } + + virtual bool changes_output_size() const { return true; } + virtual void get_output_size(unsigned *width, unsigned *height) const; + +private: + int width, height; +}; + +#endif // !defined(_RESIZE_EFFECT_H)