]> git.sesse.net Git - movit/blobdiff - resample_effect.cpp
Refactor calculate_scaling_weights() so that it is possible to get weights without...
[movit] / resample_effect.cpp
index ba8a71c0241443c0730a5ce99d68a2fa7c014ad3..802a8968a893482460988b910f2db707d68b692b 100644 (file)
@@ -9,6 +9,7 @@
 #include <math.h>
 #include <stdio.h>
 #include <algorithm>
+#include <mutex>
 #include <Eigen/Sparse>
 #include <Eigen/SparseQR>
 #include <Eigen/OrderingMethods>
@@ -72,7 +73,7 @@ float lanczos_weight(float x)
 // You need to call lanczos_table_init_done before the first call to
 // lanczos_weight_cached.
 #define LANCZOS_TABLE_SIZE 2048
-bool lanczos_table_init_done = false;
+static once_flag lanczos_table_init_done;
 float lanczos_table[LANCZOS_TABLE_SIZE + 2];
 
 void init_lanczos_table()
@@ -80,7 +81,6 @@ void init_lanczos_table()
        for (unsigned i = 0; i < LANCZOS_TABLE_SIZE + 2; ++i) {
                lanczos_table[i] = lanczos_weight(float(i) * (LANCZOS_RADIUS / LANCZOS_TABLE_SIZE));
        }
-       lanczos_table_init_done = true;
 }
 
 float lanczos_weight_cached(float x)
@@ -109,7 +109,7 @@ unsigned gcd(unsigned a, unsigned b)
 }
 
 template<class DestFloat>
-unsigned combine_samples(const Tap<float> *src, Tap<DestFloat> *dst, float num_subtexels, float inv_num_subtexels, unsigned num_src_samples, unsigned max_samples_saved)
+unsigned combine_samples(const Tap<float> *src, Tap<DestFloat> *dst, float num_subtexels, float inv_num_subtexels, unsigned num_src_samples, unsigned max_samples_saved, float pos1_pos2_diff, float inv_pos1_pos2_diff)
 {
        // Cut off near-zero values at both sides.
        unsigned num_samples_saved = 0;
@@ -129,7 +129,7 @@ unsigned combine_samples(const Tap<float> *src, Tap<DestFloat> *dst, float num_s
 
        for (unsigned i = 0, j = 0; i < num_src_samples; ++i, ++j) {
                // Copy the sample directly; it will be overwritten later if we can combine.
-               if (dst != NULL) {
+               if (dst != nullptr) {
                        dst[j].weight = convert_float<float, DestFloat>(src[i].weight);
                        dst[j].pos = convert_float<float, DestFloat>(src[i].pos);
                }
@@ -157,7 +157,7 @@ unsigned combine_samples(const Tap<float> *src, Tap<DestFloat> *dst, float num_s
 
                DestFloat pos, total_weight;
                float sum_sq_error;
-               combine_two_samples(w1, w2, pos1, pos2, num_subtexels, inv_num_subtexels, &pos, &total_weight, &sum_sq_error);
+               combine_two_samples(w1, w2, pos1, pos1_pos2_diff, inv_pos1_pos2_diff, num_subtexels, inv_num_subtexels, &pos, &total_weight, &sum_sq_error);
 
                // If the interpolation error is larger than that of about sqrt(2) of
                // a level at 8-bit precision, don't combine. (You'd think 1.0 was enough,
@@ -169,7 +169,7 @@ unsigned combine_samples(const Tap<float> *src, Tap<DestFloat> *dst, float num_s
                }
 
                // OK, we can combine this and the next sample.
-               if (dst != NULL) {
+               if (dst != nullptr) {
                        dst[j].weight = total_weight;
                        dst[j].pos = pos;
                }
@@ -210,10 +210,12 @@ unsigned combine_many_samples(const Tap<float> *weights, unsigned src_size, unsi
 {
        float num_subtexels = src_size / movit_texel_subpixel_precision;
        float inv_num_subtexels = movit_texel_subpixel_precision / src_size;
+       float pos1_pos2_diff = 1.0f / src_size;
+       float inv_pos1_pos2_diff = src_size;
 
        unsigned max_samples_saved = UINT_MAX;
        for (unsigned y = 0; y < dst_samples && max_samples_saved > 0; ++y) {
-               unsigned num_samples_saved = combine_samples<DestFloat>(weights + y * src_samples, NULL, num_subtexels, inv_num_subtexels, src_samples, max_samples_saved);
+               unsigned num_samples_saved = combine_samples<DestFloat>(weights + y * src_samples, nullptr, num_subtexels, inv_num_subtexels, src_samples, max_samples_saved, pos1_pos2_diff, inv_pos1_pos2_diff);
                max_samples_saved = min(max_samples_saved, num_samples_saved);
        }
 
@@ -228,7 +230,9 @@ unsigned combine_many_samples(const Tap<float> *weights, unsigned src_size, unsi
                        num_subtexels,
                        inv_num_subtexels,
                        src_samples,
-                       max_samples_saved);
+                       max_samples_saved,
+                       pos1_pos2_diff,
+                       inv_pos1_pos2_diff);
                assert(num_samples_saved == max_samples_saved);
                normalize_sum(bilinear_weights_ptr, src_bilinear_samples);
        }
@@ -306,18 +310,24 @@ ResampleEffect::ResampleEffect()
        register_int("height", &output_height);
 
        // The first blur pass will forward resolution information to us.
-       hpass = new SingleResamplePassEffect(this);
+       hpass_owner.reset(new SingleResamplePassEffect(this));
+       hpass = hpass_owner.get();
        CHECK(hpass->set_int("direction", SingleResamplePassEffect::HORIZONTAL));
-       vpass = new SingleResamplePassEffect(NULL);
+       vpass_owner.reset(new SingleResamplePassEffect(this));
+       vpass = vpass_owner.get();
        CHECK(vpass->set_int("direction", SingleResamplePassEffect::VERTICAL));
 
        update_size();
 }
 
+ResampleEffect::~ResampleEffect()
+{
+}
+
 void ResampleEffect::rewrite_graph(EffectChain *graph, Node *self)
 {
-       Node *hpass_node = graph->add_node(hpass);
-       Node *vpass_node = graph->add_node(vpass);
+       Node *hpass_node = graph->add_node(hpass_owner.release());
+       Node *vpass_node = graph->add_node(vpass_owner.release());
        graph->connect_nodes(hpass_node, vpass_node);
        graph->replace_receiver(self, hpass_node);
        graph->replace_sender(self, vpass_node);
@@ -425,8 +435,8 @@ bool ResampleEffect::set_float(const string &key, float value) {
 SingleResamplePassEffect::SingleResamplePassEffect(ResampleEffect *parent)
        : parent(parent),
          direction(HORIZONTAL),
-         input_width(1280),
-         input_height(720),
+         input_width(1280),
+         input_height(720),
          offset(0.0),
          zoom(1.0),
          last_input_width(-1),
@@ -434,8 +444,7 @@ SingleResamplePassEffect::SingleResamplePassEffect(ResampleEffect *parent)
          last_output_width(-1),
          last_output_height(-1),
          last_offset(0.0 / 0.0),  // NaN.
-         last_zoom(0.0 / 0.0),  // NaN.
-         last_texture_width(-1), last_texture_height(-1)
+         last_zoom(0.0 / 0.0)  // NaN.
 {
        register_int("direction", (int *)&direction);
        register_int("input_width", &input_width);
@@ -452,18 +461,11 @@ SingleResamplePassEffect::SingleResamplePassEffect(ResampleEffect *parent)
        register_uniform_float("sample_x_offset", &uniform_sample_x_offset);
        register_uniform_float("whole_pixel_offset", &uniform_whole_pixel_offset);
 
-       glGenTextures(1, &texnum);
-
-       if (!lanczos_table_init_done) {
-               // Could in theory race between two threads if we are unlucky,
-               // but that is harmless, since they'll write the same data.
-               init_lanczos_table();
-       }
+       call_once(lanczos_table_init_done, init_lanczos_table);
 }
 
 SingleResamplePassEffect::~SingleResamplePassEffect()
 {
-       glDeleteTextures(1, &texnum);
 }
 
 string SingleResamplePassEffect::output_fragment_shader()
@@ -500,7 +502,7 @@ void SingleResamplePassEffect::update_texture(GLuint glsl_program_num, const str
                assert(false);
        }
 
-       ScalingWeights weights = calculate_scaling_weights(src_size, dst_size, zoom, offset);
+       ScalingWeights weights = calculate_bilinear_scaling_weights(src_size, dst_size, zoom, offset);
        src_bilinear_samples = weights.src_bilinear_samples;
        num_loops = weights.num_loops;
        slice_height = 1.0f / weights.num_loops;
@@ -508,17 +510,8 @@ void SingleResamplePassEffect::update_texture(GLuint glsl_program_num, const str
        // Encode as a two-component texture. Note the GL_REPEAT.
        glActiveTexture(GL_TEXTURE0 + *sampler_num);
        check_error();
-       glBindTexture(GL_TEXTURE_2D, texnum);
+       glBindTexture(GL_TEXTURE_2D, tex.get_texnum());
        check_error();
-       if (last_texture_width == -1) {
-               // Need to set this state the first time.
-               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-               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();
-       }
 
        GLenum type, internal_format;
        void *pixels;
@@ -533,27 +526,15 @@ void SingleResamplePassEffect::update_texture(GLuint glsl_program_num, const str
                pixels = weights.bilinear_weights_fp16.get();
        }
 
-       if (int(weights.src_bilinear_samples) == last_texture_width &&
-           int(weights.dst_samples) == last_texture_height &&
-           internal_format == last_texture_internal_format) {
-               // Texture dimensions and type are unchanged; it is more efficient
-               // to just update it rather than making an entirely new texture.
-               glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, weights.src_bilinear_samples, weights.dst_samples, GL_RG, type, pixels);
-       } else {
-               glTexImage2D(GL_TEXTURE_2D, 0, internal_format, weights.src_bilinear_samples, weights.dst_samples, 0, GL_RG, type, pixels);
-               last_texture_width = weights.src_bilinear_samples;
-               last_texture_height = weights.dst_samples;
-               last_texture_internal_format = internal_format;
-       }
-       check_error();
+       tex.update(weights.src_bilinear_samples, weights.dst_samples, internal_format, GL_RG, type, pixels);
 }
 
+namespace {
+
 ScalingWeights calculate_scaling_weights(unsigned src_size, unsigned dst_size, float zoom, float offset)
 {
-       if (!lanczos_table_init_done) {
-               // Only needed if run from outside ResampleEffect.
-               init_lanczos_table();
-       }
+       // Only needed if run from outside ResampleEffect.
+       call_once(lanczos_table_init_done, init_lanczos_table);
 
        // For many resamplings (e.g. 640 -> 1280), we will end up with the same
        // set of samples over and over again in a loop. Thus, we can compute only
@@ -626,15 +607,16 @@ ScalingWeights calculate_scaling_weights(unsigned src_size, unsigned dst_size, f
        // to compute the destination pixel, and how many depend on the scaling factor.
        // Thus, the kernel width will vary with how much we scale.
        float radius_scaling_factor = min(scaling_factor, 1.0f);
-       int int_radius = lrintf(LANCZOS_RADIUS / radius_scaling_factor);
-       int src_samples = int_radius * 2 + 1;
+       const int int_radius = lrintf(LANCZOS_RADIUS / radius_scaling_factor);
+       const int src_samples = int_radius * 2 + 1;
        unique_ptr<Tap<float>[]> weights(new Tap<float>[dst_samples * src_samples]);
        float subpixel_offset = offset - lrintf(offset);  // The part not covered by whole_pixel_offset.
        assert(subpixel_offset >= -0.5f && subpixel_offset <= 0.5f);
+       float inv_scaling_factor = 1.0f / scaling_factor;
        for (unsigned y = 0; y < dst_samples; ++y) {
                // Find the point around which we want to sample the source image,
                // compensating for differing pixel centers as the scale changes.
-               float center_src_y = (y + 0.5f) scaling_factor - 0.5f;
+               float center_src_y = (y + 0.5f) * inv_scaling_factor - 0.5f;
                int base_src_y = lrintf(center_src_y);
 
                // Now sample <int_radius> pixels on each side around that point.
@@ -647,16 +629,33 @@ ScalingWeights calculate_scaling_weights(unsigned src_size, unsigned dst_size, f
                }
        }
 
+       ScalingWeights ret;
+       ret.src_bilinear_samples = src_samples;
+       ret.dst_samples = dst_samples;
+       ret.num_loops = num_loops;
+       ret.bilinear_weights_fp16 = nullptr;
+       ret.bilinear_weights_fp32 = move(weights);
+       return ret;
+}
+
+}  // namespace
+
+ScalingWeights calculate_bilinear_scaling_weights(unsigned src_size, unsigned dst_size, float zoom, float offset)
+{
+       ScalingWeights ret = calculate_scaling_weights(src_size, dst_size, zoom, offset);
+       unique_ptr<Tap<float>[]> weights = move(ret.bilinear_weights_fp32);
+       const int src_samples = ret.src_bilinear_samples;
+
        // Now make use of the bilinear filtering in the GPU to reduce the number of samples
        // we need to make. Try fp16 first; if it's not accurate enough, we go to fp32.
        // Our tolerance level for total error is a bit higher than the one for invididual
        // samples, since one would assume overall errors in the shape don't matter as much.
        const float max_error = 2.0f / (255.0f * 255.0f);
        unique_ptr<Tap<fp16_int_t>[]> bilinear_weights_fp16;
-       int src_bilinear_samples = combine_many_samples(weights.get(), src_size, src_samples, dst_samples, &bilinear_weights_fp16);
-       unique_ptr<Tap<float>[]> bilinear_weights_fp32 = NULL;
+       int src_bilinear_samples = combine_many_samples(weights.get(), src_size, src_samples, ret.dst_samples, &bilinear_weights_fp16);
+       unique_ptr<Tap<float>[]> bilinear_weights_fp32 = nullptr;
        double max_sum_sq_error_fp16 = 0.0;
-       for (unsigned y = 0; y < dst_samples; ++y) {
+       for (unsigned y = 0; y < ret.dst_samples; ++y) {
                double sum_sq_error_fp16 = compute_sum_sq_error(
                        weights.get() + y * src_samples, src_samples,
                        bilinear_weights_fp16.get() + y * src_bilinear_samples, src_bilinear_samples,
@@ -669,13 +668,10 @@ ScalingWeights calculate_scaling_weights(unsigned src_size, unsigned dst_size, f
 
        if (max_sum_sq_error_fp16 > max_error) {
                bilinear_weights_fp16.reset();
-               src_bilinear_samples = combine_many_samples(weights.get(), src_size, src_samples, dst_samples, &bilinear_weights_fp32);
+               src_bilinear_samples = combine_many_samples(weights.get(), src_size, src_samples, ret.dst_samples, &bilinear_weights_fp32);
        }
 
-       ScalingWeights ret;
        ret.src_bilinear_samples = src_bilinear_samples;
-       ret.dst_samples = dst_samples;
-       ret.num_loops = num_loops;
        ret.bilinear_weights_fp16 = move(bilinear_weights_fp16);
        ret.bilinear_weights_fp32 = move(bilinear_weights_fp32);
        return ret;
@@ -707,7 +703,7 @@ void SingleResamplePassEffect::set_gl_state(GLuint glsl_program_num, const strin
 
        glActiveTexture(GL_TEXTURE0 + *sampler_num);
        check_error();
-       glBindTexture(GL_TEXTURE_2D, texnum);
+       glBindTexture(GL_TEXTURE_2D, tex.get_texnum());
        check_error();
 
        uniform_sample_tex = *sampler_num;
@@ -737,4 +733,44 @@ void SingleResamplePassEffect::set_gl_state(GLuint glsl_program_num, const strin
        }
 }
 
+Support2DTexture::Support2DTexture()
+{
+       glGenTextures(1, &texnum);
+       check_error();
+       glBindTexture(GL_TEXTURE_2D, texnum);
+       check_error();
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+       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();
+}
+
+Support2DTexture::~Support2DTexture()
+{
+       glDeleteTextures(1, &texnum);
+       check_error();
+}
+
+void Support2DTexture::update(GLint width, GLint height, GLenum internal_format, GLenum format, GLenum type, const GLvoid * data)
+{
+       glBindTexture(GL_TEXTURE_2D, texnum);
+       check_error();
+       if (width == last_texture_width &&
+           height == last_texture_height &&
+           internal_format == last_texture_internal_format) {
+               // Texture dimensions and type are unchanged; it is more efficient
+               // to just update it rather than making an entirely new texture.
+               glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
+               check_error();
+       } else {
+               glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, type, data);
+               check_error();
+               last_texture_width = width;
+               last_texture_height = height;
+               last_texture_internal_format = internal_format;
+       }
+}
+
 }  // namespace movit