+ Phase phase;
+ phase.glsl_program_num = glsl_program_num;
+ phase.input_needs_mipmaps = input_needs_mipmaps;
+ phase.start = start_index;
+ phase.end = end_index;
+
+ return phase;
+}
+
+void EffectChain::finalize()
+{
+ // Add normalizers to get the output format right.
+ 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;
+ }
+ if (current_gamma_curve != output_format.gamma_curve) {
+ if (current_gamma_curve != GAMMA_LINEAR) {
+ normalize_to_linear_gamma();
+ }
+ 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;
+ }
+
+ // Construct the GLSL programs. We end a program every time we come
+ // to an effect marked as "needs many samples" (ie. "please let me
+ // sample directly from a texture, with no arithmetic in-between"),
+ // and of course at the end.
+ unsigned start = 0;
+ for (unsigned i = 0; i < effects.size(); ++i) {
+ if (effects[i]->needs_many_samples() && i != start) {
+ phases.push_back(compile_glsl_program(start, i));
+ start = i;
+ }
+ }
+ phases.push_back(compile_glsl_program(start, effects.size()));
+
+ // If we have more than one phase, we need intermediate render-to-texture.
+ // Construct an FBO, and then as many textures as we need.
+ if (phases.size() > 1) {
+ glGenFramebuffers(1, &fbo);
+
+ unsigned num_textures = std::max<int>(phases.size() - 1, 2);
+ glGenTextures(num_textures, temp_textures);
+
+ unsigned char *empty = new unsigned char[width * height * 4];
+ memset(empty, 0, width * height * 4);
+ for (unsigned i = 0; i < num_textures; ++i) {
+ glBindTexture(GL_TEXTURE_2D, temp_textures[i]);
+ 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();
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, empty);
+ check_error();
+ }
+ delete[] empty;
+ }
+
+ // 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();
+
+ void *mapped_pbo = glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY);
+ memset(mapped_pbo, 0, width * height * bytes_per_pixel);
+ glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB);
+
+ 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();
+ glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
+ check_error();
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
+ check_error();
+