diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-27 00:09:42 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-27 00:09:42 +0000 |
commit | ae2c20f398933a9e86c387dcc465ec0f71065ffc (patch) | |
tree | de668b1411e2ee0b4e49b6d8f8b68183134ac990 /skia/gl | |
parent | 09911bf300f1a419907a9412154760efd0b7abc3 (diff) | |
download | chromium_src-ae2c20f398933a9e86c387dcc465ec0f71065ffc.zip chromium_src-ae2c20f398933a9e86c387dcc465ec0f71065ffc.tar.gz chromium_src-ae2c20f398933a9e86c387dcc465ec0f71065ffc.tar.bz2 |
Add skia to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'skia/gl')
-rw-r--r-- | skia/gl/SkGL.cpp | 453 | ||||
-rw-r--r-- | skia/gl/SkGL.h | 307 | ||||
-rw-r--r-- | skia/gl/SkGLCanvas.cpp | 143 | ||||
-rw-r--r-- | skia/gl/SkGLDevice.cpp | 774 | ||||
-rw-r--r-- | skia/gl/SkGLDevice.h | 121 | ||||
-rw-r--r-- | skia/gl/SkGLDevice_FBO.cpp | 57 | ||||
-rw-r--r-- | skia/gl/SkGLDevice_FBO.h | 23 | ||||
-rw-r--r-- | skia/gl/SkGLDevice_SWLayer.cpp | 91 | ||||
-rw-r--r-- | skia/gl/SkGLDevice_SWLayer.h | 49 | ||||
-rw-r--r-- | skia/gl/SkGLTextCache.cpp | 201 | ||||
-rw-r--r-- | skia/gl/SkGLTextCache.h | 83 | ||||
-rw-r--r-- | skia/gl/SkTextureCache.cpp | 362 | ||||
-rw-r--r-- | skia/gl/SkTextureCache.h | 160 |
13 files changed, 2824 insertions, 0 deletions
diff --git a/skia/gl/SkGL.cpp b/skia/gl/SkGL.cpp new file mode 100644 index 0000000..e4effba --- /dev/null +++ b/skia/gl/SkGL.cpp @@ -0,0 +1,453 @@ +#include "SkGL.h" +#include "SkColorPriv.h" +#include "SkGeometry.h" +#include "SkPaint.h" +#include "SkPath.h" +#include "SkTemplates.h" +#include "SkXfermode.h" + +//#define TRACE_TEXTURE_CREATION + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_GL_HAS_COLOR4UB +static inline void gl_pmcolor(U8CPU r, U8CPU g, U8CPU b, U8CPU a) { + glColor4ub(r, g, b, a); +} + +void SkGL::SetAlpha(U8CPU alpha) { + glColor4ub(alpha, alpha, alpha, alpha); +} +#else +static inline SkFixed byte2fixed(U8CPU value) { + return (value + (value >> 7)) << 8; +} + +static inline void gl_pmcolor(U8CPU r, U8CPU g, U8CPU b, U8CPU a) { + glColor4x(byte2fixed(r), byte2fixed(g), byte2fixed(b), byte2fixed(a)); +} + +void SkGL::SetAlpha(U8CPU alpha) { + SkFixed fa = byte2fixed(alpha); + glColor4x(fa, fa, fa, fa); +} +#endif + +void SkGL::SetColor(SkColor c) { + SkPMColor pm = SkPreMultiplyColor(c); + gl_pmcolor(SkGetPackedR32(pm), + SkGetPackedG32(pm), + SkGetPackedB32(pm), + SkGetPackedA32(pm)); +} + +static const GLenum gXfermodeCoeff2Blend[] = { + GL_ZERO, + GL_ONE, + GL_SRC_COLOR, + GL_ONE_MINUS_SRC_COLOR, + GL_DST_COLOR, + GL_ONE_MINUS_DST_COLOR, + GL_SRC_ALPHA, + GL_ONE_MINUS_SRC_ALPHA, + GL_DST_ALPHA, + GL_ONE_MINUS_DST_ALPHA, +}; + +void SkGL::SetPaint(const SkPaint& paint, bool isPremul, bool justAlpha) { + if (justAlpha) { + SkGL::SetAlpha(paint.getAlpha()); + } else { + SkGL::SetColor(paint.getColor()); + } + + GLenum sm = GL_ONE; + GLenum dm = GL_ONE_MINUS_SRC_ALPHA; + + SkXfermode* mode = paint.getXfermode(); + SkXfermode::Coeff sc, dc; + if (mode && mode->asCoeff(&sc, &dc)) { + sm = gXfermodeCoeff2Blend[sc]; + dm = gXfermodeCoeff2Blend[dc]; + } + + // hack for text, which is not-premul (afaik) + if (!isPremul) { + if (GL_ONE == sm) { + sm = GL_SRC_ALPHA; + } + } + + glEnable(GL_BLEND); + glBlendFunc(sm, dm); + + if (paint.isDither()) { + glEnable(GL_DITHER); + } else { + glDisable(GL_DITHER); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkGL::DumpError(const char caller[]) { + GLenum err = glGetError(); + if (err) { + SkDebugf("---- glGetError(%s) %d\n", caller, err); + } +} + +void SkGL::SetRGBA(uint8_t rgba[], const SkColor src[], int count) { + for (int i = 0; i < count; i++) { + SkPMColor c = SkPreMultiplyColor(*src++); + *rgba++ = SkGetPackedR32(c); + *rgba++ = SkGetPackedG32(c); + *rgba++ = SkGetPackedB32(c); + *rgba++ = SkGetPackedA32(c); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkGL::Scissor(const SkIRect& r, int viewportHeight) { + glScissor(r.fLeft, viewportHeight - r.fBottom, r.width(), r.height()); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkGL::Ortho(float left, float right, float bottom, float top, + float near, float far) { + + float mat[16]; + + bzero(mat, sizeof(mat)); + + mat[0] = 2 / (right - left); + mat[5] = 2 / (top - bottom); + mat[10] = 2 / (near - far); + mat[15] = 1; + + mat[12] = (right + left) / (left - right); + mat[13] = (top + bottom) / (bottom - top); + mat[14] = (far + near) / (near - far); + + glMultMatrixf(mat); +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool canBeTexture(const SkBitmap& bm, GLenum* format, GLenum* type) { + switch (bm.config()) { + case SkBitmap::kARGB_8888_Config: + *format = GL_RGBA; + *type = GL_UNSIGNED_BYTE; + break; + case SkBitmap::kRGB_565_Config: + *format = GL_RGB; + *type = GL_UNSIGNED_SHORT_5_6_5; + break; + case SkBitmap::kARGB_4444_Config: + *format = GL_RGBA; + *type = GL_UNSIGNED_SHORT_4_4_4_4; + break; + case SkBitmap::kIndex8_Config: + // we promote index to argb32 + *format = GL_RGBA; + *type = GL_UNSIGNED_BYTE; + break; + case SkBitmap::kA8_Config: + *format = GL_ALPHA; + *type = GL_UNSIGNED_BYTE; + break; + default: + return false; + } + return true; +} + +size_t SkGL::ComputeTextureMemorySize(const SkBitmap& bitmap) { + int shift = 0; + switch (bitmap.config()) { + case SkBitmap::kARGB_8888_Config: + case SkBitmap::kRGB_565_Config: + case SkBitmap::kARGB_4444_Config: + case SkBitmap::kA8_Config: + // we're good as is + break; + case SkBitmap::kIndex8_Config: + // we promote index to argb32 + shift = 2; + break; + default: + return 0; + } + return bitmap.getSize() << shift; +} + +GLuint SkGL::BindNewTexture(const SkBitmap& origBitmap, SkPoint* max) { + SkBitmap tmpBitmap; + const SkBitmap* bitmap = &origBitmap; + + if (origBitmap.config() == SkBitmap::kIndex8_Config) { + // we promote index to argb32 + origBitmap.copyTo(&tmpBitmap, SkBitmap::kARGB_8888_Config); + bitmap = &tmpBitmap; + } + + GLenum format, type; + if (!canBeTexture(*bitmap, &format, &type)) { + return 0; + } + + SkAutoLockPixels alp(*bitmap); + if (bitmap->getPixels() == NULL) { + return 0; + } + + GLuint textureName; + glGenTextures(1, &textureName); + + glBindTexture(GL_TEXTURE_2D, textureName); + + // express rowbytes as a number of pixels for ow + int ow = bitmap->rowBytesAsPixels(); + int oh = bitmap->height(); + int nw = SkNextPow2(ow); + int nh = SkNextPow2(oh); + + glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel()); + + // check if we need to scale to create power-of-2 dimensions + if (ow != nw || oh != nh) { + glTexImage2D(GL_TEXTURE_2D, 0, format, nw, nh, 0, + format, type, NULL); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ow, oh, + format, type, bitmap->getPixels()); + } else { + // easy case, the bitmap is already pow2 + glTexImage2D(GL_TEXTURE_2D, 0, format, ow, oh, 0, + format, type, bitmap->getPixels()); + } + +#ifdef TRACE_TEXTURE_CREATION + SkDebugf("--- new texture [%d] size=(%d %d) bpp=%d\n", textureName, ow, oh, + bitmap->bytesPerPixel()); +#endif + + if (max) { + max->fX = SkFixedToScalar(bitmap->width() << (16 - SkNextLog2(nw))); + max->fY = SkFixedToScalar(oh << (16 - SkNextLog2(nh))); + } + return textureName; +} + +static const GLenum gTileMode2GLWrap[] = { + GL_CLAMP_TO_EDGE, + GL_REPEAT, +#if GL_VERSION_ES_CM_1_0 + GL_REPEAT // GLES doesn't support MIRROR +#else + GL_MIRRORED_REPEAT +#endif +}; + +void SkGL::SetTexParams(bool doFilter, + SkShader::TileMode tx, SkShader::TileMode ty) { + SkASSERT((unsigned)tx < SK_ARRAY_COUNT(gTileMode2GLWrap)); + SkASSERT((unsigned)ty < SK_ARRAY_COUNT(gTileMode2GLWrap)); + + GLenum filter = doFilter ? GL_LINEAR : GL_NEAREST; + + SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gTileMode2GLWrap[tx]); + SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gTileMode2GLWrap[ty]); +} + +void SkGL::SetTexParamsClamp(bool doFilter) { + GLenum filter = doFilter ? GL_LINEAR : GL_NEAREST; + + SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkGL::DrawVertices(int count, GLenum mode, + const SkGLVertex* SK_RESTRICT vertex, + const SkGLVertex* SK_RESTRICT texCoords, + const uint8_t* SK_RESTRICT colorArray, + const uint16_t* SK_RESTRICT indexArray, + SkGLClipIter* iter) { + SkASSERT(NULL != vertex); + + if (NULL != texCoords) { + glEnable(GL_TEXTURE_2D); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, SK_GLType, 0, texCoords); + } else { + glDisable(GL_TEXTURE_2D); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } + + if (NULL != colorArray) { + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, GL_UNSIGNED_BYTE, 0, colorArray); + glShadeModel(GL_SMOOTH); + } else { + glDisableClientState(GL_COLOR_ARRAY); + glShadeModel(GL_FLAT); + } + + glVertexPointer(2, SK_GLType, 0, vertex); + + if (NULL != indexArray) { + if (iter) { + while (!iter->done()) { + iter->scissor(); + glDrawElements(mode, count, GL_UNSIGNED_SHORT, indexArray); + iter->next(); + } + } else { + glDrawElements(mode, count, GL_UNSIGNED_SHORT, indexArray); + } + } else { + if (iter) { + while (!iter->done()) { + iter->scissor(); + glDrawArrays(mode, 0, count); + iter->next(); + } + } else { + glDrawArrays(mode, 0, count); + } + } +} + +void SkGL::PrepareForFillPath(SkPaint* paint) { + if (paint->getStrokeWidth() <= 0) { + paint->setStrokeWidth(SK_Scalar1); + } +} + +void SkGL::FillPath(const SkPath& path, const SkPaint& paint, bool useTex, + SkGLClipIter* iter) { + SkPaint p(paint); + SkPath fillPath; + + SkGL::PrepareForFillPath(&p); + p.getFillPath(path, &fillPath); + SkGL::DrawPath(fillPath, useTex, iter); +} + +// should return max of all contours, rather than the sum (to save temp RAM) +static int worst_case_edge_count(const SkPath& path) { + int edgeCount = 0; + + SkPath::Iter iter(path, true); + SkPath::Verb verb; + + while ((verb = iter.next(NULL)) != SkPath::kDone_Verb) { + switch (verb) { + case SkPath::kLine_Verb: + edgeCount += 1; + break; + case SkPath::kQuad_Verb: + edgeCount += 8; + break; + case SkPath::kCubic_Verb: + edgeCount += 16; + break; + default: + break; + } + } + return edgeCount; +} + +void SkGL::DrawPath(const SkPath& path, bool useTex, SkGLClipIter* clipIter) { + SkRect bounds; + + path.computeBounds(&bounds, SkPath::kFast_BoundsType); + if (bounds.isEmpty()) { + return; + } + + int maxPts = worst_case_edge_count(path); + // add 1 for center of fan, and 1 for closing edge + SkAutoSTMalloc<32, SkGLVertex> storage(maxPts + 2); + SkGLVertex* base = storage.get(); + SkGLVertex* vert = base; + SkGLVertex* texs = useTex ? base : NULL; + + SkPath::Iter pathIter(path, true); + SkPoint pts[4]; + + bool needEnd = false; + + for (;;) { + switch (pathIter.next(pts)) { + case SkPath::kMove_Verb: + if (needEnd) { + SkGL::DrawVertices(vert - base, GL_TRIANGLE_FAN, + base, texs, NULL, NULL, clipIter); + clipIter->safeRewind(); + vert = base; + } + needEnd = true; + // center of the FAN + vert->setScalars(bounds.centerX(), bounds.centerY()); + vert++; + // add first edge point + vert->setPoint(pts[0]); + vert++; + break; + case SkPath::kLine_Verb: + vert->setPoint(pts[1]); + vert++; + break; + case SkPath::kQuad_Verb: { + const int n = 8; + const SkScalar dt = SK_Scalar1 / n; + SkScalar t = dt; + for (int i = 1; i < n; i++) { + SkPoint loc; + SkEvalQuadAt(pts, t, &loc, NULL); + t += dt; + vert->setPoint(loc); + vert++; + } + vert->setPoint(pts[2]); + vert++; + break; + } + case SkPath::kCubic_Verb: { + const int n = 16; + const SkScalar dt = SK_Scalar1 / n; + SkScalar t = dt; + for (int i = 1; i < n; i++) { + SkPoint loc; + SkEvalCubicAt(pts, t, &loc, NULL, NULL); + t += dt; + vert->setPoint(loc); + vert++; + } + vert->setPoint(pts[3]); + vert++; + break; + } + case SkPath::kClose_Verb: + break; + case SkPath::kDone_Verb: + goto FINISHED; + } + } +FINISHED: + if (needEnd) { + SkGL::DrawVertices(vert - base, GL_TRIANGLE_FAN, base, texs, + NULL, NULL, clipIter); + } +} + diff --git a/skia/gl/SkGL.h b/skia/gl/SkGL.h new file mode 100644 index 0000000..cf3c65e --- /dev/null +++ b/skia/gl/SkGL.h @@ -0,0 +1,307 @@ +#ifndef SkGL_DEFINED +#define SkGL_DEFINED + +#ifdef SK_BUILD_FOR_MAC + #include <OpenGL/gl.h> + #include <OpenGL/glext.h> + #include <AGL/agl.h> + // use FBOs for devices + #define SK_GL_DEVICE_FBO +#elif defined(ANDROID) + #include <GLES/gl.h> + #include <GLES/egl.h> +#endif + +#include "SkColor.h" +#include "SkMatrix.h" +#include "SkShader.h" + +class SkPaint; +class SkPath; + +class SkGLClipIter; + +//#define TRACE_TEXTURE_CREATE + +static inline void* SkGetGLContext() { +#ifdef ANDROID + return (void*)eglGetCurrentContext(); +#else + return NULL; +#endif +} + +/////////////////////////////////////////////////////////////////////////////// + +#if GL_OES_fixed_point && defined(SK_SCALAR_IS_FIXED) + #define SK_GLType GL_FIXED +#else + #define SK_GLType GL_FLOAT +#endif + +#if SK_GLType == GL_FIXED + typedef SkFixed SkGLScalar; + + #define SkIntToGL(n) SkIntToFixed(n) + #define SkScalarToGL(x) SkScalarToFixed(x) + #define SK_GLScalar1 SK_Fixed1 + #define SkGLScalarMul(a, b) SkFixedMul(a, b) + #define MAKE_GL(name) name ## x + + #ifdef SK_SCALAR_IS_FIXED + #define GLSCALAR_IS_SCALAR 1 + #define SkPerspToGL(x) SkFractToFixed(x) + #else + #define GLSCALAR_IS_SCALAR 0 + #define SkPerspToGL(x) SkFractToFloat(x) + #endif +#else + typedef float SkGLScalar; + + #define SkIntToGL(n) (n) + #define SkScalarToGL(x) SkScalarToFloat(x) + #define SK_GLScalar1 (1.f) + #define SkGLScalarMul(a, b) ((a) * (b)) + #define MAKE_GL(name) name ## f + + #ifdef SK_SCALAR_IS_FLOAT + #define GLSCALAR_IS_SCALAR 1 + #define SkPerspToGL(x) (x) + #else + #define GLSCALAR_IS_SCALAR 0 + #define SkPerspToGL(x) SkFractToFloat(x) + #endif +#endif + +#if GL_OES_fixed_point + typedef SkFixed SkGLTextScalar; + #define SK_TextGLType GL_FIXED + + #define SkIntToTextGL(n) SkIntToFixed(n) + #define SkFixedToTextGL(x) (x) + + #define SK_glTexParameteri(target, pname, param) \ + glTexParameterx(target, pname, param) +#else + typedef float SkGLTextScalar; + #define SK_TextGLType SK_GLType + #define SK_GL_HAS_COLOR4UB + + #define SkIntToTextGL(n) SkIntToGL(n) + #define SkFixedToTextGL(x) SkFixedToFloat(x) + + + #define SK_glTexParameteri(target, pname, param) \ + glTexParameteri(target, pname, param) +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// text has its own vertex class, since it may want to be in fixed point (given) +// that it starts with all integers) even when the default vertices are floats +struct SkGLTextVertex { + SkGLTextScalar fX; + SkGLTextScalar fY; + + void setI(int x, int y) { + fX = SkIntToTextGL(x); + fY = SkIntToTextGL(y); + } + + void setX(SkFixed x, SkFixed y) { + fX = SkFixedToTextGL(x); + fY = SkFixedToTextGL(y); + } + + // counter-clockwise fan + void setIRectFan(int l, int t, int r, int b) { + SkGLTextVertex* SK_RESTRICT v = this; + v[0].setI(l, t); + v[1].setI(l, b); + v[2].setI(r, b); + v[3].setI(r, t); + } + + // counter-clockwise fan + void setXRectFan(SkFixed l, SkFixed t, SkFixed r, SkFixed b) { + SkGLTextVertex* SK_RESTRICT v = this; + v[0].setX(l, t); + v[1].setX(l, b); + v[2].setX(r, b); + v[3].setX(r, t); + } +}; + +struct SkGLVertex { + SkGLScalar fX; + SkGLScalar fY; + + void setGL(SkGLScalar x, SkGLScalar y) { + fX = x; + fY = y; + } + + void setScalars(SkScalar x, SkScalar y) { + fX = SkScalarToGL(x); + fY = SkScalarToGL(y); + } + + void setPoint(const SkPoint& pt) { + fX = SkScalarToGL(pt.fX); + fY = SkScalarToGL(pt.fY); + } + + void setPoints(const SkPoint* SK_RESTRICT pts, int count) { + const SkScalar* SK_RESTRICT src = (const SkScalar*)pts; + SkGLScalar* SK_RESTRICT dst = (SkGLScalar*)this; + for (int i = 0; i < count; i++) { + *dst++ = SkScalarToGL(*src++); + *dst++ = SkScalarToGL(*src++); + } + } + + // counter-clockwise fan + void setRectFan(SkScalar l, SkScalar t, SkScalar r, SkScalar b) { + SkGLVertex* v = this; + v[0].setScalars(l, t); + v[1].setScalars(l, b); + v[2].setScalars(r, b); + v[3].setScalars(r, t); + } + + // counter-clockwise fan + void setIRectFan(int l, int t, int r, int b) { + SkGLVertex* v = this; + v[0].setGL(SkIntToGL(l), SkIntToGL(t)); + v[1].setGL(SkIntToGL(l), SkIntToGL(b)); + v[2].setGL(SkIntToGL(r), SkIntToGL(b)); + v[3].setGL(SkIntToGL(r), SkIntToGL(t)); + } + + // counter-clockwise fan + void setRectFan(const SkRect& r) { + this->setRectFan(r.fLeft, r.fTop, r.fRight, r.fBottom); + } + + // counter-clockwise fan + void setIRectFan(const SkIRect& r) { + this->setIRectFan(r.fLeft, r.fTop, r.fRight, r.fBottom); + } +}; + +struct SkGLMatrix { + SkGLScalar fMat[16]; + + void reset() { + bzero(fMat, sizeof(fMat)); + fMat[0] = fMat[5] = fMat[10] = fMat[15] = SK_GLScalar1; + } + + void set(const SkMatrix& m) { + bzero(fMat, sizeof(fMat)); + fMat[0] = SkScalarToGL(m[SkMatrix::kMScaleX]); + fMat[4] = SkScalarToGL(m[SkMatrix::kMSkewX]); + fMat[12] = SkScalarToGL(m[SkMatrix::kMTransX]); + + fMat[1] = SkScalarToGL(m[SkMatrix::kMSkewY]); + fMat[5] = SkScalarToGL(m[SkMatrix::kMScaleY]); + fMat[13] = SkScalarToGL(m[SkMatrix::kMTransY]); + + fMat[3] = SkPerspToGL(m[SkMatrix::kMPersp0]); + fMat[7] = SkPerspToGL(m[SkMatrix::kMPersp1]); + fMat[15] = SkPerspToGL(m[SkMatrix::kMPersp2]); + + fMat[10] = SK_GLScalar1; // z-scale + } +}; + +class SkGL { +public: + static void SetColor(SkColor c); + static void SetAlpha(U8CPU alpha); + static void SetPaint(const SkPaint&, bool isPremul = true, + bool justAlpha = false); + static void SetPaintAlpha(const SkPaint& paint, bool isPremul = true) { + SetPaint(paint, isPremul, true); + } + + static void SetRGBA(uint8_t rgba[], const SkColor src[], int count); + static void DumpError(const char caller[]); + + static void Ortho(float left, float right, float bottom, float top, + float near, float far); + + static inline void Translate(SkScalar dx, SkScalar dy) { + MAKE_GL(glTranslate)(SkScalarToGL(dx), SkScalarToGL(dy), 0); + } + + static inline void Scale(SkScalar sx, SkScalar sy) { + MAKE_GL(glScale)(SkScalarToGL(sx), SkScalarToGL(sy), SK_GLScalar1); + } + + static inline void Rotate(SkScalar angle) { + MAKE_GL(glRotate)(SkScalarToGL(angle), 0, 0, SK_GLScalar1); + } + + static inline void MultMatrix(const SkMatrix& m) { + SkGLMatrix glm; + glm.set(m); + MAKE_GL(glMultMatrix)(glm.fMat); + } + + static inline void LoadMatrix(const SkMatrix& m) { + SkGLMatrix glm; + glm.set(m); + MAKE_GL(glLoadMatrix)(glm.fMat); + } + + static void Scissor(const SkIRect&, int viewportHeight); + + // return the byte size for the associated texture memory. This doesn't + // always == bitmap.getSize(), since on a given port we may have to change + // the format when the bitmap's pixels are copied over to GL + static size_t ComputeTextureMemorySize(const SkBitmap&); + // return 0 on failure + static GLuint BindNewTexture(const SkBitmap&, SkPoint* dimension); + + static void SetTexParams(bool filter, + SkShader::TileMode tx, SkShader::TileMode ty); + static void SetTexParamsClamp(bool filter); + + static void DrawVertices(int count, GLenum mode, + const SkGLVertex* SK_RESTRICT vertex, + const SkGLVertex* SK_RESTRICT texCoords, + const uint8_t* SK_RESTRICT colorArray, + const uint16_t* SK_RESTRICT indexArray, + SkGLClipIter*); + + static void PrepareForFillPath(SkPaint* paint); + static void FillPath(const SkPath& path, const SkPaint& paint, bool useTex, + SkGLClipIter*); + static void DrawPath(const SkPath& path, bool useTex, SkGLClipIter*); +}; + +#include "SkRegion.h" + +class SkGLClipIter : public SkRegion::Iterator { +public: + SkGLClipIter(int viewportHeight) : fViewportHeight(viewportHeight) {} + + // call rewind only if this is non-null + void safeRewind() { + if (this) { + this->rewind(); + } + } + + void scissor() { + SkASSERT(!this->done()); + SkGL::Scissor(this->rect(), fViewportHeight); + } + +private: + const int fViewportHeight; +}; + +#endif + diff --git a/skia/gl/SkGLCanvas.cpp b/skia/gl/SkGLCanvas.cpp new file mode 100644 index 0000000..d9f2201 --- /dev/null +++ b/skia/gl/SkGLCanvas.cpp @@ -0,0 +1,143 @@ +#include "SkGLCanvas.h" +#include "SkGLDevice.h" +#include "SkBlitter.h" +#include "SkDraw.h" +#include "SkDrawProcs.h" +#include "SkGL.h" +#include "SkTemplates.h" +#include "SkUtils.h" +#include "SkXfermode.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) + +/////////////////////////////////////////////////////////////////////////////// + +SkGLCanvas::SkGLCanvas() { + glEnable(GL_TEXTURE_2D); + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_VERTEX_ARRAY); + + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + fViewportSize.set(0, 0); +} + +SkGLCanvas::~SkGLCanvas() { + // call this now, while our override of restore() is in effect + this->restoreToCount(1); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkGLCanvas::getViewport(SkIPoint* size) const { + if (size) { + *size = fViewportSize; + } + return true; +} + +bool SkGLCanvas::setViewport(int width, int height) { + fViewportSize.set(width, height); + + const bool isOpaque = false; // should this be a parameter to setViewport? + const bool isForLayer = false; // viewport is the base layer + SkDevice* device = this->createDevice(SkBitmap::kARGB_8888_Config, width, + height, isOpaque, isForLayer); + this->setDevice(device)->unref(); + + return true; +} + +SkDevice* SkGLCanvas::createDevice(SkBitmap::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 +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "SkTextureCache.h" +#include "SkThread.h" + +static SkMutex gTextureCacheMutex; +static SkTextureCache gTextureCache(kTexCountMax_Default, kTexSizeMax_Default); +static void* gTextureGLContext; + +SkGLDevice::TexCache* SkGLDevice::LockTexCache(const SkBitmap& bitmap, + GLuint* name, SkPoint* size) { + SkAutoMutexAcquire amc(gTextureCacheMutex); + + void* ctx = SkGetGLContext(); + if (gTextureGLContext != ctx) { + gTextureGLContext = ctx; + gTextureCache.zapAllTextures(); + } + + 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 SkGLCanvas::GetTextureCacheMaxCount() { + SkAutoMutexAcquire amc(gTextureCacheMutex); + return gTextureCache.getMaxCount(); +} + +size_t SkGLCanvas::GetTextureCacheMaxSize() { + SkAutoMutexAcquire amc(gTextureCacheMutex); + return gTextureCache.getMaxSize(); +} + +void SkGLCanvas::SetTextureCacheMaxCount(size_t count) { + SkAutoMutexAcquire amc(gTextureCacheMutex); + gTextureCache.setMaxCount(count); +} + +void SkGLCanvas::SetTextureCacheMaxSize(size_t size) { + SkAutoMutexAcquire amc(gTextureCacheMutex); + gTextureCache.setMaxSize(size); +} + diff --git a/skia/gl/SkGLDevice.cpp b/skia/gl/SkGLDevice.cpp new file mode 100644 index 0000000..2271b05 --- /dev/null +++ b/skia/gl/SkGLDevice.cpp @@ -0,0 +1,774 @@ +#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); + + fCtx = SkGetGLContext(); + } + + void* ctx() const { return fCtx; } + 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; + void* fCtx; +}; + +/////////////////////////////////////////////////////////////////////////////// + +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.getPixels() == NULL) { + 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.getPixels() == NULL) { + 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.getPixels() == NULL) { + 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" + +static void SkGL_GlyphCacheAuxProc(void* data) { + SkGLTextCache* cache = (SkGLTextCache*)data; + + SkDebugf("-------------- delete text texture cache, ctx=%p\n", + cache->getCtx()); + 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(SkGL_GlyphCacheAuxProc, &auxData)) { + textCache = (SkGLTextCache*)auxData; + if (textCache->getCtx() != procs->ctx()) { + SkDebugf("------- textcache: old ctx %p new ctx %p\n", + textCache->getCtx(), procs->ctx()); + SkDELETE(textCache); + textCache = NULL; + } + } + if (NULL == textCache) { + // need to create one + textCache = SkNEW(SkGLTextCache); + gcache->setAuxProc(SkGL_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 +} + diff --git a/skia/gl/SkGLDevice.h b/skia/gl/SkGLDevice.h new file mode 100644 index 0000000..9e86f3f --- /dev/null +++ b/skia/gl/SkGLDevice.h @@ -0,0 +1,121 @@ +#ifndef SkGLDevice_DEFINED +#define SkGLDevice_DEFINED + +#include "SkDevice.h" +#include "SkGL.h" +#include "SkRegion.h" + +struct SkGLDrawProcs; + +class SkGLDevice : public SkDevice { +public: + SkGLDevice(const SkBitmap& bitmap, bool offscreen); + virtual ~SkGLDevice(); + + enum TexOrientation { + kNo_TexOrientation, + kTopToBottom_TexOrientation, + kBottomToTop_TexOrientation + }; + + /** Called when this device is no longer a candidate for a render target, + but will instead be used as a texture to be drawn. Be sure to call + the base impl if you override, as it will compute size and max. + */ + virtual TexOrientation bindDeviceAsTexture(); + + // returns true if complex + SkGLClipIter* updateMatrixClip(); + // call to set the clip to the specified rect + void scissor(const SkIRect&); + + // overrides from SkDevice + virtual void gainFocus(SkCanvas*); + virtual void setMatrixClip(const SkMatrix& matrix, const SkRegion& clip); + + virtual void drawPaint(const SkDraw&, const SkPaint& paint); + virtual void drawPoints(const SkDraw&, SkCanvas::PointMode mode, size_t count, + const SkPoint[], const SkPaint& paint); + virtual void drawRect(const SkDraw&, const SkRect& r, + const SkPaint& paint); + virtual void drawPath(const SkDraw&, const SkPath& path, + const SkPaint& paint); + virtual void drawBitmap(const SkDraw&, const SkBitmap& bitmap, + const SkMatrix& matrix, const SkPaint& paint); + virtual void drawSprite(const SkDraw&, const SkBitmap& bitmap, + int x, int y, const SkPaint& paint); + virtual void drawText(const SkDraw&, const void* text, size_t len, + SkScalar x, SkScalar y, const SkPaint& paint); + virtual void drawPosText(const SkDraw&, const void* text, size_t len, + const SkScalar pos[], SkScalar constY, + int scalarsPerPos, const SkPaint& paint); + virtual void drawTextOnPath(const SkDraw&, const void* text, size_t len, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint); + virtual void drawVertices(const SkDraw&, SkCanvas::VertexMode, int vertexCount, + const SkPoint verts[], const SkPoint texs[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint); + virtual void drawDevice(const SkDraw&, SkDevice*, int x, int y, + const SkPaint&); + +protected: + /** Return the current glmatrix, from a previous call to setMatrixClip */ + const SkMatrix& matrix() const { return fMatrix; } + /** Return the current clip, from a previous call to setMatrixClip */ + const SkRegion& clip() const { return fClip; } + +private: + SkGLMatrix fGLMatrix; + SkMatrix fMatrix; + SkRegion fClip; + bool fDirty; + + SkGLClipIter fClipIter; + SkGLDrawProcs* fDrawProcs; + + void setupForText(SkDraw* draw, const SkPaint& paint); + + // global texture cache methods + class TexCache; + static TexCache* LockTexCache(const SkBitmap&, GLuint* name, + SkPoint* size); + static void UnlockTexCache(TexCache*); + class SkAutoLockTexCache { + public: + SkAutoLockTexCache(const SkBitmap& bitmap, GLuint* name, + SkPoint* size) { + fTex = SkGLDevice::LockTexCache(bitmap, name, size); + } + ~SkAutoLockTexCache() { + if (fTex) { + SkGLDevice::UnlockTexCache(fTex); + } + } + TexCache* get() const { return fTex; } + private: + TexCache* fTex; + }; + friend class SkAutoTexCache; + + // returns cache if the texture is bound for the shader + TexCache* setupGLPaintShader(const SkPaint& paint); + + class AutoPaintShader { + public: + AutoPaintShader(SkGLDevice*, const SkPaint& paint); + ~AutoPaintShader(); + + bool useTex() const { return fTexCache != 0; } + private: + SkGLDevice* fDevice; + TexCache* fTexCache; + }; + friend class AutoPaintShader; + + typedef SkDevice INHERITED; +}; + +#endif + diff --git a/skia/gl/SkGLDevice_FBO.cpp b/skia/gl/SkGLDevice_FBO.cpp new file mode 100644 index 0000000..552d619 --- /dev/null +++ b/skia/gl/SkGLDevice_FBO.cpp @@ -0,0 +1,57 @@ +#include "SkGLDevice_FBO.h" +#include "SkRegion.h" + +SkGLDevice_FBO::SkGLDevice_FBO(const SkBitmap& bitmap, bool offscreen) + : SkGLDevice(bitmap, offscreen) { + fFBO = 0; + fTextureID = 0; + + if (offscreen) { + int nw = SkNextPow2(bitmap.rowBytesAsPixels()); + int nh = SkNextPow2(bitmap.height()); + + glGenFramebuffersEXT(1, &fFBO); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fFBO); + + glGenTextures(1, &fTextureID); + glBindTexture(GL_TEXTURE_2D, fTextureID); + SkGL::SetTexParamsClamp(false); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nw, nh, 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, fTextureID, 0); + GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { + SkDebugf("-- glCheckFramebufferStatusEXT %x\n", status); + } + + // now reset back to "normal" drawing target + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + } +} + +SkGLDevice_FBO::~SkGLDevice_FBO() { + if (fTextureID) { + glDeleteTextures(1, &fTextureID); + } + if (fFBO) { + glDeleteFramebuffersEXT(1, &fFBO); + } +} + +SkGLDevice::TexOrientation SkGLDevice_FBO::bindDeviceAsTexture() { + if (fTextureID) { + glBindTexture(GL_TEXTURE_2D, fTextureID); + return kBottomToTop_TexOrientation; + } + return kNo_TexOrientation; +} + +void SkGLDevice_FBO::gainFocus(SkCanvas* canvas) { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fFBO); + + // now we're ready for the viewport and projection matrix + this->INHERITED::gainFocus(canvas); +} + diff --git a/skia/gl/SkGLDevice_FBO.h b/skia/gl/SkGLDevice_FBO.h new file mode 100644 index 0000000..d695ff0 --- /dev/null +++ b/skia/gl/SkGLDevice_FBO.h @@ -0,0 +1,23 @@ +#ifndef SkGLDevice_FBO_DEFINED +#define SkGLDevice_FBO_DEFINED + +#include "SkGLDevice.h" + +class SkGLDevice_FBO : public SkGLDevice { +public: + SkGLDevice_FBO(const SkBitmap& bitmap, bool offscreen); + virtual ~SkGLDevice_FBO(); + + // overrides from SkGLDevice + virtual void gainFocus(SkCanvas*); + virtual TexOrientation bindDeviceAsTexture(); + +private: + GLuint fFBO; + GLuint fTextureID; + + typedef SkGLDevice INHERITED; +}; + +#endif + diff --git a/skia/gl/SkGLDevice_SWLayer.cpp b/skia/gl/SkGLDevice_SWLayer.cpp new file mode 100644 index 0000000..4b75d4c --- /dev/null +++ b/skia/gl/SkGLDevice_SWLayer.cpp @@ -0,0 +1,91 @@ +#include "SkGLDevice_SWLayer.h" +#include "SkRegion.h" + +SkGLDevice_SWLayer::SkGLDevice_SWLayer(const SkBitmap& bitmap) + : SkGLDevice(bitmap, true) { + fTextureID = 0; + + SkASSERT(bitmap.getPixels()); +} + +SkGLDevice_SWLayer::~SkGLDevice_SWLayer() { + if (fTextureID) { + glDeleteTextures(1, &fTextureID); + } +} + +SkGLDevice::TexOrientation SkGLDevice_SWLayer::bindDeviceAsTexture() { + const SkBitmap& bitmap = this->accessBitmap(false); + + if (0 == fTextureID) { + fTextureID = SkGL::BindNewTexture(bitmap, NULL); + } + return kTopToBottom_TexOrientation; +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "SkDraw.h" + +void SkGLDevice_SWLayer::drawPaint(const SkDraw& draw, const SkPaint& paint) { + draw.drawPaint(paint); +} + +void SkGLDevice_SWLayer::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, size_t count, + const SkPoint pts[], const SkPaint& paint) { + draw.drawPoints(mode, count, pts, paint); +} + +void SkGLDevice_SWLayer::drawRect(const SkDraw& draw, const SkRect& r, + const SkPaint& paint) { + draw.drawRect(r, paint); +} + +void SkGLDevice_SWLayer::drawPath(const SkDraw& draw, const SkPath& path, + const SkPaint& paint) { + draw.drawPath(path, paint); +} + +void SkGLDevice_SWLayer::drawBitmap(const SkDraw& draw, const SkBitmap& bitmap, + const SkMatrix& matrix, const SkPaint& paint) { + draw.drawBitmap(bitmap, matrix, paint); +} + +void SkGLDevice_SWLayer::drawSprite(const SkDraw& draw, const SkBitmap& bitmap, + int x, int y, const SkPaint& paint) { + draw.drawSprite(bitmap, x, y, paint); +} + +void SkGLDevice_SWLayer::drawText(const SkDraw& draw, const void* text, size_t len, + SkScalar x, SkScalar y, const SkPaint& paint) { + draw.drawText((const char*)text, len, x, y, paint); +} + +void SkGLDevice_SWLayer::drawPosText(const SkDraw& draw, const void* text, size_t len, + const SkScalar xpos[], SkScalar y, + int scalarsPerPos, const SkPaint& paint) { + draw.drawPosText((const char*)text, len, xpos, y, scalarsPerPos, paint); +} + +void SkGLDevice_SWLayer::drawTextOnPath(const SkDraw& draw, const void* text, + size_t len, const SkPath& path, + const SkMatrix* matrix, + const SkPaint& paint) { + draw.drawTextOnPath((const char*)text, len, path, matrix, paint); +} + +void SkGLDevice_SWLayer::drawVertices(const SkDraw& draw, SkCanvas::VertexMode vmode, + int vertexCount, + const SkPoint verts[], const SkPoint textures[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint) { + draw.drawVertices(vmode, vertexCount, verts, textures, colors, xmode, + indices, indexCount, paint); +} + +void SkGLDevice_SWLayer::drawDevice(const SkDraw& draw, SkDevice* dev, + int x, int y, const SkPaint& paint) { + this->SkDevice::drawDevice(draw, dev, x, y, paint); +} + diff --git a/skia/gl/SkGLDevice_SWLayer.h b/skia/gl/SkGLDevice_SWLayer.h new file mode 100644 index 0000000..7e61370 --- /dev/null +++ b/skia/gl/SkGLDevice_SWLayer.h @@ -0,0 +1,49 @@ +#ifndef SkGLDevice_SWLayer_DEFINED +#define SkGLDevice_SWLayer_DEFINED + +#include "SkGLDevice.h" + +class SkGLDevice_SWLayer : public SkGLDevice { +public: + SkGLDevice_SWLayer(const SkBitmap& bitmap); + virtual ~SkGLDevice_SWLayer(); + + // overrides from SkGLDevice + virtual TexOrientation bindDeviceAsTexture(); + + // overrides from SkDevice + virtual void drawPaint(const SkDraw&, const SkPaint& paint); + virtual void drawPoints(const SkDraw&, SkCanvas::PointMode mode, size_t count, + const SkPoint[], const SkPaint& paint); + virtual void drawRect(const SkDraw&, const SkRect& r, + const SkPaint& paint); + virtual void drawPath(const SkDraw&, const SkPath& path, + const SkPaint& paint); + virtual void drawBitmap(const SkDraw&, const SkBitmap& bitmap, + const SkMatrix& matrix, const SkPaint& paint); + virtual void drawSprite(const SkDraw&, const SkBitmap& bitmap, + int x, int y, const SkPaint& paint); + virtual void drawText(const SkDraw&, const void* text, size_t len, + SkScalar x, SkScalar y, const SkPaint& paint); + virtual void drawPosText(const SkDraw&, const void* text, size_t len, + const SkScalar pos[], SkScalar constY, + int scalarsPerPos, const SkPaint& paint); + virtual void drawTextOnPath(const SkDraw&, const void* text, size_t len, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint); + virtual void drawVertices(const SkDraw&, SkCanvas::VertexMode, int vertexCount, + const SkPoint verts[], const SkPoint texs[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint); + virtual void drawDevice(const SkDraw&, SkDevice*, int x, int y, + const SkPaint&); + +private: + GLuint fTextureID; + + typedef SkGLDevice INHERITED; +}; + +#endif + diff --git a/skia/gl/SkGLTextCache.cpp b/skia/gl/SkGLTextCache.cpp new file mode 100644 index 0000000..25f822e --- /dev/null +++ b/skia/gl/SkGLTextCache.cpp @@ -0,0 +1,201 @@ +#include "SkGLTextCache.h" +#include "SkScalerContext.h" +#include "SkTSearch.h" + +const GLenum gTextTextureFormat = GL_ALPHA; +const GLenum gTextTextureType = GL_UNSIGNED_BYTE; + +SkGLTextCache::Strike::Strike(Strike* next, int width, int height) { + fStrikeWidth = SkNextPow2(SkMax32(kMinStrikeWidth, width)); + fStrikeHeight = SkNextPow2(height); + fGlyphCount = 0; + fNextFreeOffsetX = 0; + fNext = next; + + fStrikeWidthShift = SkNextLog2(fStrikeWidth); + fStrikeHeightShift = SkNextLog2(fStrikeHeight); + + if (next) { + SkASSERT(next->fStrikeHeight == fStrikeHeight); + } + + // create an empty texture to receive glyphs + fTexName = 0; + glGenTextures(1, &fTexName); + glBindTexture(GL_TEXTURE_2D, fTexName); + glTexImage2D(GL_TEXTURE_2D, 0, gTextTextureFormat, + fStrikeWidth, fStrikeHeight, 0, + gTextTextureFormat, gTextTextureType, NULL); + + SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +SkGLTextCache::Strike::~Strike() { + if (fTexName != 0) { + glDeleteTextures(1, &fTexName); + } +} + +SkGLTextCache::Strike* +SkGLTextCache::Strike::findGlyph(const SkGlyph& glyph, int* offset) { + Strike* strike = this; + SkDEBUGCODE(const int height = SkNextPow2(glyph.fHeight);) + + do { + SkASSERT(height == strike->fStrikeHeight); + + int index = SkTSearch(strike->fGlyphIDArray, strike->fGlyphCount, + glyph.fID, sizeof(strike->fGlyphIDArray[0])); + if (index >= 0) { + if (offset) { + *offset = strike->fGlyphOffsetX[index]; + } + return strike; + } + strike = strike->fNext; + } while (NULL != strike); + return NULL; +} + +static void make_a_whole(void* buffer, int index, int count, size_t elemSize) { + SkASSERT(index >= 0 && index <= count); + size_t offset = index * elemSize; + memmove((char*)buffer + offset + elemSize, + (const char*)buffer + offset, + (count - index) * elemSize); +} + +SkGLTextCache::Strike* +SkGLTextCache::Strike::addGlyphAndBind(const SkGlyph& glyph, + const uint8_t image[], int* offset) { +#ifdef SK_DEBUG + SkASSERT(this->findGlyph(glyph, NULL) == NULL); + const int height = SkNextPow2(glyph.fHeight); + SkASSERT(height <= fStrikeHeight && height > (fStrikeHeight >> 1)); +#endif + + int rowBytes = glyph.rowBytes(); + SkASSERT(rowBytes >= glyph.fWidth); + + Strike* strike; + if (fGlyphCount == kMaxGlyphCount || + fNextFreeOffsetX + rowBytes >= fStrikeWidth) { + // this will bind the next texture for us + SkDebugf("--- extend strike %p\n", this); + strike = SkNEW_ARGS(Strike, (this, rowBytes, glyph.fHeight)); + } else { + glBindTexture(GL_TEXTURE_2D, fTexName); + strike = this; + } + + uint32_t* idArray = strike->fGlyphIDArray; + uint16_t* offsetArray = strike->fGlyphOffsetX; + const int glyphCount = strike->fGlyphCount; + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexSubImage2D(GL_TEXTURE_2D, 0, strike->fNextFreeOffsetX, 0, rowBytes, + glyph.fHeight, gTextTextureFormat, gTextTextureType, + image); + + // need to insert the offset + int index = SkTSearch(idArray, glyphCount, glyph.fID, sizeof(idArray[0])); + SkASSERT(index < 0); + index = ~index; // this is where we should insert it + make_a_whole(idArray, index, glyphCount, sizeof(idArray)); + make_a_whole(offsetArray, index, glyphCount, sizeof(offsetArray[0])); + idArray[index] = glyph.fID; + offsetArray[index] = strike->fNextFreeOffsetX; + if (offset) { + *offset = strike->fNextFreeOffsetX; + } + +#if 0 + SkDebugf("--- strike %p glyph %x [%d %d] offset %d count %d\n", + strike, glyph.fID, glyph.fWidth, glyph.fHeight, + strike->fNextFreeOffsetX, glyphCount + 1); +#endif + + // now update our header + strike->fGlyphCount = glyphCount + 1; + strike->fNextFreeOffsetX += glyph.fWidth; + return strike; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkGLTextCache::SkGLTextCache() { + fCtx = SkGetGLContext(); + bzero(fStrikeList, sizeof(fStrikeList)); +} + +SkGLTextCache::~SkGLTextCache() { + SkDebugf("--- delete textcache %p\n", this); + + // if true, we need to not call glDeleteTexture, since they will have + // already gone out of scope + bool zap = SkGetGLContext() != fCtx; + + for (size_t i = 0; i < SK_ARRAY_COUNT(fStrikeList); i++) { + Strike* strike = fStrikeList[i]; + while (strike != NULL) { + Strike* next = strike->fNext; + if (zap) { + strike->zapTexture(); + } + SkDELETE(strike); + strike = next; + } + } +} + +SkGLTextCache::Strike* SkGLTextCache::findGlyph(const SkGlyph& glyph, + int* offset) { + if (SkGetGLContext() != fCtx) { + SkDebugf("====== stale context for text texture\n"); + } + + SkASSERT(glyph.fWidth != 0); + SkASSERT(glyph.fHeight != 0); + + size_t index = SkNextLog2(glyph.fHeight); + if (index >= SK_ARRAY_COUNT(fStrikeList)) { + // too big for us to cache; + return NULL; + } + + Strike* strike = fStrikeList[index]; + if (strike) { + strike = strike->findGlyph(glyph, offset); + } + return strike; +} + +SkGLTextCache::Strike* SkGLTextCache::addGlyphAndBind(const SkGlyph& glyph, + const uint8_t image[], int* offset) { + if (SkGetGLContext() != fCtx) { + SkDebugf("====== stale context for text texture\n"); + } + + SkASSERT(image != NULL); + SkASSERT(glyph.fWidth != 0); + SkASSERT(glyph.fHeight != 0); + + size_t index = SkNextLog2(glyph.fHeight); + if (index >= SK_ARRAY_COUNT(fStrikeList)) { + // too big for us to cache; + return NULL; + } + + Strike* strike = fStrikeList[index]; + if (NULL == strike) { + strike = SkNEW_ARGS(Strike, (NULL, glyph.rowBytes(), glyph.fHeight)); +// SkDebugf("--- create strike [%d] %p cache %p\n", index, strike, this); + } + strike = strike->addGlyphAndBind(glyph, image, offset); + fStrikeList[index] = strike; + return strike; +} + diff --git a/skia/gl/SkGLTextCache.h b/skia/gl/SkGLTextCache.h new file mode 100644 index 0000000..386b274 --- /dev/null +++ b/skia/gl/SkGLTextCache.h @@ -0,0 +1,83 @@ +#ifndef SkGLTextCache_DEFINED +#define SkGLTextCache_DEFINED + +#include "SkGL.h" + +class SkGlyph; + +class SkGLTextCache { +public: + SkGLTextCache(); + ~SkGLTextCache(); + + void* getCtx() const { return fCtx; } + + class Strike { + public: + int width() const { return fStrikeWidth; } + int height() const { return fStrikeHeight; } + GLuint texture() const { return fTexName; } + int widthShift() const { return fStrikeWidthShift; } + int heightShift() const { return fStrikeHeightShift; } + + // call this to force us to ignore the texture name in our destructor + // only call it right before our destructor + void zapTexture() { fTexName = 0; } + + private: + // if next is non-null, its height must match our height + Strike(Strike* next, int width, int height); + ~Strike(); + + Strike* findGlyph(const SkGlyph&, int* offset); + Strike* addGlyphAndBind(const SkGlyph&, const uint8_t*, int* offset); + + enum { + kMinStrikeWidth = 1024, + kMaxGlyphCount = 256 + }; + + Strike* fNext; + GLuint fTexName; + uint32_t fGlyphIDArray[kMaxGlyphCount]; // stores glyphIDs + uint16_t fGlyphOffsetX[kMaxGlyphCount]; // stores x-offsets + uint16_t fGlyphCount; + uint16_t fNextFreeOffsetX; + uint16_t fStrikeWidth; + uint16_t fStrikeHeight; + uint8_t fStrikeWidthShift; // pow2(fStrikeWidth) + uint8_t fStrikeHeightShift; // pow2(fStrikeHeight) + + friend class SkGLTextCache; + }; + + /** If found, returns the exact strike containing it (there may be more than + one with a given height), and sets offset to the offset for that glyph + (if not null). Does NOT bind the texture. + If not found, returns null and ignores offset param. + */ + Strike* findGlyph(const SkGlyph&, int* offset); + + /** Adds the specified glyph to this list of strikes, returning the new + head of the list. If offset is not null, it is set to the offset + for this glyph within the strike. The associated texture is bound + to the gl context. + */ + Strike* addGlyphAndBind(const SkGlyph&, const uint8_t image[], int* offset); + +private: + void* fCtx; + + enum { + // greater than this we won't cache + kMaxGlyphHeightShift = 9, + + kMaxGlyphHeight = 1 << kMaxGlyphHeightShift, + kMaxStrikeListCount = kMaxGlyphHeightShift + 1 + }; + + // heads of the N families, one for each pow2 height + Strike* fStrikeList[kMaxStrikeListCount]; +}; + +#endif diff --git a/skia/gl/SkTextureCache.cpp b/skia/gl/SkTextureCache.cpp new file mode 100644 index 0000000..6d578fe --- /dev/null +++ b/skia/gl/SkTextureCache.cpp @@ -0,0 +1,362 @@ +#include "SkTextureCache.h" + +//#define TRACE_HASH_HITS + +SkTextureCache::Entry::Entry(const SkBitmap& bitmap) + : fName(0), fKey(bitmap), fPrev(NULL), fNext(NULL) { + + fMemSize = SkGL::ComputeTextureMemorySize(bitmap); + fLockCount = 0; +} + +SkTextureCache::Entry::~Entry() { + if (fName != 0) { + glDeleteTextures(1, &fName); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +SkTextureCache::SkTextureCache(size_t countMax, size_t sizeMax) + : fHead(NULL), fTail(NULL), + fTexCountMax(countMax), fTexSizeMax(sizeMax), + fTexCount(0), fTexSize(0) { + + bzero(fHash, sizeof(fHash)); + this->validate(); +} + +SkTextureCache::~SkTextureCache() { +#ifdef SK_DEBUG + Entry* entry = fHead; + while (entry) { + SkASSERT(entry->lockCount() == 0); + entry = entry->fNext; + } +#endif + this->validate(); +} + +/////////////////////////////////////////////////////////////////////////////// + +int SkTextureCache::findInSorted(const Key& key) const { + int count = fSorted.count(); + if (count == 0) { + return ~0; + } + + Entry** sorted = fSorted.begin(); + int lo = 0; + int hi = count - 1; + while (lo < hi) { + int mid = (hi + lo) >> 1; + if (sorted[mid]->getKey() < key) { + lo = mid + 1; + } else { + hi = mid; + } + } + + // hi is now our best guess + const Entry* entry = sorted[hi]; + if (entry->getKey() == key) { + return hi; + } + + // return where to insert it + if (entry->getKey() < key) { + hi += 1; + } + return ~hi; // we twiddle to indicate not-found +} + +#ifdef TRACE_HASH_HITS +static int gHashHits; +static int gSortedHits; +#endif + +SkTextureCache::Entry* SkTextureCache::find(const Key& key, int* insert) const { + int count = fSorted.count(); + if (count == 0) { + *insert = 0; + return NULL; + } + + // check the hash first + int hashIndex = key.getHashIndex(); + Entry* entry = fHash[hashIndex]; + if (NULL != entry && entry->getKey() == key) { +#ifdef TRACE_HASH_HITS + gHashHits += 1; +#endif + return entry; + } + + int index = this->findInSorted(key); + if (index >= 0) { +#ifdef TRACE_HASH_HITS + gSortedHits += 1; +#endif + entry = fSorted[index]; + fHash[hashIndex] = entry; + return entry; + } + + // ~index is where to insert the entry + *insert = ~index; + return NULL; +} + +SkTextureCache::Entry* SkTextureCache::lock(const SkBitmap& bitmap) { + this->validate(); + + // call this before we call find(), so we don't reorder after find() and + // invalidate our index + this->purgeIfNecessary(SkGL::ComputeTextureMemorySize(bitmap)); + + Key key(bitmap); + int index; + Entry* entry = this->find(key, &index); + + if (NULL == entry) { + entry = SkNEW_ARGS(Entry, (bitmap)); + + entry->fName = SkGL::BindNewTexture(bitmap, &entry->fTexSize); + if (0 == entry->fName) { + SkDELETE(entry); + return NULL; + } + fHash[key.getHashIndex()] = entry; + *fSorted.insert(index) = entry; + + fTexCount += 1; + fTexSize += entry->memSize(); + } else { + // detach from our llist + Entry* prev = entry->fPrev; + Entry* next = entry->fNext; + if (prev) { + prev->fNext = next; + } else { + SkASSERT(fHead == entry); + fHead = next; + } + if (next) { + next->fPrev = prev; + } else { + SkASSERT(fTail == entry); + fTail = prev; + } + // now bind the texture + glBindTexture(GL_TEXTURE_2D, entry->fName); + } + + // add to head of llist for LRU + entry->fPrev = NULL; + entry->fNext = fHead; + if (NULL != fHead) { + SkASSERT(NULL == fHead->fPrev); + fHead->fPrev = entry; + } + fHead = entry; + if (NULL == fTail) { + fTail = entry; + } + + this->validate(); + entry->lock(); + +#ifdef TRACE_HASH_HITS + SkDebugf("---- texture cache hash=%d sorted=%d\n", gHashHits, gSortedHits); +#endif + return entry; +} + +void SkTextureCache::unlock(Entry* entry) { + this->validate(); + +#ifdef SK_DEBUG + SkASSERT(entry); + int index = this->findInSorted(entry->getKey()); + SkASSERT(fSorted[index] == entry); +#endif + + SkASSERT(entry->fLockCount > 0); + entry->unlock(); +} + +void SkTextureCache::purgeIfNecessary(size_t extraSize) { + this->validate(); + + size_t countMax = fTexCountMax; + size_t sizeMax = fTexSizeMax; + + // take extraSize into account, but watch for underflow of size_t + if (extraSize > sizeMax) { + sizeMax = 0; + } else { + sizeMax -= extraSize; + } + + Entry* entry = fTail; + while (entry) { + if (fTexCount <= countMax && fTexSize <= sizeMax) { + break; + } + + Entry* prev = entry->fPrev; + // don't purge an entry that is locked + if (entry->isLocked()) { + entry = prev; + continue; + } + + fTexCount -= 1; + fTexSize -= entry->memSize(); + + // remove from our sorted and hash arrays + int index = this->findInSorted(entry->getKey()); + SkASSERT(index >= 0); + fSorted.remove(index); + index = entry->getKey().getHashIndex(); + if (entry == fHash[index]) { + fHash[index] = NULL; + } + + // now detach it from our llist + Entry* next = entry->fNext; + if (prev) { + prev->fNext = next; + } else { + fHead = next; + } + if (next) { + next->fPrev = prev; + } else { + fTail = prev; + } + + // now delete it + SkDebugf("---- purge texture cache %d size=%d\n", + entry->name(), entry->memSize()); + SkDELETE(entry); + + // keep going + entry = prev; + } + + this->validate(); +} + +void SkTextureCache::setMaxCount(size_t count) { + if (fTexCountMax != count) { + fTexCountMax = count; + this->purgeIfNecessary(0); + } +} + +void SkTextureCache::setMaxSize(size_t size) { + if (fTexSizeMax != size) { + fTexSizeMax = size; + this->purgeIfNecessary(0); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkTextureCache::zapAllTextures() { + SkDebugf("---- zapAllTextures\n"); + + this->validate(); + + Entry* entry = fHead; + while (entry) { + Entry* next = entry->fNext; + entry->zapName(); + SkDELETE(entry); + entry = next; + } + + fSorted.reset(); + bzero(fHash, sizeof(fHash)); + + fTexCount = 0; + fTexSize = 0; + + fTail = fHead = NULL; + + this->validate(); +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG +void SkTextureCache::validate() const { + if (0 == fTexCount) { + SkASSERT(0 == fTexSize); + SkASSERT(NULL == fHead); + SkASSERT(NULL == fTail); + return; + } + + SkASSERT(fTexSize); // do we allow a zero-sized texture? + SkASSERT(fHead); + SkASSERT(fTail); + + SkASSERT(NULL == fHead->fPrev); + SkASSERT(NULL == fTail->fNext); + if (1 == fTexCount) { + SkASSERT(fHead == fTail); + } + + const Entry* entry = fHead; + size_t count = 0; + size_t size = 0; + size_t i; + + while (entry != NULL) { + SkASSERT(count < fTexCount); + SkASSERT(size < fTexSize); + size += entry->memSize(); + count += 1; + if (NULL == entry->fNext) { + SkASSERT(fTail == entry); + } + entry = entry->fNext; + } + SkASSERT(count == fTexCount); + SkASSERT(size == fTexSize); + + count = 0; + size = 0; + entry = fTail; + while (entry != NULL) { + SkASSERT(count < fTexCount); + SkASSERT(size < fTexSize); + size += entry->memSize(); + count += 1; + if (NULL == entry->fPrev) { + SkASSERT(fHead == entry); + } + entry = entry->fPrev; + } + SkASSERT(count == fTexCount); + SkASSERT(size == fTexSize); + + SkASSERT(count == (size_t)fSorted.count()); + for (i = 1; i < count; i++) { + SkASSERT(fSorted[i-1]->getKey() < fSorted[i]->getKey()); + } + + for (i = 0; i < kHashCount; i++) { + if (fHash[i]) { + size_t index = fHash[i]->getKey().getHashIndex(); + SkASSERT(index == i); + index = fSorted.find(fHash[i]); + SkASSERT((size_t)index < count); + } + } +} +#endif + + diff --git a/skia/gl/SkTextureCache.h b/skia/gl/SkTextureCache.h new file mode 100644 index 0000000..e4ffc78 --- /dev/null +++ b/skia/gl/SkTextureCache.h @@ -0,0 +1,160 @@ +#ifndef SkTextureCache_DEFINED +#define SkTextureCache_DEFINED + +#include "SkBitmap.h" +#include "SkPoint.h" +#include "SkGL.h" +#include "SkTDArray.h" + +class SkTextureCache { +public: + SkTextureCache(size_t maxCount, size_t maxSize); + ~SkTextureCache(); + + size_t getMaxCount() { return fTexCountMax; } + size_t getMaxSize() { return fTexSizeMax; } + + void setMaxCount(size_t count); + void setMaxSize(size_t size); + + /** Call this if the context has changed behind our backs, and the cache + needs to abandon all of its existing textures. This saves us from using + or deleting a texture created from a different context + */ + void zapAllTextures(); + + static int HashMask() { return kHashMask; } + + class Key { + public: + Key(const SkBitmap& bm) { + fGenID = bm.getGenerationID(); + fOffset = bm.pixelRefOffset(); + fWH = (bm.width() << 16) | bm.height(); + this->computeHash(); + } + + int getHashIndex() const { return fHashIndex; } + + friend bool operator==(const Key& a, const Key& b) { + return a.fHash == b.fHash && + a.fGenID == b.fGenID && + a.fOffset == b.fOffset && + a.fWH == b.fWH; + } + + friend bool operator<(const Key& a, const Key& b) { + if (a.fHash < b.fHash) { + return true; + } else if (a.fHash > b.fHash) { + return false; + } + + if (a.fGenID < b.fGenID) { + return true; + } else if (a.fGenID > b.fGenID) { + return false; + } + + if (a.fOffset < b.fOffset) { + return true; + } else if (a.fOffset > b.fOffset) { + return false; + } + + return a.fWH < b.fWH; + } + + private: + void computeHash() { + uint32_t hash = fGenID ^ fOffset ^ fWH; + fHash = hash; + hash ^= hash >> 16; + fHashIndex = hash & SkTextureCache::HashMask(); + } + + uint32_t fHash; // computed from the other fields + uint32_t fGenID; + size_t fOffset; + uint32_t fWH; + // for indexing into the texturecache's fHash + int fHashIndex; + }; + + class Entry { + public: + GLuint name() const { return fName; } + SkPoint texSize() const { return fTexSize; } + size_t memSize() const { return fMemSize; } + const Key& getKey() const { return fKey; } + + // call this to clear the texture name, in case the context has changed + // in which case we should't reference or delete this texture in GL + void zapName() { fName = 0; } + + private: + Entry(const SkBitmap& bitmap); + ~Entry(); + + int lockCount() const { return fLockCount; } + bool isLocked() const { return fLockCount > 0; } + + void lock() { fLockCount += 1; } + void unlock() { + SkASSERT(fLockCount > 0); + fLockCount -= 1; + } + + private: + GLuint fName; + SkPoint fTexSize; + Key fKey; + size_t fMemSize; + int fLockCount; + + Entry* fPrev; + Entry* fNext; + + friend class SkTextureCache; + }; + + Entry* lock(const SkBitmap&); + void unlock(Entry*); + +private: + void purgeIfNecessary(size_t extraSize); + +#ifdef SK_DEBUG + void validate() const; +#else + void validate() const {} +#endif + + Entry* fHead; + Entry* fTail; + + // limits for the cache + size_t fTexCountMax; + size_t fTexSizeMax; + + // current values for the cache + size_t fTexCount; + size_t fTexSize; + + enum { + kHashBits = 6, + kHashCount = 1 << kHashBits, + kHashMask = kHashCount - 1 + }; + mutable Entry* fHash[kHashCount]; + SkTDArray<Entry*> fSorted; + + /* If we find the key, return the entry and ignore index. If we don't, + return NULL and set index to the place to insert the entry in fSorted + */ + Entry* find(const Key&, int* index) const; + // returns index or <0 if not found. Does NOT update hash + int findInSorted(const Key& key) const; +}; + +#endif |