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 26 namespace android { 27 namespace uirenderer { 28 29 static int bytesPerPixel(GLint glFormat) { 30 switch (glFormat) { 31 // The wrapped-texture case, usually means a SurfaceTexture 32 case 0: 33 return 0; 34 case GL_ALPHA: 35 return 1; 36 case GL_RGB: 37 return 3; 38 case GL_RGBA: 39 return 4; 40 case GL_RGBA16F: 41 return 16; 42 default: 43 LOG_ALWAYS_FATAL("UNKNOWN FORMAT %d", glFormat); 44 } 45 } 46 47 void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force, 48 GLenum renderTarget) { 49 50 if (force || wrapS != mWrapS || wrapT != mWrapT) { 51 mWrapS = wrapS; 52 mWrapT = wrapT; 53 54 if (bindTexture) { 55 mCaches.textureState().bindTexture(renderTarget, mId); 56 } 57 58 glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS); 59 glTexParameteri(renderTarget, GL_TEXTURE_WRAP_T, wrapT); 60 } 61 } 62 63 void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool force, 64 GLenum renderTarget) { 65 66 if (force || min != mMinFilter || mag != mMagFilter) { 67 mMinFilter = min; 68 mMagFilter = mag; 69 70 if (bindTexture) { 71 mCaches.textureState().bindTexture(renderTarget, mId); 72 } 73 74 if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR; 75 76 glTexParameteri(renderTarget, GL_TEXTURE_MIN_FILTER, min); 77 glTexParameteri(renderTarget, GL_TEXTURE_MAG_FILTER, mag); 78 } 79 } 80 81 void Texture::deleteTexture() { 82 mCaches.textureState().deleteTexture(mId); 83 mId = 0; 84 } 85 86 bool Texture::updateSize(uint32_t width, uint32_t height, GLint format) { 87 if (mWidth == width && mHeight == height && mFormat == format) { 88 return false; 89 } 90 mWidth = width; 91 mHeight = height; 92 mFormat = format; 93 notifySizeChanged(mWidth * mHeight * bytesPerPixel(mFormat)); 94 return true; 95 } 96 97 void Texture::resetCachedParams() { 98 mWrapS = GL_REPEAT; 99 mWrapT = GL_REPEAT; 100 mMinFilter = GL_NEAREST_MIPMAP_LINEAR; 101 mMagFilter = GL_LINEAR; 102 } 103 104 void Texture::upload(GLint internalformat, uint32_t width, uint32_t height, 105 GLenum format, GLenum type, const void* pixels) { 106 GL_CHECKPOINT(MODERATE); 107 bool needsAlloc = updateSize(width, height, internalformat); 108 if (!mId) { 109 glGenTextures(1, &mId); 110 needsAlloc = true; 111 resetCachedParams(); 112 } 113 mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId); 114 if (needsAlloc) { 115 glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0, 116 format, type, pixels); 117 } else if (pixels) { 118 glTexSubImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0, 119 format, type, pixels); 120 } 121 GL_CHECKPOINT(MODERATE); 122 } 123 124 static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei stride, GLsizei bpp, 125 GLsizei width, GLsizei height, const GLvoid * data) { 126 127 const bool useStride = stride != width 128 && Caches::getInstance().extensions().hasUnpackRowLength(); 129 if ((stride == width) || useStride) { 130 if (useStride) { 131 glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); 132 } 133 134 if (resize) { 135 glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data); 136 } else { 137 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data); 138 } 139 140 if (useStride) { 141 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 142 } 143 } else { 144 // With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer 145 // if the stride doesn't match the width 146 147 GLvoid * temp = (GLvoid *) malloc(width * height * bpp); 148 if (!temp) return; 149 150 uint8_t * pDst = (uint8_t *)temp; 151 uint8_t * pSrc = (uint8_t *)data; 152 for (GLsizei i = 0; i < height; i++) { 153 memcpy(pDst, pSrc, width * bpp); 154 pDst += width * bpp; 155 pSrc += stride * bpp; 156 } 157 158 if (resize) { 159 glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp); 160 } else { 161 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp); 162 } 163 164 free(temp); 165 } 166 } 167 168 static void uploadSkBitmapToTexture(const SkBitmap& bitmap, 169 bool resize, GLenum format, GLenum type) { 170 uploadToTexture(resize, format, type, bitmap.rowBytesAsPixels(), bitmap.bytesPerPixel(), 171 bitmap.width(), bitmap.height(), bitmap.getPixels()); 172 } 173 174 static void colorTypeToGlFormatAndType(SkColorType colorType, 175 GLint* outFormat, GLint* outType) { 176 switch (colorType) { 177 case kAlpha_8_SkColorType: 178 *outFormat = GL_ALPHA; 179 *outType = GL_UNSIGNED_BYTE; 180 break; 181 case kRGB_565_SkColorType: 182 *outFormat = GL_RGB; 183 *outType = GL_UNSIGNED_SHORT_5_6_5; 184 break; 185 // ARGB_4444 and Index_8 are both upconverted to RGBA_8888 186 case kARGB_4444_SkColorType: 187 case kIndex_8_SkColorType: 188 case kN32_SkColorType: 189 *outFormat = GL_RGBA; 190 *outType = GL_UNSIGNED_BYTE; 191 break; 192 case kGray_8_SkColorType: 193 *outFormat = GL_LUMINANCE; 194 *outType = GL_UNSIGNED_BYTE; 195 break; 196 default: 197 LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType); 198 break; 199 } 200 } 201 202 void Texture::upload(const SkBitmap& bitmap) { 203 SkAutoLockPixels alp(bitmap); 204 205 if (!bitmap.readyToDraw()) { 206 ALOGE("Cannot generate texture from bitmap"); 207 return; 208 } 209 210 ATRACE_FORMAT("Upload %ux%u Texture", bitmap.width(), bitmap.height()); 211 212 // We could also enable mipmapping if both bitmap dimensions are powers 213 // of 2 but we'd have to deal with size changes. Let's keep this simple 214 const bool canMipMap = mCaches.extensions().hasNPot(); 215 216 // If the texture had mipmap enabled but not anymore, 217 // force a glTexImage2D to discard the mipmap levels 218 bool needsAlloc = canMipMap && mipMap && !bitmap.hasHardwareMipMap(); 219 bool setDefaultParams = false; 220 221 if (!mId) { 222 glGenTextures(1, &mId); 223 needsAlloc = true; 224 setDefaultParams = true; 225 } 226 227 GLint format, type; 228 colorTypeToGlFormatAndType(bitmap.colorType(), &format, &type); 229 230 if (updateSize(bitmap.width(), bitmap.height(), format)) { 231 needsAlloc = true; 232 } 233 234 blend = !bitmap.isOpaque(); 235 mCaches.textureState().bindTexture(mId); 236 237 if (CC_UNLIKELY(bitmap.colorType() == kARGB_4444_SkColorType 238 || bitmap.colorType() == kIndex_8_SkColorType)) { 239 SkBitmap rgbaBitmap; 240 rgbaBitmap.allocPixels(SkImageInfo::MakeN32(mWidth, mHeight, 241 bitmap.alphaType())); 242 rgbaBitmap.eraseColor(0); 243 244 SkCanvas canvas(rgbaBitmap); 245 canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr); 246 247 uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, format, type); 248 } else { 249 uploadSkBitmapToTexture(bitmap, needsAlloc, format, type); 250 } 251 252 if (canMipMap) { 253 mipMap = bitmap.hasHardwareMipMap(); 254 if (mipMap) { 255 glGenerateMipmap(GL_TEXTURE_2D); 256 } 257 } 258 259 if (setDefaultParams) { 260 setFilter(GL_NEAREST); 261 setWrap(GL_CLAMP_TO_EDGE); 262 } 263 } 264 265 void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint format) { 266 mId = id; 267 mWidth = width; 268 mHeight = height; 269 mFormat = format; 270 // We're wrapping an existing texture, so don't double count this memory 271 notifySizeChanged(0); 272 } 273 274 }; // namespace uirenderer 275 }; // namespace android 276