]> git.sesse.net Git - movit/blobdiff - effect_chain.cpp
Convert a loop to range-based for.
[movit] / effect_chain.cpp
index 30849306db298a31a0bf5661f35f6618956faace..f11a5e45a3ada2ccbbaf38a5b76630ceb2f41915 100644 (file)
@@ -163,7 +163,7 @@ Node *EffectChain::add_node(Effect *effect)
        node->output_color_space = COLORSPACE_INVALID;
        node->output_gamma_curve = GAMMA_INVALID;
        node->output_alpha_type = ALPHA_INVALID;
-       node->needs_mipmaps = false;
+       node->needs_mipmaps = Effect::DOES_NOT_NEED_MIPMAPS;
        node->one_to_one_sampling = false;
        node->strong_one_to_one_sampling = false;
 
@@ -376,7 +376,7 @@ void EffectChain::compile_glsl_program(Phase *phase)
                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));
+               phase->effect_ids.insert(make_pair(make_pair(input, IN_ANOTHER_PHASE), effect_id));
        
                frag_shader += string("uniform sampler2D tex_") + effect_id + ";\n";
                frag_shader += string("vec4 ") + effect_id + "(vec2 tc) {\n";
@@ -405,28 +405,36 @@ void EffectChain::compile_glsl_program(Phase *phase)
                Node *node = phase->effects[i];
                char effect_id[256];
                sprintf(effect_id, "eff%u", i);
-               phase->effect_ids.insert(make_pair(node, effect_id));
+               bool inserted = phase->effect_ids.insert(make_pair(make_pair(node, IN_SAME_PHASE), effect_id)).second;
+               assert(inserted);
        }
 
        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) {
-                       Node *input = node->incoming_links[0];
-                       if (i != 0 && input->effect->is_compute_shader()) {
-                               // First effect after the compute shader reads the value
-                               // that cs_output() wrote to a global variable.
-                               frag_shader += string("#define INPUT(tc) CS_OUTPUT_VAL\n");
+               const string effect_id = phase->effect_ids[make_pair(node, IN_SAME_PHASE)];
+               for (unsigned j = 0; j < node->incoming_links.size(); ++j) {
+                       if (node->incoming_links.size() == 1) {
+                               frag_shader += "#define INPUT";
                        } else {
-                               frag_shader += string("#define INPUT ") + phase->effect_ids[input] + "\n";
-                       }
-               } else {
-                       for (unsigned j = 0; j < node->incoming_links.size(); ++j) {
-                               assert(!node->incoming_links[j]->effect->is_compute_shader());
                                char buf[256];
-                               sprintf(buf, "#define INPUT%d %s\n", j + 1, phase->effect_ids[node->incoming_links[j]].c_str());
+                               sprintf(buf, "#define INPUT%d", j + 1);
                                frag_shader += buf;
                        }
+
+                       Node *input = node->incoming_links[j];
+                       NodeLinkType link_type = node->incoming_link_type[j];
+                       if (i != 0 &&
+                           input->effect->is_compute_shader() &&
+                           node->incoming_link_type[j] == IN_SAME_PHASE) {
+                               // First effect after the compute shader reads the value
+                               // that cs_output() wrote to a global variable,
+                               // ignoring the tc (since all such effects have to be
+                               // strong one-to-one).
+                               frag_shader += "(tc) CS_OUTPUT_VAL\n";
+                       } else {
+                               assert(phase->effect_ids.count(make_pair(input, link_type)));
+                               frag_shader += string(" ") + phase->effect_ids[make_pair(input, link_type)] + "\n";
+                       }
                }
        
                frag_shader += "\n";
@@ -448,15 +456,17 @@ void EffectChain::compile_glsl_program(Phase *phase)
                frag_shader += "\n";
        }
        if (phase->is_compute_shader) {
-               frag_shader += string("#define INPUT ") + phase->effect_ids[phase->compute_shader_node] + "\n";
+               assert(phase->effect_ids.count(make_pair(phase->compute_shader_node, IN_SAME_PHASE)));
+               frag_shader += string("#define INPUT ") + phase->effect_ids[make_pair(phase->compute_shader_node, IN_SAME_PHASE)] + "\n";
                if (phase->compute_shader_node == phase->effects.back()) {
                        // No postprocessing.
                        frag_shader += "#define CS_POSTPROC(tc) CS_OUTPUT_VAL\n";
                } else {
-                       frag_shader += string("#define CS_POSTPROC ") + phase->effect_ids[phase->effects.back()] + "\n";
+                       frag_shader += string("#define CS_POSTPROC ") + phase->effect_ids[make_pair(phase->effects.back(), IN_SAME_PHASE)] + "\n";
                }
        } else {
-               frag_shader += string("#define INPUT ") + phase->effect_ids[phase->effects.back()] + "\n";
+               assert(phase->effect_ids.count(make_pair(phase->effects.back(), IN_SAME_PHASE)));
+               frag_shader += string("#define INPUT ") + phase->effect_ids[make_pair(phase->effects.back(), IN_SAME_PHASE)] + "\n";
        }
 
        // If we're the last phase, add the right #defines for Y'CbCr multi-output as needed.
@@ -540,7 +550,7 @@ void EffectChain::compile_glsl_program(Phase *phase)
        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];
+               const string effect_id = phase->effect_ids[make_pair(node, IN_SAME_PHASE)];
                extract_uniform_declarations(effect->uniforms_image2d, "image2D", effect_id, &phase->uniforms_image2d, &frag_shader_uniforms);
                extract_uniform_declarations(effect->uniforms_sampler2d, "sampler2D", effect_id, &phase->uniforms_sampler2d, &frag_shader_uniforms);
                extract_uniform_declarations(effect->uniforms_bool, "bool", effect_id, &phase->uniforms_bool, &frag_shader_uniforms);
@@ -655,8 +665,12 @@ Phase *EffectChain::construct_phase(Node *output, map<Node *, Phase *> *complete
 
                assert(node->effect->one_to_one_sampling() >= node->effect->strong_one_to_one_sampling());
 
-               if (node->effect->needs_mipmaps()) {
-                       node->needs_mipmaps = true;
+               if (node->effect->needs_mipmaps() != Effect::DOES_NOT_NEED_MIPMAPS) {
+                       // Can't have incompatible requirements imposed on us from a dependent effect;
+                       // if so, it should have started a new phase instead.
+                       assert(node->needs_mipmaps == Effect::DOES_NOT_NEED_MIPMAPS ||
+                              node->needs_mipmaps == node->effect->needs_mipmaps());
+                       node->needs_mipmaps = node->effect->needs_mipmaps();
                }
 
                // This should currently only happen for effects that are inputs
@@ -672,19 +686,22 @@ Phase *EffectChain::construct_phase(Node *output, map<Node *, Phase *> *complete
 
                phase->effects.push_back(node);
                if (node->effect->is_compute_shader()) {
+                       assert(phase->compute_shader_node == nullptr ||
+                              phase->compute_shader_node == node);
                        phase->is_compute_shader = true;
                        phase->compute_shader_node = node;
                }
 
                // Find all the dependencies of this effect, and add them to the stack.
-               vector<Node *> deps = node->incoming_links;
-               assert(node->effect->num_inputs() == deps.size());
-               for (unsigned i = 0; i < deps.size(); ++i) {
+               assert(node->effect->num_inputs() == node->incoming_links.size());
+               for (Node *dep : node->incoming_links) {
                        bool start_new_phase = false;
 
+                       Effect::MipmapRequirements save_needs_mipmaps = dep->needs_mipmaps;
+
                        if (node->effect->needs_texture_bounce() &&
-                           !deps[i]->effect->is_single_texture() &&
-                           !deps[i]->effect->override_disable_bounce()) {
+                           !dep->effect->is_single_texture() &&
+                           !dep->effect->override_disable_bounce()) {
                                start_new_phase = true;
                        }
 
@@ -694,17 +711,34 @@ Phase *EffectChain::construct_phase(Node *output, map<Node *, Phase *> *complete
                        // 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 (node->needs_mipmaps != Effect::DOES_NOT_NEED_MIPMAPS) {
+                               // The node can have a value set (ie. not DOES_NOT_NEED_MIPMAPS)
+                               // if we have diamonds in the graph; if so, choose that.
+                               // If not, the effect on the node can also decide (this is the
+                               // more common case).
+                               Effect::MipmapRequirements dep_mipmaps = dep->needs_mipmaps;
+                               if (dep_mipmaps == Effect::DOES_NOT_NEED_MIPMAPS) {
+                                       if (dep->effect->num_inputs() == 0) {
+                                               Input *input = static_cast<Input *>(dep->effect);
+                                               dep_mipmaps = input->can_supply_mipmaps() ? Effect::DOES_NOT_NEED_MIPMAPS : Effect::CANNOT_ACCEPT_MIPMAPS;
+                                       } else {
+                                               dep_mipmaps = dep->effect->needs_mipmaps();
+                                       }
+                               }
+                               if (dep_mipmaps == Effect::DOES_NOT_NEED_MIPMAPS) {
+                                       dep->needs_mipmaps = node->needs_mipmaps;
+                               } else if (dep_mipmaps != node->needs_mipmaps) {
+                                       // The dependency cannot supply our mipmap demands
+                                       // (either because it's an input that can't do mipmaps,
+                                       // or because there's a conflict between mipmap-needing
+                                       // and mipmap-refusing effects somewhere in the graph),
+                                       // so they cannot be in the same phase.
+                                       start_new_phase = true;
                                }
                        }
 
-                       if (deps[i]->outgoing_links.size() > 1) {
-                               if (!deps[i]->effect->is_single_texture()) {
+                       if (dep->outgoing_links.size() > 1) {
+                               if (!dep->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
@@ -712,7 +746,7 @@ Phase *EffectChain::construct_phase(Node *output, map<Node *, Phase *> *complete
                                        // and then let the next passes read from that.
                                        start_new_phase = true;
                                } else {
-                                       assert(deps[i]->effect->num_inputs() == 0);
+                                       assert(dep->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
@@ -721,43 +755,54 @@ Phase *EffectChain::construct_phase(Node *output, map<Node *, Phase *> *complete
                                        // 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];
+                                       for (unsigned j = 0; j < dep->outgoing_links.size(); ++j) {
+                                               Node *rdep = dep->outgoing_links[j];
                                                start_new_phase |= rdep->effect->needs_texture_bounce();
                                        }
                                }
                        }
 
-                       if (deps[i]->effect->is_compute_shader()) {
-                               // Only one compute shader per phase; we should have been stopped
-                               // already due to the fact that compute shaders are not one-to-one.
-                               assert(!phase->is_compute_shader);
-
-                               // If all nodes so far are strong one-to-one, we can put them after
-                               // the compute shader (ie., process them on the output).
-                               start_new_phase = !node->strong_one_to_one_sampling;
-                       } else if (deps[i]->effect->sets_virtual_output_size()) {
-                               assert(deps[i]->effect->changes_output_size());
+                       if (dep->effect->is_compute_shader()) {
+                               if (phase->is_compute_shader) {
+                                       // Only one compute shader per phase.
+                                       start_new_phase = true;
+                               } else if (!node->strong_one_to_one_sampling) {
+                                       // If all nodes so far are strong one-to-one, we can put them after
+                                       // the compute shader (ie., process them on the output).
+                                       start_new_phase = true;
+                               } else if (!start_new_phase) {
+                                       phase->is_compute_shader = true;
+                                       phase->compute_shader_node = dep;
+                               }
+                       } else if (dep->effect->sets_virtual_output_size()) {
+                               assert(dep->effect->changes_output_size());
                                // If the next effect sets a virtual size to rely on OpenGL's
                                // bilinear sampling, we'll really need to break the phase here.
                                start_new_phase = true;
-                       } else if (deps[i]->effect->changes_output_size() && !node->one_to_one_sampling) {
+                       } else if (dep->effect->changes_output_size() && !node->one_to_one_sampling) {
                                // If the next effect changes size and we don't have one-to-one sampling,
                                // we also need to break here.
                                start_new_phase = true;
                        }
 
                        if (start_new_phase) {
-                               phase->inputs.push_back(construct_phase(deps[i], completed_effects));
+                               // Since we're starting a new phase here, we don't need to impose any
+                               // new demands on this effect. Restore the status we had before we
+                               // started looking at it.
+                               dep->needs_mipmaps = save_needs_mipmaps;
+
+                               phase->inputs.push_back(construct_phase(dep, completed_effects));
                        } else {
-                               effects_todo_this_phase.push(deps[i]);
+                               effects_todo_this_phase.push(dep);
 
                                // Propagate the one-to-one status down through the dependency.
-                               deps[i]->one_to_one_sampling = node->one_to_one_sampling &&
-                                       deps[i]->effect->one_to_one_sampling();
-                               deps[i]->strong_one_to_one_sampling = node->strong_one_to_one_sampling &&
-                                       deps[i]->effect->strong_one_to_one_sampling();
+                               dep->one_to_one_sampling = node->one_to_one_sampling &&
+                                       dep->effect->one_to_one_sampling();
+                               dep->strong_one_to_one_sampling = node->strong_one_to_one_sampling &&
+                                       dep->effect->strong_one_to_one_sampling();
                        }
+
+                       node->incoming_link_type.push_back(start_new_phase ? IN_ANOTHER_PHASE : IN_SAME_PHASE);
                }
        }
 
@@ -785,17 +830,13 @@ Phase *EffectChain::construct_phase(Node *output, map<Node *, Phase *> *complete
        phase->effects = topological_sort(phase->effects);
 
        // 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();
-       }
+       // (RTT inputs have different logic, which is checked in execute_phase().)
        for (unsigned i = 0; i < phase->effects.size(); ++i) {
                Node *node = phase->effects[i];
                if (node->effect->num_inputs() == 0) {
                        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));
+                       assert(node->needs_mipmaps != Effect::NEEDS_MIPMAPS || input->can_supply_mipmaps());
+                       CHECK(input->set_int("needs_mipmaps", node->needs_mipmaps == Effect::NEEDS_MIPMAPS));
                }
        }
 
@@ -1761,23 +1802,38 @@ void EffectChain::add_dither_if_needed()
        dither_effect = dither->effect;
 }
 
+namespace {
+
+// Whether this effect will cause the phase it is in to become a compute shader phase.
+bool induces_compute_shader(Node *node)
+{
+       if (node->effect->is_compute_shader()) {
+               return true;
+       }
+       if (!node->effect->strong_one_to_one_sampling()) {
+               // This effect can't be chained after a compute shader.
+               return false;
+       }
+       // If at least one of the effects we depend on is a compute shader,
+       // one of them will be put in the same phase as us (the other ones,
+       // if any, will be bounced).
+       for (Node *dep : node->incoming_links) {
+               if (induces_compute_shader(dep)) {
+                       return true;
+               }
+       }
+       return false;
+}
+
+}  // namespace
+
 // Compute shaders can't output to the framebuffer, so if the last
 // phase ends in a compute shader, add a dummy phase at the end that
 // only blits directly from the temporary texture.
-//
-// TODO: Add an API for rendering directly to textures, for the cases
-// where we're only rendering to an FBO anyway.
 void EffectChain::add_dummy_effect_if_needed()
 {
        Node *output = find_output_node();
-
-       // See if the last effect that's not strong one-to-one is a compute shader.
-       Node *last_effect = output;
-       while (last_effect->effect->num_inputs() == 1 &&
-              last_effect->effect->strong_one_to_one_sampling()) {
-               last_effect = last_effect->incoming_links[0];
-       }
-       if (last_effect->effect->is_compute_shader()) {
+       if (induces_compute_shader(output)) {
                Node *dummy = add_node(new ComputeShaderOutputDisplayEffect());
                connect_nodes(output, dummy);
                has_dummy_effect = true;
@@ -1869,6 +1925,22 @@ void EffectChain::finalize()
 
        output_dot("step21-split-to-phases.dot");
 
+       // There are some corner cases where we thought we needed to add a dummy
+       // effect, but then it turned out later we didn't (e.g. induces_compute_shader()
+       // didn't see a mipmap conflict coming, which would cause the compute shader
+       // to be split off from the inal phase); if so, remove the extra phase
+       // at the end, since it will give us some trouble during execution.
+       //
+       // TODO: Remove induces_compute_shader() and replace it with precise tracking.
+       if (has_dummy_effect && !phases[phases.size() - 2]->is_compute_shader) {
+               resource_pool->release_glsl_program(phases.back()->glsl_program_num);
+               delete phases.back();
+               phases.pop_back();
+               has_dummy_effect = false;
+       }
+
+       output_dot("step22-dummy-phase-removal.dot");
+
        assert(phases[0]->inputs.empty());
        
        finalized = true;
@@ -1959,6 +2031,7 @@ void EffectChain::render(GLuint dest_fbo, const vector<DestinationTexture> &dest
                assert(y == 0);
                assert(num_phases >= 2);
                assert(!phases.back()->is_compute_shader);
+               assert(phases[phases.size() - 2]->is_compute_shader);
                assert(phases.back()->effects.size() == 1);
                assert(phases.back()->effects[0]->effect->effect_type_id() == "ComputeShaderOutputDisplayEffect");
 
@@ -1982,9 +2055,10 @@ void EffectChain::render(GLuint dest_fbo, const vector<DestinationTexture> &dest
                        phase->timer_query_objects_running.push_back(timer_query_object);
                }
                bool last_phase = (phase_num == num_phases - 1);
-               if (phase_num == num_phases - 1) {
+               if (last_phase) {
                        // Last phase goes to the output the user specified.
                        if (!phase->is_compute_shader) {
+                               assert(dest_fbo != (GLuint)-1);
                                glBindFramebuffer(GL_FRAMEBUFFER, dest_fbo);
                                check_error();
                                GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
@@ -2123,7 +2197,7 @@ void EffectChain::print_phase_timing()
 
 void EffectChain::execute_phase(Phase *phase,
                                 const map<Phase *, GLuint> &output_textures,
-                                const std::vector<DestinationTexture> &destinations,
+                                const vector<DestinationTexture> &destinations,
                                 set<Phase *> *generated_mipmaps)
 {
        // Set up RTT inputs for this phase.
@@ -2135,12 +2209,36 @@ void EffectChain::execute_phase(Phase *phase,
                assert(it != output_textures.end());
                glBindTexture(GL_TEXTURE_2D, it->second);
                check_error();
-               if (phase->input_needs_mipmaps && generated_mipmaps->count(input) == 0) {
+
+               // See if anything using this RTT input (in this phase) needs mipmaps.
+               // TODO: It could be that we get conflicting logic here, if we have
+               // multiple effects with incompatible mipmaps using the same
+               // RTT input. However, that is obscure enough that we can deal
+               // with it at some future point (preferably when we have
+               // universal support for separate sampler objects!). For now,
+               // an assert is good enough. See also the TODO at bound_sampler_num.
+               bool any_needs_mipmaps = false, any_refuses_mipmaps = false;
+               for (Node *node : phase->effects) {
+                       assert(node->incoming_links.size() == node->incoming_link_type.size());
+                       for (size_t i = 0; i < node->incoming_links.size(); ++i) {
+                               if (node->incoming_links[i] == input->output_node &&
+                                   node->incoming_link_type[i] == IN_ANOTHER_PHASE) {
+                                       if (node->needs_mipmaps == Effect::NEEDS_MIPMAPS) {
+                                               any_needs_mipmaps = true;
+                                       } else if (node->needs_mipmaps == Effect::CANNOT_ACCEPT_MIPMAPS) {
+                                               any_refuses_mipmaps = true;
+                                       }
+                               }
+                       }
+               }
+               assert(!(any_needs_mipmaps && any_refuses_mipmaps));
+
+               if (any_needs_mipmaps && generated_mipmaps->count(input) == 0) {
                        glGenerateMipmap(GL_TEXTURE_2D);
                        check_error();
                        generated_mipmaps->insert(input);
                }
-               setup_rtt_sampler(sampler, phase->input_needs_mipmaps);
+               setup_rtt_sampler(sampler, any_needs_mipmaps);
                phase->input_samplers[sampler] = sampler;  // Bind the sampler to the right uniform.
        }
 
@@ -2175,7 +2273,7 @@ void EffectChain::execute_phase(Phase *phase,
        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(instance_program_num, phase->effect_ids[node], &sampler_num);
+               node->effect->set_gl_state(instance_program_num, phase->effect_ids[make_pair(node, IN_SAME_PHASE)], &sampler_num);
                check_error();
 
                if (node->effect->is_single_texture()) {