#define GL_GLEXT_PROTOTYPES 1
-#include <GL/glew.h>
+#include <epoxy/gl.h>
#include <assert.h>
-#include <locale.h>
#include <math.h>
#include <stddef.h>
#include <stdio.h>
node->output_color_space = COLORSPACE_INVALID;
node->output_gamma_curve = GAMMA_INVALID;
node->output_alpha_type = ALPHA_INVALID;
+ node->needs_mipmaps = false;
nodes.push_back(node);
node_map[effect] = node;
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) {
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";
}
frag_shader += "\n";
}
frag_shader += string("#define INPUT ") + phase->effect_ids[phase->effects.back()] + "\n";
- frag_shader.append(read_file("footer.frag"));
+ frag_shader.append(read_version_dependent_file("footer", "frag"));
- phase->glsl_program_num = resource_pool->compile_glsl_program(read_file("vs.vert"), frag_shader);
+ 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
Node *node = effects_todo_this_phase.top();
effects_todo_this_phase.pop();
+ if (node->effect->needs_mipmaps()) {
+ node->needs_mipmaps = true;
+ }
+
// 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.
start_new_phase = true;
}
+ // Propagate information about needing mipmaps down the chain,
+ // breaking the phase if we notice an incompatibility.
+ //
+ // Note that we cannot do this propagation as a normal pass,
+ // because it needs information about where the phases end
+ // (we should not propagate the flag across phases).
+ if (node->needs_mipmaps) {
+ if (deps[i]->effect->num_inputs() == 0) {
+ Input *input = static_cast<Input *>(deps[i]->effect);
+ start_new_phase |= !input->can_supply_mipmaps();
+ } else {
+ deps[i]->needs_mipmaps = true;
+ }
+ }
+
if (deps[i]->outgoing_links.size() > 1) {
if (!deps[i]->effect->is_single_texture()) {
// More than one effect uses this as the input,
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));
+ Input *input = static_cast<Input *>(node->effect);
+ assert(!phase->input_needs_mipmaps || input->can_supply_mipmaps());
+ CHECK(input->set_int("needs_mipmaps", phase->input_needs_mipmaps));
}
}
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");
-
// Output the graph as it is before we do any conversions on it.
output_dot("step0-start.dot");
assert(phases[0]->inputs.empty());
finalized = true;
- setlocale(LC_NUMERIC, saved_locale);
}
void EffectChain::render_to_fbo(GLuint dest_fbo, unsigned width, unsigned height)
// Save original viewport.
GLuint x = 0, y = 0;
- GLuint fbo = 0;
if (width == 0 && height == 0) {
GLint viewport[4];
glDepthMask(GL_FALSE);
check_error();
- if (phases.size() > 1) {
- glGenFramebuffers(1, &fbo);
- check_error();
- 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));
- }
-
- 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();
- }
+ for (unsigned phase_num = 0; phase_num < phases.size(); ++phase_num) {
+ Phase *phase = phases[phase_num];
- // 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();
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;
- // Now draw!
- float vertices[] = {
- 0.0f, 1.0f,
- 0.0f, 0.0f,
- 1.0f, 1.0f,
- 1.0f, 0.0f
- };
+ // Find a texture for this phase.
+ inform_input_sizes(phase);
+ if (!last_phase) {
+ find_output_size(phase);
- GLuint vao;
- glGenVertexArrays(1, &vao);
- check_error();
- glBindVertexArray(vao);
- check_error();
+ GLuint tex_num = resource_pool->create_2d_texture(GL_RGBA16F, phase->output_width, phase->output_height);
+ output_textures->insert(make_pair(phase, tex_num));
+ }
- 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.
+ const GLuint glsl_program_num = phase->glsl_program_num;
+ check_error();
+ glUseProgram(glsl_program_num);
+ check_error();
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ // 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();
+ 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);
+ }
- cleanup_vertex_attribute(glsl_program_num, "position", position_vbo);
- cleanup_vertex_attribute(glsl_program_num, "texcoord", texcoord_vbo);
+ // 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);
+ }
- glUseProgram(0);
+ // 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;
}
+ }
- glDeleteVertexArrays(1, &vao);
- check_error();
+ // 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);
+ 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();
}
- 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 (!last_phase) {
+ resource_pool->release_fbo(fbo);
}
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glDeleteVertexArrays(1, &vao);
check_error();
+}
- if (fbo != 0) {
- glDeleteFramebuffers(1, &fbo);
+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