blob: 70968e24284e1a1221364525b3395c25d160d8ab [file] [log] [blame]
#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)) {
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());
}
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());
}
void SkGLDevice::drawRect(const SkDraw& draw, const SkRect& rect,
const SkPaint& paint) {
TRACE_DRAW("coreDrawRect", this, draw);
if (paint.getStyle() == SkPaint::kStroke_Style) {
return;
}
if (paint.getStrokeJoin() != SkPaint::kMiter_Join) {
SkPath path;
path.addRect(rect);
this->drawPath(draw, path, paint);
return;
}
AutoPaintShader shader(this, paint);
SkGLVertex vertex[4];
vertex->setRectFan(rect);
const SkGLVertex* texs = shader.useTex() ? vertex : NULL;
SkGL::DrawVertices(4, GL_TRIANGLE_FAN, 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) {
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) {
// 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) {
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) {
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) {
// not supported yet
}