#include "SkGLDevice.h" #include "SkGL.h" #include "SkDrawProcs.h" #include "SkRegion.h" #include "SkThread.h" 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; }; /////////////////////////////////////////////////////////////////////////////// SkGLDevice::SkGLDevice(const SkBitmap& bitmap, bool offscreen) : SkDevice(bitmap), fClipIter(bitmap.height()) { 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"); }