| #include "SkGLDevice.h" | 
 | #include "SkGL.h" | 
 | #include "SkDrawProcs.h" | 
 | #include "SkRegion.h" | 
 | #include "SkThread.h" | 
 |  | 
 | #ifdef SK_GL_DEVICE_FBO | 
 |     #define USE_FBO_DEVICE | 
 |     #include "SkGLDevice_FBO.h" | 
 | #else | 
 |     #define USE_SWLAYER_DEVICE | 
 |     #include "SkGLDevice_SWLayer.h" | 
 | #endif | 
 |  | 
 | // maximum number of entries in our texture cache (before purging) | 
 | #define kTexCountMax_Default    256 | 
 | // maximum number of bytes used (by gl) for the texture cache (before purging) | 
 | #define kTexSizeMax_Default     (4 * 1024 * 1024) | 
 |  | 
 | static void TRACE_DRAW(const char func[], SkGLDevice* device, | 
 |                        const SkDraw& draw) { | 
 |     //    SkDebugf("--- <%s> %p %p\n", func, canvas, draw.fDevice); | 
 | } | 
 |  | 
 | struct SkGLDrawProcs : public SkDrawProcs { | 
 | public: | 
 |     void init(const SkRegion* clip, int height) { | 
 |         fCurrQuad = 0; | 
 |         fCurrTexture = 0; | 
 |         fClip = clip; | 
 |         fViewportHeight = height; | 
 |  | 
 |         glEnableClientState(GL_TEXTURE_COORD_ARRAY); | 
 |         glTexCoordPointer(2, SK_TextGLType, 0, fTexs); | 
 |         glDisableClientState(GL_COLOR_ARRAY); | 
 |         glVertexPointer(2, SK_TextGLType, 0, fVerts); | 
 |     } | 
 |  | 
 |     GLenum texture() const { return fCurrTexture; } | 
 |  | 
 |     void flush() { | 
 |         if (fCurrQuad && fCurrTexture) { | 
 |             this->drawQuads(); | 
 |         } | 
 |         fCurrQuad = 0; | 
 |     } | 
 |  | 
 |     void addQuad(GLuint texture, int x, int y, const SkGlyph& glyph, | 
 |                  SkFixed left, SkFixed right, SkFixed bottom) { | 
 |         SkASSERT((size_t)fCurrQuad <= SK_ARRAY_COUNT(fVerts)); | 
 |          | 
 |         if (fCurrTexture != texture || fCurrQuad == SK_ARRAY_COUNT(fVerts)) { | 
 |             if (fCurrQuad && fCurrTexture) { | 
 |                 this->drawQuads(); | 
 |             } | 
 |             fCurrQuad = 0; | 
 |             fCurrTexture = texture; | 
 |         } | 
 |          | 
 |         fVerts[fCurrQuad].setIRectFan(x, y, | 
 |                                       x + glyph.fWidth, y + glyph.fHeight); | 
 |         fTexs[fCurrQuad].setXRectFan(left, 0, right, bottom); | 
 |         fCurrQuad += 4; | 
 |     } | 
 |      | 
 |     void drawQuads(); | 
 |  | 
 | private: | 
 |     enum { | 
 |         MAX_QUADS = 32 | 
 |     }; | 
 |      | 
 |     SkGLTextVertex fVerts[MAX_QUADS * 4]; | 
 |     SkGLTextVertex fTexs[MAX_QUADS * 4]; | 
 |      | 
 |     // these are initialized in setupForText | 
 |     GLuint          fCurrTexture;     | 
 |     int             fCurrQuad; | 
 |     int             fViewportHeight; | 
 |     const SkRegion* fClip; | 
 | }; | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | SkDevice* SkGLDeviceFactory::newDevice(SkBitmap::Config config, int width, | 
 |                                        int height, bool isOpaque, | 
 |                                        bool isForLayer) { | 
 |     SkBitmap bitmap; | 
 |  | 
 |     bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); | 
 |     bitmap.setIsOpaque(isOpaque); | 
 |  | 
 | #ifdef USE_FBO_DEVICE | 
 |     return SkNEW_ARGS(SkGLDevice_FBO, (bitmap, isForLayer)); | 
 | #elif defined(USE_SWLAYER_DEVICE) | 
 |     if (isForLayer) { | 
 |         bitmap.allocPixels(); | 
 |         if (!bitmap.isOpaque()) { | 
 |             bitmap.eraseColor(0); | 
 |         } | 
 |         return SkNEW_ARGS(SkGLDevice_SWLayer, (bitmap)); | 
 |     } else { | 
 |         return SkNEW_ARGS(SkGLDevice, (bitmap, isForLayer)); | 
 |     } | 
 | #else | 
 |     return SkNEW_ARGS(SkGLDevice, (bitmap, isForLayer)); | 
 | #endif | 
 | } | 
 |  | 
 | SkGLDevice::SkGLDevice(const SkBitmap& bitmap, bool offscreen) | 
 |         : SkDevice(bitmap), fClipIter(bitmap.height()) { | 
 |     glEnable(GL_TEXTURE_2D); | 
 |     glEnable(GL_SCISSOR_TEST); | 
 |     glEnableClientState(GL_VERTEX_ARRAY); | 
 |  | 
 |     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); | 
 |  | 
 |     fDrawProcs = NULL; | 
 | } | 
 |  | 
 | SkGLDevice::~SkGLDevice() { | 
 |     if (fDrawProcs) { | 
 |         SkDELETE(fDrawProcs); | 
 |     } | 
 | } | 
 |  | 
 | void SkGLDevice::setMatrixClip(const SkMatrix& matrix, const SkRegion& clip) { | 
 |     this->INHERITED::setMatrixClip(matrix, clip); | 
 |      | 
 |     fGLMatrix.set(matrix); | 
 |     fMatrix = matrix; | 
 |     fClip = clip; | 
 |     fDirty = true; | 
 | } | 
 |  | 
 | SkGLDevice::TexOrientation SkGLDevice::bindDeviceAsTexture() { | 
 |     return kNo_TexOrientation; | 
 | } | 
 |  | 
 | void SkGLDevice::gainFocus(SkCanvas* canvas) { | 
 |     this->INHERITED::gainFocus(canvas); | 
 |  | 
 |     const int w = this->width(); | 
 |     const int h = this->height(); | 
 |     glViewport(0, 0, w, h); | 
 |     glMatrixMode(GL_PROJECTION); | 
 |     glLoadIdentity(); | 
 |     SkGL::Ortho(0, w, h, 0, -1, 1); | 
 |     glMatrixMode(GL_MODELVIEW); | 
 |     fDirty = true;     | 
 | } | 
 |  | 
 | SkGLClipIter* SkGLDevice::updateMatrixClip() { | 
 |     bool useIter = false; | 
 |  | 
 |     // first handle the clip | 
 |     if (fDirty || !fClip.isRect()) { | 
 |         fClipIter.reset(fClip); | 
 |         useIter = true; | 
 |     } else if (fDirty) { | 
 |         // no iter means caller is not respecting complex clips :( | 
 |         SkGL::Scissor(fClip.getBounds(), this->height()); | 
 |     } | 
 |     // else we're just a rect, and we've already call scissor | 
 |  | 
 |     // now handle the matrix | 
 |     if (fDirty) { | 
 |         MAKE_GL(glLoadMatrix)(fGLMatrix.fMat); | 
 | #if 0 | 
 |         SkDebugf("--- gldevice update matrix %p %p\n", this, fFBO); | 
 |         for (int y = 0; y < 4; y++) { | 
 |             SkDebugf(" [ "); | 
 |             for (int x = 0; x < 4; x++) { | 
 |                 SkDebugf("%g ", fGLMatrix.fMat[y*4 + x]); | 
 |             } | 
 |             SkDebugf("]\n"); | 
 |         } | 
 | #endif | 
 |         fDirty = false; | 
 |     } | 
 |  | 
 |     return useIter ? &fClipIter : NULL; | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | // must be in the same order as SkXfermode::Coeff in SkXfermode.h | 
 | SkGLDevice::AutoPaintShader::AutoPaintShader(SkGLDevice* device, | 
 |                                              const SkPaint& paint) { | 
 |     fDevice = device; | 
 |     fTexCache = device->setupGLPaintShader(paint); | 
 | } | 
 |  | 
 | SkGLDevice::AutoPaintShader::~AutoPaintShader() { | 
 |     if (fTexCache) { | 
 |         SkGLDevice::UnlockTexCache(fTexCache); | 
 |     } | 
 | } | 
 |  | 
 | SkGLDevice::TexCache* SkGLDevice::setupGLPaintShader(const SkPaint& paint) { | 
 |     SkGL::SetPaint(paint); | 
 |      | 
 |     SkShader* shader = paint.getShader(); | 
 |     if (NULL == shader) { | 
 |         return NULL; | 
 |     } | 
 |      | 
 |     if (!shader->setContext(this->accessBitmap(false), paint, this->matrix())) { | 
 |         return NULL; | 
 |     } | 
 |      | 
 |     SkBitmap bitmap; | 
 |     SkMatrix matrix; | 
 |     SkShader::TileMode tileModes[2]; | 
 |     if (!shader->asABitmap(&bitmap, &matrix, tileModes)) { | 
 |         SkGL_unimpl("shader->asABitmap() == false"); | 
 |         return NULL; | 
 |     } | 
 |      | 
 |     bitmap.lockPixels(); | 
 |     if (!bitmap.readyToDraw()) { | 
 |         return NULL; | 
 |     } | 
 |      | 
 |     // see if we've already cached the bitmap from the shader | 
 |     SkPoint max; | 
 |     GLuint name; | 
 |     TexCache* cache = SkGLDevice::LockTexCache(bitmap, &name, &max); | 
 |     // the lock has already called glBindTexture for us | 
 |     SkGL::SetTexParams(paint.isFilterBitmap(), tileModes[0], tileModes[1]); | 
 |      | 
 |     // since our texture coords will be in local space, we wack the texture | 
 |     // matrix to map them back into 0...1 before we load it | 
 |     SkMatrix localM; | 
 |     if (shader->getLocalMatrix(&localM)) { | 
 |         SkMatrix inverse; | 
 |         if (localM.invert(&inverse)) { | 
 |             matrix.preConcat(inverse); | 
 |         } | 
 |     } | 
 |      | 
 |     matrix.postScale(max.fX / bitmap.width(), max.fY / bitmap.height()); | 
 |     glMatrixMode(GL_TEXTURE); | 
 |     SkGL::LoadMatrix(matrix); | 
 |     glMatrixMode(GL_MODELVIEW); | 
 |      | 
 |     // since we're going to use a shader/texture, we don't want the color, | 
 |     // just its alpha | 
 |     SkGL::SetAlpha(paint.getAlpha()); | 
 |     // report that we have setup the texture | 
 |     return cache; | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | void SkGLDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) { | 
 |     TRACE_DRAW("coreDrawPaint", this, draw); | 
 |      | 
 |     AutoPaintShader   shader(this, paint); | 
 |     SkGLVertex        vertex[4]; | 
 |     const SkGLVertex* texs = shader.useTex() ? vertex : NULL; | 
 |      | 
 |     // set vert to be big enough to fill the space, but not super-huge, to we | 
 |     // don't overflow fixed-point implementations | 
 |     { | 
 |         SkRect r; | 
 |         r.set(this->clip().getBounds()); | 
 |         SkMatrix inverse; | 
 |         if (draw.fMatrix->invert(&inverse)) { | 
 |             inverse.mapRect(&r); | 
 |         } | 
 |         vertex->setRectFan(r); | 
 |     } | 
 |      | 
 |     SkGL::DrawVertices(4, GL_TRIANGLE_FAN, vertex, texs, NULL, NULL, | 
 |                        this->updateMatrixClip()); | 
 | } | 
 |  | 
 | // must be in SkCanvas::PointMode order | 
 | static const GLenum gPointMode2GL[] = { | 
 |     GL_POINTS, | 
 |     GL_LINES, | 
 |     GL_LINE_STRIP | 
 | }; | 
 |  | 
 | void SkGLDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, | 
 |                             size_t count, const SkPoint pts[], const SkPaint& paint) { | 
 |     TRACE_DRAW("coreDrawPoints", this, draw); | 
 |      | 
 |     SkScalar width = paint.getStrokeWidth(); | 
 |     if (width < 0) { | 
 |         return; | 
 |     } | 
 |      | 
 |     /*  We should really only use drawverts for hairlines, since gl and skia | 
 |      treat the thickness differently... | 
 |      */ | 
 |      | 
 |     AutoPaintShader shader(this, paint); | 
 |      | 
 |     if (width <= 0) { | 
 |         width = SK_Scalar1; | 
 |     } | 
 |      | 
 |     if (SkCanvas::kPoints_PointMode == mode) { | 
 |         glPointSize(SkScalarToFloat(width)); | 
 |     } else { | 
 |         glLineWidth(SkScalarToFloat(width)); | 
 |     } | 
 |      | 
 |     const SkGLVertex* verts; | 
 |      | 
 | #if GLSCALAR_IS_SCALAR | 
 |     verts = (const SkGLVertex*)pts; | 
 | #else | 
 |     SkAutoSTMalloc<32, SkGLVertex> storage(count); | 
 |     SkGLVertex* v = storage.get(); | 
 |      | 
 |     v->setPoints(pts, count); | 
 |     verts = v; | 
 | #endif | 
 |      | 
 |     const SkGLVertex* texs = shader.useTex() ? verts : NULL; | 
 |      | 
 |     SkGL::DrawVertices(count, gPointMode2GL[mode], verts, texs, NULL, NULL, | 
 |                        this->updateMatrixClip()); | 
 | } | 
 |  | 
 | /*  create a triangle strip that strokes the specified triangle. There are 8 | 
 |     unique vertices, but we repreat the last 2 to close up. Alternatively we | 
 |     could use an indices array, and then only send 8 verts, but not sure that | 
 |     would be faster. | 
 |  */ | 
 | static void setStrokeRectStrip(SkGLVertex verts[10], const SkRect& rect, | 
 |                                SkScalar width) { | 
 |     const SkScalar rad = SkScalarHalf(width); | 
 |  | 
 |     verts[0].setScalars(rect.fLeft + rad, rect.fTop + rad); | 
 |     verts[1].setScalars(rect.fLeft - rad, rect.fTop - rad); | 
 |     verts[2].setScalars(rect.fRight - rad, rect.fTop + rad); | 
 |     verts[3].setScalars(rect.fRight + rad, rect.fTop - rad); | 
 |     verts[4].setScalars(rect.fRight - rad, rect.fBottom - rad); | 
 |     verts[5].setScalars(rect.fRight + rad, rect.fBottom + rad); | 
 |     verts[6].setScalars(rect.fLeft + rad, rect.fBottom - rad); | 
 |     verts[7].setScalars(rect.fLeft - rad, rect.fBottom + rad); | 
 |     verts[8] = verts[0]; | 
 |     verts[9] = verts[1]; | 
 | } | 
 |  | 
 | void SkGLDevice::drawRect(const SkDraw& draw, const SkRect& rect, | 
 |                           const SkPaint& paint) { | 
 |     TRACE_DRAW("coreDrawRect", this, draw); | 
 |  | 
 |     bool doStroke = paint.getStyle() == SkPaint::kStroke_Style; | 
 |  | 
 |     if (doStroke) { | 
 |         if (paint.getStrokeJoin() != SkPaint::kMiter_Join) { | 
 |             SkGL_unimpl("non-miter stroke rect"); | 
 |             return; | 
 |         } | 
 |     } else if (paint.getStrokeJoin() != SkPaint::kMiter_Join) { | 
 |         SkPath  path; | 
 |         path.addRect(rect); | 
 |         this->drawPath(draw, path, paint); | 
 |         return; | 
 |     } | 
 |      | 
 |     AutoPaintShader shader(this, paint); | 
 |     SkScalar width = paint.getStrokeWidth(); | 
 |     SkGLVertex vertex[10];   // max needed for all cases | 
 |     int vertCount; | 
 |     GLenum vertMode; | 
 |  | 
 |     if (doStroke) { | 
 |         if (width > 0) { | 
 |             vertCount = 10; | 
 |             vertMode = GL_TRIANGLE_STRIP; | 
 |             setStrokeRectStrip(vertex, rect, width); | 
 |         } else {    // hairline | 
 |             vertCount = 5; | 
 |             vertMode = GL_LINE_STRIP; | 
 |             vertex[0].setScalars(rect.fLeft, rect.fTop); | 
 |             vertex[1].setScalars(rect.fRight, rect.fTop); | 
 |             vertex[2].setScalars(rect.fRight, rect.fBottom); | 
 |             vertex[3].setScalars(rect.fLeft, rect.fBottom); | 
 |             vertex[4].setScalars(rect.fLeft, rect.fTop); | 
 |             glLineWidth(1); | 
 |         } | 
 |     } else { | 
 |         vertCount = 4; | 
 |         vertMode = GL_TRIANGLE_FAN; | 
 |         vertex->setRectFan(rect); | 
 |     } | 
 |  | 
 |     const SkGLVertex* texs = shader.useTex() ? vertex : NULL; | 
 |     SkGL::DrawVertices(vertCount, vertMode, vertex, texs, NULL, NULL, | 
 |                        this->updateMatrixClip()); | 
 | } | 
 |  | 
 | void SkGLDevice::drawPath(const SkDraw& draw, const SkPath& path, | 
 |                           const SkPaint& paint) { | 
 |     TRACE_DRAW("coreDrawPath", this, draw); | 
 |     if (paint.getStyle() == SkPaint::kStroke_Style) { | 
 |         SkGL_unimpl("stroke path"); | 
 |         return; | 
 |     } | 
 |      | 
 |     AutoPaintShader shader(this, paint); | 
 |      | 
 |     SkGL::FillPath(path, paint, shader.useTex(), this->updateMatrixClip()); | 
 | } | 
 |  | 
 | void SkGLDevice::drawBitmap(const SkDraw& draw, const SkBitmap& bitmap, | 
 |                             const SkMatrix& m, const SkPaint& paint) { | 
 |     TRACE_DRAW("coreDrawBitmap", this, draw); | 
 |      | 
 |     SkAutoLockPixels alp(bitmap); | 
 |     if (!bitmap.readyToDraw()) { | 
 |         return; | 
 |     } | 
 |      | 
 |     SkGLClipIter* iter = this->updateMatrixClip(); | 
 |      | 
 |     SkPoint max; | 
 |     GLenum name; | 
 |     SkAutoLockTexCache(bitmap, &name, &max); | 
 |     // the lock has already called glBindTexture for us | 
 |     SkGL::SetTexParamsClamp(paint.isFilterBitmap()); | 
 |      | 
 |     glMatrixMode(GL_TEXTURE); | 
 |     glLoadIdentity(); | 
 |     glMatrixMode(GL_MODELVIEW); | 
 |     glPushMatrix(); | 
 |     SkGL::MultMatrix(m); | 
 |      | 
 |     SkGLVertex  pts[4], tex[4]; | 
 |      | 
 |     pts->setIRectFan(0, 0, bitmap.width(), bitmap.height()); | 
 |     tex->setRectFan(0, 0, max.fX, max.fY); | 
 |      | 
 |     // now draw the mesh | 
 |     SkGL::SetPaintAlpha(paint); | 
 |     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); | 
 |      | 
 |     SkGL::DrawVertices(4, GL_TRIANGLE_FAN, pts, tex, NULL, NULL, iter); | 
 |      | 
 |     glPopMatrix();     | 
 | } | 
 |  | 
 | // move this guy into SkGL, so we can call it from SkGLDevice | 
 | static void gl_drawSprite(int x, int y, int w, int h, const SkPoint& max, | 
 |                           const SkPaint& paint, SkGLClipIter* iter) { | 
 |     SkGL::SetTexParamsClamp(false); | 
 |      | 
 |     glMatrixMode(GL_TEXTURE); | 
 |     glLoadIdentity(); | 
 |     glMatrixMode(GL_MODELVIEW); | 
 |     glPushMatrix(); | 
 |     glLoadIdentity(); | 
 |      | 
 |     SkGLVertex  pts[4], tex[4]; | 
 |      | 
 |     // if h < 0, then the texture is bottom-to-top, but since our projection | 
 |     // matrix always inverts Y, we have to re-invert our texture coord here | 
 |     if (h < 0) { | 
 |         h = -h; | 
 |         tex->setRectFan(0, max.fY, max.fX, 0); | 
 |     } else { | 
 |         tex->setRectFan(0, 0, max.fX, max.fY); | 
 |     } | 
 |     pts->setIRectFan(x, y, x + w, y + h); | 
 |      | 
 |     SkGL::SetPaintAlpha(paint); | 
 |     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); | 
 |      | 
 |     // should look to use glDrawTexi() has we do for text... | 
 |     SkGL::DrawVertices(4, GL_TRIANGLE_FAN, pts, tex, NULL, NULL, iter); | 
 |      | 
 |     glPopMatrix(); | 
 | } | 
 |  | 
 | void SkGLDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap, | 
 |                             int left, int top, const SkPaint& paint) { | 
 |     TRACE_DRAW("coreDrawSprite", this, draw); | 
 |      | 
 |     SkAutoLockPixels alp(bitmap); | 
 |     if (!bitmap.readyToDraw()) { | 
 |         return; | 
 |     } | 
 |      | 
 |     SkGLClipIter* iter = this->updateMatrixClip(); | 
 |      | 
 |     SkPoint max; | 
 |     GLuint name; | 
 |     SkAutoLockTexCache(bitmap, &name, &max);     | 
 |      | 
 |     gl_drawSprite(left, top, bitmap.width(), bitmap.height(), max, paint, iter); | 
 | } | 
 |  | 
 | void SkGLDevice::drawDevice(const SkDraw& draw, SkDevice* dev, | 
 |                             int x, int y, const SkPaint& paint) { | 
 |     TRACE_DRAW("coreDrawDevice", this, draw); | 
 |      | 
 |     SkGLDevice::TexOrientation to = ((SkGLDevice*)dev)->bindDeviceAsTexture(); | 
 |     if (SkGLDevice::kNo_TexOrientation != to) { | 
 |         SkGLClipIter* iter = this->updateMatrixClip(); | 
 |          | 
 |         const SkBitmap& bm = dev->accessBitmap(false); | 
 |         int w = bm.width(); | 
 |         int h = bm.height(); | 
 |         SkPoint max; | 
 |          | 
 |         max.set(SkFixedToScalar(w << (16 - SkNextLog2(bm.rowBytesAsPixels()))), | 
 |                 SkFixedToScalar(h << (16 - SkNextLog2(h)))); | 
 |          | 
 |         if (SkGLDevice::kBottomToTop_TexOrientation == to) { | 
 |             h = -h; | 
 |         } | 
 |         gl_drawSprite(x, y, w, h, max, paint, iter); | 
 |     } | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | static const GLenum gVertexModeToGL[] = { | 
 |     GL_TRIANGLES,       // kTriangles_VertexMode, | 
 |     GL_TRIANGLE_STRIP,  // kTriangleStrip_VertexMode, | 
 |     GL_TRIANGLE_FAN     // kTriangleFan_VertexMode | 
 | }; | 
 |  | 
 | #include "SkShader.h" | 
 |  | 
 | void SkGLDevice::drawVertices(const SkDraw& draw, SkCanvas::VertexMode vmode, | 
 |                               int vertexCount, const SkPoint vertices[], | 
 |                               const SkPoint texs[], const SkColor colors[], | 
 |                               SkXfermode* xmode, | 
 |                               const uint16_t indices[], int indexCount, | 
 |                               const SkPaint& paint) { | 
 |  | 
 |     if (false) { | 
 |         SkRect bounds; | 
 |         SkIRect ibounds; | 
 |          | 
 |         bounds.set(vertices, vertexCount); | 
 |         bounds.round(&ibounds); | 
 |          | 
 |         SkDebugf("---- drawverts: %d pts, texs=%d colors=%d indices=%d bounds [%d %d]\n", | 
 |                  vertexCount, texs!=0, colors!=0, indexCount, ibounds.width(), ibounds.height()); | 
 |     } | 
 |      | 
 |     SkGLClipIter* iter = this->updateMatrixClip(); | 
 |      | 
 |     SkGL::SetPaint(paint); | 
 |      | 
 |     const SkGLVertex* glVerts; | 
 |     const SkGLVertex* glTexs = NULL; | 
 |      | 
 | #if GLSCALAR_IS_SCALAR | 
 |     glVerts = (const SkGLVertex*)vertices; | 
 | #else | 
 |     SkAutoSTMalloc<32, SkGLVertex> storage(vertexCount); | 
 |     storage.get()->setPoints(vertices, vertexCount); | 
 |     glVerts = storage.get(); | 
 | #endif | 
 |      | 
 |     uint8_t* colorArray = NULL; | 
 |     if (colors) { | 
 |         colorArray = (uint8_t*)sk_malloc_throw(vertexCount*4); | 
 |         SkGL::SetRGBA(colorArray, colors, vertexCount); | 
 |     } | 
 |     SkAutoFree afca(colorArray); | 
 |      | 
 |     SkGLVertex* texArray = NULL; | 
 |     TexCache* cache = NULL; | 
 |  | 
 |     if (texs && paint.getShader()) { | 
 |         SkShader* shader = paint.getShader(); | 
 |          | 
 |         //        if (!shader->setContext(this->accessBitmap(), paint, *draw.fMatrix)) { | 
 |         if (!shader->setContext(*draw.fBitmap, paint, *draw.fMatrix)) { | 
 |             goto DONE; | 
 |         } | 
 |          | 
 |         SkBitmap bitmap; | 
 |         SkMatrix matrix; | 
 |         SkShader::TileMode tileModes[2]; | 
 |         if (shader->asABitmap(&bitmap, &matrix, tileModes)) { | 
 |             SkPoint max; | 
 |             GLuint name; | 
 |             cache = SkGLDevice::LockTexCache(bitmap, &name, &max); | 
 |             if (NULL == cache) { | 
 |                 return; | 
 |             } | 
 |  | 
 |             matrix.postScale(max.fX / bitmap.width(), max.fY / bitmap.height()); | 
 |             glMatrixMode(GL_TEXTURE); | 
 |             SkGL::LoadMatrix(matrix); | 
 |             glMatrixMode(GL_MODELVIEW); | 
 |              | 
 | #if GLSCALAR_IS_SCALAR | 
 |             glTexs = (const SkGLVertex*)texs; | 
 | #else | 
 |             texArray = (SkGLVertex*)sk_malloc_throw(vertexCount * sizeof(SkGLVertex)); | 
 |             texArray->setPoints(texs, vertexCount); | 
 |             glTexs = texArray; | 
 | #endif | 
 |              | 
 |             SkGL::SetPaintAlpha(paint); | 
 |             SkGL::SetTexParams(paint.isFilterBitmap(), | 
 |                                tileModes[0], tileModes[1]); | 
 |         } | 
 |     } | 
 | DONE: | 
 |     SkAutoFree aftex(texArray); | 
 |      | 
 |     SkGL::DrawVertices(indices ? indexCount : vertexCount, | 
 |                        gVertexModeToGL[vmode], | 
 |                        glVerts, glTexs, colorArray, indices, iter); | 
 |      | 
 |     if (cache) { | 
 |         SkGLDevice::UnlockTexCache(cache); | 
 |     } | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | #include "SkGlyphCache.h" | 
 | #include "SkGLTextCache.h" | 
 |  | 
 | void SkGLDevice::GlyphCacheAuxProc(void* data) { | 
 |     SkDebugf("-------------- delete text texture cache\n"); | 
 |     SkDELETE((SkGLTextCache*)data); | 
 | } | 
 |  | 
 | #ifdef SK_SCALAR_IS_FIXED | 
 | #define SkDiv16ToScalar(numer, denom)    (SkIntToFixed(numer) / (denom)) | 
 | #else | 
 | #define SkDiv16ToScalar(numer, denom)    SkScalarDiv(numer, denom) | 
 | #endif | 
 |  | 
 | // stolen from SkDraw.cpp - D1G_NoBounder_RectClip | 
 | static void SkGL_Draw1Glyph(const SkDraw1Glyph& state, const SkGlyph& glyph, | 
 |                             int x, int y) { | 
 |     SkASSERT(glyph.fWidth > 0 && glyph.fHeight > 0); | 
 |  | 
 |     SkGLDrawProcs* procs = (SkGLDrawProcs*)state.fDraw->fProcs; | 
 |      | 
 |     x += glyph.fLeft; | 
 |     y += glyph.fTop; | 
 |      | 
 |     // check if we're clipped out (nothing to draw) | 
 | 	SkIRect bounds; | 
 | 	bounds.set(x, y, x + glyph.fWidth, y + glyph.fHeight); | 
 |     if (!SkIRect::Intersects(state.fClip->getBounds(), bounds)) { | 
 |         return; | 
 |     } | 
 |      | 
 |     // now dig up our texture cache | 
 |      | 
 |     SkGlyphCache* gcache = state.fCache; | 
 |     void* auxData; | 
 |     SkGLTextCache* textCache = NULL; | 
 |      | 
 |     if (gcache->getAuxProcData(SkGLDevice::GlyphCacheAuxProc, &auxData)) { | 
 |         textCache = (SkGLTextCache*)auxData;             | 
 |     } | 
 |     if (NULL == textCache) { | 
 |         // need to create one | 
 |         textCache = SkNEW(SkGLTextCache); | 
 |         gcache->setAuxProc(SkGLDevice::GlyphCacheAuxProc, textCache); | 
 |     } | 
 |      | 
 |     int offset; | 
 |     SkGLTextCache::Strike* strike = textCache->findGlyph(glyph, &offset); | 
 |     if (NULL == strike) { | 
 |         // make sure the glyph has an image | 
 |         uint8_t* aa = (uint8_t*)glyph.fImage;                | 
 |         if (NULL == aa) { | 
 |             aa = (uint8_t*)gcache->findImage(glyph); | 
 |             if (NULL == aa) { | 
 |                 return; // can't rasterize glyph | 
 |             } | 
 |         } | 
 |         strike = textCache->addGlyphAndBind(glyph, aa, &offset); | 
 |         if (NULL == strike) { | 
 |             SkGL_unimpl("addGlyphAndBind failed, too big"); | 
 |             // too big to cache, need to draw as is... | 
 |             return; | 
 |         } | 
 |     } | 
 |      | 
 |     const int shiftW = strike->widthShift(); | 
 |     const int shiftH = strike->heightShift(); | 
 |      | 
 |     SkFixed left = offset << (16 - shiftW); | 
 |     SkFixed right = (offset + glyph.fWidth) << (16 - shiftW); | 
 |     SkFixed bottom = glyph.fHeight << (16 - shiftH); | 
 |  | 
 |     procs->addQuad(strike->texture(), x, y, glyph, left, right, bottom); | 
 | } | 
 |  | 
 | #if 1 | 
 | // matches the orientation used in SkGL::setRectFan. Too bad we can't rely on | 
 | // QUADS in android's GL | 
 | static const uint8_t gQuadIndices[] = { | 
 |     0,   1,   2,   0,   2,   3, | 
 |     4,   5,   6,   4,   6,   7, | 
 |     8,   9,  10,   8,  10,  11, | 
 |     12,  13,  14,  12,  14,  15, | 
 |     16,  17,  18,  16,  18,  19, | 
 |     20,  21,  22,  20,  22,  23, | 
 |     24,  25,  26,  24,  26,  27, | 
 |     28,  29,  30,  28,  30,  31, | 
 |     32,  33,  34,  32,  34,  35, | 
 |     36,  37,  38,  36,  38,  39, | 
 |     40,  41,  42,  40,  42,  43, | 
 |     44,  45,  46,  44,  46,  47, | 
 |     48,  49,  50,  48,  50,  51, | 
 |     52,  53,  54,  52,  54,  55, | 
 |     56,  57,  58,  56,  58,  59, | 
 |     60,  61,  62,  60,  62,  63, | 
 |     64,  65,  66,  64,  66,  67, | 
 |     68,  69,  70,  68,  70,  71, | 
 |     72,  73,  74,  72,  74,  75, | 
 |     76,  77,  78,  76,  78,  79, | 
 |     80,  81,  82,  80,  82,  83, | 
 |     84,  85,  86,  84,  86,  87, | 
 |     88,  89,  90,  88,  90,  91, | 
 |     92,  93,  94,  92,  94,  95, | 
 |     96,  97,  98,  96,  98,  99, | 
 |     100, 101, 102, 100, 102, 103, | 
 |     104, 105, 106, 104, 106, 107, | 
 |     108, 109, 110, 108, 110, 111, | 
 |     112, 113, 114, 112, 114, 115, | 
 |     116, 117, 118, 116, 118, 119, | 
 |     120, 121, 122, 120, 122, 123, | 
 |     124, 125, 126, 124, 126, 127 | 
 | }; | 
 | #else | 
 | static void generateQuadIndices(int n) { | 
 |     int index = 0; | 
 |     for (int i = 0; i < n; i++) { | 
 |         SkDebugf("    %3d, %3d, %3d, %3d, %3d, %3d,\n", | 
 |                  index, index + 1, index + 2, index, index + 2, index + 3); | 
 |         index += 4; | 
 |     } | 
 | } | 
 | #endif | 
 |  | 
 | void SkGLDrawProcs::drawQuads() { | 
 |     SkASSERT(SK_ARRAY_COUNT(gQuadIndices) == MAX_QUADS * 6); | 
 |  | 
 |     glBindTexture(GL_TEXTURE_2D, fCurrTexture); | 
 |  | 
 | #if 0 | 
 |     static bool gOnce; | 
 |     if (!gOnce) { | 
 |         generateQuadIndices(MAX_QUADS); | 
 |         gOnce = true; | 
 |     } | 
 | #endif | 
 |  | 
 |     // convert from quad vertex count to triangle vertex count | 
 |     // 6/4 * n == n + (n >> 1) since n is always a multiple of 4 | 
 |     SkASSERT((fCurrQuad & 3) == 0); | 
 |     int count = fCurrQuad + (fCurrQuad >> 1); | 
 |  | 
 |     if (fClip->isComplex()) { | 
 |         SkGLClipIter iter(fViewportHeight); | 
 |         iter.reset(*fClip); | 
 |         while (!iter.done()) { | 
 |             iter.scissor(); | 
 |             glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_BYTE, gQuadIndices); | 
 |             iter.next(); | 
 |         } | 
 |     } else { | 
 |         glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_BYTE, gQuadIndices); | 
 |     } | 
 | } | 
 |  | 
 | void SkGLDevice::setupForText(SkDraw* draw, const SkPaint& paint) { | 
 |     // we handle complex clips in the SkDraw common code, so we don't check | 
 |     // for it here | 
 |     this->updateMatrixClip(); | 
 |      | 
 |     SkGL::SetPaint(paint, false); | 
 |      | 
 |     glMatrixMode(GL_TEXTURE); | 
 |     glLoadIdentity(); | 
 |      | 
 |     glMatrixMode(GL_MODELVIEW); | 
 |     glPushMatrix(); | 
 |     glLoadIdentity(); | 
 |  | 
 |     // deferred allocation | 
 |     if (NULL == fDrawProcs) { | 
 |         fDrawProcs = SkNEW(SkGLDrawProcs); | 
 |         fDrawProcs->fD1GProc = SkGL_Draw1Glyph; | 
 |     } | 
 |  | 
 |     // init our (and GL's) state | 
 |     fDrawProcs->init(draw->fClip, this->height()); | 
 |     // assign to the caller's SkDraw | 
 |     draw->fProcs = fDrawProcs; | 
 |  | 
 |     glEnable(GL_TEXTURE_2D); | 
 |     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); | 
 |     glShadeModel(GL_FLAT);  | 
 | } | 
 |  | 
 | void SkGLDevice::drawText(const SkDraw& draw, const void* text, | 
 |                           size_t byteLength, SkScalar x, SkScalar y, | 
 |                           const SkPaint& paint) { | 
 |     /*  Currently, perspective text is draw via paths, invoked directly by | 
 |      SkDraw. This can't work for us, since the bitmap that our draw points | 
 |      to has no pixels, so we just abort if we're in perspective. | 
 |       | 
 |      Better fix would be to... | 
 |      - have a callback inside draw to handle path drawing | 
 |      - option to have draw call the font cache, which we could patch (?) | 
 |      */ | 
 |     if (draw.fMatrix->getType() & SkMatrix::kPerspective_Mask) { | 
 |         SkGL_unimpl("drawText in perspective"); | 
 |         return; | 
 |     } | 
 |      | 
 |     SkDraw myDraw(draw); | 
 |     this->setupForText(&myDraw, paint); | 
 |     this->INHERITED::drawText(myDraw, text, byteLength, x, y, paint); | 
 |     fDrawProcs->flush(); | 
 |     glPopMatrix();  // GL_MODELVIEW | 
 | } | 
 |  | 
 | void SkGLDevice::drawPosText(const SkDraw& draw, const void* text, | 
 |                              size_t byteLength, const SkScalar pos[], | 
 |                              SkScalar constY, int scalarsPerPos, | 
 |                              const SkPaint& paint) { | 
 |     if (draw.fMatrix->getType() & SkMatrix::kPerspective_Mask) { | 
 |         SkGL_unimpl("drawPosText in perspective"); | 
 |         return; | 
 |     } | 
 |      | 
 |     SkDraw myDraw(draw); | 
 |     this->setupForText(&myDraw, paint); | 
 |     this->INHERITED::drawPosText(myDraw, text, byteLength, pos, constY, | 
 |                                  scalarsPerPos, paint); | 
 |     fDrawProcs->flush(); | 
 |     glPopMatrix();  // GL_MODELVIEW | 
 | } | 
 |  | 
 | void SkGLDevice::drawTextOnPath(const SkDraw& draw, const void* text, | 
 |                                 size_t byteLength, const SkPath& path, | 
 |                                 const SkMatrix* m, const SkPaint& paint) { | 
 |     SkGL_unimpl("drawTextOnPath"); | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | #include "SkTextureCache.h" | 
 | #include "SkThread.h" | 
 |  | 
 | static SkMutex gTextureCacheMutex; | 
 | static SkTextureCache gTextureCache(kTexCountMax_Default, kTexSizeMax_Default); | 
 |  | 
 | SkGLDevice::TexCache* SkGLDevice::LockTexCache(const SkBitmap& bitmap, | 
 |                                                  GLuint* name, SkPoint* size) { | 
 |     SkAutoMutexAcquire amc(gTextureCacheMutex); | 
 |  | 
 |     SkTextureCache::Entry* entry = gTextureCache.lock(bitmap); | 
 |     if (NULL != entry) { | 
 |         if (name) { | 
 |             *name = entry->name(); | 
 |         } | 
 |         if (size) { | 
 |             *size = entry->texSize(); | 
 |         } | 
 |     } | 
 |     return (TexCache*)entry; | 
 | } | 
 |  | 
 | void SkGLDevice::UnlockTexCache(TexCache* cache) { | 
 |     SkAutoMutexAcquire amc(gTextureCacheMutex); | 
 |     gTextureCache.unlock((SkTextureCache::Entry*)cache); | 
 | } | 
 |  | 
 | // public exposure of texture cache settings | 
 |  | 
 | size_t SkGLDevice::GetTextureCacheMaxCount() { | 
 |     SkAutoMutexAcquire amc(gTextureCacheMutex); | 
 |     return gTextureCache.getMaxCount(); | 
 | } | 
 |  | 
 | size_t SkGLDevice::GetTextureCacheMaxSize() { | 
 |     SkAutoMutexAcquire amc(gTextureCacheMutex); | 
 |     return gTextureCache.getMaxSize(); | 
 | } | 
 |  | 
 | void SkGLDevice::SetTextureCacheMaxCount(size_t count) { | 
 |     SkAutoMutexAcquire amc(gTextureCacheMutex); | 
 |     gTextureCache.setMaxCount(count); | 
 | } | 
 |  | 
 | void SkGLDevice::SetTextureCacheMaxSize(size_t size) { | 
 |     SkAutoMutexAcquire amc(gTextureCacheMutex); | 
 |     gTextureCache.setMaxSize(size); | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | #include "SkGLTextCache.h" | 
 |  | 
 | static bool deleteCachesProc(SkGlyphCache* cache, void* texturesAreValid) { | 
 |     void* auxData; | 
 |     if (cache->getAuxProcData(SkGLDevice::GlyphCacheAuxProc, &auxData)) { | 
 |         bool valid = texturesAreValid != NULL; | 
 |         SkGLTextCache* textCache = static_cast<SkGLTextCache*>(auxData); | 
 |         // call this before delete, in case valid is false | 
 |         textCache->deleteAllStrikes(valid); | 
 |         // now free the memory for the cache itself | 
 |         SkDELETE(textCache); | 
 |         // now remove the entry in the glyphcache (does not call the proc) | 
 |         cache->removeAuxProc(SkGLDevice::GlyphCacheAuxProc); | 
 |     } | 
 |     return false;   // keep going | 
 | } | 
 |  | 
 | void SkGLDevice::DeleteAllTextures() { | 
 |     // free the textures in our cache | 
 |  | 
 |     gTextureCacheMutex.acquire(); | 
 |     gTextureCache.deleteAllCaches(true); | 
 |     gTextureCacheMutex.release(); | 
 |  | 
 |     // now free the textures in the font cache | 
 |  | 
 |     SkGlyphCache::VisitAllCaches(deleteCachesProc, reinterpret_cast<void*>(true) | 
 | ); | 
 | } | 
 |  | 
 | void SkGLDevice::AbandonAllTextures() { | 
 |     // abandon the textures in our cache | 
 |  | 
 |     gTextureCacheMutex.acquire(); | 
 |     gTextureCache.deleteAllCaches(false); | 
 |     gTextureCacheMutex.release(); | 
 |  | 
 |     // abandon the textures in the font cache | 
 |  | 
 |     SkGlyphCache::VisitAllCaches(deleteCachesProc, reinterpret_cast<void*>(false | 
 | )); | 
 | } | 
 |  |