blob: 19f18c0a7f73e230f456492bc620a69807c2526a [file] [log] [blame]
/*
* Copyright 2019 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 "BlurFilter.h"
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#include <ui/GraphicTypes.h>
#include <cstdint>
#include <utils/Trace.h>
namespace android {
namespace renderengine {
namespace gl {
BlurFilter::BlurFilter(GLESRenderEngine& engine)
: mEngine(engine),
mCompositionFbo(engine),
mPingFbo(engine),
mPongFbo(engine),
mMixProgram(engine),
mBlurProgram(engine) {
mMixProgram.compile(getVertexShader(), getMixFragShader());
mMPosLoc = mMixProgram.getAttributeLocation("aPosition");
mMUvLoc = mMixProgram.getAttributeLocation("aUV");
mMTextureLoc = mMixProgram.getUniformLocation("uTexture");
mMCompositionTextureLoc = mMixProgram.getUniformLocation("uCompositionTexture");
mMMixLoc = mMixProgram.getUniformLocation("uMix");
mBlurProgram.compile(getVertexShader(), getFragmentShader());
mBPosLoc = mBlurProgram.getAttributeLocation("aPosition");
mBUvLoc = mBlurProgram.getAttributeLocation("aUV");
mBTextureLoc = mBlurProgram.getUniformLocation("uTexture");
mBOffsetLoc = mBlurProgram.getUniformLocation("uOffset");
static constexpr auto size = 2.0f;
static constexpr auto translation = 1.0f;
const GLfloat vboData[] = {
// Vertex data
translation - size, -translation - size,
translation - size, -translation + size,
translation + size, -translation + size,
// UV data
0.0f, 0.0f - translation,
0.0f, size - translation,
size, size - translation
};
mMeshBuffer.allocateBuffers(vboData, 12 /* size */);
}
status_t BlurFilter::setAsDrawTarget(const DisplaySettings& display, uint32_t radius) {
ATRACE_NAME("BlurFilter::setAsDrawTarget");
mRadius = radius;
mDisplayX = display.physicalDisplay.left;
mDisplayY = display.physicalDisplay.top;
if (mDisplayWidth < display.physicalDisplay.width() ||
mDisplayHeight < display.physicalDisplay.height()) {
ATRACE_NAME("BlurFilter::allocatingTextures");
mDisplayWidth = display.physicalDisplay.width();
mDisplayHeight = display.physicalDisplay.height();
mCompositionFbo.allocateBuffers(mDisplayWidth, mDisplayHeight);
const uint32_t fboWidth = floorf(mDisplayWidth * kFboScale);
const uint32_t fboHeight = floorf(mDisplayHeight * kFboScale);
mPingFbo.allocateBuffers(fboWidth, fboHeight);
mPongFbo.allocateBuffers(fboWidth, fboHeight);
if (mPingFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
ALOGE("Invalid ping buffer");
return mPingFbo.getStatus();
}
if (mPongFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
ALOGE("Invalid pong buffer");
return mPongFbo.getStatus();
}
if (mCompositionFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
ALOGE("Invalid composition buffer");
return mCompositionFbo.getStatus();
}
if (!mBlurProgram.isValid()) {
ALOGE("Invalid shader");
return GL_INVALID_OPERATION;
}
}
mCompositionFbo.bind();
glViewport(0, 0, mCompositionFbo.getBufferWidth(), mCompositionFbo.getBufferHeight());
return NO_ERROR;
}
void BlurFilter::drawMesh(GLuint uv, GLuint position) {
glEnableVertexAttribArray(uv);
glEnableVertexAttribArray(position);
mMeshBuffer.bind();
glVertexAttribPointer(position, 2 /* size */, GL_FLOAT, GL_FALSE,
2 * sizeof(GLfloat) /* stride */, 0 /* offset */);
glVertexAttribPointer(uv, 2 /* size */, GL_FLOAT, GL_FALSE, 0 /* stride */,
(GLvoid*)(6 * sizeof(GLfloat)) /* offset */);
mMeshBuffer.unbind();
// draw mesh
glDrawArrays(GL_TRIANGLES, 0 /* first */, 3 /* count */);
}
status_t BlurFilter::prepare() {
ATRACE_NAME("BlurFilter::prepare");
// 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.
const auto radius = mRadius / 6.0f;
// Calculate how many passes we'll do, based on the radius.
// Too many passes will make the operation expensive.
const auto passes = min(kMaxPasses, (uint32_t)ceil(radius));
const float radiusByPasses = radius / (float)passes;
const float stepX = radiusByPasses / (float)mCompositionFbo.getBufferWidth();
const float stepY = radiusByPasses / (float)mCompositionFbo.getBufferHeight();
// Let's start by downsampling and blurring the composited frame simultaneously.
mBlurProgram.useProgram();
glActiveTexture(GL_TEXTURE0);
glUniform1i(mBTextureLoc, 0);
glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
glUniform2f(mBOffsetLoc, stepX, stepY);
glViewport(0, 0, mPingFbo.getBufferWidth(), mPingFbo.getBufferHeight());
mPingFbo.bind();
drawMesh(mBUvLoc, mBPosLoc);
// And now we'll ping pong between our textures, to accumulate the result of various offsets.
GLFramebuffer* read = &mPingFbo;
GLFramebuffer* draw = &mPongFbo;
glViewport(0, 0, draw->getBufferWidth(), draw->getBufferHeight());
for (auto i = 1; i < passes; i++) {
ATRACE_NAME("BlurFilter::renderPass");
draw->bind();
glBindTexture(GL_TEXTURE_2D, read->getTextureName());
glUniform2f(mBOffsetLoc, stepX * i, stepY * i);
drawMesh(mBUvLoc, mBPosLoc);
// Swap buffers for next iteration
auto tmp = draw;
draw = read;
read = tmp;
}
mLastDrawTarget = read;
return NO_ERROR;
}
status_t BlurFilter::render(bool multiPass) {
ATRACE_NAME("BlurFilter::render");
// Now let's scale our blur up. It will be interpolated with the larger composited
// texture for the first frames, to hide downscaling artifacts.
GLfloat mix = fmin(1.0, mRadius / kMaxCrossFadeRadius);
// When doing multiple passes, we cannot try to read mCompositionFbo, given that we'll
// be writing onto it. Let's disable the crossfade, otherwise we'd need 1 extra frame buffer,
// as large as the screen size.
if (mix >= 1 || multiPass) {
mLastDrawTarget->bindAsReadBuffer();
glBlitFramebuffer(0, 0, mLastDrawTarget->getBufferWidth(),
mLastDrawTarget->getBufferHeight(), mDisplayX, mDisplayY, mDisplayWidth,
mDisplayHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
return NO_ERROR;
}
mMixProgram.useProgram();
glUniform1f(mMMixLoc, mix);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mLastDrawTarget->getTextureName());
glUniform1i(mMTextureLoc, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
glUniform1i(mMCompositionTextureLoc, 1);
drawMesh(mMUvLoc, mMPosLoc);
glUseProgram(0);
glActiveTexture(GL_TEXTURE0);
mEngine.checkErrors("Drawing blur mesh");
return NO_ERROR;
}
string BlurFilter::getVertexShader() const {
return R"SHADER(#version 310 es
precision mediump float;
in vec2 aPosition;
in highp vec2 aUV;
out highp vec2 vUV;
void main() {
vUV = aUV;
gl_Position = vec4(aPosition, 0.0, 1.0);
}
)SHADER";
}
string BlurFilter::getFragmentShader() const {
return R"SHADER(#version 310 es
precision mediump float;
uniform sampler2D uTexture;
uniform vec2 uOffset;
in highp vec2 vUV;
out vec4 fragColor;
void main() {
fragColor = texture(uTexture, vUV, 0.0);
fragColor += texture(uTexture, vUV + vec2( uOffset.x, uOffset.y), 0.0);
fragColor += texture(uTexture, vUV + vec2( uOffset.x, -uOffset.y), 0.0);
fragColor += texture(uTexture, vUV + vec2(-uOffset.x, uOffset.y), 0.0);
fragColor += texture(uTexture, vUV + vec2(-uOffset.x, -uOffset.y), 0.0);
fragColor = vec4(fragColor.rgb * 0.2, 1.0);
}
)SHADER";
}
string BlurFilter::getMixFragShader() const {
string shader = R"SHADER(#version 310 es
precision mediump float;
in highp vec2 vUV;
out vec4 fragColor;
uniform sampler2D uCompositionTexture;
uniform sampler2D uTexture;
uniform float uMix;
void main() {
vec4 blurred = texture(uTexture, vUV);
vec4 composition = texture(uCompositionTexture, vUV);
fragColor = mix(composition, blurred, uMix);
}
)SHADER";
return shader;
}
} // namespace gl
} // namespace renderengine
} // namespace android