| |
| /* |
| * 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); |
| } |
| } |
| |