]> git.sesse.net Git - movit/blobdiff - effect_chain.cpp
Cleanup: Make uniforms for RTT samplers like all other uniforms.
[movit] / effect_chain.cpp
index 519cb2c6b9ea1c5816ead123b05260904519f96e..4e3f322bfe68cc5a68f2530ac1b5a5ef8a0d285f 100644 (file)
@@ -12,6 +12,7 @@
 #include <stack>
 #include <utility>
 #include <vector>
+#include <Eigen/Core>
 
 #include "alpha_division_effect.h"
 #include "alpha_multiplication_effect.h"
 #include "dither_effect.h"
 #include "effect.h"
 #include "effect_chain.h"
+#include "effect_util.h"
 #include "gamma_compression_effect.h"
 #include "gamma_expansion_effect.h"
 #include "init.h"
 #include "input.h"
 #include "resource_pool.h"
 #include "util.h"
+#include "ycbcr_conversion_effect.h"
 
+using namespace Eigen;
 using namespace std;
 
 namespace movit {
@@ -74,6 +78,20 @@ void EffectChain::add_output(const ImageFormat &format, OutputAlphaFormat alpha_
        assert(!finalized);
        output_format = format;
        output_alpha_format = alpha_format;
+       output_color_type = OUTPUT_COLOR_RGB;
+}
+
+void EffectChain::add_ycbcr_output(const ImageFormat &format, OutputAlphaFormat alpha_format,
+                                   const YCbCrFormat &ycbcr_format)
+{
+       assert(!finalized);
+       output_format = format;
+       output_alpha_format = alpha_format;
+       output_color_type = OUTPUT_COLOR_YCBCR;
+       output_ycbcr_format = ycbcr_format;
+
+       assert(ycbcr_format.chroma_subsampling_x == 1);
+       assert(ycbcr_format.chroma_subsampling_y == 1);
 }
 
 Node *EffectChain::add_node(Effect *effect)
@@ -231,9 +249,10 @@ string replace_prefix(const string &text, const string &prefix)
 
 void EffectChain::compile_glsl_program(Phase *phase)
 {
-       string frag_shader = read_version_dependent_file("header", "frag");
+       string frag_shader_header = read_version_dependent_file("header", "frag");
+       string frag_shader = "";
 
-       // Create functions for all the texture inputs that we need.
+       // Create functions and uniforms for all the texture inputs that we need.
        for (unsigned i = 0; i < phase->inputs.size(); ++i) {
                Node *input = phase->inputs[i]->output_node;
                char effect_id[256];
@@ -245,14 +264,27 @@ void EffectChain::compile_glsl_program(Phase *phase)
                frag_shader += "\treturn tex2D(tex_" + string(effect_id) + ", tc);\n";
                frag_shader += "}\n";
                frag_shader += "\n";
+
+               Uniform<int> uniform;
+               uniform.name = effect_id;
+               uniform.value = &phase->input_samplers[i];
+               uniform.prefix = "tex";
+               uniform.num_values = 1;
+               uniform.location = -1;
+               phase->uniforms_sampler2d.push_back(uniform);
        }
 
+       // Give each effect in the phase its own ID.
        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));
+       }
 
+       for (unsigned i = 0; i < phase->effects.size(); ++i) {
+               Node *node = phase->effects[i];
+               const string effect_id = phase->effect_ids[node];
                if (node->incoming_links.size() == 1) {
                        frag_shader += string("#define INPUT ") + phase->effect_ids[node->incoming_links[0]] + "\n";
                } else {
@@ -265,7 +297,6 @@ void EffectChain::compile_glsl_program(Phase *phase)
        
                frag_shader += "\n";
                frag_shader += string("#define FUNCNAME ") + effect_id + "\n";
-               frag_shader += replace_prefix(node->effect->output_convenience_uniforms(), effect_id);
                frag_shader += replace_prefix(node->effect->output_fragment_shader(), effect_id);
                frag_shader += "#undef PREFIX\n";
                frag_shader += "#undef FUNCNAME\n";
@@ -283,8 +314,123 @@ void EffectChain::compile_glsl_program(Phase *phase)
        frag_shader += string("#define INPUT ") + phase->effect_ids[phase->effects.back()] + "\n";
        frag_shader.append(read_version_dependent_file("footer", "frag"));
 
+       // Collect uniforms from all effects and output them. Note that this needs
+       // to happen after output_fragment_shader(), even though the uniforms come
+       // before in the output source, since output_fragment_shader() is allowed
+       // to register new uniforms (e.g. arrays that are of unknown length until
+       // finalization time).
+       // TODO: Make a uniform block for platforms that support it.
+       string frag_shader_uniforms = "";
+       for (unsigned i = 0; i < phase->effects.size(); ++i) {
+               Node *node = phase->effects[i];
+               Effect *effect = node->effect;
+               const string effect_id = phase->effect_ids[node];
+               for (unsigned j = 0; j < effect->uniforms_sampler2d.size(); ++j) {
+                       phase->uniforms_sampler2d.push_back(effect->uniforms_sampler2d[j]);
+                       phase->uniforms_sampler2d.back().prefix = effect_id;
+                       frag_shader_uniforms += string("uniform sampler2D ") + effect_id
+                               + "_" + effect->uniforms_sampler2d[j].name + ";\n";
+               }
+               for (unsigned j = 0; j < effect->uniforms_bool.size(); ++j) {
+                       phase->uniforms_bool.push_back(effect->uniforms_bool[j]);
+                       phase->uniforms_bool.back().prefix = effect_id;
+                       frag_shader_uniforms += string("uniform bool ") + effect_id
+                               + "_" + effect->uniforms_bool[j].name + ";\n";
+               }
+               for (unsigned j = 0; j < effect->uniforms_int.size(); ++j) {
+                       phase->uniforms_int.push_back(effect->uniforms_int[j]);
+                       phase->uniforms_int.back().prefix = effect_id;
+                       frag_shader_uniforms += string("uniform int ") + effect_id
+                               + "_" + effect->uniforms_int[j].name + ";\n";
+               }
+               for (unsigned j = 0; j < effect->uniforms_float.size(); ++j) {
+                       phase->uniforms_float.push_back(effect->uniforms_float[j]);
+                       phase->uniforms_float.back().prefix = effect_id;
+                       frag_shader_uniforms += string("uniform float ") + effect_id
+                               + "_" + effect->uniforms_float[j].name + ";\n";
+               }
+               for (unsigned j = 0; j < effect->uniforms_vec2.size(); ++j) {
+                       phase->uniforms_vec2.push_back(effect->uniforms_vec2[j]);
+                       phase->uniforms_vec2.back().prefix = effect_id;
+                       frag_shader_uniforms += string("uniform vec2 ") + effect_id
+                               + "_" + effect->uniforms_vec2[j].name + ";\n";
+               }
+               for (unsigned j = 0; j < effect->uniforms_vec3.size(); ++j) {
+                       phase->uniforms_vec3.push_back(effect->uniforms_vec3[j]);
+                       phase->uniforms_vec3.back().prefix = effect_id;
+                       frag_shader_uniforms += string("uniform vec3 ") + effect_id
+                               + "_" + effect->uniforms_vec3[j].name + ";\n";
+               }
+               for (unsigned j = 0; j < effect->uniforms_vec4.size(); ++j) {
+                       phase->uniforms_vec4.push_back(effect->uniforms_vec4[j]);
+                       phase->uniforms_vec4.back().prefix = effect_id;
+                       frag_shader_uniforms += string("uniform vec4 ") + effect_id
+                               + "_" + effect->uniforms_vec4[j].name + ";\n";
+               }
+               for (unsigned j = 0; j < effect->uniforms_vec2_array.size(); ++j) {
+                       char buf[256];
+                       phase->uniforms_vec2.push_back(effect->uniforms_vec2_array[j]);
+                       phase->uniforms_vec2.back().prefix = effect_id;
+                       snprintf(buf, sizeof(buf), "uniform vec2 %s_%s[%d];\n",
+                               effect_id.c_str(), effect->uniforms_vec2_array[j].name.c_str(),
+                               int(effect->uniforms_vec2_array[j].num_values));
+                       frag_shader_uniforms += buf;
+               }
+               for (unsigned j = 0; j < effect->uniforms_vec4_array.size(); ++j) {
+                       char buf[256];
+                       phase->uniforms_vec4.push_back(effect->uniforms_vec4_array[j]);
+                       phase->uniforms_vec4.back().prefix = effect_id;
+                       snprintf(buf, sizeof(buf), "uniform vec4 %s_%s[%d];\n",
+                               effect_id.c_str(), effect->uniforms_vec4_array[j].name.c_str(),
+                               int(effect->uniforms_vec4_array[j].num_values));
+                       frag_shader_uniforms += buf;
+               }
+               for (unsigned j = 0; j < effect->uniforms_mat3.size(); ++j) {
+                       phase->uniforms_mat3.push_back(effect->uniforms_mat3[j]);
+                       phase->uniforms_mat3.back().prefix = effect_id;
+                       frag_shader_uniforms += string("uniform mat3 ") + effect_id
+                               + "_" + effect->uniforms_mat3[j].name + ";\n";
+               }
+       }
+
+       frag_shader = frag_shader_header + frag_shader_uniforms + frag_shader;
+
        string vert_shader = read_version_dependent_file("vs", "vert");
        phase->glsl_program_num = resource_pool->compile_glsl_program(vert_shader, frag_shader);
+
+       // Collect the resulting program numbers for each uniform.
+       for (unsigned i = 0; i < phase->uniforms_sampler2d.size(); ++i) {
+               Uniform<int> &uniform = phase->uniforms_sampler2d[i];
+               uniform.location = get_uniform_location(phase->glsl_program_num, uniform.prefix, uniform.name);
+       }
+       for (unsigned i = 0; i < phase->uniforms_bool.size(); ++i) {
+               Uniform<bool> &uniform = phase->uniforms_bool[i];
+               uniform.location = get_uniform_location(phase->glsl_program_num, uniform.prefix, uniform.name);
+       }
+       for (unsigned i = 0; i < phase->uniforms_int.size(); ++i) {
+               Uniform<int> &uniform = phase->uniforms_int[i];
+               uniform.location = get_uniform_location(phase->glsl_program_num, uniform.prefix, uniform.name);
+       }
+       for (unsigned i = 0; i < phase->uniforms_float.size(); ++i) {
+               Uniform<float> &uniform = phase->uniforms_float[i];
+               uniform.location = get_uniform_location(phase->glsl_program_num, uniform.prefix, uniform.name);
+       }
+       for (unsigned i = 0; i < phase->uniforms_vec2.size(); ++i) {
+               Uniform<float> &uniform = phase->uniforms_vec2[i];
+               uniform.location = get_uniform_location(phase->glsl_program_num, uniform.prefix, uniform.name);
+       }
+       for (unsigned i = 0; i < phase->uniforms_vec3.size(); ++i) {
+               Uniform<float> &uniform = phase->uniforms_vec3[i];
+               uniform.location = get_uniform_location(phase->glsl_program_num, uniform.prefix, uniform.name);
+       }
+       for (unsigned i = 0; i < phase->uniforms_vec4.size(); ++i) {
+               Uniform<float> &uniform = phase->uniforms_vec4[i];
+               uniform.location = get_uniform_location(phase->glsl_program_num, uniform.prefix, uniform.name);
+       }
+       for (unsigned i = 0; i < phase->uniforms_mat3.size(); ++i) {
+               Uniform<Matrix3d> &uniform = phase->uniforms_mat3[i];
+               uniform.location = get_uniform_location(phase->glsl_program_num, uniform.prefix, uniform.name);
+       }
 }
 
 // Construct GLSL programs, starting at the given effect and following
@@ -419,6 +565,9 @@ Phase *EffectChain::construct_phase(Node *output, map<Node *, Phase *> *complete
        sort(phase->inputs.begin(), phase->inputs.end());
        phase->inputs.erase(unique(phase->inputs.begin(), phase->inputs.end()), phase->inputs.end());
 
+       // Allocate samplers for each input.
+       phase->input_samplers.resize(phase->inputs.size());
+
        // 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);
@@ -1363,6 +1512,22 @@ void EffectChain::fix_output_gamma()
                connect_nodes(output, conversion);
        }
 }
+
+// If the user has requested Y'CbCr output, we need to do this conversion
+// _after_ GammaCompressionEffect etc., but before dither (see below).
+// This is because Y'CbCr, with the exception of a special optional mode
+// in Rec. 2020 (which we currently don't support), is defined to work on
+// gamma-encoded data.
+void EffectChain::add_ycbcr_conversion_if_needed()
+{
+       assert(output_color_type == OUTPUT_COLOR_RGB || output_color_type == OUTPUT_COLOR_YCBCR);
+       if (output_color_type != OUTPUT_COLOR_YCBCR) {
+               return;
+       }
+       Node *output = find_output_node();
+       Node *ycbcr = add_node(new YCbCrConversionEffect(output_ycbcr_format));
+       connect_nodes(output, ycbcr);
+}
        
 // If the user has requested dither, add a DitherEffect right at the end
 // (after GammaCompressionEffect etc.). This needs to be done after everything else,
@@ -1445,11 +1610,13 @@ void EffectChain::finalize()
        fix_internal_gamma_by_asking_inputs(15);
        fix_internal_gamma_by_inserting_nodes(16);
 
-       output_dot("step17-before-dither.dot");
+       output_dot("step17-before-ycbcr.dot");
+       add_ycbcr_conversion_if_needed();
 
+       output_dot("step18-before-dither.dot");
        add_dither_if_needed();
 
-       output_dot("step18-final.dot");
+       output_dot("step19-final.dot");
        
        // Construct all needed GLSL programs, starting at the output.
        // We need to keep track of which effects have already been computed,
@@ -1458,7 +1625,7 @@ void EffectChain::finalize()
        map<Node *, Phase *> completed_effects;
        construct_phase(find_output_node(), &completed_effects);
 
-       output_dot("step19-split-to-phases.dot");
+       output_dot("step20-split-to-phases.dot");
 
        assert(phases[0]->inputs.empty());
        
@@ -1612,7 +1779,8 @@ void EffectChain::execute_phase(Phase *phase, bool last_phase, map<Phase *, GLui
                        check_error();
                        generated_mipmaps->insert(input);
                }
-               setup_rtt_sampler(glsl_program_num, sampler, phase->effect_ids[input->output_node], phase->input_needs_mipmaps);
+               setup_rtt_sampler(sampler, phase->input_needs_mipmaps);
+               phase->input_samplers[sampler] = sampler;  // Bind the sampler to the right uniform.
        }
 
        // And now the output. (Already set up for us if it is the last phase.)
@@ -1638,6 +1806,10 @@ void EffectChain::execute_phase(Phase *phase, bool last_phase, map<Phase *, GLui
                }
        }
 
+       // Uniforms need to come after set_gl_state(), since they can be updated
+       // from there.
+       setup_uniforms(phase);
+
        // Now draw!
        float vertices[] = {
                0.0f, 2.0f,
@@ -1676,7 +1848,69 @@ void EffectChain::execute_phase(Phase *phase, bool last_phase, map<Phase *, GLui
        check_error();
 }
 
-void EffectChain::setup_rtt_sampler(GLuint glsl_program_num, int sampler_num, const string &effect_id, bool use_mipmaps)
+void EffectChain::setup_uniforms(Phase *phase)
+{
+       // TODO: Use UBO blocks.
+       for (size_t i = 0; i < phase->uniforms_sampler2d.size(); ++i) {
+               const Uniform<int> &uniform = phase->uniforms_sampler2d[i];
+               if (uniform.location != -1) {
+                       glUniform1iv(uniform.location, uniform.num_values, uniform.value);
+               }
+       }
+       for (size_t i = 0; i < phase->uniforms_bool.size(); ++i) {
+               const Uniform<bool> &uniform = phase->uniforms_bool[i];
+               assert(uniform.num_values == 1);
+               if (uniform.location != -1) {
+                       glUniform1i(uniform.location, *uniform.value);
+               }
+       }
+       for (size_t i = 0; i < phase->uniforms_int.size(); ++i) {
+               const Uniform<int> &uniform = phase->uniforms_int[i];
+               if (uniform.location != -1) {
+                       glUniform1iv(uniform.location, uniform.num_values, uniform.value);
+               }
+       }
+       for (size_t i = 0; i < phase->uniforms_float.size(); ++i) {
+               const Uniform<float> &uniform = phase->uniforms_float[i];
+               if (uniform.location != -1) {
+                       glUniform1fv(uniform.location, uniform.num_values, uniform.value);
+               }
+       }
+       for (size_t i = 0; i < phase->uniforms_vec2.size(); ++i) {
+               const Uniform<float> &uniform = phase->uniforms_vec2[i];
+               if (uniform.location != -1) {
+                       glUniform2fv(uniform.location, uniform.num_values, uniform.value);
+               }
+       }
+       for (size_t i = 0; i < phase->uniforms_vec3.size(); ++i) {
+               const Uniform<float> &uniform = phase->uniforms_vec3[i];
+               if (uniform.location != -1) {
+                       glUniform3fv(uniform.location, uniform.num_values, uniform.value);
+               }
+       }
+       for (size_t i = 0; i < phase->uniforms_vec4.size(); ++i) {
+               const Uniform<float> &uniform = phase->uniforms_vec4[i];
+               if (uniform.location != -1) {
+                       glUniform4fv(uniform.location, uniform.num_values, uniform.value);
+               }
+       }
+       for (size_t i = 0; i < phase->uniforms_mat3.size(); ++i) {
+               const Uniform<Matrix3d> &uniform = phase->uniforms_mat3[i];
+               assert(uniform.num_values == 1);
+               if (uniform.location != -1) {
+                       // Convert to float (GLSL has no double matrices).
+                       float matrixf[9];
+                       for (unsigned y = 0; y < 3; ++y) {
+                               for (unsigned x = 0; x < 3; ++x) {
+                                       matrixf[y + x * 3] = (*uniform.value)(y, x);
+                               }
+                       }
+                       glUniformMatrix3fv(uniform.location, 1, GL_FALSE, matrixf);
+               }
+       }
+}
+
+void EffectChain::setup_rtt_sampler(int sampler_num, bool use_mipmaps)
 {
        glActiveTexture(GL_TEXTURE0 + sampler_num);
        check_error();
@@ -1691,10 +1925,6 @@ void EffectChain::setup_rtt_sampler(GLuint glsl_program_num, int sampler_num, co
        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