Texture.cpp revision ab12c1fe73734a18ac19a06b97f276528f6d027a
1/* 2 * Copyright (C) 2013 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#include "Caches.h" 18#include "Texture.h" 19#include "utils/GLUtils.h" 20#include "utils/TraceUtils.h" 21 22#include <utils/Log.h> 23 24#include <SkCanvas.h> 25 26namespace android { 27namespace uirenderer { 28 29// Number of bytes used by a texture in the given format 30static int bytesPerPixel(GLint glFormat) { 31 switch (glFormat) { 32 // The wrapped-texture case, usually means a SurfaceTexture 33 case 0: 34 return 0; 35 case GL_LUMINANCE: 36 case GL_ALPHA: 37 return 1; 38 case GL_SRGB8: 39 case GL_RGB: 40 return 3; 41 case GL_SRGB8_ALPHA8: 42 case GL_RGBA: 43 return 4; 44 case GL_RGBA16F: 45 return 8; 46 default: 47 LOG_ALWAYS_FATAL("UNKNOWN FORMAT %d", glFormat); 48 } 49} 50 51void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force) { 52 53 if (force || wrapS != mWrapS || wrapT != mWrapT) { 54 mWrapS = wrapS; 55 mWrapT = wrapT; 56 57 if (bindTexture) { 58 mCaches.textureState().bindTexture(mTarget, mId); 59 } 60 61 glTexParameteri(mTarget, GL_TEXTURE_WRAP_S, wrapS); 62 glTexParameteri(mTarget, GL_TEXTURE_WRAP_T, wrapT); 63 } 64} 65 66void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool force) { 67 if (force || min != mMinFilter || mag != mMagFilter) { 68 mMinFilter = min; 69 mMagFilter = mag; 70 71 if (bindTexture) { 72 mCaches.textureState().bindTexture(mTarget, mId); 73 } 74 75 if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR; 76 77 glTexParameteri(mTarget, GL_TEXTURE_MIN_FILTER, min); 78 glTexParameteri(mTarget, GL_TEXTURE_MAG_FILTER, mag); 79 } 80} 81 82void Texture::deleteTexture() { 83 mCaches.textureState().deleteTexture(mId); 84 mId = 0; 85 mTarget = GL_NONE; 86 if (mEglImageHandle != EGL_NO_IMAGE_KHR) { 87 EGLDisplay eglDisplayHandle = eglGetCurrentDisplay(); 88 eglDestroyImageKHR(eglDisplayHandle, mEglImageHandle); 89 mEglImageHandle = EGL_NO_IMAGE_KHR; 90 } 91} 92 93bool Texture::updateSize(uint32_t width, uint32_t height, GLint internalFormat, 94 GLint format, GLenum target) { 95 if (mWidth == width 96 && mHeight == height 97 && mFormat == format 98 && mInternalFormat == internalFormat 99 && mTarget == target) { 100 return false; 101 } 102 mWidth = width; 103 mHeight = height; 104 mFormat = format; 105 mInternalFormat = internalFormat; 106 mTarget = target; 107 notifySizeChanged(mWidth * mHeight * bytesPerPixel(internalFormat)); 108 return true; 109} 110 111void Texture::resetCachedParams() { 112 mWrapS = GL_REPEAT; 113 mWrapT = GL_REPEAT; 114 mMinFilter = GL_NEAREST_MIPMAP_LINEAR; 115 mMagFilter = GL_LINEAR; 116} 117 118void Texture::upload(GLint internalFormat, uint32_t width, uint32_t height, 119 GLenum format, GLenum type, const void* pixels) { 120 GL_CHECKPOINT(MODERATE); 121 bool needsAlloc = updateSize(width, height, internalFormat, format, GL_TEXTURE_2D); 122 if (!mId) { 123 glGenTextures(1, &mId); 124 needsAlloc = true; 125 resetCachedParams(); 126 } 127 mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId); 128 if (needsAlloc) { 129 glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0, 130 format, type, pixels); 131 } else if (pixels) { 132 glTexSubImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0, 133 format, type, pixels); 134 } 135 GL_CHECKPOINT(MODERATE); 136} 137 138void Texture::uploadHardwareBitmapToTexture(GraphicBuffer* buffer) { 139 EGLDisplay eglDisplayHandle = eglGetCurrentDisplay(); 140 if (mEglImageHandle != EGL_NO_IMAGE_KHR) { 141 eglDestroyImageKHR(eglDisplayHandle, mEglImageHandle); 142 mEglImageHandle = EGL_NO_IMAGE_KHR; 143 } 144 mEglImageHandle = eglCreateImageKHR(eglDisplayHandle, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, 145 buffer->getNativeBuffer(), 0); 146 glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, mEglImageHandle); 147} 148 149static void uploadToTexture(bool resize, GLint internalFormat, GLenum format, GLenum type, 150 GLsizei stride, GLsizei bpp, GLsizei width, GLsizei height, const GLvoid * data) { 151 152 const bool useStride = stride != width 153 && Caches::getInstance().extensions().hasUnpackRowLength(); 154 if ((stride == width) || useStride) { 155 if (useStride) { 156 glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); 157 } 158 159 if (resize) { 160 glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, data); 161 } else { 162 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data); 163 } 164 165 if (useStride) { 166 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 167 } 168 } else { 169 // With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer 170 // if the stride doesn't match the width 171 172 GLvoid * temp = (GLvoid *) malloc(width * height * bpp); 173 if (!temp) return; 174 175 uint8_t * pDst = (uint8_t *)temp; 176 uint8_t * pSrc = (uint8_t *)data; 177 for (GLsizei i = 0; i < height; i++) { 178 memcpy(pDst, pSrc, width * bpp); 179 pDst += width * bpp; 180 pSrc += stride * bpp; 181 } 182 183 if (resize) { 184 glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, temp); 185 } else { 186 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp); 187 } 188 189 free(temp); 190 } 191} 192 193void Texture::colorTypeToGlFormatAndType(const Caches& caches, SkColorType colorType, 194 bool needSRGB, GLint* outInternalFormat, GLint* outFormat, GLint* outType) { 195 switch (colorType) { 196 case kAlpha_8_SkColorType: 197 *outFormat = GL_ALPHA; 198 *outInternalFormat = GL_ALPHA; 199 *outType = GL_UNSIGNED_BYTE; 200 break; 201 case kRGB_565_SkColorType: 202 if (needSRGB) { 203 // We would ideally use a GL_RGB/GL_SRGB8 texture but the 204 // intermediate Skia bitmap needs to be ARGB_8888 205 *outFormat = GL_RGBA; 206 *outInternalFormat = caches.rgbaInternalFormat(); 207 *outType = GL_UNSIGNED_BYTE; 208 } else { 209 *outFormat = GL_RGB; 210 *outInternalFormat = GL_RGB; 211 *outType = GL_UNSIGNED_SHORT_5_6_5; 212 } 213 break; 214 // ARGB_4444 and Index_8 are both upconverted to RGBA_8888 215 case kARGB_4444_SkColorType: 216 case kIndex_8_SkColorType: 217 case kN32_SkColorType: 218 *outFormat = GL_RGBA; 219 *outInternalFormat = caches.rgbaInternalFormat(needSRGB); 220 *outType = GL_UNSIGNED_BYTE; 221 break; 222 case kGray_8_SkColorType: 223 // TODO: Handle sRGB 224 *outFormat = GL_LUMINANCE; 225 *outInternalFormat = GL_LUMINANCE; 226 *outType = GL_UNSIGNED_BYTE; 227 break; 228 default: 229 LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType); 230 break; 231 } 232} 233 234SkBitmap Texture::uploadToN32(const SkBitmap& bitmap, bool hasSRGB, sk_sp<SkColorSpace> sRGB) { 235 SkBitmap rgbaBitmap; 236 rgbaBitmap.allocPixels(SkImageInfo::MakeN32(bitmap.width(), bitmap.height(), 237 bitmap.info().alphaType(), hasSRGB ? sRGB : nullptr)); 238 rgbaBitmap.eraseColor(0); 239 SkCanvas canvas(rgbaBitmap); 240 canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr); 241 return rgbaBitmap; 242} 243 244bool Texture::hasUnsupportedColorType(const SkImageInfo& info, bool hasSRGB, SkColorSpace* sRGB) { 245 bool needSRGB = info.colorSpace() == sRGB; 246 return info.colorType() == kARGB_4444_SkColorType 247 || info.colorType() == kIndex_8_SkColorType 248 || (info.colorType() == kRGB_565_SkColorType && hasSRGB && needSRGB); 249} 250 251 252void Texture::upload(Bitmap& bitmap) { 253 if (!bitmap.readyToDraw()) { 254 ALOGE("Cannot generate texture from bitmap"); 255 return; 256 } 257 258 ATRACE_FORMAT("Upload %ux%u Texture", bitmap.width(), bitmap.height()); 259 260 // We could also enable mipmapping if both bitmap dimensions are powers 261 // of 2 but we'd have to deal with size changes. Let's keep this simple 262 const bool canMipMap = mCaches.extensions().hasNPot(); 263 264 // If the texture had mipmap enabled but not anymore, 265 // force a glTexImage2D to discard the mipmap levels 266 bool needsAlloc = canMipMap && mipMap && !bitmap.hasHardwareMipMap(); 267 bool setDefaultParams = false; 268 269 if (!mId) { 270 glGenTextures(1, &mId); 271 needsAlloc = true; 272 setDefaultParams = true; 273 } 274 275 sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named); 276 bool needSRGB = bitmap.info().colorSpace() == sRGB.get(); 277 278 GLint internalFormat, format, type; 279 colorTypeToGlFormatAndType(mCaches, bitmap.colorType(), needSRGB, &internalFormat, &format, &type); 280 281 GLenum target = bitmap.isHardware() ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; 282 needsAlloc |= updateSize(bitmap.width(), bitmap.height(), internalFormat, format, target); 283 284 blend = !bitmap.isOpaque(); 285 mCaches.textureState().bindTexture(mTarget, mId); 286 287 // TODO: Handle sRGB gray bitmaps 288 bool hasSRGB = mCaches.extensions().hasSRGB(); 289 if (CC_UNLIKELY(hasUnsupportedColorType(bitmap.info(), hasSRGB, sRGB.get()))) { 290 SkBitmap skBitmap; 291 bitmap.getSkBitmap(&skBitmap); 292 SkBitmap rgbaBitmap = uploadToN32(skBitmap, hasSRGB, std::move(sRGB)); 293 uploadToTexture(needsAlloc, internalFormat, format, type, rgbaBitmap.rowBytesAsPixels(), 294 rgbaBitmap.bytesPerPixel(), rgbaBitmap.width(), 295 rgbaBitmap.height(), rgbaBitmap.getPixels()); 296 } else if (bitmap.isHardware()) { 297 uploadHardwareBitmapToTexture(bitmap.graphicBuffer()); 298 } else { 299 uploadToTexture(needsAlloc, internalFormat, format, type, bitmap.rowBytesAsPixels(), 300 bitmap.info().bytesPerPixel(), bitmap.width(), bitmap.height(), bitmap.pixels()); 301 } 302 303 if (canMipMap) { 304 mipMap = bitmap.hasHardwareMipMap(); 305 if (mipMap) { 306 glGenerateMipmap(GL_TEXTURE_2D); 307 } 308 } 309 310 if (setDefaultParams) { 311 setFilter(GL_NEAREST); 312 setWrap(GL_CLAMP_TO_EDGE); 313 } 314} 315 316void Texture::wrap(GLuint id, uint32_t width, uint32_t height, 317 GLint internalFormat, GLint format, GLenum target) { 318 mId = id; 319 mWidth = width; 320 mHeight = height; 321 mFormat = format; 322 mInternalFormat = internalFormat; 323 mTarget = target; 324 // We're wrapping an existing texture, so don't double count this memory 325 notifySizeChanged(0); 326} 327 328}; // namespace uirenderer 329}; // namespace android 330