22e7d0ba781f40e4cd2ef385bb194005cf39fc70
[movit] / blur_effect.cpp
1 #include <math.h>
2 #include <assert.h>
3
4 #include "blur_effect.h"
5 #include "util.h"
6 #include "opengl.h"
7
8 // Must match blur_effect.frag.
9 #define NUM_TAPS 16
10         
11 BlurEffect::BlurEffect() {
12         hpass = new SingleBlurPassEffect();
13         hpass->set_int("direction", SingleBlurPassEffect::HORIZONTAL);
14         vpass = new SingleBlurPassEffect();
15         vpass->set_int("direction", SingleBlurPassEffect::VERTICAL);
16 }
17
18 void BlurEffect::add_self_to_effect_chain(EffectChain *chain, const std::vector<Effect *> &inputs) {
19         assert(inputs.size() == 1);
20         hpass->add_self_to_effect_chain(chain, inputs);
21
22         std::vector<Effect *> vpass_inputs;
23         vpass_inputs.push_back(hpass);
24         vpass->add_self_to_effect_chain(chain, vpass_inputs);
25 }
26
27 bool BlurEffect::set_float(const std::string &key, float value) {
28         if (!hpass->set_float(key, value)) {
29                 return false;
30         }
31         return vpass->set_float(key, value);
32 }
33
34 SingleBlurPassEffect::SingleBlurPassEffect()
35         : radius(3.0f),
36           direction(HORIZONTAL)
37 {
38         register_float("radius", (float *)&radius);
39         register_int("direction", (int *)&direction);
40 }
41
42 std::string SingleBlurPassEffect::output_fragment_shader()
43 {
44         return read_file("blur_effect.frag");
45 }
46
47 void SingleBlurPassEffect::set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num)
48 {
49         Effect::set_gl_state(glsl_program_num, prefix, sampler_num);
50
51         int base_texture_size, texture_size;
52         if (direction == HORIZONTAL) {
53                 base_texture_size = texture_size = 1280;  // FIXME
54         } else if (direction == VERTICAL) {
55                 base_texture_size = texture_size = 720;  // FIXME
56         } else {
57                 assert(false);
58         }
59
60         // We only have 16 taps to work with on each side, and we want that to
61         // reach out to about 2.5*sigma.  Bump up the mipmap levels (giving us
62         // box blurs) until we have what we need.
63         //
64         // FIXME: we really need to pick the same mipmap level for both horizontal and vertical!
65         unsigned base_mipmap_level = 0;
66         float adjusted_radius = radius;
67         while (texture_size > 1 && adjusted_radius * 2.5f > NUM_TAPS / 2) {
68                 ++base_mipmap_level;
69                 texture_size /= 2;  // Rounding down.
70                 adjusted_radius = radius * float(texture_size) / float(base_texture_size);
71         }
72
73         // In the second pass, we do the same, but don't sample from a mipmap;
74         // that would re-blur the other direction in an ugly fashion, and we already
75         // have the vertical box blur we need from that pass.
76         //
77         // TODO: We really need to present horizontal+vertical as a unit;
78         // currently, there's really no guarantee vertical blur is the second pass.
79         if (direction == VERTICAL) {
80                 base_mipmap_level = 0;
81         }
82
83         glActiveTexture(GL_TEXTURE0);
84         check_error();
85         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, base_mipmap_level);
86         check_error();
87         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, base_mipmap_level);
88         check_error();
89
90         // Compute the weights; they will be symmetrical, so we only compute
91         // the right side.
92         float weight[NUM_TAPS + 1];
93         if (radius < 1e-3) {
94                 weight[0] = 1.0f;
95                 for (unsigned i = 1; i < NUM_TAPS + 1; ++i) {
96                         weight[i] = 0.0f;
97                 }
98         } else {
99                 float sum = 0.0f;
100                 for (unsigned i = 0; i < NUM_TAPS + 1; ++i) {
101                         float z = i / adjusted_radius;
102
103                         // Gaussian blur is a common, but maybe not the prettiest choice;
104                         // it can feel a bit too blurry in the fine detail and too little
105                         // long-tail. This is a simple logistic distribution, which has
106                         // a narrower peak but longer tails.
107                         weight[i] = 1.0f / (cosh(z) * cosh(z));
108
109                         if (i == 0) {
110                                 sum += weight[i];
111                         } else {
112                                 sum += 2.0f * weight[i];
113                         }
114                 }
115                 for (unsigned i = 0; i < NUM_TAPS + 1; ++i) {
116                         weight[i] /= sum;
117                 }
118         }
119
120 #if 0
121         // NOTE: This is currently broken.
122
123         // Since the GPU gives us bilinear sampling for free, we can get two
124         // samples for the price of one (for every but the center sample,
125         // in which case this trick doesn't buy us anything). Simply sample
126         // between the two pixel centers, and we can do with fewer weights.
127         // (This is right even in the vertical pass where we don't actually
128         // sample between the pixels, because we have linear interpolation
129         // there too.)
130         //
131         // We pack the parameters into a float4: The relative sample coordinates
132         // in (x,y), and the weight in z. w is unused.
133         float samples[4 * (NUM_TAPS / 2 + 1)];
134
135         // Center sample.
136         samples[4 * 0 + 0] = 0.0f;
137         samples[4 * 0 + 1] = 0.0f;
138         samples[4 * 0 + 2] = weight[0];
139         samples[4 * 0 + 3] = 0.0f;
140
141         // All other samples.
142         for (unsigned i = 1; i < NUM_TAPS / 2 + 1; ++i) {
143                 unsigned base_pos = i * 2 - 1;
144                 float w1 = weight[base_pos];
145                 float w2 = weight[base_pos + 1];
146
147                 float offset, total_weight;
148                 if (w1 + w2 < 1e-6) {
149                         offset = 0.5f;
150                         total_weight = 0.0f;
151                 } else {
152                         offset = w2 / (w1 + w2);
153                         total_weight = w1 + w2;
154                 }
155 #if 0
156                 // hack for easier visualization
157                 offset = 0.5f;
158                 total_weight = 8.0f;
159 #endif
160                 float x = 0.0f, y = 0.0f;
161
162                 if (direction == HORIZONTAL) {
163                         x = (base_pos + offset) / (float)texture_size;
164                 } else if (direction == VERTICAL) {
165                         y = (base_pos + offset) / (float)texture_size;
166                 } else {
167                         assert(false);
168                 }
169
170                 samples[4 * i + 0] = x;
171                 samples[4 * i + 1] = y;
172                 samples[4 * i + 2] = total_weight;
173                 samples[4 * i + 3] = 0.0f;
174         }
175
176         set_uniform_vec4_array(glsl_program_num, prefix, "samples", samples, NUM_TAPS / 2 + 1);
177 #else
178         // Boring, at-whole-pixels sampling.
179         float samples[4 * NUM_TAPS];
180
181         // All other samples.
182         for (unsigned i = 0; i < NUM_TAPS + 1; ++i) {
183                 float x = 0.0f, y = 0.0f;
184
185                 if (direction == HORIZONTAL) {
186                         x = i / (float)texture_size;
187                 } else if (direction == VERTICAL) {
188                         y = i / (float)texture_size;
189                 } else {
190                         assert(false);
191                 }
192
193                 samples[4 * i + 0] = x;
194                 samples[4 * i + 1] = y;
195                 samples[4 * i + 2] = weight[i];
196                 samples[4 * i + 3] = 0.0f;
197         }
198
199         set_uniform_vec4_array(glsl_program_num, prefix, "samples", samples, NUM_TAPS + 1);
200 #endif
201 }
202
203 void SingleBlurPassEffect::clear_gl_state()
204 {
205         glActiveTexture(GL_TEXTURE0);
206         check_error();
207         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
208         check_error();
209         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1000);
210         check_error();
211 }