blob: ec819d28b01e152eae8e0797a42661848b827978 [file] [log] [blame]
/*
* 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 "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
));
}