]> git.sesse.net Git - movit/blobdiff - effect_chain.cpp
Properly restore the LC_NUMERIC locale after finalizing.
[movit] / effect_chain.cpp
index 7f6c9430b00fc780a72b26f60a36cf7a1c46808c..a5c3bbdcb08fc29700119c1625892519da73b39a 100644 (file)
@@ -53,26 +53,12 @@ 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<void *, GLuint>::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)
@@ -243,7 +229,7 @@ string replace_prefix(const string &text, const string &prefix)
 
 void EffectChain::compile_glsl_program(Phase *phase)
 {
-       string frag_shader = read_file("header.frag");
+       string frag_shader = read_version_dependent_file("header", "frag");
 
        // Create functions for all the texture inputs that we need.
        for (unsigned i = 0; i < phase->inputs.size(); ++i) {
@@ -254,7 +240,7 @@ void EffectChain::compile_glsl_program(Phase *phase)
        
                frag_shader += string("uniform sampler2D tex_") + effect_id + ";\n";
                frag_shader += string("vec4 ") + effect_id + "(vec2 tc) {\n";
-               frag_shader += "\treturn texture2D(tex_" + string(effect_id) + ", tc);\n";
+               frag_shader += "\treturn tex2D(tex_" + string(effect_id) + ", tc);\n";
                frag_shader += "}\n";
                frag_shader += "\n";
        }
@@ -293,30 +279,10 @@ void EffectChain::compile_glsl_program(Phase *phase)
                frag_shader += "\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);
-
-       // 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
-       };
+       frag_shader.append(read_version_dependent_file("footer", "frag"));
 
-       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();
+       string vert_shader = read_version_dependent_file("vs", "vert");
+       phase->glsl_program_num = resource_pool->compile_glsl_program(vert_shader, frag_shader);
 }
 
 // Construct GLSL programs, starting at the given effect and following
@@ -1366,7 +1332,11 @@ void EffectChain::finalize()
 {
        // Save the current locale, and set it to C, so that we can output decimal
        // numbers with printf and be sure to get them in the format mandated by GLSL.
-       char *saved_locale = setlocale(LC_NUMERIC, "C");
+       // Note that the OpenGL driver might call setlocale() behind-the-scenes,
+       // and that might corrupt the returned pointer, so we need to take our own
+       // copy of it here.
+       char *saved_locale = strdup(setlocale(LC_NUMERIC, NULL));
+       setlocale(LC_NUMERIC, "C");
 
        // Output the graph as it is before we do any conversions on it.
        output_dot("step0-start.dot");
@@ -1431,6 +1401,7 @@ void EffectChain::finalize()
        
        finalized = true;
        setlocale(LC_NUMERIC, saved_locale);
+       free(saved_locale);
 }
 
 void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height)
@@ -1439,8 +1410,6 @@ 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];
@@ -1459,70 +1428,16 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
        glDepthMask(GL_FALSE);
        check_error();
 
-       if (phases.size() > 1) {
-               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<Phase *> generated_mipmaps;
 
        // We choose the simplest option of having one texture per output,
        // since otherwise this turns into an (albeit simple) register allocation problem.
        map<Phase *, GLuint> output_textures;
 
-       for (unsigned phase = 0; phase < phases.size(); ++phase) {
-               // Find a texture for this phase.
-               inform_input_sizes(phases[phase]);
-               if (phase != phases.size() - 1) {
-                       find_output_size(phases[phase]);
-
-                       GLuint tex_num = resource_pool->create_2d_texture(GL_RGBA16F_ARB, phases[phase]->output_width, phases[phase]->output_height);
-                       output_textures.insert(make_pair(phases[phase], tex_num));
-               }
+       for (unsigned phase_num = 0; phase_num < phases.size(); ++phase_num) {
+               Phase *phase = phases[phase_num];
 
-               const GLuint glsl_program_num = phases[phase]->glsl_program_num;
-               check_error();
-               glUseProgram(glsl_program_num);
-               check_error();
-
-               // Set up RTT inputs for this phase.
-               for (unsigned sampler = 0; sampler < phases[phase]->inputs.size(); ++sampler) {
-                       glActiveTexture(GL_TEXTURE0 + sampler);
-                       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) {
-                                       glGenerateMipmap(GL_TEXTURE_2D);
-                                       check_error();
-                                       generated_mipmaps.insert(input);
-                               }
-                               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
-                               check_error();
-                       } else {
-                               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-                               check_error();
-                       }
-                       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-                       check_error();
-                       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-                       check_error();
-
-                       string texture_name = string("tex_") + phases[phase]->effect_ids[input->output_node];
-                       glUniform1i(glGetUniformLocation(glsl_program_num, texture_name.c_str()), sampler);
-                       check_error();
-               }
-
-               // And now the output.
-               if (phase == phases.size() - 1) {
+               if (phase_num == phases.size() - 1) {
                        // Last phase goes to the output the user specified.
                        glBindFramebuffer(GL_FRAMEBUFFER, dest_fbo);
                        check_error();
@@ -1533,58 +1448,136 @@ void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height
                                CHECK(dither_effect->set_int("output_width", width));
                                CHECK(dither_effect->set_int("output_height", height));
                        }
-               } else {
-                       glFramebufferTexture2D(
-                               GL_FRAMEBUFFER,
-                               GL_COLOR_ATTACHMENT0,
-                               GL_TEXTURE_2D,
-                               output_textures[phases[phase]],
-                               0);
-                       check_error();
-                       GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
-                       assert(status == GL_FRAMEBUFFER_COMPLETE);
-                       glViewport(0, 0, phases[phase]->output_width, phases[phase]->output_height);
                }
+               execute_phase(phase, phase_num == phases.size() - 1, &output_textures, &generated_mipmaps);
+       }
 
-               // 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) {
-                       Node *node = phases[phase]->effects[i];
-                       unsigned old_sampler_num = sampler_num;
-                       node->effect->set_gl_state(glsl_program_num, phases[phase]->effect_ids[node], &sampler_num);
-                       check_error();
+       for (map<Phase *, GLuint>::const_iterator texture_it = output_textures.begin();
+            texture_it != output_textures.end();
+            ++texture_it) {
+               resource_pool->release_2d_texture(texture_it->second);
+       }
 
-                       if (node->effect->is_single_texture()) {
-                               assert(sampler_num - old_sampler_num == 1);
-                               node->bound_sampler_num = old_sampler_num;
-                       } else {
-                               node->bound_sampler_num = -1;
-                       }
-               }
+       glBindFramebuffer(GL_FRAMEBUFFER, 0);
+       check_error();
+       glUseProgram(0);
+       check_error();
+}
+
+void EffectChain::execute_phase(Phase *phase, bool last_phase, map<Phase *, GLuint> *output_textures, set<Phase *> *generated_mipmaps)
+{
+       GLuint fbo = 0;
 
-               glBindVertexArray(phases[phase]->vao);
+       // Find a texture for this phase.
+       inform_input_sizes(phase);
+       if (!last_phase) {
+               find_output_size(phase);
+
+               GLuint tex_num = resource_pool->create_2d_texture(GL_RGBA16F, phase->output_width, phase->output_height);
+               output_textures->insert(make_pair(phase, tex_num));
+       }
+
+       const GLuint glsl_program_num = phase->glsl_program_num;
+       check_error();
+       glUseProgram(glsl_program_num);
+       check_error();
+
+       // Set up RTT inputs for this phase.
+       for (unsigned sampler = 0; sampler < phase->inputs.size(); ++sampler) {
+               glActiveTexture(GL_TEXTURE0 + sampler);
+               Phase *input = phase->inputs[sampler];
+               input->output_node->bound_sampler_num = sampler;
+               glBindTexture(GL_TEXTURE_2D, (*output_textures)[input]);
                check_error();
-               glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+               if (phase->input_needs_mipmaps && generated_mipmaps->count(input) == 0) {
+                       glGenerateMipmap(GL_TEXTURE_2D);
+                       check_error();
+                       generated_mipmaps->insert(input);
+               }
+               setup_rtt_sampler(glsl_program_num, sampler, phase->effect_ids[input->output_node], phase->input_needs_mipmaps);
+       }
+
+       // And now the output. (Already set up for us if it is the last phase.)
+       if (!last_phase) {
+               fbo = resource_pool->create_fbo((*output_textures)[phase]);
+               glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+               glViewport(0, 0, phase->output_width, phase->output_height);
+       }
+
+       // Give the required parameters to all the effects.
+       unsigned sampler_num = phase->inputs.size();
+       for (unsigned i = 0; i < phase->effects.size(); ++i) {
+               Node *node = phase->effects[i];
+               unsigned old_sampler_num = sampler_num;
+               node->effect->set_gl_state(glsl_program_num, phase->effect_ids[node], &sampler_num);
                check_error();
 
-               for (unsigned i = 0; i < phases[phase]->effects.size(); ++i) {
-                       Node *node = phases[phase]->effects[i];
-                       node->effect->clear_gl_state();
+               if (node->effect->is_single_texture()) {
+                       assert(sampler_num - old_sampler_num == 1);
+                       node->bound_sampler_num = old_sampler_num;
+               } else {
+                       node->bound_sampler_num = -1;
                }
        }
 
-       for (map<Phase *, GLuint>::const_iterator texture_it = output_textures.begin();
-            texture_it != output_textures.end();
-            ++texture_it) {
-               resource_pool->release_2d_texture(texture_it->second);
-       }
+       // Now draw!
+       float vertices[] = {
+               0.0f, 1.0f,
+               0.0f, 0.0f,
+               1.0f, 1.0f,
+               1.0f, 0.0f
+       };
 
-       glBindFramebuffer(GL_FRAMEBUFFER, 0);
+       GLuint vao;
+       glGenVertexArrays(1, &vao);
        check_error();
-       glBindVertexArray(0);
+       glBindVertexArray(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 < phase->effects.size(); ++i) {
+               Node *node = phase->effects[i];
+               node->effect->clear_gl_state();
+       }
+
+       if (!last_phase) {
+               resource_pool->release_fbo(fbo);
+       }
+
+       glDeleteVertexArrays(1, &vao);
+       check_error();
+}
+
+void EffectChain::setup_rtt_sampler(GLuint glsl_program_num, int sampler_num, const string &effect_id, bool use_mipmaps)
+{
+       glActiveTexture(GL_TEXTURE0 + sampler_num);
+       check_error();
+       if (use_mipmaps) {
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+               check_error();
+       } else {
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+               check_error();
+       }
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+       check_error();
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+       check_error();
+
+       string texture_name = string("tex_") + effect_id;
+       glUniform1i(glGetUniformLocation(glsl_program_num, texture_name.c_str()), sampler_num);
+       check_error();
 }
 
 }  // namespace movit