blob: fc26bccc523a97ee3eabb19d984dfb3ff83150e6 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include "KawaseBlurFilter.h"
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#include <ui/GraphicTypes.h>
#include <utils/Trace.h>
namespace android {
namespace renderengine {
namespace gl {
KawaseBlurFilter::KawaseBlurFilter(GLESRenderEngine& engine)
: BlurFilter(engine), mFbo(engine), mProgram(engine) {
mProgram.compile(getVertexShader(), getFragmentShader());
mPosLoc = mProgram.getAttributeLocation("aPosition");
mUvLoc = mProgram.getAttributeLocation("aUV");
mTextureLoc = mProgram.getUniformLocation("uTexture");
mOffsetLoc = mProgram.getUniformLocation("uOffset");
}
void KawaseBlurFilter::allocateTextures() {
mFbo.allocateBuffers(mBlurredFbo.getBufferWidth(), mBlurredFbo.getBufferHeight());
}
status_t KawaseBlurFilter::prepare() {
ATRACE_NAME("KawaseBlurFilter::prepare");
if (mFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
ALOGE("Invalid FBO");
return mFbo.getStatus();
}
if (!mProgram.isValid()) {
ALOGE("Invalid shader");
return GL_INVALID_OPERATION;
}
blit(mCompositionFbo, mBlurredFbo);
// Kawase is an approximation of Gaussian, but it behaves differently from it.
// A radius transformation is required for approximating them, and also to introduce
// non-integer steps, necessary to smoothly interpolate large radii.
auto radius = mRadius / 6.0f;
// Calculate how many passes we'll do, based on the radius.
// Too many passes will make the operation expensive.
auto passes = min(kMaxPasses, (uint32_t)ceil(radius));
// We'll ping pong between our textures, to accumulate the result of various offsets.
mProgram.useProgram();
GLFramebuffer* draw = &mFbo;
GLFramebuffer* read = &mBlurredFbo;
float stepX = radius / (float)mCompositionFbo.getBufferWidth() / (float)passes;
float stepY = radius / (float)mCompositionFbo.getBufferHeight() / (float)passes;
glActiveTexture(GL_TEXTURE0);
glUniform1i(mTextureLoc, 0);
for (auto i = 0; i < passes; i++) {
ATRACE_NAME("KawaseBlurFilter::renderPass");
draw->bind();
glViewport(0, 0, draw->getBufferWidth(), draw->getBufferHeight());
glBindTexture(GL_TEXTURE_2D, read->getTextureName());
glUniform2f(mOffsetLoc, stepX * i, stepY * i);
mEngine.checkErrors("Setting uniforms");
drawMesh(mUvLoc, mPosLoc);
// Swap buffers for next iteration
auto tmp = draw;
draw = read;
read = tmp;
}
// Copy texture, given that we're expected to end on mBlurredFbo.
if (draw == &mBlurredFbo) {
blit(mFbo, mBlurredFbo);
}
// Cleanup
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return NO_ERROR;
}
string KawaseBlurFilter::getFragmentShader() const {
return R"SHADER(#version 310 es
precision mediump float;
uniform sampler2D uTexture;
highp uniform vec2 uOffset;
highp in vec2 vUV;
out vec4 fragColor;
vec4 kawaseBlur() {
return (texture(uTexture, vec2(-1.0, 1.0) * uOffset + vUV, 0.0)
+ texture(uTexture, uOffset + vUV, 0.0)
+ texture(uTexture, vec2(1.0, -1.0) * uOffset + vUV, 0.0)
+ texture(uTexture, vec2(-1.0) * uOffset + vUV, 0.0))
* 0.25;
}
void main() {
fragColor = kawaseBlur();
}
)SHADER";
}
void KawaseBlurFilter::blit(GLFramebuffer& read, GLFramebuffer& draw) const {
read.bindAsReadBuffer();
draw.bindAsDrawBuffer();
glBlitFramebuffer(0, 0, read.getBufferWidth(), read.getBufferHeight(), 0, 0,
draw.getBufferWidth(), draw.getBufferHeight(), GL_COLOR_BUFFER_BIT,
GL_LINEAR);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
} // namespace gl
} // namespace renderengine
} // namespace android