#include "input.h"
#include "resource_pool.h"
#include "util.h"
+#include "ycbcr_conversion_effect.h"
using namespace std;
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)
node->output_gamma_curve = GAMMA_INVALID;
node->output_alpha_type = ALPHA_INVALID;
node->needs_mipmaps = false;
+ node->one_to_one_sampling = false;
nodes.push_back(node);
node_map[effect] = node;
// Construct GLSL programs, starting at the given effect and following
// the chain from there. We end a program every time we come to an effect
// marked as "needs texture bounce", one that is used by multiple other
-// effects, every time an effect wants to change the output size,
-// and of course at the end.
+// effects, every time we need to bounce due to output size change
+// (not all size changes require ending), and of course at the end.
//
// We follow a quite simple depth-first search from the output, although
// without recursing explicitly within each phase.
Phase *phase = new Phase;
phase->output_node = output;
+ // If the output effect has one-to-one sampling, we try to trace this
+ // status down through the dependency chain. This is important in case
+ // we hit an effect that changes output size (and not sets a virtual
+ // output size); if we have one-to-one sampling, we don't have to break
+ // the phase.
+ output->one_to_one_sampling = output->effect->one_to_one_sampling();
+
// Effects that we have yet to calculate, but that we know should
// be in the current phase.
stack<Node *> effects_todo_this_phase;
}
}
- if (deps[i]->effect->changes_output_size()) {
+ 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
+ // 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) {
+ // 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;
}
phase->inputs.push_back(construct_phase(deps[i], completed_effects));
} else {
effects_todo_this_phase.push(deps[i]);
+
+ // 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();
}
}
}
}
}
+ // Tell each node which phase it ended up in, so that the unit test
+ // can check that the phases were split in the right place.
+ // Note that this ignores that effects may be part of multiple phases;
+ // if the unit tests need to test such cases, we'll reconsider.
+ for (unsigned i = 0; i < phase->effects.size(); ++i) {
+ phase->effects[i]->containing_phase = phase;
+ }
+
// Actually make the shader for this phase.
compile_glsl_program(phase);
// Now propagate from the inputs towards the end, and inform as we go.
// The rules are simple:
//
- // 1. Don't touch effects that already have given sizes (ie., inputs).
+ // 1. Don't touch effects that already have given sizes (ie., inputs
+ // or effects that change the output size).
// 2. If all of your inputs have the same size, that will be your output size.
// 3. Otherwise, your output size is 0x0.
for (unsigned i = 0; i < phase->effects.size(); ++i) {
this_output_height = 0;
}
}
- node->output_width = this_output_width;
- node->output_height = this_output_height;
+ if (node->effect->changes_output_size()) {
+ // We cannot call get_output_size() before we've done inform_input_size()
+ // on all inputs.
+ unsigned real_width, real_height;
+ node->effect->get_output_size(&real_width, &real_height,
+ &node->output_width, &node->output_height);
+ assert(node->effect->sets_virtual_output_size() ||
+ (real_width == node->output_width &&
+ real_height == node->output_height));
+ } else {
+ node->output_width = this_output_width;
+ node->output_height = this_output_height;
+ }
}
}
if (output_node->effect->changes_output_size()) {
output_node->effect->get_output_size(&phase->output_width, &phase->output_height,
&phase->virtual_output_width, &phase->virtual_output_height);
+ assert(output_node->effect->sets_virtual_output_size() ||
+ (phase->output_width == phase->virtual_output_width &&
+ phase->output_height == phase->virtual_output_height));
return;
}
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,
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,
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());
// Now draw!
float vertices[] = {
- 0.0f, 1.0f,
+ 0.0f, 2.0f,
0.0f, 0.0f,
- 1.0f, 1.0f,
- 1.0f, 0.0f
+ 2.0f, 0.0f
};
GLuint vao;
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);
+ glDrawArrays(GL_TRIANGLES, 0, 3);
check_error();
cleanup_vertex_attribute(glsl_program_num, "position", position_vbo);