From: Steinar H. Gunderson Date: Tue, 18 Mar 2014 23:21:54 +0000 (+0100) Subject: Merge branch 'epoxy' into epoxy X-Git-Tag: 1.1~12^2~27 X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=926348692a4138e9c88f49ef43657dedb5f36897;hp=144496bf79000c3971090195fbfedcb2cb22a0be;p=movit Merge branch 'epoxy' into epoxy Conflicts: Makefile.in movit.pc.in --- diff --git a/Makefile.in b/Makefile.in index cd56fa6..69fa663 100644 --- a/Makefile.in +++ b/Makefile.in @@ -28,8 +28,8 @@ CXXFLAGS += -DHAVE_SDL2 endif LDFLAGS=@LDFLAGS@ LDLIBS=@epoxy_LIBS@ @FFTW3_LIBS@ -lpthread -TEST_LDLIBS=@epoxy_LIBS@ @SDL_LIBS@ -lpthread -DEMO_LDLIBS=@SDL_image_LIBS@ -lrt -lpthread @libpng_LIBS@ @FFTW3_LIBS@ +TEST_LDLIBS=@epoxy_LIBS@ @SDL2_LIBS@ @SDL_LIBS@ -lpthread +DEMO_LDLIBS=@SDL2_image_LIBS@ @SDL_image_LIBS@ -lrt -lpthread @libpng_LIBS@ @FFTW3_LIBS@ SHELL=@SHELL@ LIBTOOL=@LIBTOOL@ --tag=CXX RANLIB=ranlib diff --git a/README b/README index b6b2b9d..74be5e5 100644 --- a/README +++ b/README @@ -25,8 +25,8 @@ OK, you need part of OpenGL 3.0 or newer, although most OpenGL 2.0 cards also have what's needed through extensions). If your machine is less than five years old _and you have the appropriate drivers_, you're home free. -* The [Eigen 3] and [Google Test] libraries. (The library itself - depends only on the former, but you probably want to run the unit tests.) +* The [Eigen 3], [FFTW3] and [Google Test] libraries. (The library itself + does not depend on the latter, but you probably want to run the unit tests.) * The [epoxy] library, for dealing with OpenGL extensions on various platforms. diff --git a/blur_effect.cpp b/blur_effect.cpp index b0327e2..903737a 100644 --- a/blur_effect.cpp +++ b/blur_effect.cpp @@ -108,7 +108,9 @@ SingleBlurPassEffect::SingleBlurPassEffect(BlurEffect *parent) string SingleBlurPassEffect::output_fragment_shader() { - return read_file("blur_effect.frag"); + char buf[256]; + sprintf(buf, "#define DIRECTION_VERTICAL %d\n", (direction == VERTICAL)); + return buf + read_file("blur_effect.frag"); } void SingleBlurPassEffect::set_gl_state(GLuint glsl_program_num, const string &prefix, unsigned *sampler_num) @@ -159,13 +161,11 @@ void SingleBlurPassEffect::set_gl_state(GLuint glsl_program_num, const string &p // // We pack the parameters into a float4: The relative sample coordinates // in (x,y), and the weight in z. w is unused. - float samples[4 * (NUM_TAPS / 2 + 1)]; + float samples[2 * (NUM_TAPS / 2 + 1)]; // Center sample. - samples[4 * 0 + 0] = 0.0f; - samples[4 * 0 + 1] = 0.0f; - samples[4 * 0 + 2] = weight[0]; - samples[4 * 0 + 3] = 0.0f; + samples[2 * 0 + 0] = 0.0f; + samples[2 * 0 + 1] = weight[0]; // All other samples. for (unsigned i = 1; i < NUM_TAPS / 2 + 1; ++i) { @@ -176,23 +176,18 @@ void SingleBlurPassEffect::set_gl_state(GLuint glsl_program_num, const string &p float offset, total_weight; combine_two_samples(w1, w2, &offset, &total_weight, NULL); - float x = 0.0f, y = 0.0f; - if (direction == HORIZONTAL) { - x = (base_pos + offset) / (float)width; + samples[2 * i + 0] = (base_pos + offset) / (float)width; } else if (direction == VERTICAL) { - y = (base_pos + offset) / (float)height; + samples[2 * i + 0] = (base_pos + offset) / (float)height; } else { assert(false); } - samples[4 * i + 0] = x; - samples[4 * i + 1] = y; - samples[4 * i + 2] = total_weight; - samples[4 * i + 3] = 0.0f; + samples[2 * i + 1] = total_weight; } - set_uniform_vec4_array(glsl_program_num, prefix, "samples", samples, NUM_TAPS / 2 + 1); + set_uniform_vec2_array(glsl_program_num, prefix, "samples", samples, NUM_TAPS / 2 + 1); } void SingleBlurPassEffect::clear_gl_state() diff --git a/blur_effect.frag b/blur_effect.frag index c6e4cf5..8853854 100644 --- a/blur_effect.frag +++ b/blur_effect.frag @@ -1,14 +1,24 @@ -// A simple unidirectional blur. +// A simple un.directional blur. +// DIRECTION_VERTICAL will be #defined to 1 if we are doing a vertical blur, +// 0 otherwise. #define NUM_TAPS 16 -uniform vec4 PREFIX(samples)[NUM_TAPS + 1]; +uniform vec2 PREFIX(samples)[NUM_TAPS + 1]; vec4 FUNCNAME(vec2 tc) { - vec4 sum = vec4(PREFIX(samples)[0].z) * INPUT(tc); + vec4 sum = vec4(PREFIX(samples)[0].y) * INPUT(tc); for (int i = 1; i < NUM_TAPS + 1; ++i) { - vec4 sample = PREFIX(samples)[i]; - sum += vec4(sample.z) * (INPUT(tc - sample.xy) + INPUT(tc + sample.xy)); + vec2 sample = PREFIX(samples)[i]; + vec2 sample1_tc = tc, sample2_tc = tc; +#if DIRECTION_VERTICAL + sample1_tc.y -= sample.x; + sample2_tc.y += sample.x; +#else + sample1_tc.x -= sample.x; + sample2_tc.x += sample.x; +#endif + sum += vec4(sample.y) * (INPUT(sample1_tc) + INPUT(sample2_tc)); } return sum; } diff --git a/effect_chain.cpp b/effect_chain.cpp index ef44024..7f6c943 100644 --- a/effect_chain.cpp +++ b/effect_chain.cpp @@ -53,12 +53,26 @@ EffectChain::~EffectChain() delete nodes[i]; } for (unsigned i = 0; i < phases.size(); ++i) { + glBindVertexArray(phases[i]->vao); + check_error(); + + cleanup_vertex_attribute(phases[i]->glsl_program_num, "position", phases[i]->position_vbo); + cleanup_vertex_attribute(phases[i]->glsl_program_num, "texcoord", phases[i]->texcoord_vbo); + + glBindVertexArray(0); + check_error(); + resource_pool->release_glsl_program(phases[i]->glsl_program_num); delete phases[i]; } if (owns_resource_pool) { delete resource_pool; } + for (map::const_iterator fbo_it = fbos.begin(); + fbo_it != fbos.end(); ++fbo_it) { + glDeleteFramebuffers(1, &fbo_it->second); + check_error(); + } } Input *EffectChain::add_input(Input *input) @@ -227,24 +241,13 @@ string replace_prefix(const string &text, const string &prefix) return output; } -Phase *EffectChain::compile_glsl_program( - const vector &inputs, - const vector &effects) +void EffectChain::compile_glsl_program(Phase *phase) { - Phase *phase = new Phase; - assert(!effects.empty()); - - // Deduplicate the inputs. - vector true_inputs = inputs; - sort(true_inputs.begin(), true_inputs.end()); - true_inputs.erase(unique(true_inputs.begin(), true_inputs.end()), true_inputs.end()); - - bool input_needs_mipmaps = false; string frag_shader = read_file("header.frag"); // Create functions for all the texture inputs that we need. - for (unsigned i = 0; i < true_inputs.size(); ++i) { - Node *input = true_inputs[i]; + for (unsigned i = 0; i < phase->inputs.size(); ++i) { + Node *input = phase->inputs[i]->output_node; char effect_id[256]; sprintf(effect_id, "in%u", i); phase->effect_ids.insert(make_pair(input, effect_id)); @@ -256,10 +259,8 @@ Phase *EffectChain::compile_glsl_program( frag_shader += "\n"; } - vector sorted_effects = topological_sort(effects); - - for (unsigned i = 0; i < sorted_effects.size(); ++i) { - Node *node = sorted_effects[i]; + for (unsigned i = 0; i < phase->effects.size(); ++i) { + Node *node = phase->effects[i]; char effect_id[256]; sprintf(effect_id, "eff%u", i); phase->effect_ids.insert(make_pair(node, effect_id)); @@ -290,24 +291,32 @@ Phase *EffectChain::compile_glsl_program( } } frag_shader += "\n"; - - input_needs_mipmaps |= node->effect->needs_mipmaps(); } - for (unsigned i = 0; i < sorted_effects.size(); ++i) { - Node *node = sorted_effects[i]; - if (node->effect->num_inputs() == 0) { - CHECK(node->effect->set_int("needs_mipmaps", input_needs_mipmaps)); - } - } - frag_shader += string("#define INPUT ") + phase->effect_ids[sorted_effects.back()] + "\n"; + frag_shader += string("#define INPUT ") + phase->effect_ids[phase->effects.back()] + "\n"; frag_shader.append(read_file("footer.frag")); phase->glsl_program_num = resource_pool->compile_glsl_program(read_file("vs.vert"), frag_shader); - phase->input_needs_mipmaps = input_needs_mipmaps; - phase->inputs = true_inputs; - phase->effects = sorted_effects; - return phase; + // Prepare the geometry for the fullscreen quad used in this phase. + // (We have separate VAOs per shader, since the bindings can in theory + // be different.) + float vertices[] = { + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 1.0f, + 1.0f, 0.0f + }; + + glGenVertexArrays(1, &phase->vao); + check_error(); + glBindVertexArray(phase->vao); + check_error(); + + phase->position_vbo = fill_vertex_attribute(phase->glsl_program_num, "position", 2, GL_FLOAT, sizeof(vertices), vertices); + phase->texcoord_vbo = fill_vertex_attribute(phase->glsl_program_num, "texcoord", 2, GL_FLOAT, sizeof(vertices), vertices); // Same as vertices. + + glBindVertexArray(0); + check_error(); } // Construct GLSL programs, starting at the given effect and following @@ -317,130 +326,118 @@ Phase *EffectChain::compile_glsl_program( // and of course at the end. // // We follow a quite simple depth-first search from the output, although -// without any explicit recursion. -void EffectChain::construct_glsl_programs(Node *output) +// without recursing explicitly within each phase. +Phase *EffectChain::construct_phase(Node *output, map *completed_effects) { - // Which effects have already been completed? - // We need to keep track of it, as an effect with multiple outputs - // could otherwise be calculated multiple times. - set completed_effects; + if (completed_effects->count(output)) { + return (*completed_effects)[output]; + } - // Effects in the current phase, as well as inputs (outputs from other phases - // that we depend on). Note that since we start iterating from the end, - // the effect list will be in the reverse order. - vector this_phase_inputs; - vector this_phase_effects; + Phase *phase = new Phase; + phase->output_node = output; // Effects that we have yet to calculate, but that we know should // be in the current phase. stack effects_todo_this_phase; - - // Effects that we have yet to calculate, but that come from other phases. - // We delay these until we have this phase done in its entirety, - // at which point we pick any of them and start a new phase from that. - stack effects_todo_other_phases; - effects_todo_this_phase.push(output); - for ( ;; ) { // Termination condition within loop. - if (!effects_todo_this_phase.empty()) { - // OK, we have more to do this phase. - Node *node = effects_todo_this_phase.top(); - effects_todo_this_phase.pop(); - - // This should currently only happen for effects that are inputs - // (either true inputs or phase outputs). We special-case inputs, - // and then deduplicate phase outputs in compile_glsl_program(). - if (node->effect->num_inputs() == 0) { - if (find(this_phase_effects.begin(), this_phase_effects.end(), node) != this_phase_effects.end()) { - continue; - } - } else { - assert(completed_effects.count(node) == 0); + while (!effects_todo_this_phase.empty()) { + Node *node = effects_todo_this_phase.top(); + effects_todo_this_phase.pop(); + + // This should currently only happen for effects that are inputs + // (either true inputs or phase outputs). We special-case inputs, + // and then deduplicate phase outputs below. + if (node->effect->num_inputs() == 0) { + if (find(phase->effects.begin(), phase->effects.end(), node) != phase->effects.end()) { + continue; } + } else { + assert(completed_effects->count(node) == 0); + } - this_phase_effects.push_back(node); - completed_effects.insert(node); + phase->effects.push_back(node); - // Find all the dependencies of this effect, and add them to the stack. - vector deps = node->incoming_links; - assert(node->effect->num_inputs() == deps.size()); - for (unsigned i = 0; i < deps.size(); ++i) { - bool start_new_phase = false; + // Find all the dependencies of this effect, and add them to the stack. + vector deps = node->incoming_links; + assert(node->effect->num_inputs() == deps.size()); + for (unsigned i = 0; i < deps.size(); ++i) { + bool start_new_phase = false; - if (node->effect->needs_texture_bounce() && - !deps[i]->effect->is_single_texture()) { - start_new_phase = true; - } + if (node->effect->needs_texture_bounce() && + !deps[i]->effect->is_single_texture()) { + start_new_phase = true; + } - if (deps[i]->outgoing_links.size() > 1) { - if (!deps[i]->effect->is_single_texture()) { - // More than one effect uses this as the input, - // and it is not a texture itself. - // The easiest thing to do (and probably also the safest - // performance-wise in most cases) is to bounce it to a texture - // and then let the next passes read from that. - start_new_phase = true; - } else { - assert(deps[i]->effect->num_inputs() == 0); - - // For textures, we try to be slightly more clever; - // if none of our outputs need a bounce, we don't bounce - // but instead simply use the effect many times. - // - // Strictly speaking, we could bounce it for some outputs - // and use it directly for others, but the processing becomes - // somewhat simpler if the effect is only used in one such way. - for (unsigned j = 0; j < deps[i]->outgoing_links.size(); ++j) { - Node *rdep = deps[i]->outgoing_links[j]; - start_new_phase |= rdep->effect->needs_texture_bounce(); - } + if (deps[i]->outgoing_links.size() > 1) { + if (!deps[i]->effect->is_single_texture()) { + // More than one effect uses this as the input, + // and it is not a texture itself. + // The easiest thing to do (and probably also the safest + // performance-wise in most cases) is to bounce it to a texture + // and then let the next passes read from that. + start_new_phase = true; + } else { + assert(deps[i]->effect->num_inputs() == 0); + + // For textures, we try to be slightly more clever; + // if none of our outputs need a bounce, we don't bounce + // but instead simply use the effect many times. + // + // Strictly speaking, we could bounce it for some outputs + // and use it directly for others, but the processing becomes + // somewhat simpler if the effect is only used in one such way. + for (unsigned j = 0; j < deps[i]->outgoing_links.size(); ++j) { + Node *rdep = deps[i]->outgoing_links[j]; + start_new_phase |= rdep->effect->needs_texture_bounce(); } } + } - if (deps[i]->effect->changes_output_size()) { - start_new_phase = true; - } + if (deps[i]->effect->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]); - } else { - effects_todo_this_phase.push(deps[i]); - } + if (start_new_phase) { + phase->inputs.push_back(construct_phase(deps[i], completed_effects)); + } else { + effects_todo_this_phase.push(deps[i]); } - continue; } + } - // No more effects to do this phase. Take all the ones we have, - // and create a GLSL program for it. - 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)); - this_phase_effects.back()->phase = phases.back(); - this_phase_inputs.clear(); - this_phase_effects.clear(); - } - assert(this_phase_inputs.empty()); - assert(this_phase_effects.empty()); + // No more effects to do this phase. Take all the ones we have, + // and create a GLSL program for it. + assert(!phase->effects.empty()); - // If we have no effects left, exit. - if (effects_todo_other_phases.empty()) { - break; - } + // Deduplicate the inputs. + sort(phase->inputs.begin(), phase->inputs.end()); + phase->inputs.erase(unique(phase->inputs.begin(), phase->inputs.end()), phase->inputs.end()); - Node *node = effects_todo_other_phases.top(); - effects_todo_other_phases.pop(); + // We added the effects from the output and back, but we need to output + // them in topological sort order in the shader. + phase->effects = topological_sort(phase->effects); - if (completed_effects.count(node) == 0) { - // Start a new phase, calculating from this effect. - effects_todo_this_phase.push(node); + // Figure out if we need mipmaps or not, and if so, tell the inputs that. + phase->input_needs_mipmaps = false; + for (unsigned i = 0; i < phase->effects.size(); ++i) { + Node *node = phase->effects[i]; + phase->input_needs_mipmaps |= node->effect->needs_mipmaps(); + } + for (unsigned i = 0; i < phase->effects.size(); ++i) { + Node *node = phase->effects[i]; + if (node->effect->num_inputs() == 0) { + CHECK(node->effect->set_int("needs_mipmaps", phase->input_needs_mipmaps)); } } - // Finally, since the phases are found from the output but must be executed - // from the input(s), reverse them, too. - reverse(phases.begin(), phases.end()); + // Actually make the shader for this phase. + compile_glsl_program(phase); + + assert(completed_effects->count(output) == 0); + completed_effects->insert(make_pair(output, phase)); + phases.push_back(phase); + return phase; } void EffectChain::output_dot(const char *filename) @@ -621,11 +618,11 @@ void EffectChain::inform_input_sizes(Phase *phase) } } for (unsigned i = 0; i < phase->inputs.size(); ++i) { - Node *input = phase->inputs[i]; - input->output_width = input->phase->virtual_output_width; - input->output_height = input->phase->virtual_output_height; - assert(input->output_width != 0); - assert(input->output_height != 0); + 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. @@ -676,14 +673,14 @@ void EffectChain::find_output_size(Phase *phase) bool all_inputs_same_size = true; 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); + 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->phase->virtual_output_width; - output_height = input->phase->virtual_output_height; - } else if (output_width != input->phase->virtual_output_width || - output_height != input->phase->virtual_output_height) { + 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; } } @@ -715,10 +712,10 @@ void EffectChain::find_output_size(Phase *phase) output_width = 0; output_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); - size_rectangle_to_fit(input->phase->output_width, input->phase->output_height, &output_width, &output_height); + 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; @@ -1422,7 +1419,11 @@ void EffectChain::finalize() output_dot("step18-final.dot"); // Construct all needed GLSL programs, starting at the output. - construct_glsl_programs(find_output_node()); + // We need to keep track of which effects have already been computed, + // as an effect with multiple users could otherwise be calculated + // multiple times. + map completed_effects; + construct_phase(find_output_node(), &completed_effects); output_dot("step19-split-to-phases.dot"); @@ -1439,6 +1440,7 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height // Save original viewport. GLuint x = 0, y = 0; GLuint fbo = 0; + void *context = get_gl_context_identifier(); if (width == 0 && height == 0) { GLint viewport[4]; @@ -1458,13 +1460,18 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height check_error(); if (phases.size() > 1) { - glGenFramebuffers(1, &fbo); - check_error(); + if (fbos.count(context) == 0) { + glGenFramebuffers(1, &fbo); + check_error(); + fbos.insert(make_pair(context, fbo)); + } else { + fbo = fbos[context]; + } glBindFramebuffer(GL_FRAMEBUFFER, fbo); check_error(); } - set generated_mipmaps; + set generated_mipmaps; // We choose the simplest option of having one texture per output, // since otherwise this turns into an (albeit simple) register allocation problem. @@ -1488,9 +1495,9 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height // Set up RTT inputs for this phase. for (unsigned sampler = 0; sampler < phases[phase]->inputs.size(); ++sampler) { glActiveTexture(GL_TEXTURE0 + sampler); - Node *input = phases[phase]->inputs[sampler]; - input->bound_sampler_num = sampler; - glBindTexture(GL_TEXTURE_2D, output_textures[input->phase]); + Phase *input = phases[phase]->inputs[sampler]; + input->output_node->bound_sampler_num = sampler; + glBindTexture(GL_TEXTURE_2D, output_textures[input]); check_error(); if (phases[phase]->input_needs_mipmaps) { if (generated_mipmaps.count(input) == 0) { @@ -1509,7 +1516,7 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); check_error(); - string texture_name = string("tex_") + phases[phase]->effect_ids[input]; + string texture_name = string("tex_") + phases[phase]->effect_ids[input->output_node]; glUniform1i(glGetUniformLocation(glsl_program_num, texture_name.c_str()), sampler); check_error(); } @@ -1555,39 +1562,15 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height } } - // Now draw! - float vertices[] = { - 0.0f, 1.0f, - 0.0f, 0.0f, - 1.0f, 1.0f, - 1.0f, 0.0f - }; - - GLuint vao; - glGenVertexArrays(1, &vao); - check_error(); - glBindVertexArray(vao); + glBindVertexArray(phases[phase]->vao); check_error(); - - GLuint position_vbo = fill_vertex_attribute(glsl_program_num, "position", 2, GL_FLOAT, sizeof(vertices), vertices); - GLuint texcoord_vbo = fill_vertex_attribute(glsl_program_num, "texcoord", 2, GL_FLOAT, sizeof(vertices), vertices); // Same as vertices. - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); check_error(); - cleanup_vertex_attribute(glsl_program_num, "position", position_vbo); - cleanup_vertex_attribute(glsl_program_num, "texcoord", texcoord_vbo); - - glUseProgram(0); - check_error(); - for (unsigned i = 0; i < phases[phase]->effects.size(); ++i) { Node *node = phases[phase]->effects[i]; node->effect->clear_gl_state(); } - - glDeleteVertexArrays(1, &vao); - check_error(); } for (map::const_iterator texture_it = output_textures.begin(); @@ -1598,11 +1581,10 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height glBindFramebuffer(GL_FRAMEBUFFER, 0); check_error(); - - if (fbo != 0) { - glDeleteFramebuffers(1, &fbo); - check_error(); - } + glBindVertexArray(0); + check_error(); + glUseProgram(0); + check_error(); } } // namespace movit diff --git a/effect_chain.h b/effect_chain.h index 02906fe..1ecee63 100644 --- a/effect_chain.h +++ b/effect_chain.h @@ -66,11 +66,6 @@ private: // they will be equal. unsigned output_width, output_height; - // If output goes to RTT, which phase it is in (otherwise unset). - // This is a bit ugly; we should probably fix so that Phase takes other - // phases as inputs, instead of Node. - Phase *phase; - // If the effect has is_single_texture(), or if the output went to RTT // and that texture has been bound to a sampler, the sampler number // will be stored here. @@ -90,19 +85,27 @@ private: // A rendering phase; a single GLSL program rendering a single quad. struct Phase { + Node *output_node; + GLuint glsl_program_num; // Owned by the resource_pool. bool input_needs_mipmaps; // Inputs are only inputs from other phases (ie., those that come from RTT); - // input textures are not counted here. - std::vector inputs; - + // input textures are counted as part of . + std::vector inputs; std::vector effects; // In order. unsigned output_width, output_height, virtual_output_width, virtual_output_height; // Identifier used to create unique variables in GLSL. // Unique per-phase to increase cacheability of compiled shaders. std::map effect_ids; + + // The geometry needed to draw this quad, bound to the vertex array + // object. (Seemingly it's actually a win not to upload geometry every + // frame, even for something as small as a quad, due to fewer state + // changes.) + GLuint vao; + GLuint position_vbo, texcoord_vbo; }; class EffectChain { @@ -224,13 +227,13 @@ private: // output gamma different from GAMMA_LINEAR. void find_all_nonlinear_inputs(Node *effect, std::vector *nonlinear_inputs); - // Create a GLSL program computing the given effects in order. - Phase *compile_glsl_program(const std::vector &inputs, - const std::vector &effects); + // Create a GLSL program computing the effects for this phase in order. + void compile_glsl_program(Phase *phase); // Create all GLSL programs needed to compute the given effect, and all outputs - // that depends on it (whenever possible). - void construct_glsl_programs(Node *output); + // that depend on it (whenever possible). Returns the phase that has + // as the last effect. Also pushes all phases in order onto . + Phase *construct_phase(Node *output, std::map *completed_effects); // Output the current graph to the given file in a Graphviz-compatible format; // only useful for debugging. @@ -284,6 +287,7 @@ private: std::map node_map; Effect *dither_effect; + std::map fbos; // One for each OpenGL context. std::vector inputs; // Also contained in nodes. std::vector phases; diff --git a/effect_util.cpp b/effect_util.cpp index b671620..dbeb48b 100644 --- a/effect_util.cpp +++ b/effect_util.cpp @@ -69,6 +69,17 @@ void set_uniform_vec4(GLuint glsl_program_num, const string &prefix, const strin check_error(); } +void set_uniform_vec2_array(GLuint glsl_program_num, const string &prefix, const string &key, const float *values, size_t num_values) +{ + GLint location = get_uniform_location(glsl_program_num, prefix, key); + if (location == -1) { + return; + } + check_error(); + glUniform2fv(location, num_values, values); + check_error(); +} + void set_uniform_vec4_array(GLuint glsl_program_num, const string &prefix, const string &key, const float *values, size_t num_values) { GLint location = get_uniform_location(glsl_program_num, prefix, key); diff --git a/effect_util.h b/effect_util.h index da0f95a..a1588ef 100644 --- a/effect_util.h +++ b/effect_util.h @@ -26,6 +26,7 @@ void set_uniform_float(GLuint glsl_program_num, const std::string &prefix, const void set_uniform_vec2(GLuint glsl_program_num, const std::string &prefix, const std::string &key, const float *values); void set_uniform_vec3(GLuint glsl_program_num, const std::string &prefix, const std::string &key, const float *values); void set_uniform_vec4(GLuint glsl_program_num, const std::string &prefix, const std::string &key, const float *values); +void set_uniform_vec2_array(GLuint glsl_program_num, const std::string &prefix, const std::string &key, const float *values, size_t num_values); void set_uniform_vec4_array(GLuint glsl_program_num, const std::string &prefix, const std::string &key, const float *values, size_t num_values); void set_uniform_mat3(GLuint glsl_program_num, const std::string &prefix, const std::string &key, const Eigen::Matrix3d &matrix); diff --git a/util.cpp b/util.cpp index ac6a218..cce3d54 100644 --- a/util.cpp +++ b/util.cpp @@ -9,6 +9,14 @@ #include "init.h" #include "util.h" +#if defined(__DARWIN__) +#include +#elif defined(WIN32) +#include +#else +#include +#endif + using namespace std; namespace movit { @@ -233,4 +241,15 @@ unsigned next_power_of_two(unsigned v) return v; } +void *get_gl_context_identifier() +{ +#if defined(__DARWIN__) + return (void *)CGLGetCurrentContext(); +#elif defined(WIN32) + return (void *)wglGetCurrentContext(); +#else + return (void *)glXGetCurrentContext(); +#endif +} + } // namespace movit diff --git a/util.h b/util.h index a3e262f..6def56d 100644 --- a/util.h +++ b/util.h @@ -56,6 +56,12 @@ void cleanup_vertex_attribute(GLuint glsl_program_num, const std::string &attrib // If v is not already a power of two, return the first higher power of two. unsigned next_power_of_two(unsigned v); +// Get a pointer that represents the current OpenGL context, in a cross-platform way. +// This is not intended for anything but identification (ie., so you can associate +// different FBOs with different contexts); you should probably not try to cast it +// back into anything you intend to pass into OpenGL. +void *get_gl_context_identifier(); + } // namespace movit #ifdef NDEBUG