зеркало из https://github.com/mozilla/moz-skia.git
527 строки
16 KiB
C++
527 строки
16 KiB
C++
#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];
|
|
|
|
sk_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:
|
|
#ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D
|
|
*format = GL_PALETTE8_RGBA8_OES;
|
|
*type = GL_UNSIGNED_BYTE; // unused I think
|
|
#else
|
|
// we promote index to argb32
|
|
*format = GL_RGBA;
|
|
*type = GL_UNSIGNED_BYTE;
|
|
#endif
|
|
break;
|
|
case SkBitmap::kA8_Config:
|
|
*format = GL_ALPHA;
|
|
*type = GL_UNSIGNED_BYTE;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define SK_GL_SIZE_OF_PALETTE (256 * sizeof(SkPMColor))
|
|
|
|
size_t SkGL::ComputeTextureMemorySize(const SkBitmap& bitmap) {
|
|
int shift = 0;
|
|
size_t adder = 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:
|
|
#ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D
|
|
// account for the colortable
|
|
adder = SK_GL_SIZE_OF_PALETTE;
|
|
#else
|
|
// we promote index to argb32
|
|
shift = 2;
|
|
#endif
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
return (bitmap.getSize() << shift) + adder;
|
|
}
|
|
|
|
#ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D
|
|
/* Fill out buffer with the compressed format GL expects from a colortable
|
|
based bitmap. [palette (colortable) + indices].
|
|
|
|
At the moment I always take the 8bit version, since that's what my data
|
|
is. I could detect that the colortable.count is <= 16, and then repack the
|
|
indices as nibbles to save RAM, but it would take more time (i.e. a lot
|
|
slower than memcpy), so I'm skipping that for now.
|
|
|
|
GL wants a full 256 palette entry, even though my ctable is only as big
|
|
as the colortable.count says it is. I presume it is OK to leave any
|
|
trailing entries uninitialized, since none of my indices should exceed
|
|
ctable->count().
|
|
*/
|
|
static void build_compressed_data(void* buffer, const SkBitmap& bitmap) {
|
|
SkASSERT(SkBitmap::kIndex8_Config == bitmap.config());
|
|
|
|
SkColorTable* ctable = bitmap.getColorTable();
|
|
uint8_t* dst = (uint8_t*)buffer;
|
|
|
|
memcpy(dst, ctable->lockColors(), ctable->count() * sizeof(SkPMColor));
|
|
ctable->unlockColors(false);
|
|
|
|
// always skip a full 256 number of entries, even if we memcpy'd fewer
|
|
dst += SK_GL_SIZE_OF_PALETTE;
|
|
memcpy(dst, bitmap.getPixels(), bitmap.getSafeSize()); // Just copy what we need.
|
|
}
|
|
#endif
|
|
|
|
/* Return true if the bitmap cannot be supported in its current config as a
|
|
texture, and it needs to be promoted to ARGB32.
|
|
*/
|
|
static bool needToPromoteTo32bit(const SkBitmap& bitmap) {
|
|
if (bitmap.config() == SkBitmap::kIndex8_Config) {
|
|
#ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D
|
|
const int w = bitmap.width();
|
|
const int h = bitmap.height();
|
|
if (SkNextPow2(w) == w && SkNextPow2(h) == h) {
|
|
// we can handle Indx8 if we're a POW2
|
|
return false;
|
|
}
|
|
#endif
|
|
return true; // must promote to ARGB32
|
|
}
|
|
return false;
|
|
}
|
|
|
|
GLuint SkGL::BindNewTexture(const SkBitmap& origBitmap, SkPoint* max) {
|
|
SkBitmap tmpBitmap;
|
|
const SkBitmap* bitmap = &origBitmap;
|
|
|
|
if (needToPromoteTo32bit(origBitmap)) {
|
|
origBitmap.copyTo(&tmpBitmap, SkBitmap::kARGB_8888_Config);
|
|
// now bitmap points to our temp, which has been promoted to 32bits
|
|
bitmap = &tmpBitmap;
|
|
}
|
|
|
|
GLenum format, type;
|
|
if (!canBeTexture(*bitmap, &format, &type)) {
|
|
return 0;
|
|
}
|
|
|
|
SkAutoLockPixels alp(*bitmap);
|
|
if (!bitmap->readyToDraw()) {
|
|
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
|
|
#ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D
|
|
if (SkBitmap::kIndex8_Config == bitmap->config()) {
|
|
size_t imagesize = bitmap->getSize() + SK_GL_SIZE_OF_PALETTE;
|
|
SkAutoMalloc storage(imagesize);
|
|
|
|
build_compressed_data(storage.get(), *bitmap);
|
|
// we only support POW2 here (GLES 1.0 restriction)
|
|
SkASSERT(ow == nw);
|
|
SkASSERT(oh == nh);
|
|
glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, ow, oh, 0,
|
|
imagesize, storage.get());
|
|
} else // fall through to non-compressed logic
|
|
#endif
|
|
{
|
|
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) {
|
|
const SkRect& bounds = path.getBounds();
|
|
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);
|
|
}
|
|
}
|
|
|