X-Git-Url: https://git.sesse.net/?p=movit;a=blobdiff_plain;f=effect_chain.cpp;h=0775042c5ccb114c2f0ed2e4bf7d1a023e061ec2;hp=5cbb7f9208ad9a375df716d23820f1dd8deb87a0;hb=c79d94d05d560fa59b23d542bca6d7a94b907b6f;hpb=88e9f85b9e48e05f0f9d35d199db03e5352919c4 diff --git a/effect_chain.cpp b/effect_chain.cpp index 5cbb7f9..0775042 100644 --- a/effect_chain.cpp +++ b/effect_chain.cpp @@ -412,24 +412,29 @@ void EffectChain::compile_glsl_program(Phase *phase) for (unsigned i = 0; i < phase->effects.size(); ++i) { Node *node = phase->effects[i]; const string effect_id = phase->effect_ids[make_pair(node, IN_SAME_PHASE)]; - if (node->incoming_links.size() == 1) { - Node *input = node->incoming_links[0]; - NodeLinkType link_type = node->incoming_link_type[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"); + 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[make_pair(input, link_type)] + "\n"; - } - } else { - for (unsigned j = 0; j < node->incoming_links.size(); ++j) { - assert(!node->incoming_links[j]->effect->is_compute_shader()); char buf[256]; - string effect_id = phase->effect_ids[make_pair(node->incoming_links[j], node->incoming_link_type[j])]; - sprintf(buf, "#define INPUT%d %s\n", j + 1, effect_id.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"; @@ -451,6 +456,7 @@ void EffectChain::compile_glsl_program(Phase *phase) frag_shader += "\n"; } if (phase->is_compute_shader) { + 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. @@ -459,6 +465,7 @@ void EffectChain::compile_glsl_program(Phase *phase) frag_shader += string("#define CS_POSTPROC ") + phase->effect_ids[make_pair(phase->effects.back(), IN_SAME_PHASE)] + "\n"; } } else { + 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"; } @@ -679,6 +686,8 @@ Phase *EffectChain::construct_phase(Node *output, map *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; } @@ -689,6 +698,8 @@ Phase *EffectChain::construct_phase(Node *output, map *complete for (unsigned i = 0; i < deps.size(); ++i) { bool start_new_phase = false; + Effect::MipmapRequirements save_needs_mipmaps = deps[i]->needs_mipmaps; + if (node->effect->needs_texture_bounce() && !deps[i]->effect->is_single_texture() && !deps[i]->effect->override_disable_bounce()) { @@ -753,13 +764,17 @@ Phase *EffectChain::construct_phase(Node *output, map *complete } 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; + 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 = deps[i]; + } } else if (deps[i]->effect->sets_virtual_output_size()) { assert(deps[i]->effect->changes_output_size()); // If the next effect sets a virtual size to rely on OpenGL's @@ -772,6 +787,11 @@ Phase *EffectChain::construct_phase(Node *output, map *complete } if (start_new_phase) { + // 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. + deps[i]->needs_mipmaps = save_needs_mipmaps; + phase->inputs.push_back(construct_phase(deps[i], completed_effects)); } else { effects_todo_this_phase.push(deps[i]); @@ -1783,20 +1803,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. 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; @@ -1888,6 +1926,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; @@ -1978,6 +2032,7 @@ void EffectChain::render(GLuint dest_fbo, const vector &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"); @@ -2001,9 +2056,10 @@ void EffectChain::render(GLuint dest_fbo, const vector &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);