]> git.sesse.net Git - movit/blobdiff - fft_input.cpp
Add an FFT convolution effect.
[movit] / fft_input.cpp
diff --git a/fft_input.cpp b/fft_input.cpp
new file mode 100644 (file)
index 0000000..ba539b6
--- /dev/null
@@ -0,0 +1,139 @@
+#include <string.h>
+#include <assert.h>
+#include <GL/glew.h>
+#include <fftw3.h>
+
+#include "effect_util.h"
+#include "fp16.h"
+#include "fft_input.h"
+#include "resource_pool.h"
+#include "util.h"
+
+using namespace std;
+
+namespace movit {
+
+FFTInput::FFTInput(unsigned width, unsigned height)
+       : texture_num(0),
+         fft_width(width),
+         fft_height(height),
+         convolve_width(width),
+         convolve_height(height),
+         pixel_data(NULL)
+{
+       register_int("fft_width", &fft_width);
+       register_int("fft_height", &fft_height);
+}
+
+FFTInput::~FFTInput()
+{
+       if (texture_num != 0) {
+               resource_pool->release_2d_texture(texture_num);
+       }
+}
+
+void FFTInput::set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
+{
+       glActiveTexture(GL_TEXTURE0 + *sampler_num);
+       check_error();
+
+       if (texture_num == 0) {
+               assert(pixel_data != NULL);
+
+               // Do the FFT. Our FFTs should typically be small enough and
+               // the data changed often enough that FFTW_ESTIMATE should be
+               // quite OK. Otherwise, we'd need to worry about caching these
+               // plans (possibly including FFTW wisdom).
+               fftw_complex *in = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * fft_width * fft_height);
+               fftw_complex *out = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * fft_width * fft_height);
+               fftw_plan p = fftw_plan_dft_2d(fft_height, fft_width, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
+
+               // Zero pad.
+               for (int i = 0; i < fft_height * fft_width; ++i) {
+                       in[i][0] = 0.0;
+                       in[i][1] = 0.0;
+               }
+               for (unsigned y = 0; y < convolve_height; ++y) {
+                       for (unsigned x = 0; x < convolve_width; ++x) {
+                               int i = y * fft_width + x;
+                               in[i][0] = pixel_data[y * convolve_width + x];
+                               in[i][1] = 0.0;
+                       }
+               }
+
+               fftw_execute(p);
+
+               // Convert to fp16.
+               fp16_int_t *kernel = new fp16_int_t[fft_width * fft_height * 2];
+               for (int i = 0; i < fft_width * fft_height; ++i) {
+                       kernel[i * 2 + 0] = fp64_to_fp16(out[i][0]);
+                       kernel[i * 2 + 1] = fp64_to_fp16(out[i][1]);
+               }
+
+               // (Re-)upload the texture.
+               texture_num = resource_pool->create_2d_texture(GL_RG16F, fft_width, fft_height);
+               glBindTexture(GL_TEXTURE_2D, texture_num);
+               check_error();
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+               check_error();
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+               check_error();
+               glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+               check_error();
+               glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, fft_width, fft_height, GL_RG, GL_HALF_FLOAT, kernel);
+               check_error();
+               glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+               check_error();
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+               check_error();
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+               check_error();
+
+               fftw_free(in);
+               fftw_free(out);
+               delete[] kernel;
+       } else {
+               glBindTexture(GL_TEXTURE_2D, texture_num);
+               check_error();
+       }
+
+       // Bind it to a sampler.
+       set_uniform_int(glsl_program_num, prefix, "tex", *sampler_num);
+       ++*sampler_num;
+}
+
+string FFTInput::output_fragment_shader()
+{
+       return read_file("flat_input.frag");
+}
+
+void FFTInput::invalidate_pixel_data()
+{
+       if (texture_num != 0) {
+               resource_pool->release_2d_texture(texture_num);
+               texture_num = 0;
+       }
+}
+
+bool FFTInput::set_int(const std::string& key, int value)
+{
+       if (key == "needs_mipmaps") {
+               // We cannot supply mipmaps; it would not make any sense for FFT data.
+               return (value == 0);
+       }
+       if (key == "fft_width") {
+               if (value < int(convolve_width)) {
+                       return false;
+               }
+               invalidate_pixel_data();
+       }
+       if (key == "fft_height") {
+               if (value < int(convolve_height)) {
+                       return false;
+               }
+               invalidate_pixel_data();
+       }
+       return Effect::set_int(key, value);
+}
+
+}  // namespace movit