1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
18 
19 #include "BlurFilter.h"
20 #include <EGL/egl.h>
21 #include <EGL/eglext.h>
22 #include <GLES3/gl3.h>
23 #include <GLES3/gl3ext.h>
24 #include <ui/GraphicTypes.h>
25 #include <cstdint>
26 
27 #include <utils/Trace.h>
28 
29 namespace android {
30 namespace renderengine {
31 namespace gl {
32 
BlurFilter(GLESRenderEngine & engine)33 BlurFilter::BlurFilter(GLESRenderEngine& engine)
34       : mEngine(engine),
35         mCompositionFbo(engine),
36         mPingFbo(engine),
37         mPongFbo(engine),
38         mMixProgram(engine),
39         mBlurProgram(engine) {
40     mMixProgram.compile(getVertexShader(), getMixFragShader());
41     mMPosLoc = mMixProgram.getAttributeLocation("aPosition");
42     mMUvLoc = mMixProgram.getAttributeLocation("aUV");
43     mMTextureLoc = mMixProgram.getUniformLocation("uTexture");
44     mMCompositionTextureLoc = mMixProgram.getUniformLocation("uCompositionTexture");
45     mMMixLoc = mMixProgram.getUniformLocation("uMix");
46 
47     mBlurProgram.compile(getVertexShader(), getFragmentShader());
48     mBPosLoc = mBlurProgram.getAttributeLocation("aPosition");
49     mBUvLoc = mBlurProgram.getAttributeLocation("aUV");
50     mBTextureLoc = mBlurProgram.getUniformLocation("uTexture");
51     mBOffsetLoc = mBlurProgram.getUniformLocation("uOffset");
52 
53     static constexpr auto size = 2.0f;
54     static constexpr auto translation = 1.0f;
55     const GLfloat vboData[] = {
56         // Vertex data
57         translation - size, -translation - size,
58         translation - size, -translation + size,
59         translation + size, -translation + size,
60         // UV data
61         0.0f, 0.0f - translation,
62         0.0f, size - translation,
63         size, size - translation
64     };
65     mMeshBuffer.allocateBuffers(vboData, 12 /* size */);
66 }
67 
setAsDrawTarget(const DisplaySettings & display,uint32_t radius)68 status_t BlurFilter::setAsDrawTarget(const DisplaySettings& display, uint32_t radius) {
69     ATRACE_NAME("BlurFilter::setAsDrawTarget");
70     mRadius = radius;
71     mDisplayX = display.physicalDisplay.left;
72     mDisplayY = display.physicalDisplay.top;
73 
74     if (mDisplayWidth < display.physicalDisplay.width() ||
75         mDisplayHeight < display.physicalDisplay.height()) {
76         ATRACE_NAME("BlurFilter::allocatingTextures");
77 
78         mDisplayWidth = display.physicalDisplay.width();
79         mDisplayHeight = display.physicalDisplay.height();
80         mCompositionFbo.allocateBuffers(mDisplayWidth, mDisplayHeight);
81 
82         const uint32_t fboWidth = floorf(mDisplayWidth * kFboScale);
83         const uint32_t fboHeight = floorf(mDisplayHeight * kFboScale);
84         mPingFbo.allocateBuffers(fboWidth, fboHeight);
85         mPongFbo.allocateBuffers(fboWidth, fboHeight);
86 
87         if (mPingFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
88             ALOGE("Invalid ping buffer");
89             return mPingFbo.getStatus();
90         }
91         if (mPongFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
92             ALOGE("Invalid pong buffer");
93             return mPongFbo.getStatus();
94         }
95         if (mCompositionFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
96             ALOGE("Invalid composition buffer");
97             return mCompositionFbo.getStatus();
98         }
99         if (!mBlurProgram.isValid()) {
100             ALOGE("Invalid shader");
101             return GL_INVALID_OPERATION;
102         }
103     }
104 
105     mCompositionFbo.bind();
106     glViewport(0, 0, mCompositionFbo.getBufferWidth(), mCompositionFbo.getBufferHeight());
107     return NO_ERROR;
108 }
109 
drawMesh(GLuint uv,GLuint position)110 void BlurFilter::drawMesh(GLuint uv, GLuint position) {
111 
112     glEnableVertexAttribArray(uv);
113     glEnableVertexAttribArray(position);
114     mMeshBuffer.bind();
115     glVertexAttribPointer(position, 2 /* size */, GL_FLOAT, GL_FALSE,
116                           2 * sizeof(GLfloat) /* stride */, 0 /* offset */);
117     glVertexAttribPointer(uv, 2 /* size */, GL_FLOAT, GL_FALSE, 0 /* stride */,
118                           (GLvoid*)(6 * sizeof(GLfloat)) /* offset */);
119     mMeshBuffer.unbind();
120 
121     // draw mesh
122     glDrawArrays(GL_TRIANGLES, 0 /* first */, 3 /* count */);
123 }
124 
prepare()125 status_t BlurFilter::prepare() {
126     ATRACE_NAME("BlurFilter::prepare");
127 
128     // Kawase is an approximation of Gaussian, but it behaves differently from it.
129     // A radius transformation is required for approximating them, and also to introduce
130     // non-integer steps, necessary to smoothly interpolate large radii.
131     const auto radius = mRadius / 6.0f;
132 
133     // Calculate how many passes we'll do, based on the radius.
134     // Too many passes will make the operation expensive.
135     const auto passes = min(kMaxPasses, (uint32_t)ceil(radius));
136 
137     const float radiusByPasses = radius / (float)passes;
138     const float stepX = radiusByPasses / (float)mCompositionFbo.getBufferWidth();
139     const float stepY = radiusByPasses / (float)mCompositionFbo.getBufferHeight();
140 
141     // Let's start by downsampling and blurring the composited frame simultaneously.
142     mBlurProgram.useProgram();
143     glActiveTexture(GL_TEXTURE0);
144     glUniform1i(mBTextureLoc, 0);
145     glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
146     glUniform2f(mBOffsetLoc, stepX, stepY);
147     glViewport(0, 0, mPingFbo.getBufferWidth(), mPingFbo.getBufferHeight());
148     mPingFbo.bind();
149     drawMesh(mBUvLoc, mBPosLoc);
150 
151     // And now we'll ping pong between our textures, to accumulate the result of various offsets.
152     GLFramebuffer* read = &mPingFbo;
153     GLFramebuffer* draw = &mPongFbo;
154     glViewport(0, 0, draw->getBufferWidth(), draw->getBufferHeight());
155     for (auto i = 1; i < passes; i++) {
156         ATRACE_NAME("BlurFilter::renderPass");
157         draw->bind();
158 
159         glBindTexture(GL_TEXTURE_2D, read->getTextureName());
160         glUniform2f(mBOffsetLoc, stepX * i, stepY * i);
161 
162         drawMesh(mBUvLoc, mBPosLoc);
163 
164         // Swap buffers for next iteration
165         auto tmp = draw;
166         draw = read;
167         read = tmp;
168     }
169     mLastDrawTarget = read;
170 
171     return NO_ERROR;
172 }
173 
render(bool multiPass)174 status_t BlurFilter::render(bool multiPass) {
175     ATRACE_NAME("BlurFilter::render");
176 
177     // Now let's scale our blur up. It will be interpolated with the larger composited
178     // texture for the first frames, to hide downscaling artifacts.
179     GLfloat mix = fmin(1.0, mRadius / kMaxCrossFadeRadius);
180 
181     // When doing multiple passes, we cannot try to read mCompositionFbo, given that we'll
182     // be writing onto it. Let's disable the crossfade, otherwise we'd need 1 extra frame buffer,
183     // as large as the screen size.
184     if (mix >= 1 || multiPass) {
185         mLastDrawTarget->bindAsReadBuffer();
186         glBlitFramebuffer(0, 0, mLastDrawTarget->getBufferWidth(),
187                           mLastDrawTarget->getBufferHeight(), mDisplayX, mDisplayY, mDisplayWidth,
188                           mDisplayHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
189         return NO_ERROR;
190     }
191 
192     mMixProgram.useProgram();
193     glUniform1f(mMMixLoc, mix);
194     glActiveTexture(GL_TEXTURE0);
195     glBindTexture(GL_TEXTURE_2D, mLastDrawTarget->getTextureName());
196     glUniform1i(mMTextureLoc, 0);
197     glActiveTexture(GL_TEXTURE1);
198     glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
199     glUniform1i(mMCompositionTextureLoc, 1);
200 
201     drawMesh(mMUvLoc, mMPosLoc);
202 
203     glUseProgram(0);
204     glActiveTexture(GL_TEXTURE0);
205     mEngine.checkErrors("Drawing blur mesh");
206     return NO_ERROR;
207 }
208 
getVertexShader() const209 string BlurFilter::getVertexShader() const {
210     return R"SHADER(#version 310 es
211         precision mediump float;
212 
213         in vec2 aPosition;
214         in highp vec2 aUV;
215         out highp vec2 vUV;
216 
217         void main() {
218             vUV = aUV;
219             gl_Position = vec4(aPosition, 0.0, 1.0);
220         }
221     )SHADER";
222 }
223 
getFragmentShader() const224 string BlurFilter::getFragmentShader() const {
225     return R"SHADER(#version 310 es
226         precision mediump float;
227 
228         uniform sampler2D uTexture;
229         uniform vec2 uOffset;
230 
231         in highp vec2 vUV;
232         out vec4 fragColor;
233 
234         void main() {
235             fragColor  = texture(uTexture, vUV, 0.0);
236             fragColor += texture(uTexture, vUV + vec2( uOffset.x,  uOffset.y), 0.0);
237             fragColor += texture(uTexture, vUV + vec2( uOffset.x, -uOffset.y), 0.0);
238             fragColor += texture(uTexture, vUV + vec2(-uOffset.x,  uOffset.y), 0.0);
239             fragColor += texture(uTexture, vUV + vec2(-uOffset.x, -uOffset.y), 0.0);
240 
241             fragColor = vec4(fragColor.rgb * 0.2, 1.0);
242         }
243     )SHADER";
244 }
245 
getMixFragShader() const246 string BlurFilter::getMixFragShader() const {
247     string shader = R"SHADER(#version 310 es
248         precision mediump float;
249 
250         in highp vec2 vUV;
251         out vec4 fragColor;
252 
253         uniform sampler2D uCompositionTexture;
254         uniform sampler2D uTexture;
255         uniform float uMix;
256 
257         void main() {
258             vec4 blurred = texture(uTexture, vUV);
259             vec4 composition = texture(uCompositionTexture, vUV);
260             fragColor = mix(composition, blurred, uMix);
261         }
262     )SHADER";
263     return shader;
264 }
265 
266 } // namespace gl
267 } // namespace renderengine
268 } // namespace android
269