#include "mirror_effect.h"
#include "vignette_effect.h"
#include "blur_effect.h"
+#include "diffusion_effect.h"
+#include "input.h"
EffectChain::EffectChain(unsigned width, unsigned height)
: width(width),
height(height),
- last_added_effect(NULL),
- use_srgb_texture_format(false),
finalized(false) {}
-void EffectChain::add_input(const ImageFormat &format)
+Input *EffectChain::add_input(const ImageFormat &format)
{
- input_format = format;
- output_color_space.insert(std::make_pair(static_cast<Effect *>(NULL), format.color_space));
- output_gamma_curve.insert(std::make_pair(static_cast<Effect *>(NULL), format.gamma_curve));
- effect_ids.insert(std::make_pair(static_cast<Effect *>(NULL), "src_image"));
+ Input *input = new Input(format, width, height);
+ effects.push_back(input);
+ output_color_space.insert(std::make_pair(input, format.color_space));
+ output_gamma_curve.insert(std::make_pair(input, format.gamma_curve));
+ effect_ids.insert(std::make_pair(input, "src_image"));
+ incoming_links.insert(std::make_pair(input, std::vector<Effect *>()));
+ return input;
}
void EffectChain::add_output(const ImageFormat &format)
effect_ids.insert(std::make_pair(effect, effect_id));
assert(inputs.size() == effect->num_inputs());
for (unsigned i = 0; i < inputs.size(); ++i) {
- if (inputs[i] != NULL) {
- assert(std::find(effects.begin(), effects.end(), inputs[i]) != effects.end());
- }
+ assert(std::find(effects.begin(), effects.end(), inputs[i]) != effects.end());
outgoing_links[inputs[i]].push_back(effect);
}
incoming_links.insert(std::make_pair(effect, inputs));
- last_added_effect = effect;
+ output_gamma_curve[effect] = output_gamma_curve[last_added_effect()];
+ output_color_space[effect] = output_color_space[last_added_effect()];
}
Effect *instantiate_effect(EffectId effect)
return new VignetteEffect();
case EFFECT_BLUR:
return new BlurEffect();
+ case EFFECT_DIFFUSION:
+ return new DiffusionEffect();
}
assert(false);
}
Effect *EffectChain::normalize_to_linear_gamma(Effect *input)
{
- GammaCurve current_gamma_curve = output_gamma_curve[input];
- if (current_gamma_curve == GAMMA_sRGB) {
+ assert(output_gamma_curve.count(input) != 0);
+ if (output_gamma_curve[input] == GAMMA_sRGB) {
// TODO: check if the extension exists
- use_srgb_texture_format = true;
- current_gamma_curve = GAMMA_LINEAR;
+ effects[0]->set_int("use_srgb_texture_format", 1);
+ output_gamma_curve[input] = GAMMA_LINEAR;
return input;
} else {
GammaExpansionEffect *gamma_conversion = new GammaExpansionEffect();
- gamma_conversion->set_int("source_curve", current_gamma_curve);
+ gamma_conversion->set_int("source_curve", output_gamma_curve[input]);
std::vector<Effect *> inputs;
inputs.push_back(input);
gamma_conversion->add_self_to_effect_chain(this, inputs);
- current_gamma_curve = GAMMA_LINEAR;
+ output_gamma_curve[gamma_conversion] = GAMMA_LINEAR;
return gamma_conversion;
}
}
Effect *EffectChain::normalize_to_srgb(Effect *input)
{
- GammaCurve current_gamma_curve = output_gamma_curve[input];
- ColorSpace current_color_space = output_color_space[input];
- assert(current_gamma_curve == GAMMA_LINEAR);
+ assert(output_gamma_curve.count(input) != 0);
+ assert(output_color_space.count(input) != 0);
+ assert(output_gamma_curve[input] == GAMMA_LINEAR);
ColorSpaceConversionEffect *colorspace_conversion = new ColorSpaceConversionEffect();
- colorspace_conversion->set_int("source_space", current_color_space);
+ colorspace_conversion->set_int("source_space", output_color_space[input]);
colorspace_conversion->set_int("destination_space", COLORSPACE_sRGB);
std::vector<Effect *> inputs;
inputs.push_back(input);
colorspace_conversion->add_self_to_effect_chain(this, inputs);
- current_color_space = COLORSPACE_sRGB;
+ output_color_space[colorspace_conversion] = COLORSPACE_sRGB;
return colorspace_conversion;
}
std::vector<Effect *> normalized_inputs = inputs;
for (unsigned i = 0; i < normalized_inputs.size(); ++i) {
+ assert(output_gamma_curve.count(normalized_inputs[i]) != 0);
if (effect->needs_linear_light() && output_gamma_curve[normalized_inputs[i]] != GAMMA_LINEAR) {
normalized_inputs[i] = normalize_to_linear_gamma(normalized_inputs[i]);
}
+ assert(output_color_space.count(normalized_inputs[i]) != 0);
if (effect->needs_srgb_primaries() && output_color_space[normalized_inputs[i]] != COLORSPACE_sRGB) {
normalized_inputs[i] = normalize_to_srgb(normalized_inputs[i]);
}
frag_shader += std::string("uniform sampler2D tex_") + effect_id + ";\n";
frag_shader += std::string("vec4 ") + effect_id + "(vec2 tc) {\n";
- if (effect == NULL) {
+ if (effect->num_inputs() == 0) {
// OpenGL's origin is bottom-left, but most graphics software assumes
// a top-left origin. Thus, for inputs that come from the user,
// we flip the y coordinate. However, for FBOs, the origin
input_needs_mipmaps |= effect->needs_mipmaps();
}
+ for (unsigned i = 0; i < effects.size(); ++i) {
+ Effect *effect = effects[i];
+ if (effect->num_inputs() == 0) {
+ effect->set_int("needs_mipmaps", input_needs_mipmaps);
+ }
+ }
assert(!last_effect_id.empty());
frag_shader += std::string("#define INPUT ") + last_effect_id + "\n";
frag_shader.append(read_file("footer.frag"));
// effects, and of course at the end.
void EffectChain::construct_glsl_programs(Effect *start, std::set<Effect *> *completed_effects)
{
+ assert(start != NULL);
if (completed_effects->count(start) != 0) {
// This has already been done for us.
return;
std::vector<Effect *> this_phase_effects;
Effect *node = start;
for ( ;; ) { // Termination condition within loop.
- if (node == NULL) {
- this_phase_inputs.push_back(node);
- } else {
- // Check that we have all the inputs we need for this effect.
- // If not, we end the phase here right away; the other side
- // of the input chain will eventually come and pick the effect up.
- assert(incoming_links.count(node) != 0);
- std::vector<Effect *> deps = incoming_links[node];
- assert(!deps.empty());
+ assert(node != NULL);
+
+ // Check that we have all the inputs we need for this effect.
+ // If not, we end the phase here right away; the other side
+ // of the input chain will eventually come and pick the effect up.
+ assert(incoming_links.count(node) == 1);
+ std::vector<Effect *> deps = incoming_links[node];
+ assert(node->num_inputs() == deps.size());
+ if (!deps.empty()) {
bool have_all_deps = true;
for (unsigned i = 0; i < deps.size(); ++i) {
if (completed_effects->count(deps[i]) == 0) {
return;
}
this_phase_inputs.insert(this_phase_inputs.end(), deps.begin(), deps.end());
- this_phase_effects.push_back(node);
}
+ this_phase_effects.push_back(node);
completed_effects->insert(node);
// Find all the effects that use this one as a direct input.
std::vector<Effect *> next = outgoing_links[node];
assert(!next.empty());
if (next.size() > 1) {
- // More than one effect uses this as the input.
- // The easiest thing to do (and probably also the safest
- // performance-wise in most cases) is to bounce it to a texture
- // and then let the next passes read from that.
- if (node != NULL) {
+ if (node->num_inputs() != 0) {
+ // 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
+ // performance-wise in most cases) is to bounce it to a texture
+ // and then let the next passes read from that.
phases.push_back(compile_glsl_program(this_phase_inputs, this_phase_effects));
}
void EffectChain::finalize()
{
// Add normalizers to get the output format right.
- GammaCurve current_gamma_curve = output_gamma_curve[last_added_effect]; // FIXME
- ColorSpace current_color_space = output_color_space[last_added_effect]; // FIXME
+ assert(output_gamma_curve.count(last_added_effect()) != 0);
+ assert(output_color_space.count(last_added_effect()) != 0);
+ ColorSpace current_color_space = output_color_space[last_added_effect()]; // FIXME
if (current_color_space != output_format.color_space) {
ColorSpaceConversionEffect *colorspace_conversion = new ColorSpaceConversionEffect();
colorspace_conversion->set_int("source_space", current_color_space);
colorspace_conversion->set_int("destination_space", output_format.color_space);
- effects.push_back(colorspace_conversion);
- current_color_space = output_format.color_space;
+ std::vector<Effect *> inputs;
+ inputs.push_back(last_added_effect());
+ colorspace_conversion->add_self_to_effect_chain(this, inputs);
+ output_color_space[colorspace_conversion] = output_format.color_space;
}
+ GammaCurve current_gamma_curve = output_gamma_curve[last_added_effect()]; // FIXME
if (current_gamma_curve != output_format.gamma_curve) {
if (current_gamma_curve != GAMMA_LINEAR) {
- normalize_to_linear_gamma(last_added_effect); // FIXME
+ normalize_to_linear_gamma(last_added_effect()); // FIXME
}
assert(current_gamma_curve == GAMMA_LINEAR);
GammaCompressionEffect *gamma_conversion = new GammaCompressionEffect();
gamma_conversion->set_int("destination_curve", output_format.gamma_curve);
- effects.push_back(gamma_conversion);
- current_gamma_curve = output_format.gamma_curve;
+ std::vector<Effect *> inputs;
+ inputs.push_back(last_added_effect());
+ gamma_conversion->add_self_to_effect_chain(this, inputs);
+ output_gamma_curve[gamma_conversion] = output_format.gamma_curve;
}
// Construct all needed GLSL programs, starting at the input.
std::set<Effect *> completed_effects;
- construct_glsl_programs(NULL, &completed_effects);
+ construct_glsl_programs(effects[0], &completed_effects);
// If we have more than one phase, we need intermediate render-to-texture.
// Construct an FBO, and then as many textures as we need.
effect_output_textures.insert(std::make_pair(output_effect, temp_texture));
}
}
+
+ (static_cast<Input *>(effects[0]))->finalize();
- // Translate the input format to OpenGL's enums.
- GLenum internal_format;
- if (use_srgb_texture_format) {
- internal_format = GL_SRGB8;
- } else {
- internal_format = GL_RGBA8;
- }
- if (input_format.pixel_format == FORMAT_RGB) {
- format = GL_RGB;
- bytes_per_pixel = 3;
- } else if (input_format.pixel_format == FORMAT_RGBA) {
- format = GL_RGBA;
- bytes_per_pixel = 4;
- } else if (input_format.pixel_format == FORMAT_BGR) {
- format = GL_BGR;
- bytes_per_pixel = 3;
- } else if (input_format.pixel_format == FORMAT_BGRA) {
- format = GL_BGRA;
- bytes_per_pixel = 4;
- } else {
- assert(false);
- }
-
- // Create PBO to hold the texture holding the input image, and then the texture itself.
- glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 2);
- check_error();
- glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * bytes_per_pixel, NULL, GL_STREAM_DRAW);
- check_error();
- glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
- check_error();
-
- glGenTextures(1, &source_image_num);
- check_error();
- glBindTexture(GL_TEXTURE_2D, source_image_num);
- check_error();
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- check_error();
- // Intel/Mesa seems to have a broken glGenerateMipmap() for non-FBO textures, so do it here.
- glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, phases[0].input_needs_mipmaps ? GL_TRUE : GL_FALSE);
- check_error();
- glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
- check_error();
-
finalized = true;
}
-void EffectChain::render_to_screen(unsigned char *src)
+void EffectChain::render_to_screen()
{
assert(finalized);
- // Copy the pixel data into the PBO.
- glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 2);
- check_error();
- void *mapped_pbo = glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY);
- memcpy(mapped_pbo, src, width * height * bytes_per_pixel);
- glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB);
- check_error();
-
- // Re-upload the texture from the PBO.
- glActiveTexture(GL_TEXTURE0);
- check_error();
- glBindTexture(GL_TEXTURE_2D, source_image_num);
- check_error();
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
- 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();
- glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
- check_error();
-
// Basic state.
glDisable(GL_BLEND);
check_error();
}
std::set<Effect *> generated_mipmaps;
- generated_mipmaps.insert(NULL); // Already done further up.
+ generated_mipmaps.insert(effects[0]); // Already done further up.
for (unsigned phase = 0; phase < phases.size(); ++phase) {
glUseProgram(phases[phase].glsl_program_num);
check_error();
- // Set up inputs for this phase.
- assert(!phases[phase].inputs.empty());
+ // Set up RTT inputs for this phase.
for (unsigned sampler = 0; sampler < phases[phase].inputs.size(); ++sampler) {
glActiveTexture(GL_TEXTURE0 + sampler);
Effect *input = phases[phase].inputs[sampler];
- if (input == NULL) {
- glBindTexture(GL_TEXTURE_2D, source_image_num);
- check_error();
- } else {
- assert(effect_output_textures.count(input) != 0);
- glBindTexture(GL_TEXTURE_2D, effect_output_textures[input]);
- check_error();
- }
+ assert(effect_output_textures.count(input) != 0);
+ glBindTexture(GL_TEXTURE_2D, effect_output_textures[input]);
+ check_error();
if (phases[phase].input_needs_mipmaps) {
if (generated_mipmaps.count(input) == 0) {
glGenerateMipmap(GL_TEXTURE_2D);
unsigned sampler_num = phases[phase].inputs.size();
for (unsigned i = 0; i < phases[phase].effects.size(); ++i) {
Effect *effect = phases[phase].effects[i];
- effect->set_uniforms(phases[phase].glsl_program_num, effect_ids[effect], &sampler_num);
+ effect->set_gl_state(phases[phase].glsl_program_num, effect_ids[effect], &sampler_num);
}
// Now draw!
glEnd();
check_error();
- // HACK
- glActiveTexture(GL_TEXTURE0);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
- check_error();
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1000);
- check_error();
+ for (unsigned i = 0; i < phases[phase].effects.size(); ++i) {
+ Effect *effect = phases[phase].effects[i];
+ effect->clear_gl_state();
+ }
}
}