Files
frameworks_base/libs/hwui/OpenGLReadback.cpp
Arun 530a2b44d9 Disable hwui blending for first draw to main FBO
bug:34809371

In some applications, the first draw is not opaque - either because the
application is misbehaved, or because hwui is not able to reliably tell
whether the layer is opaque or translucent. This is undefined behaviour
in OpenGL ES and has a significant performance and bandwidth impact on
some tiler GPUs as it requires loading the previous frame's color data.
This change disables blending in that case and also for effectively
opaque blend modes (SRC=GL_ONE, DST=GL_ZERO). It increases performance
by ~10% for Leanback CTS on some low-end GPUs (gradient layer that hwui
incorrectly believes to be translucent).

Test: manual - visual inspection on fugu (nexus player)

Change-Id: I2cbf1c76678acae1a36923e72fd18ed55cd89dc2
2017-08-18 16:52:55 -07:00

291 lines
11 KiB
C++

/*
* Copyright (C) 2016 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.
*/
#include "OpenGLReadback.h"
#include "Caches.h"
#include "Image.h"
#include "GlopBuilder.h"
#include "GlLayer.h"
#include "renderstate/RenderState.h"
#include "renderthread/EglManager.h"
#include "utils/GLUtils.h"
#include <GLES2/gl2.h>
#include <ui/Fence.h>
#include <ui/GraphicBuffer.h>
#include <gui/Surface.h>
namespace android {
namespace uirenderer {
CopyResult OpenGLReadback::copySurfaceInto(Surface& surface, const Rect& srcRect,
SkBitmap* bitmap) {
ATRACE_CALL();
// Setup the source
sp<GraphicBuffer> sourceBuffer;
sp<Fence> sourceFence;
Matrix4 texTransform;
status_t err = surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence,
texTransform.data);
texTransform.invalidateType();
if (err != NO_ERROR) {
ALOGW("Failed to get last queued buffer, error = %d", err);
return CopyResult::UnknownError;
}
if (!sourceBuffer.get()) {
ALOGW("Surface doesn't have any previously queued frames, nothing to readback from");
return CopyResult::SourceEmpty;
}
if (sourceBuffer->getUsage() & GRALLOC_USAGE_PROTECTED) {
ALOGW("Surface is protected, unable to copy from it");
return CopyResult::SourceInvalid;
}
err = sourceFence->wait(500 /* ms */);
if (err != NO_ERROR) {
ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
return CopyResult::Timeout;
}
return copyGraphicBufferInto(sourceBuffer.get(), texTransform, srcRect, bitmap);
}
CopyResult OpenGLReadback::copyGraphicBufferInto(GraphicBuffer* graphicBuffer,
Matrix4& texTransform, const Rect& srcRect, SkBitmap* bitmap) {
mRenderThread.eglManager().initialize();
// TODO: Can't use Image helper since it forces GL_TEXTURE_2D usage via
// GL_OES_EGL_image, which doesn't work since we need samplerExternalOES
// to be able to properly sample from the buffer.
// Create the EGLImage object that maps the GraphicBuffer
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLClientBuffer clientBuffer = (EGLClientBuffer) graphicBuffer->getNativeBuffer();
EGLint attrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
EGLImageKHR sourceImage = eglCreateImageKHR(display, EGL_NO_CONTEXT,
EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs);
if (sourceImage == EGL_NO_IMAGE_KHR) {
ALOGW("eglCreateImageKHR failed (%#x)", eglGetError());
return CopyResult::UnknownError;
}
uint32_t width = graphicBuffer->getWidth();
uint32_t height = graphicBuffer->getHeight();
CopyResult copyResult = copyImageInto(sourceImage, texTransform, width, height,
srcRect, bitmap);
// All we're flushing & finishing is the deletion of the texture since
// copyImageInto already did a major flush & finish as an implicit
// part of glReadPixels, so this shouldn't pose any major stalls.
glFinish();
eglDestroyImageKHR(display, sourceImage);
return copyResult;
}
CopyResult OpenGLReadback::copyGraphicBufferInto(GraphicBuffer* graphicBuffer, SkBitmap* bitmap) {
Rect srcRect;
Matrix4 transform;
transform.loadScale(1, -1, 1);
transform.translate(0, -1);
return copyGraphicBufferInto(graphicBuffer, transform, srcRect, bitmap);
}
static float sFlipVInit[16] = {
1, 0, 0, 0,
0, -1, 0, 0,
0, 0, 1, 0,
0, 1, 0, 1,
};
static const Matrix4 sFlipV(sFlipVInit);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState,
Texture& sourceTexture, const Matrix4& texTransform, const Rect& srcRect,
SkBitmap* bitmap) {
int destWidth = bitmap->width();
int destHeight = bitmap->height();
if (destWidth > caches.maxTextureSize
|| destHeight > caches.maxTextureSize) {
ALOGW("Can't copy surface into bitmap, %dx%d exceeds max texture size %d",
destWidth, destHeight, caches.maxTextureSize);
return CopyResult::DestinationInvalid;
}
if (bitmap->colorType() == kRGBA_F16_SkColorType && !caches.extensions().hasFloatTextures()) {
ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
return CopyResult::DestinationInvalid;
}
GLuint fbo = renderState.createFramebuffer();
if (!fbo) {
ALOGW("Could not obtain an FBO");
return CopyResult::UnknownError;
}
GLuint texture;
GLenum format;
GLenum internalFormat;
GLenum type;
switch (bitmap->colorType()) {
case kAlpha_8_SkColorType:
format = GL_ALPHA;
internalFormat = GL_ALPHA;
type = GL_UNSIGNED_BYTE;
break;
case kRGB_565_SkColorType:
format = GL_RGB;
internalFormat = GL_RGB;
type = GL_UNSIGNED_SHORT_5_6_5;
break;
case kARGB_4444_SkColorType:
format = GL_RGBA;
internalFormat = GL_RGBA;
type = GL_UNSIGNED_SHORT_4_4_4_4;
break;
case kRGBA_F16_SkColorType:
format = GL_RGBA;
internalFormat = GL_RGBA16F;
type = GL_HALF_FLOAT;
break;
case kN32_SkColorType:
default:
format = GL_RGBA;
internalFormat = GL_RGBA;
type = GL_UNSIGNED_BYTE;
break;
}
renderState.bindFramebuffer(fbo);
// TODO: Use layerPool or something to get this maybe? But since we
// need explicit format control we can't currently.
// Setup the rendertarget
glGenTextures(1, &texture);
caches.textureState().activateTexture(0);
caches.textureState().bindTexture(texture);
glPixelStorei(GL_PACK_ALIGNMENT, bitmap->bytesPerPixel());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, destWidth, destHeight,
0, format, type, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, texture, 0);
{
bool requiresFilter;
// Draw & readback
renderState.setViewport(destWidth, destHeight);
renderState.scissor().setEnabled(false);
renderState.blend().syncEnabled();
renderState.stencil().disable();
Matrix4 croppedTexTransform(texTransform);
if (!srcRect.isEmpty()) {
// We flipV to convert to 0,0 top-left for the srcRect
// coordinates then flip back to 0,0 bottom-left for
// GLES coordinates.
croppedTexTransform.multiply(sFlipV);
croppedTexTransform.translate(srcRect.left / sourceTexture.width(),
srcRect.top / sourceTexture.height(), 0);
croppedTexTransform.scale(srcRect.getWidth() / sourceTexture.width(),
srcRect.getHeight() / sourceTexture.height(), 1);
croppedTexTransform.multiply(sFlipV);
requiresFilter = srcRect.getWidth() != (float) destWidth
|| srcRect.getHeight() != (float) destHeight;
} else {
requiresFilter = sourceTexture.width() != (uint32_t) destWidth
|| sourceTexture.height() != (uint32_t) destHeight;
}
Glop glop;
GlopBuilder(renderState, caches, &glop)
.setRoundRectClipState(nullptr)
.setMeshTexturedUnitQuad(nullptr)
.setFillExternalTexture(sourceTexture, croppedTexTransform, requiresFilter)
.setTransform(Matrix4::identity(), TransformFlags::None)
.setModelViewMapUnitToRect(Rect(destWidth, destHeight))
.build();
Matrix4 ortho;
ortho.loadOrtho(destWidth, destHeight);
renderState.render(glop, ortho, false);
// TODO: We should convert to linear space when the target is RGBA16F
glReadPixels(0, 0, bitmap->width(), bitmap->height(), format,
type, bitmap->getPixels());
bitmap->notifyPixelsChanged();
}
// Cleanup
caches.textureState().deleteTexture(texture);
renderState.deleteFramebuffer(fbo);
GL_CHECKPOINT(MODERATE);
return CopyResult::Success;
}
CopyResult OpenGLReadbackImpl::copyImageInto(EGLImageKHR eglImage,
const Matrix4& imgTransform, int imgWidth, int imgHeight, const Rect& srcRect,
SkBitmap* bitmap) {
// If this is a 90 or 270 degree rotation we need to swap width/height
// This is a fuzzy way of checking that.
if (imgTransform[Matrix4::kSkewX] >= 0.5f || imgTransform[Matrix4::kSkewX] <= -0.5f) {
std::swap(imgWidth, imgHeight);
}
Caches& caches = Caches::getInstance();
GLuint sourceTexId;
// Create a 2D texture to sample from the EGLImage
glGenTextures(1, &sourceTexId);
caches.textureState().bindTexture(GL_TEXTURE_EXTERNAL_OES, sourceTexId);
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, eglImage);
GLenum status = GL_NO_ERROR;
while ((status = glGetError()) != GL_NO_ERROR) {
ALOGW("glEGLImageTargetTexture2DOES failed (%#x)", status);
return CopyResult::UnknownError;
}
Texture sourceTexture(caches);
sourceTexture.wrap(sourceTexId, imgWidth, imgHeight, 0, 0 /* total lie */,
GL_TEXTURE_EXTERNAL_OES);
CopyResult copyResult = copyTextureInto(caches, mRenderThread.renderState(),
sourceTexture, imgTransform, srcRect, bitmap);
sourceTexture.deleteTexture();
return copyResult;
}
bool OpenGLReadbackImpl::copyLayerInto(renderthread::RenderThread& renderThread,
GlLayer& layer, SkBitmap* bitmap) {
return CopyResult::Success == copyTextureInto(Caches::getInstance(),
renderThread.renderState(), layer.getTexture(), layer.getTexTransform(),
Rect(), bitmap);
}
} // namespace uirenderer
} // namespace android