diff options
author | Stan Iliev <stani@google.com> | 2018-08-30 18:56:41 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2018-08-30 18:56:41 +0000 |
commit | e6cfb09cc4938078d14b1b4abca1678ac8fb170c (patch) | |
tree | c71f82bc74a53be28d30c88ca3b8f84b666578de /libs/hwui/Texture.cpp | |
parent | c72888de5dec8327da3d1a8d328f2c92a629d91e (diff) | |
parent | 867c43de0544217d26c3ee18f4d6603bb2ea97ce (diff) |
Merge "Revert "TextureView Vulkan support and optimized OpenGL draw""
Diffstat (limited to 'libs/hwui/Texture.cpp')
-rw-r--r-- | libs/hwui/Texture.cpp | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp new file mode 100644 index 000000000000..1e90eebe3bb8 --- /dev/null +++ b/libs/hwui/Texture.cpp @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2013 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 "Texture.h" +#include "Caches.h" +#include "utils/GLUtils.h" +#include "utils/MathUtils.h" +#include "utils/TraceUtils.h" + +#include <utils/Log.h> + +#include <math/mat4.h> + +#include <SkCanvas.h> + +namespace android { +namespace uirenderer { + +// Number of bytes used by a texture in the given format +static int bytesPerPixel(GLint glFormat) { + switch (glFormat) { + // The wrapped-texture case, usually means a SurfaceTexture + case 0: + return 0; + case GL_LUMINANCE: + case GL_ALPHA: + return 1; + case GL_SRGB8: + case GL_RGB: + return 3; + case GL_SRGB8_ALPHA8: + case GL_RGBA: + return 4; + case GL_RGBA16F: + return 8; + default: + LOG_ALWAYS_FATAL("UNKNOWN FORMAT 0x%x", glFormat); + } +} + +void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force) { + if (force || wrapS != mWrapS || wrapT != mWrapT) { + mWrapS = wrapS; + mWrapT = wrapT; + + if (bindTexture) { + mCaches.textureState().bindTexture(mTarget, mId); + } + + glTexParameteri(mTarget, GL_TEXTURE_WRAP_S, wrapS); + glTexParameteri(mTarget, GL_TEXTURE_WRAP_T, wrapT); + } +} + +void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool force) { + if (force || min != mMinFilter || mag != mMagFilter) { + mMinFilter = min; + mMagFilter = mag; + + if (bindTexture) { + mCaches.textureState().bindTexture(mTarget, mId); + } + + if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR; + + glTexParameteri(mTarget, GL_TEXTURE_MIN_FILTER, min); + glTexParameteri(mTarget, GL_TEXTURE_MAG_FILTER, mag); + } +} + +void Texture::deleteTexture() { + mCaches.textureState().deleteTexture(mId); + mId = 0; + mTarget = GL_NONE; + if (mEglImageHandle != EGL_NO_IMAGE_KHR) { + EGLDisplay eglDisplayHandle = eglGetCurrentDisplay(); + eglDestroyImageKHR(eglDisplayHandle, mEglImageHandle); + mEglImageHandle = EGL_NO_IMAGE_KHR; + } +} + +bool Texture::updateLayout(uint32_t width, uint32_t height, GLint internalFormat, GLint format, + GLenum target) { + if (mWidth == width && mHeight == height && mFormat == format && + mInternalFormat == internalFormat && mTarget == target) { + return false; + } + mWidth = width; + mHeight = height; + mFormat = format; + mInternalFormat = internalFormat; + mTarget = target; + notifySizeChanged(mWidth * mHeight * bytesPerPixel(internalFormat)); + return true; +} + +void Texture::resetCachedParams() { + mWrapS = GL_REPEAT; + mWrapT = GL_REPEAT; + mMinFilter = GL_NEAREST_MIPMAP_LINEAR; + mMagFilter = GL_LINEAR; +} + +void Texture::upload(GLint internalFormat, uint32_t width, uint32_t height, GLenum format, + GLenum type, const void* pixels) { + GL_CHECKPOINT(MODERATE); + + // We don't have color space information, we assume the data is gamma encoded + mIsLinear = false; + + bool needsAlloc = updateLayout(width, height, internalFormat, format, GL_TEXTURE_2D); + if (!mId) { + glGenTextures(1, &mId); + needsAlloc = true; + resetCachedParams(); + } + mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId); + if (needsAlloc) { + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0, format, type, pixels); + } else if (pixels) { + glTexSubImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0, format, type, pixels); + } + GL_CHECKPOINT(MODERATE); +} + +void Texture::uploadHardwareBitmapToTexture(GraphicBuffer* buffer) { + EGLDisplay eglDisplayHandle = eglGetCurrentDisplay(); + if (mEglImageHandle != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(eglDisplayHandle, mEglImageHandle); + mEglImageHandle = EGL_NO_IMAGE_KHR; + } + mEglImageHandle = eglCreateImageKHR(eglDisplayHandle, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, + buffer->getNativeBuffer(), 0); + glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, mEglImageHandle); +} + +static void uploadToTexture(bool resize, GLint internalFormat, GLenum format, GLenum type, + GLsizei stride, GLsizei bpp, GLsizei width, GLsizei height, + const GLvoid* data) { + const bool useStride = + stride != width && Caches::getInstance().extensions().hasUnpackRowLength(); + if ((stride == width) || useStride) { + if (useStride) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); + } + + if (resize) { + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, data); + } else { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data); + } + + if (useStride) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + } + } else { + // With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer + // if the stride doesn't match the width + + GLvoid* temp = (GLvoid*)malloc(width * height * bpp); + if (!temp) return; + + uint8_t* pDst = (uint8_t*)temp; + uint8_t* pSrc = (uint8_t*)data; + for (GLsizei i = 0; i < height; i++) { + memcpy(pDst, pSrc, width * bpp); + pDst += width * bpp; + pSrc += stride * bpp; + } + + if (resize) { + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, temp); + } else { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp); + } + + free(temp); + } +} + +void Texture::colorTypeToGlFormatAndType(const Caches& caches, SkColorType colorType, bool needSRGB, + GLint* outInternalFormat, GLint* outFormat, + GLint* outType) { + switch (colorType) { + case kAlpha_8_SkColorType: + *outFormat = GL_ALPHA; + *outInternalFormat = GL_ALPHA; + *outType = GL_UNSIGNED_BYTE; + break; + case kRGB_565_SkColorType: + if (needSRGB) { + // We would ideally use a GL_RGB/GL_SRGB8 texture but the + // intermediate Skia bitmap needs to be ARGB_8888 + *outFormat = GL_RGBA; + *outInternalFormat = caches.rgbaInternalFormat(); + *outType = GL_UNSIGNED_BYTE; + } else { + *outFormat = GL_RGB; + *outInternalFormat = GL_RGB; + *outType = GL_UNSIGNED_SHORT_5_6_5; + } + break; + // ARGB_4444 is upconverted to RGBA_8888 + case kARGB_4444_SkColorType: + case kN32_SkColorType: + *outFormat = GL_RGBA; + *outInternalFormat = caches.rgbaInternalFormat(needSRGB); + *outType = GL_UNSIGNED_BYTE; + break; + case kGray_8_SkColorType: + *outFormat = GL_LUMINANCE; + *outInternalFormat = GL_LUMINANCE; + *outType = GL_UNSIGNED_BYTE; + break; + case kRGBA_F16_SkColorType: + if (caches.extensions().getMajorGlVersion() >= 3) { + // This format is always linear + *outFormat = GL_RGBA; + *outInternalFormat = GL_RGBA16F; + *outType = GL_HALF_FLOAT; + } else { + *outFormat = GL_RGBA; + *outInternalFormat = caches.rgbaInternalFormat(true); + *outType = GL_UNSIGNED_BYTE; + } + break; + default: + LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType); + break; + } +} + +SkBitmap Texture::uploadToN32(const SkBitmap& bitmap, bool hasLinearBlending, + sk_sp<SkColorSpace> sRGB) { + SkBitmap rgbaBitmap; + rgbaBitmap.allocPixels(SkImageInfo::MakeN32(bitmap.width(), bitmap.height(), + bitmap.info().alphaType(), + hasLinearBlending ? sRGB : nullptr)); + rgbaBitmap.eraseColor(0); + + if (bitmap.colorType() == kRGBA_F16_SkColorType) { + // Drawing RGBA_F16 onto ARGB_8888 is not supported + bitmap.readPixels(rgbaBitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()), + rgbaBitmap.getPixels(), rgbaBitmap.rowBytes(), 0, 0); + } else { + SkCanvas canvas(rgbaBitmap); + canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr); + } + + return rgbaBitmap; +} + +bool Texture::hasUnsupportedColorType(const SkImageInfo& info, bool hasLinearBlending) { + return info.colorType() == kARGB_4444_SkColorType || + (info.colorType() == kRGB_565_SkColorType && hasLinearBlending && + info.colorSpace()->isSRGB()) || + (info.colorType() == kRGBA_F16_SkColorType && + Caches::getInstance().extensions().getMajorGlVersion() < 3); +} + +void Texture::upload(Bitmap& bitmap) { + ATRACE_FORMAT("Upload %ux%u Texture", bitmap.width(), bitmap.height()); + + // We could also enable mipmapping if both bitmap dimensions are powers + // of 2 but we'd have to deal with size changes. Let's keep this simple + const bool canMipMap = mCaches.extensions().hasNPot(); + + // If the texture had mipmap enabled but not anymore, + // force a glTexImage2D to discard the mipmap levels + bool needsAlloc = canMipMap && mipMap && !bitmap.hasHardwareMipMap(); + bool setDefaultParams = false; + + if (!mId) { + glGenTextures(1, &mId); + needsAlloc = true; + setDefaultParams = true; + } + + bool hasLinearBlending = mCaches.extensions().hasLinearBlending(); + bool needSRGB = transferFunctionCloseToSRGB(bitmap.info().colorSpace()); + + GLint internalFormat, format, type; + colorTypeToGlFormatAndType(mCaches, bitmap.colorType(), needSRGB && hasLinearBlending, + &internalFormat, &format, &type); + + // Some devices don't support GL_RGBA16F, so we need to compare the color type + // and internal GL format to decide what to do with 16 bit bitmaps + bool rgba16fNeedsConversion = + bitmap.colorType() == kRGBA_F16_SkColorType && internalFormat != GL_RGBA16F; + + // RGBA16F is always linear extended sRGB + if (internalFormat == GL_RGBA16F) { + mIsLinear = true; + } + + mConnector.reset(); + + // Alpha masks don't have color profiles + // If an RGBA16F bitmap needs conversion, we know the target will be sRGB + if (!mIsLinear && internalFormat != GL_ALPHA && !rgba16fNeedsConversion) { + SkColorSpace* colorSpace = bitmap.info().colorSpace(); + // If the bitmap is sRGB we don't need conversion + if (colorSpace != nullptr && !colorSpace->isSRGB()) { + SkMatrix44 xyzMatrix(SkMatrix44::kUninitialized_Constructor); + if (!colorSpace->toXYZD50(&xyzMatrix)) { + ALOGW("Incompatible color space!"); + } else { + SkColorSpaceTransferFn fn; + if (!colorSpace->isNumericalTransferFn(&fn)) { + ALOGW("Incompatible color space, no numerical transfer function!"); + } else { + float data[16]; + xyzMatrix.asColMajorf(data); + + ColorSpace::TransferParameters p = {fn.fG, fn.fA, fn.fB, fn.fC, + fn.fD, fn.fE, fn.fF}; + ColorSpace src("Unnamed", mat4f((const float*)&data[0]).upperLeft(), p); + mConnector.reset(new ColorSpaceConnector(src, ColorSpace::sRGB())); + + // A non-sRGB color space might have a transfer function close enough to sRGB + // that we can save shader instructions by using an sRGB sampler + // This is only possible if we have hardware support for sRGB textures + if (needSRGB && internalFormat == GL_RGBA && mCaches.extensions().hasSRGB() && + !bitmap.isHardware()) { + internalFormat = GL_SRGB8_ALPHA8; + } + } + } + } + } + + GLenum target = bitmap.isHardware() ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; + needsAlloc |= updateLayout(bitmap.width(), bitmap.height(), internalFormat, format, target); + + blend = !bitmap.isOpaque(); + mCaches.textureState().bindTexture(mTarget, mId); + + // TODO: Handle sRGB gray bitmaps + if (CC_UNLIKELY(hasUnsupportedColorType(bitmap.info(), hasLinearBlending))) { + SkBitmap skBitmap; + bitmap.getSkBitmap(&skBitmap); + sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeSRGB(); + SkBitmap rgbaBitmap = uploadToN32(skBitmap, hasLinearBlending, std::move(sRGB)); + uploadToTexture(needsAlloc, internalFormat, format, type, rgbaBitmap.rowBytesAsPixels(), + rgbaBitmap.bytesPerPixel(), rgbaBitmap.width(), rgbaBitmap.height(), + rgbaBitmap.getPixels()); + } else if (bitmap.isHardware()) { + uploadHardwareBitmapToTexture(bitmap.graphicBuffer()); + } else { + uploadToTexture(needsAlloc, internalFormat, format, type, bitmap.rowBytesAsPixels(), + bitmap.info().bytesPerPixel(), bitmap.width(), bitmap.height(), + bitmap.pixels()); + } + + if (canMipMap) { + mipMap = bitmap.hasHardwareMipMap(); + if (mipMap) { + glGenerateMipmap(GL_TEXTURE_2D); + } + } + + if (setDefaultParams) { + setFilter(GL_NEAREST); + setWrap(GL_CLAMP_TO_EDGE); + } +} + +void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat, GLint format, + GLenum target) { + mId = id; + mWidth = width; + mHeight = height; + mFormat = format; + mInternalFormat = internalFormat; + mTarget = target; + mConnector.reset(); + // We're wrapping an existing texture, so don't double count this memory + notifySizeChanged(0); +} + +TransferFunctionType Texture::getTransferFunctionType() const { + if (mConnector.get() != nullptr && mInternalFormat != GL_SRGB8_ALPHA8) { + const ColorSpace::TransferParameters& p = mConnector->getSource().getTransferParameters(); + if (MathUtils::isZero(p.e) && MathUtils::isZero(p.f)) { + if (MathUtils::areEqual(p.a, 1.0f) && MathUtils::isZero(p.b) && + MathUtils::isZero(p.c) && MathUtils::isZero(p.d)) { + if (MathUtils::areEqual(p.g, 1.0f)) { + return TransferFunctionType::None; + } + return TransferFunctionType::Gamma; + } + return TransferFunctionType::Limited; + } + return TransferFunctionType::Full; + } + return TransferFunctionType::None; +} + +}; // namespace uirenderer +}; // namespace android |