Alex Vakulenko | e4eec20 | 2017-01-27 14:41:04 -0800 | [diff] [blame] | 1 | #include "include/private/dvr/graphics/blur.h" |
| 2 | |
| 3 | // clang-format off |
| 4 | #include <EGL/egl.h> |
| 5 | #include <EGL/eglext.h> |
| 6 | #include <GLES/gl.h> |
| 7 | #include <GLES/glext.h> |
| 8 | #include <GLES2/gl2.h> |
| 9 | // clang-format on |
| 10 | #include <hardware/gralloc.h> |
| 11 | |
| 12 | #include <string> |
| 13 | |
Alex Vakulenko | 4fe6058 | 2017-02-02 11:35:59 -0800 | [diff] [blame] | 14 | #include <log/log.h> |
Alex Vakulenko | e4eec20 | 2017-01-27 14:41:04 -0800 | [diff] [blame] | 15 | #include <private/dvr/debug.h> |
| 16 | #include <private/dvr/graphics/egl_image.h> |
| 17 | #include <private/dvr/graphics/shader_program.h> |
| 18 | #include <private/dvr/types.h> |
| 19 | |
| 20 | #define POSITION_ATTR 0 |
| 21 | #define OFFSET_BINDING 0 |
| 22 | #define SAMPLER_BINDING 1 |
| 23 | |
| 24 | namespace { |
| 25 | |
| 26 | std::string screen_space_vert_shader = SHADER0([]() { // NOLINT |
| 27 | layout(location = 0) in vec4 position_uv; |
| 28 | out vec2 texCoords; |
| 29 | |
| 30 | void main() { |
| 31 | gl_Position = vec4(position_uv.xy, 0.0, 1.0); |
| 32 | texCoords = position_uv.zw; |
| 33 | } |
| 34 | }); |
| 35 | |
| 36 | std::string kawase_blur_frag_shader = SHADER0([]() { // NOLINT |
| 37 | precision mediump float; |
| 38 | layout(location = 0) uniform vec2 uSampleOffsets[4]; |
| 39 | layout(binding = 1) uniform APP_SAMPLER_2D uTexture; |
| 40 | in vec2 texCoords; |
| 41 | out vec4 color; |
| 42 | |
| 43 | void main() { |
| 44 | vec2 tc = texCoords; |
| 45 | color = texture(uTexture, tc + uSampleOffsets[0]); |
| 46 | color += texture(uTexture, tc + uSampleOffsets[1]); |
| 47 | color += texture(uTexture, tc + uSampleOffsets[2]); |
| 48 | color += texture(uTexture, tc + uSampleOffsets[3]); |
| 49 | color *= (1.0 / 4.0); |
| 50 | } |
| 51 | }); |
| 52 | |
| 53 | constexpr int g_num_samples = 4; |
| 54 | |
| 55 | // Modified kernel patterns originally based on: |
| 56 | // https://software.intel.com/en-us/blogs/2014/07/15/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms |
| 57 | // The modification is left and right rotations of the 3rd and 4th patterns. |
| 58 | const android::dvr::vec2 g_blur_samples[][g_num_samples] = { |
| 59 | {{0.5f, 0.5f}, {-0.5f, 0.5f}, {0.5f, -0.5f}, {-0.5f, -0.5f}}, |
| 60 | {{1.5f, 1.5f}, {-1.5f, 1.5f}, {1.5f, -1.5f}, {-1.5f, -1.5f}}, |
| 61 | {{2.5f, 1.5f}, {-1.5f, 2.5f}, {1.5f, -2.5f}, {-2.5f, -1.5f}}, |
| 62 | {{2.5f, 3.5f}, {-3.5f, 2.5f}, {3.5f, -2.5f}, {-2.5f, -3.5f}}, |
| 63 | // Last pass disabled, because it is more blur than we need. |
| 64 | // {{3.5f, 3.5f}, {-3.5f, 3.5f}, {3.5f, -3.5f}, {-3.5f, -3.5f}}, |
| 65 | }; |
| 66 | |
| 67 | } // namespace |
| 68 | |
| 69 | namespace android { |
| 70 | namespace dvr { |
| 71 | |
| 72 | Blur::Blur(int w, int h, GLuint source_texture, GLint source_texture_target, |
| 73 | GLint target_texture_target, bool is_external, EGLDisplay display, |
| 74 | int num_blur_outputs) |
| 75 | : display_(display), |
| 76 | target_texture_target_(target_texture_target), |
| 77 | width_(w), |
| 78 | height_(h), |
| 79 | fbo_q_free_(1 + num_blur_outputs) { |
Alex Vakulenko | 4fe6058 | 2017-02-02 11:35:59 -0800 | [diff] [blame] | 80 | LOG_ALWAYS_FATAL_IF(num_blur_outputs <= 0); |
Alex Vakulenko | e4eec20 | 2017-01-27 14:41:04 -0800 | [diff] [blame] | 81 | source_fbo_ = |
| 82 | CreateFbo(w, h, source_texture, source_texture_target, is_external); |
| 83 | fbo_half_ = CreateFbo(w / 2, h / 2, 0, target_texture_target, is_external); |
| 84 | // Create the quarter res fbos. |
| 85 | for (size_t i = 0; i < fbo_q_free_.GetCapacity(); ++i) |
| 86 | fbo_q_.push_back( |
| 87 | CreateFbo(w / 4, h / 4, 0, target_texture_target, is_external)); |
| 88 | scale_ = 1.0f; |
| 89 | } |
| 90 | |
| 91 | Blur::~Blur() { |
| 92 | glFinish(); |
| 93 | glDeleteFramebuffers(1, &source_fbo_.fbo); |
| 94 | glDeleteFramebuffers(1, &fbo_half_.fbo); |
| 95 | // Note: source_fbo_.texture is not deleted because it was created externally. |
| 96 | glDeleteTextures(1, &fbo_half_.texture); |
| 97 | if (fbo_half_.egl_image) |
| 98 | eglDestroyImageKHR(display_, fbo_half_.egl_image); |
| 99 | for (const auto& fbo : fbo_q_) { |
| 100 | glDeleteFramebuffers(1, &fbo.fbo); |
| 101 | glDeleteTextures(1, &fbo.texture); |
| 102 | if (fbo.egl_image) |
| 103 | eglDestroyImageKHR(display_, fbo.egl_image); |
| 104 | } |
| 105 | CHECK_GL(); |
| 106 | } |
| 107 | |
| 108 | void Blur::StartFrame() { |
| 109 | fbo_q_free_.Clear(); |
| 110 | for (const auto& fbo : fbo_q_) |
| 111 | fbo_q_free_.Append(fbo); |
| 112 | } |
| 113 | |
| 114 | GLuint Blur::DrawBlur(GLuint source_texture) { |
Alex Vakulenko | 4fe6058 | 2017-02-02 11:35:59 -0800 | [diff] [blame] | 115 | LOG_ALWAYS_FATAL_IF(fbo_q_free_.GetSize() < 2); |
Alex Vakulenko | e4eec20 | 2017-01-27 14:41:04 -0800 | [diff] [blame] | 116 | |
| 117 | // Downsample to half w x half h. |
| 118 | glBindFramebuffer(GL_READ_FRAMEBUFFER, source_fbo_.fbo); |
| 119 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_half_.fbo); |
| 120 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| 121 | target_texture_target_, source_texture, 0); |
| 122 | glBlitFramebuffer(0, 0, width_, height_, 0, 0, width_ / 2, height_ / 2, |
| 123 | GL_COLOR_BUFFER_BIT, GL_LINEAR); |
| 124 | CHECK_GL(); |
| 125 | |
| 126 | // Downsample to quarter w x quarter h. |
| 127 | glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_half_.fbo); |
| 128 | Fbo fbo_out = fbo_q_free_.Front(); |
| 129 | fbo_q_free_.PopFront(); |
| 130 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_out.fbo); |
| 131 | glBlitFramebuffer(0, 0, width_ / 2, height_ / 2, 0, 0, width_ / 4, |
| 132 | height_ / 4, GL_COLOR_BUFFER_BIT, GL_LINEAR); |
| 133 | glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); |
| 134 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); |
| 135 | CHECK_GL(); |
| 136 | |
| 137 | // Blur shader is initialized statically to share between multiple blur |
| 138 | // instances. |
| 139 | static ShaderProgram kawase_prog[2]; |
| 140 | int prog_index = (target_texture_target_ == GL_TEXTURE_EXTERNAL_OES) ? 1 : 0; |
| 141 | if (!kawase_prog[prog_index].IsUsable()) { |
| 142 | std::string prefix = "#version 310 es\n"; |
| 143 | if (target_texture_target_ == GL_TEXTURE_EXTERNAL_OES) { |
| 144 | prefix += "#extension GL_OES_EGL_image_external_essl3 : require\n"; |
| 145 | prefix += "#define APP_SAMPLER_2D samplerExternalOES\n"; |
| 146 | } else { |
| 147 | prefix += "#define APP_SAMPLER_2D sampler2D\n"; |
| 148 | } |
| 149 | std::string vert = prefix + screen_space_vert_shader; |
| 150 | std::string frag = prefix + kawase_blur_frag_shader; |
| 151 | kawase_prog[prog_index].Link(vert, frag); |
| 152 | CHECK_GL(); |
| 153 | } |
| 154 | |
| 155 | int blur_w = width_ / 4; |
| 156 | int blur_h = height_ / 4; |
| 157 | float pix_w = 1.0f / static_cast<float>(blur_w); |
| 158 | float pix_h = 1.0f / static_cast<float>(blur_h); |
| 159 | vec2 pixel_size(pix_w, pix_h); |
| 160 | constexpr int num_passes = sizeof(g_blur_samples) / sizeof(g_blur_samples[0]); |
| 161 | vec2 blur_offsets[num_passes][g_num_samples]; |
| 162 | for (int i = 0; i < num_passes; ++i) { |
| 163 | for (int dir = 0; dir < g_num_samples; ++dir) { |
| 164 | blur_offsets[i][dir] = pixel_size.array() * |
| 165 | g_blur_samples[i][dir].array() * scale_; |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | kawase_prog[prog_index].Use(); |
| 170 | |
| 171 | vec4 screen_tri_strip[4] = {vec4(-1, 1, 0, 1), vec4(-1, -1, 0, 0), |
| 172 | vec4(1, 1, 1, 1), vec4(1, -1, 1, 0)}; |
| 173 | |
| 174 | glViewport(0, 0, blur_w, blur_h); |
| 175 | glVertexAttribPointer(POSITION_ATTR, 4, GL_FLOAT, GL_FALSE, sizeof(vec4), |
| 176 | screen_tri_strip); |
| 177 | glEnableVertexAttribArray(POSITION_ATTR); |
| 178 | CHECK_GL(); |
| 179 | |
| 180 | // Ping-pong between fbos from fbo_q_free_ to compute the passes. |
| 181 | Fbo fbo_in = fbo_out; |
| 182 | for (int i = 0; i < num_passes; ++i) { |
| 183 | fbo_out = fbo_q_free_.Front(); |
| 184 | fbo_q_free_.PopFront(); |
| 185 | glBindFramebuffer(GL_FRAMEBUFFER, fbo_out.fbo); |
| 186 | glActiveTexture(GL_TEXTURE0 + SAMPLER_BINDING); |
| 187 | glBindTexture(target_texture_target_, fbo_in.texture); |
| 188 | glUniform2fv(OFFSET_BINDING, 4, &blur_offsets[i][0][0]); |
| 189 | glClear(GL_COLOR_BUFFER_BIT); |
| 190 | glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| 191 | CHECK_GL(); |
| 192 | // Put fbo_in back into the free fbo pool. |
| 193 | fbo_q_free_.Append(fbo_in); |
| 194 | // Next iteration's in buffer is this iteration's out buffer. |
| 195 | fbo_in = fbo_out; |
| 196 | } |
| 197 | glDisableVertexAttribArray(POSITION_ATTR); |
| 198 | glBindTexture(target_texture_target_, 0); |
| 199 | glUseProgram(0); |
| 200 | glActiveTexture(GL_TEXTURE0); |
| 201 | CHECK_GL(); |
| 202 | // fbo_out remains out of the fbo_q_free_ list, since the application will be |
| 203 | // using it as a texture. |
| 204 | return fbo_out.texture; |
| 205 | } |
| 206 | |
| 207 | Blur::Fbo Blur::CreateFbo(int w, int h, GLuint source_texture, GLint tex_target, |
| 208 | bool is_external) { |
| 209 | Fbo fbo; |
| 210 | glGenFramebuffers(1, &fbo.fbo); |
| 211 | if (source_texture) { |
| 212 | fbo.texture = source_texture; |
| 213 | } else { |
| 214 | glGenTextures(1, &fbo.texture); |
| 215 | } |
| 216 | |
| 217 | glBindFramebuffer(GL_FRAMEBUFFER, fbo.fbo); |
| 218 | CHECK_GL(); |
| 219 | |
| 220 | if (!source_texture) { |
| 221 | glBindTexture(tex_target, fbo.texture); |
| 222 | glTexParameteri(tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| 223 | glTexParameteri(tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| 224 | glTexParameteri(tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| 225 | glTexParameteri(tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| 226 | if (is_external) { |
| 227 | fbo.egl_image = |
| 228 | CreateEglImage(display_, w, h, HAL_PIXEL_FORMAT_RGBA_8888, |
| 229 | GRALLOC_USAGE_HW_FB | GRALLOC_USAGE_HW_RENDER); |
| 230 | glEGLImageTargetTexture2DOES(tex_target, fbo.egl_image); |
| 231 | } else { |
| 232 | glTexImage2D(tex_target, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| 233 | nullptr); |
| 234 | } |
| 235 | } |
| 236 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex_target, |
| 237 | fbo.texture, 0); |
| 238 | CHECK_GL(); |
| 239 | CHECK_GL_FBO(); |
| 240 | |
| 241 | glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| 242 | return fbo; |
| 243 | } |
| 244 | |
| 245 | } // namespace dvr |
| 246 | } // namespace android |