/* * Copyright 2011 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkGL.h" #include "SkColorPriv.h" #include "SkGeometry.h" #include "SkPaint.h" #include "SkPath.h" #include "SkTemplates.h" #include "SkXfermode.h" //#define TRACE_TEXTURE_CREATION /////////////////////////////////////////////////////////////////////////////// #ifdef SK_GL_HAS_COLOR4UB static inline void gl_pmcolor(U8CPU r, U8CPU g, U8CPU b, U8CPU a) { glColor4ub(r, g, b, a); } void SkGL::SetAlpha(U8CPU alpha) { glColor4ub(alpha, alpha, alpha, alpha); } #else static inline SkFixed byte2fixed(U8CPU value) { return (value + (value >> 7)) << 8; } static inline void gl_pmcolor(U8CPU r, U8CPU g, U8CPU b, U8CPU a) { glColor4x(byte2fixed(r), byte2fixed(g), byte2fixed(b), byte2fixed(a)); } void SkGL::SetAlpha(U8CPU alpha) { SkFixed fa = byte2fixed(alpha); glColor4x(fa, fa, fa, fa); } #endif void SkGL::SetColor(SkColor c) { SkPMColor pm = SkPreMultiplyColor(c); gl_pmcolor(SkGetPackedR32(pm), SkGetPackedG32(pm), SkGetPackedB32(pm), SkGetPackedA32(pm)); } static const GLenum gXfermodeCoeff2Blend[] = { GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, }; void SkGL::SetPaint(const SkPaint& paint, bool isPremul, bool justAlpha) { if (justAlpha) { SkGL::SetAlpha(paint.getAlpha()); } else { SkGL::SetColor(paint.getColor()); } GLenum sm = GL_ONE; GLenum dm = GL_ONE_MINUS_SRC_ALPHA; SkXfermode* mode = paint.getXfermode(); SkXfermode::Coeff sc, dc; if (mode && mode->asCoeff(&sc, &dc)) { sm = gXfermodeCoeff2Blend[sc]; dm = gXfermodeCoeff2Blend[dc]; } // hack for text, which is not-premul (afaik) if (!isPremul) { if (GL_ONE == sm) { sm = GL_SRC_ALPHA; } } glEnable(GL_BLEND); glBlendFunc(sm, dm); if (paint.isDither()) { glEnable(GL_DITHER); } else { glDisable(GL_DITHER); } } /////////////////////////////////////////////////////////////////////////////// void SkGL::DumpError(const char caller[]) { GLenum err = glGetError(); if (err) { SkDebugf("---- glGetError(%s) %d\n", caller, err); } } void SkGL::SetRGBA(uint8_t rgba[], const SkColor src[], int count) { for (int i = 0; i < count; i++) { SkPMColor c = SkPreMultiplyColor(*src++); *rgba++ = SkGetPackedR32(c); *rgba++ = SkGetPackedG32(c); *rgba++ = SkGetPackedB32(c); *rgba++ = SkGetPackedA32(c); } } /////////////////////////////////////////////////////////////////////////////// void SkGL::Scissor(const SkIRect& r, int viewportHeight) { glScissor(r.fLeft, viewportHeight - r.fBottom, r.width(), r.height()); } /////////////////////////////////////////////////////////////////////////////// void SkGL::Ortho(float left, float right, float bottom, float top, float near, float far) { float mat[16]; sk_bzero(mat, sizeof(mat)); mat[0] = 2 / (right - left); mat[5] = 2 / (top - bottom); mat[10] = 2 / (near - far); mat[15] = 1; mat[12] = (right + left) / (left - right); mat[13] = (top + bottom) / (bottom - top); mat[14] = (far + near) / (near - far); glMultMatrixf(mat); } /////////////////////////////////////////////////////////////////////////////// static bool canBeTexture(const SkBitmap& bm, GLenum* format, GLenum* type) { switch (bm.config()) { case SkBitmap::kARGB_8888_Config: *format = GL_RGBA; *type = GL_UNSIGNED_BYTE; break; case SkBitmap::kRGB_565_Config: *format = GL_RGB; *type = GL_UNSIGNED_SHORT_5_6_5; break; case SkBitmap::kARGB_4444_Config: *format = GL_RGBA; *type = GL_UNSIGNED_SHORT_4_4_4_4; break; case SkBitmap::kIndex8_Config: #ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D *format = GL_PALETTE8_RGBA8_OES; *type = GL_UNSIGNED_BYTE; // unused I think #else // we promote index to argb32 *format = GL_RGBA; *type = GL_UNSIGNED_BYTE; #endif break; case SkBitmap::kA8_Config: *format = GL_ALPHA; *type = GL_UNSIGNED_BYTE; break; default: return false; } return true; } #define SK_GL_SIZE_OF_PALETTE (256 * sizeof(SkPMColor)) size_t SkGL::ComputeTextureMemorySize(const SkBitmap& bitmap) { int shift = 0; size_t adder = 0; switch (bitmap.config()) { case SkBitmap::kARGB_8888_Config: case SkBitmap::kRGB_565_Config: case SkBitmap::kARGB_4444_Config: case SkBitmap::kA8_Config: // we're good as is break; case SkBitmap::kIndex8_Config: #ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D // account for the colortable adder = SK_GL_SIZE_OF_PALETTE; #else // we promote index to argb32 shift = 2; #endif break; default: return 0; } return (bitmap.getSize() << shift) + adder; } #ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D /* Fill out buffer with the compressed format GL expects from a colortable based bitmap. [palette (colortable) + indices]. At the moment I always take the 8bit version, since that's what my data is. I could detect that the colortable.count is <= 16, and then repack the indices as nibbles to save RAM, but it would take more time (i.e. a lot slower than memcpy), so I'm skipping that for now. GL wants a full 256 palette entry, even though my ctable is only as big as the colortable.count says it is. I presume it is OK to leave any trailing entries uninitialized, since none of my indices should exceed ctable->count(). */ static void build_compressed_data(void* buffer, const SkBitmap& bitmap) { SkASSERT(SkBitmap::kIndex8_Config == bitmap.config()); SkColorTable* ctable = bitmap.getColorTable(); uint8_t* dst = (uint8_t*)buffer; memcpy(dst, ctable->lockColors(), ctable->count() * sizeof(SkPMColor)); ctable->unlockColors(false); // always skip a full 256 number of entries, even if we memcpy'd fewer dst += SK_GL_SIZE_OF_PALETTE; memcpy(dst, bitmap.getPixels(), bitmap.getSafeSize()); // Just copy what we need. } #endif /* Return true if the bitmap cannot be supported in its current config as a texture, and it needs to be promoted to ARGB32. */ static bool needToPromoteTo32bit(const SkBitmap& bitmap) { if (bitmap.config() == SkBitmap::kIndex8_Config) { #ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D const int w = bitmap.width(); const int h = bitmap.height(); if (SkNextPow2(w) == w && SkNextPow2(h) == h) { // we can handle Indx8 if we're a POW2 return false; } #endif return true; // must promote to ARGB32 } return false; } GLuint SkGL::BindNewTexture(const SkBitmap& origBitmap, SkPoint* max) { SkBitmap tmpBitmap; const SkBitmap* bitmap = &origBitmap; if (needToPromoteTo32bit(origBitmap)) { origBitmap.copyTo(&tmpBitmap, SkBitmap::kARGB_8888_Config); // now bitmap points to our temp, which has been promoted to 32bits bitmap = &tmpBitmap; } GLenum format, type; if (!canBeTexture(*bitmap, &format, &type)) { return 0; } SkAutoLockPixels alp(*bitmap); if (!bitmap->readyToDraw()) { return 0; } GLuint textureName; glGenTextures(1, &textureName); glBindTexture(GL_TEXTURE_2D, textureName); // express rowbytes as a number of pixels for ow int ow = bitmap->rowBytesAsPixels(); int oh = bitmap->height(); int nw = SkNextPow2(ow); int nh = SkNextPow2(oh); glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel()); // check if we need to scale to create power-of-2 dimensions #ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D if (SkBitmap::kIndex8_Config == bitmap->config()) { size_t imagesize = bitmap->getSize() + SK_GL_SIZE_OF_PALETTE; SkAutoMalloc storage(imagesize); build_compressed_data(storage.get(), *bitmap); // we only support POW2 here (GLES 1.0 restriction) SkASSERT(ow == nw); SkASSERT(oh == nh); glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, ow, oh, 0, imagesize, storage.get()); } else // fall through to non-compressed logic #endif { if (ow != nw || oh != nh) { glTexImage2D(GL_TEXTURE_2D, 0, format, nw, nh, 0, format, type, NULL); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ow, oh, format, type, bitmap->getPixels()); } else { // easy case, the bitmap is already pow2 glTexImage2D(GL_TEXTURE_2D, 0, format, ow, oh, 0, format, type, bitmap->getPixels()); } } #ifdef TRACE_TEXTURE_CREATION SkDebugf("--- new texture [%d] size=(%d %d) bpp=%d\n", textureName, ow, oh, bitmap->bytesPerPixel()); #endif if (max) { max->fX = SkFixedToScalar(bitmap->width() << (16 - SkNextLog2(nw))); max->fY = SkFixedToScalar(oh << (16 - SkNextLog2(nh))); } return textureName; } static const GLenum gTileMode2GLWrap[] = { GL_CLAMP_TO_EDGE, GL_REPEAT, #if GL_VERSION_ES_CM_1_0 GL_REPEAT // GLES doesn't support MIRROR #else GL_MIRRORED_REPEAT #endif }; void SkGL::SetTexParams(bool doFilter, SkShader::TileMode tx, SkShader::TileMode ty) { SkASSERT((unsigned)tx < SK_ARRAY_COUNT(gTileMode2GLWrap)); SkASSERT((unsigned)ty < SK_ARRAY_COUNT(gTileMode2GLWrap)); GLenum filter = doFilter ? GL_LINEAR : GL_NEAREST; SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gTileMode2GLWrap[tx]); SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gTileMode2GLWrap[ty]); } void SkGL::SetTexParamsClamp(bool doFilter) { GLenum filter = doFilter ? GL_LINEAR : GL_NEAREST; SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } /////////////////////////////////////////////////////////////////////////////// void SkGL::DrawVertices(int count, GLenum mode, const SkGLVertex* SK_RESTRICT vertex, const SkGLVertex* SK_RESTRICT texCoords, const uint8_t* SK_RESTRICT colorArray, const uint16_t* SK_RESTRICT indexArray, SkGLClipIter* iter) { SkASSERT(NULL != vertex); if (NULL != texCoords) { glEnable(GL_TEXTURE_2D); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, SK_GLType, 0, texCoords); } else { glDisable(GL_TEXTURE_2D); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } if (NULL != colorArray) { glEnableClientState(GL_COLOR_ARRAY); glColorPointer(4, GL_UNSIGNED_BYTE, 0, colorArray); glShadeModel(GL_SMOOTH); } else { glDisableClientState(GL_COLOR_ARRAY); glShadeModel(GL_FLAT); } glVertexPointer(2, SK_GLType, 0, vertex); if (NULL != indexArray) { if (iter) { while (!iter->done()) { iter->scissor(); glDrawElements(mode, count, GL_UNSIGNED_SHORT, indexArray); iter->next(); } } else { glDrawElements(mode, count, GL_UNSIGNED_SHORT, indexArray); } } else { if (iter) { while (!iter->done()) { iter->scissor(); glDrawArrays(mode, 0, count); iter->next(); } } else { glDrawArrays(mode, 0, count); } } } void SkGL::PrepareForFillPath(SkPaint* paint) { if (paint->getStrokeWidth() <= 0) { paint->setStrokeWidth(SK_Scalar1); } } void SkGL::FillPath(const SkPath& path, const SkPaint& paint, bool useTex, SkGLClipIter* iter) { SkPaint p(paint); SkPath fillPath; SkGL::PrepareForFillPath(&p); p.getFillPath(path, &fillPath); SkGL::DrawPath(fillPath, useTex, iter); } // should return max of all contours, rather than the sum (to save temp RAM) static int worst_case_edge_count(const SkPath& path) { int edgeCount = 0; SkPath::Iter iter(path, true); SkPath::Verb verb; while ((verb = iter.next(NULL)) != SkPath::kDone_Verb) { switch (verb) { case SkPath::kLine_Verb: edgeCount += 1; break; case SkPath::kQuad_Verb: edgeCount += 8; break; case SkPath::kCubic_Verb: edgeCount += 16; break; default: break; } } return edgeCount; } void SkGL::DrawPath(const SkPath& path, bool useTex, SkGLClipIter* clipIter) { const SkRect& bounds = path.getBounds(); if (bounds.isEmpty()) { return; } int maxPts = worst_case_edge_count(path); // add 1 for center of fan, and 1 for closing edge SkAutoSTMalloc<32, SkGLVertex> storage(maxPts + 2); SkGLVertex* base = storage.get(); SkGLVertex* vert = base; SkGLVertex* texs = useTex ? base : NULL; SkPath::Iter pathIter(path, true); SkPoint pts[4]; bool needEnd = false; for (;;) { switch (pathIter.next(pts)) { case SkPath::kMove_Verb: if (needEnd) { SkGL::DrawVertices(vert - base, GL_TRIANGLE_FAN, base, texs, NULL, NULL, clipIter); clipIter->safeRewind(); vert = base; } needEnd = true; // center of the FAN vert->setScalars(bounds.centerX(), bounds.centerY()); vert++; // add first edge point vert->setPoint(pts[0]); vert++; break; case SkPath::kLine_Verb: vert->setPoint(pts[1]); vert++; break; case SkPath::kQuad_Verb: { const int n = 8; const SkScalar dt = SK_Scalar1 / n; SkScalar t = dt; for (int i = 1; i < n; i++) { SkPoint loc; SkEvalQuadAt(pts, t, &loc, NULL); t += dt; vert->setPoint(loc); vert++; } vert->setPoint(pts[2]); vert++; break; } case SkPath::kCubic_Verb: { const int n = 16; const SkScalar dt = SK_Scalar1 / n; SkScalar t = dt; for (int i = 1; i < n; i++) { SkPoint loc; SkEvalCubicAt(pts, t, &loc, NULL, NULL); t += dt; vert->setPoint(loc); vert++; } vert->setPoint(pts[3]); vert++; break; } case SkPath::kClose_Verb: break; case SkPath::kDone_Verb: goto FINISHED; } } FINISHED: if (needEnd) { SkGL::DrawVertices(vert - base, GL_TRIANGLE_FAN, base, texs, NULL, NULL, clipIter); } }