Add stroke support to polygonal shape rendering

bug:4419017
bug:7230005

- Adds support for stroke/strokeAndFill for shapes without joins
- Fixes path-polygonization threshold calculation
- Fixes rendering offset (now only used for points)
- Several formatting fixes

Change-Id: If72473dc881e45752e2ec212d0dcd1e3f97979ea
This commit is contained in:
Chris Craik
2012-09-25 12:00:29 -07:00
parent fd3398c8bf
commit cb4d600957
7 changed files with 364 additions and 181 deletions

View File

@@ -68,6 +68,7 @@ void Caches::init() {
mCurrentBuffer = meshBuffer; mCurrentBuffer = meshBuffer;
mCurrentIndicesBuffer = 0; mCurrentIndicesBuffer = 0;
mCurrentPositionPointer = this; mCurrentPositionPointer = this;
mCurrentPositionStride = 0;
mCurrentTexCoordsPointer = this; mCurrentTexCoordsPointer = this;
mTexCoordsArrayEnabled = false; mTexCoordsArrayEnabled = false;
@@ -340,15 +341,18 @@ bool Caches::unbindIndicesBuffer() {
// Meshes and textures // Meshes and textures
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
void Caches::bindPositionVertexPointer(bool force, GLuint slot, GLvoid* vertices, GLsizei stride) { void Caches::bindPositionVertexPointer(bool force, GLvoid* vertices, GLsizei stride) {
if (force || vertices != mCurrentPositionPointer) { if (force || vertices != mCurrentPositionPointer || stride != mCurrentPositionStride) {
GLuint slot = currentProgram->position;
glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, stride, vertices); glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, stride, vertices);
mCurrentPositionPointer = vertices; mCurrentPositionPointer = vertices;
mCurrentPositionStride = stride;
} }
} }
void Caches::bindTexCoordsVertexPointer(bool force, GLuint slot, GLvoid* vertices) { void Caches::bindTexCoordsVertexPointer(bool force, GLvoid* vertices) {
if (force || vertices != mCurrentTexCoordsPointer) { if (force || vertices != mCurrentTexCoordsPointer) {
GLuint slot = currentProgram->texCoords;
glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, gMeshStride, vertices); glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, gMeshStride, vertices);
mCurrentTexCoordsPointer = vertices; mCurrentTexCoordsPointer = vertices;
} }

View File

@@ -173,14 +173,13 @@ public:
* Binds an attrib to the specified float vertex pointer. * Binds an attrib to the specified float vertex pointer.
* Assumes a stride of gMeshStride and a size of 2. * Assumes a stride of gMeshStride and a size of 2.
*/ */
void bindPositionVertexPointer(bool force, GLuint slot, GLvoid* vertices, void bindPositionVertexPointer(bool force, GLvoid* vertices, GLsizei stride = gMeshStride);
GLsizei stride = gMeshStride);
/** /**
* Binds an attrib to the specified float vertex pointer. * Binds an attrib to the specified float vertex pointer.
* Assumes a stride of gMeshStride and a size of 2. * Assumes a stride of gMeshStride and a size of 2.
*/ */
void bindTexCoordsVertexPointer(bool force, GLuint slot, GLvoid* vertices); void bindTexCoordsVertexPointer(bool force, GLvoid* vertices);
/** /**
* Resets the vertex pointers. * Resets the vertex pointers.
@@ -295,6 +294,7 @@ private:
GLuint mCurrentBuffer; GLuint mCurrentBuffer;
GLuint mCurrentIndicesBuffer; GLuint mCurrentIndicesBuffer;
void* mCurrentPositionPointer; void* mCurrentPositionPointer;
GLuint mCurrentPositionStride;
void* mCurrentTexCoordsPointer; void* mCurrentTexCoordsPointer;
bool mTexCoordsArrayEnabled; bool mTexCoordsArrayEnabled;

View File

@@ -374,9 +374,8 @@ void FontRenderer::issueDrawCommand() {
int offset = 2; int offset = 2;
bool force = caches.unbindMeshBuffer(); bool force = caches.unbindMeshBuffer();
caches.bindPositionVertexPointer(force, caches.currentProgram->position, buffer); caches.bindPositionVertexPointer(force, buffer);
caches.bindTexCoordsVertexPointer(force, caches.currentProgram->texCoords, caches.bindTexCoordsVertexPointer(force, buffer + offset);
buffer + offset);
} }
glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL); glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL);

View File

@@ -1248,6 +1248,15 @@ bool OpenGLRenderer::quickRejectNoScissor(float left, float top, float right, fl
return !clip.intersects(transformed); return !clip.intersects(transformed);
} }
bool OpenGLRenderer::quickRejectPreStroke(float left, float top, float right, float bottom, SkPaint* paint) {
if (paint->getStyle() != SkPaint::kFill_Style) {
float outset = paint->getStrokeWidth() * 0.5f;
return quickReject(left - outset, top - outset, right + outset, bottom + outset);
} else {
return quickReject(left, top, right, bottom);
}
}
bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom) { bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom) {
if (mSnapshot->isIgnored()) { if (mSnapshot->isIgnored()) {
return true; return true;
@@ -1490,7 +1499,7 @@ void OpenGLRenderer::setupDrawTextGammaUniforms() {
void OpenGLRenderer::setupDrawSimpleMesh() { void OpenGLRenderer::setupDrawSimpleMesh() {
bool force = mCaches.bindMeshBuffer(); bool force = mCaches.bindMeshBuffer();
mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, 0); mCaches.bindPositionVertexPointer(force, 0);
mCaches.unbindIndicesBuffer(); mCaches.unbindIndicesBuffer();
} }
@@ -1523,9 +1532,9 @@ void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint v
force = mCaches.unbindMeshBuffer(); force = mCaches.unbindMeshBuffer();
} }
mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, vertices); mCaches.bindPositionVertexPointer(force, vertices);
if (mCaches.currentProgram->texCoords >= 0) { if (mCaches.currentProgram->texCoords >= 0) {
mCaches.bindTexCoordsVertexPointer(force, mCaches.currentProgram->texCoords, texCoords); mCaches.bindTexCoordsVertexPointer(force, texCoords);
} }
mCaches.unbindIndicesBuffer(); mCaches.unbindIndicesBuffer();
@@ -1533,16 +1542,15 @@ void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint v
void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords) { void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords) {
bool force = mCaches.unbindMeshBuffer(); bool force = mCaches.unbindMeshBuffer();
mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, vertices); mCaches.bindPositionVertexPointer(force, vertices);
if (mCaches.currentProgram->texCoords >= 0) { if (mCaches.currentProgram->texCoords >= 0) {
mCaches.bindTexCoordsVertexPointer(force, mCaches.currentProgram->texCoords, texCoords); mCaches.bindTexCoordsVertexPointer(force, texCoords);
} }
} }
void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) { void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) {
bool force = mCaches.unbindMeshBuffer(); bool force = mCaches.unbindMeshBuffer();
mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, mCaches.bindPositionVertexPointer(force, vertices, gVertexStride);
vertices, gVertexStride);
mCaches.unbindIndicesBuffer(); mCaches.unbindIndicesBuffer();
} }
@@ -1560,8 +1568,7 @@ void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) {
void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* widthCoords, void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* widthCoords,
GLvoid* lengthCoords, float boundaryWidthProportion, int& widthSlot, int& lengthSlot) { GLvoid* lengthCoords, float boundaryWidthProportion, int& widthSlot, int& lengthSlot) {
bool force = mCaches.unbindMeshBuffer(); bool force = mCaches.unbindMeshBuffer();
mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, mCaches.bindPositionVertexPointer(force, vertices, gAAVertexStride);
vertices, gAAVertexStride);
mCaches.resetTexCoordsVertexPointer(); mCaches.resetTexCoordsVertexPointer();
mCaches.unbindIndicesBuffer(); mCaches.unbindIndicesBuffer();
@@ -1919,15 +1926,23 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const
} }
/** /**
* This function uses a similar approach to that of AA lines in the drawLines() function. * Renders a convex path via tessellation. For AA paths, this function uses a similar approach to
* We expand the rectangle by a half pixel in screen space on all sides. However, instead of using * that of AA lines in the drawLines() function. We expand the convex path by a half pixel in
* a fragment shader to compute the translucency of the color from its position, we simply use a * screen space in all directions. However, instead of using a fragment shader to compute the
* varying parameter to define how far a given pixel is into the region. * translucency of the color from its position, we simply use a varying parameter to define how far
* a given pixel is from the edge. For non-AA paths, the expansion and alpha varying are not used.
*
* Doesn't yet support joins, caps, or path effects.
*/ */
void OpenGLRenderer::drawConvexPath(const SkPath& path, int color, SkXfermode::Mode mode, bool isAA) { void OpenGLRenderer::drawConvexPath(const SkPath& path, SkPaint* paint) {
int color = paint->getColor();
SkPaint::Style style = paint->getStyle();
SkXfermode::Mode mode = getXfermode(paint->getXfermode());
bool isAA = paint->isAntiAlias();
VertexBuffer vertexBuffer; VertexBuffer vertexBuffer;
// TODO: try clipping large paths to viewport // TODO: try clipping large paths to viewport
PathRenderer::convexPathFillVertices(path, mSnapshot->transform, vertexBuffer, isAA); PathRenderer::convexPathVertices(path, paint, mSnapshot->transform, vertexBuffer);
setupDraw(); setupDraw();
setupDrawNoTexture(); setupDrawNoTexture();
@@ -1938,15 +1953,14 @@ void OpenGLRenderer::drawConvexPath(const SkPath& path, int color, SkXfermode::M
setupDrawShader(); setupDrawShader();
setupDrawBlending(isAA, mode); setupDrawBlending(isAA, mode);
setupDrawProgram(); setupDrawProgram();
setupDrawModelViewIdentity(true); setupDrawModelViewIdentity();
setupDrawColorUniforms(); setupDrawColorUniforms();
setupDrawColorFilterUniforms(); setupDrawColorFilterUniforms();
setupDrawShaderIdentityUniforms(); setupDrawShaderIdentityUniforms();
void* vertices = vertexBuffer.getBuffer(); void* vertices = vertexBuffer.getBuffer();
bool force = mCaches.unbindMeshBuffer(); bool force = mCaches.unbindMeshBuffer();
mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, mCaches.bindPositionVertexPointer(true, vertices, isAA ? gAlphaVertexStride : gVertexStride);
vertices, isAA ? gAlphaVertexStride : gVertexStride);
mCaches.resetTexCoordsVertexPointer(); mCaches.resetTexCoordsVertexPointer();
mCaches.unbindIndicesBuffer(); mCaches.unbindIndicesBuffer();
@@ -1960,7 +1974,7 @@ void OpenGLRenderer::drawConvexPath(const SkPath& path, int color, SkXfermode::M
glVertexAttribPointer(alphaSlot, 1, GL_FLOAT, GL_FALSE, gAlphaVertexStride, alphaCoords); glVertexAttribPointer(alphaSlot, 1, GL_FLOAT, GL_FALSE, gAlphaVertexStride, alphaCoords);
} }
SkRect bounds = path.getBounds(); SkRect bounds = PathRenderer::computePathBounds(path, paint);
dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, *mSnapshot->transform); dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, *mSnapshot->transform);
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getSize()); glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getSize());
@@ -2050,7 +2064,7 @@ status_t OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
setupDrawShader(); setupDrawShader();
setupDrawBlending(isAA, mode); setupDrawBlending(isAA, mode);
setupDrawProgram(); setupDrawProgram();
setupDrawModelViewIdentity(true); setupDrawModelViewIdentity();
setupDrawColorUniforms(); setupDrawColorUniforms();
setupDrawColorFilterUniforms(); setupDrawColorFilterUniforms();
setupDrawShaderIdentityUniforms(); setupDrawShaderIdentityUniforms();
@@ -2330,11 +2344,11 @@ status_t OpenGLRenderer::drawShape(float left, float top, const PathTexture* tex
status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float bottom, status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, SkPaint* p) { float rx, float ry, SkPaint* p) {
if (mSnapshot->isIgnored() || quickReject(left, top, right, bottom)) { if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
return DrawGlInfo::kStatusDone; return DrawGlInfo::kStatusDone;
} }
if (p->getStyle() != SkPaint::kFill_Style) { if (p->getPathEffect() != 0) {
mCaches.activeTexture(0); mCaches.activeTexture(0);
const PathTexture* texture = mCaches.roundRectShapeCache.getRoundRect( const PathTexture* texture = mCaches.roundRectShapeCache.getRoundRect(
right - left, bottom - top, rx, ry, p); right - left, bottom - top, rx, ry, p);
@@ -2343,37 +2357,47 @@ status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float
SkPath path; SkPath path;
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
if (p->getStyle() == SkPaint::kStrokeAndFill_Style) {
float outset = p->getStrokeWidth() / 2;
rect.outset(outset, outset);
rx += outset;
ry += outset;
}
path.addRoundRect(rect, rx, ry); path.addRoundRect(rect, rx, ry);
drawConvexPath(path, p->getColor(), getXfermode(p->getXfermode()), p->isAntiAlias()); drawConvexPath(path, p);
return DrawGlInfo::kStatusDrew; return DrawGlInfo::kStatusDrew;
} }
status_t OpenGLRenderer::drawCircle(float x, float y, float radius, SkPaint* p) { status_t OpenGLRenderer::drawCircle(float x, float y, float radius, SkPaint* p) {
if (mSnapshot->isIgnored() || quickReject(x - radius, y - radius, x + radius, y + radius)) { if (mSnapshot->isIgnored() || quickRejectPreStroke(x - radius, y - radius,
x + radius, y + radius, p)) {
return DrawGlInfo::kStatusDone; return DrawGlInfo::kStatusDone;
} }
if (p->getPathEffect() != 0) {
if (p->getStyle() != SkPaint::kFill_Style) {
mCaches.activeTexture(0); mCaches.activeTexture(0);
const PathTexture* texture = mCaches.circleShapeCache.getCircle(radius, p); const PathTexture* texture = mCaches.circleShapeCache.getCircle(radius, p);
return drawShape(x - radius, y - radius, texture, p); return drawShape(x - radius, y - radius, texture, p);
} }
SkPath path; SkPath path;
if (p->getStyle() == SkPaint::kStrokeAndFill_Style) {
path.addCircle(x, y, radius + p->getStrokeWidth() / 2);
} else {
path.addCircle(x, y, radius); path.addCircle(x, y, radius);
drawConvexPath(path, p->getColor(), getXfermode(p->getXfermode()), p->isAntiAlias()); }
drawConvexPath(path, p);
return DrawGlInfo::kStatusDrew; return DrawGlInfo::kStatusDrew;
} }
status_t OpenGLRenderer::drawOval(float left, float top, float right, float bottom, status_t OpenGLRenderer::drawOval(float left, float top, float right, float bottom,
SkPaint* p) { SkPaint* p) {
if (mSnapshot->isIgnored() || quickReject(left, top, right, bottom)) { if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
return DrawGlInfo::kStatusDone; return DrawGlInfo::kStatusDone;
} }
if (p->getStyle() != SkPaint::kFill_Style) { if (p->getPathEffect() != 0) {
mCaches.activeTexture(0); mCaches.activeTexture(0);
const PathTexture* texture = mCaches.ovalShapeCache.getOval(right - left, bottom - top, p); const PathTexture* texture = mCaches.ovalShapeCache.getOval(right - left, bottom - top, p);
return drawShape(left, top, texture, p); return drawShape(left, top, texture, p);
@@ -2381,8 +2405,11 @@ status_t OpenGLRenderer::drawOval(float left, float top, float right, float bott
SkPath path; SkPath path;
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
if (p->getStyle() == SkPaint::kStrokeAndFill_Style) {
rect.outset(p->getStrokeWidth() / 2, p->getStrokeWidth() / 2);
}
path.addOval(rect); path.addOval(rect);
drawConvexPath(path, p->getColor(), getXfermode(p->getXfermode()), p->isAntiAlias()); drawConvexPath(path, p);
return DrawGlInfo::kStatusDrew; return DrawGlInfo::kStatusDrew;
} }
@@ -2402,10 +2429,11 @@ status_t OpenGLRenderer::drawArc(float left, float top, float right, float botto
} }
status_t OpenGLRenderer::drawRect(float left, float top, float right, float bottom, SkPaint* p) { status_t OpenGLRenderer::drawRect(float left, float top, float right, float bottom, SkPaint* p) {
if (mSnapshot->isIgnored() || quickReject(left, top, right, bottom)) { if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
return DrawGlInfo::kStatusDone; return DrawGlInfo::kStatusDone;
} }
// only fill style is supported by drawConvexPath, since others have to handle joins
if (p->getStyle() != SkPaint::kFill_Style) { if (p->getStyle() != SkPaint::kFill_Style) {
mCaches.activeTexture(0); mCaches.activeTexture(0);
const PathTexture* texture = mCaches.rectShapeCache.getRect(right - left, bottom - top, p); const PathTexture* texture = mCaches.rectShapeCache.getRect(right - left, bottom - top, p);
@@ -2415,7 +2443,7 @@ status_t OpenGLRenderer::drawRect(float left, float top, float right, float bott
if (p->isAntiAlias() && !mSnapshot->transform->isSimple()) { if (p->isAntiAlias() && !mSnapshot->transform->isSimple()) {
SkPath path; SkPath path;
path.addRect(left, top, right, bottom); path.addRect(left, top, right, bottom);
drawConvexPath(path, p->getColor(), getXfermode(p->getXfermode()), true); drawConvexPath(path, p);
} else { } else {
drawColorRect(left, top, right, bottom, p->getColor(), getXfermode(p->getXfermode())); drawColorRect(left, top, right, bottom, p->getColor(), getXfermode(p->getXfermode()));
} }

View File

@@ -406,6 +406,11 @@ private:
bool quickRejectNoScissor(float left, float top, float right, float bottom, bool quickRejectNoScissor(float left, float top, float right, float bottom,
Rect& transformed, Rect& clip); Rect& transformed, Rect& clip);
/**
* Performs a quick reject but adjust the bounds to account for stroke width if necessary
*/
bool quickRejectPreStroke(float left, float top, float right, float bottom, SkPaint* paint);
/** /**
* Creates a new layer stored in the specified snapshot. * Creates a new layer stored in the specified snapshot.
* *
@@ -513,11 +518,9 @@ private:
* Renders the convex hull defined by the specified path as a strip of polygons. * Renders the convex hull defined by the specified path as a strip of polygons.
* *
* @param path The hull of the path to draw * @param path The hull of the path to draw
* @param color The color of the rect * @param paint The paint to render with
* @param mode The blending mode to draw the path
* @param isAA True if the drawing should be anti-aliased
*/ */
void drawConvexPath(const SkPath& path, int color, SkXfermode::Mode mode, bool isAA); void drawConvexPath(const SkPath& path, SkPaint* paint);
/** /**
* Draws a textured rectangle with the specified texture. The specified coordinates * Draws a textured rectangle with the specified texture. The specified coordinates

View File

@@ -21,6 +21,7 @@
#define VERTEX_DEBUG 0 #define VERTEX_DEBUG 0
#include <SkPath.h> #include <SkPath.h>
#include <SkPaint.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
@@ -39,10 +40,16 @@ namespace uirenderer {
#define THRESHOLD 0.5f #define THRESHOLD 0.5f
void PathRenderer::computeInverseScales(const mat4 *transform, SkRect PathRenderer::computePathBounds(const SkPath& path, const SkPaint* paint) {
float &inverseScaleX, float& inverseScaleY) { SkRect bounds = path.getBounds();
inverseScaleX = 1.0f; if (paint->getStyle() != SkPaint::kFill_Style) {
inverseScaleY = 1.0f; float outset = paint->getStrokeWidth() * 0.5f;
bounds.outset(outset, outset);
}
return bounds;
}
void computeInverseScales(const mat4 *transform, float &inverseScaleX, float& inverseScaleY) {
if (CC_UNLIKELY(!transform->isPureTranslate())) { if (CC_UNLIKELY(!transform->isPureTranslate())) {
float m00 = transform->data[Matrix4::kScaleX]; float m00 = transform->data[Matrix4::kScaleX];
float m01 = transform->data[Matrix4::kSkewY]; float m01 = transform->data[Matrix4::kSkewY];
@@ -50,76 +57,110 @@ void PathRenderer::computeInverseScales(const mat4 *transform,
float m11 = transform->data[Matrix4::kScaleY]; float m11 = transform->data[Matrix4::kScaleY];
float scaleX = sqrt(m00 * m00 + m01 * m01); float scaleX = sqrt(m00 * m00 + m01 * m01);
float scaleY = sqrt(m10 * m10 + m11 * m11); float scaleY = sqrt(m10 * m10 + m11 * m11);
inverseScaleX = (scaleX != 0) ? (inverseScaleX / scaleX) : 0; inverseScaleX = (scaleX != 0) ? (1.0f / scaleX) : 0;
inverseScaleY = (scaleY != 0) ? (inverseScaleY / scaleY) : 0; inverseScaleY = (scaleY != 0) ? (1.0f / scaleY) : 0;
} else {
inverseScaleX = 1.0f;
inverseScaleY = 1.0f;
} }
} }
void PathRenderer::convexPathFillVertices(const SkPath &path, const mat4 *transform, inline void copyVertex(Vertex* destPtr, const Vertex* srcPtr)
VertexBuffer &vertexBuffer, bool isAA) { {
ATRACE_CALL(); Vertex::set(destPtr, srcPtr->position[0], srcPtr->position[1]);
float inverseScaleX; }
float inverseScaleY;
computeInverseScales(transform, inverseScaleX, inverseScaleY);
Vector<Vertex> tempVertices; inline void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr)
float thresholdx = THRESHOLD * inverseScaleX; {
float thresholdy = THRESHOLD * inverseScaleY; AlphaVertex::set(destPtr, srcPtr->position[0], srcPtr->position[1], srcPtr->alpha);
convexPathVertices(path, }
thresholdx * thresholdx,
thresholdy * thresholdy, void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) {
tempVertices); Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size());
#if VERTEX_DEBUG
for (unsigned int i = 0; i < tempVertices.size(); i++) {
ALOGD("orig path: point at %f %f",
tempVertices[i].position[0],
tempVertices[i].position[1]);
}
#endif
int currentIndex = 0; int currentIndex = 0;
if (!isAA) {
Vertex* buffer = vertexBuffer.alloc<Vertex>(tempVertices.size());
// zig zag between all previous points on the inside of the hull to create a // zig zag between all previous points on the inside of the hull to create a
// triangle strip that fills the hull // triangle strip that fills the hull
int srcAindex = 0; int srcAindex = 0;
int srcBindex = tempVertices.size() - 1; int srcBindex = perimeter.size() - 1;
while (srcAindex <= srcBindex) { while (srcAindex <= srcBindex) {
Vertex::set(&buffer[currentIndex++], copyVertex(&buffer[currentIndex++], &perimeter[srcAindex]);
tempVertices.editArray()[srcAindex].position[0],
tempVertices.editArray()[srcAindex].position[1]);
if (srcAindex == srcBindex) break; if (srcAindex == srcBindex) break;
Vertex::set(&buffer[currentIndex++], copyVertex(&buffer[currentIndex++], &perimeter[srcBindex]);
tempVertices.editArray()[srcBindex].position[0],
tempVertices.editArray()[srcBindex].position[1]);
srcAindex++; srcAindex++;
srcBindex--; srcBindex--;
} }
return; }
}
AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(tempVertices.size() * 3 + 2);
// generate alpha points - fill Alpha vertex gaps in between each point with void getStrokeVerticesFromPerimeter(const Vector<Vertex>& perimeter, float halfStrokeWidth,
// alpha 0 vertex, offset by a scaled normal. VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
Vertex* last = &(tempVertices.editArray()[tempVertices.size()-1]); Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size() * 2 + 2);
for (unsigned int i = 0; i<tempVertices.size(); i++) {
Vertex* current = &(tempVertices.editArray()[i]);
Vertex* next = &(tempVertices.editArray()[i + 1 >= tempVertices.size() ? 0 : i + 1]);
int currentIndex = 0;
const Vertex* last = &(perimeter[perimeter.size() - 1]);
const Vertex* current = &(perimeter[0]);
vec2 lastNormal(current->position[1] - last->position[1], vec2 lastNormal(current->position[1] - last->position[1],
last->position[0] - current->position[0]); last->position[0] - current->position[0]);
lastNormal.normalize(); lastNormal.normalize();
for (unsigned int i = 0; i < perimeter.size(); i++) {
const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
vec2 nextNormal(next->position[1] - current->position[1],
current->position[0] - next->position[0]);
nextNormal.normalize();
// offset each point by its normal, out and in, by appropriate stroke offset
vec2 totalOffset = (lastNormal + nextNormal);
totalOffset.normalize();
if (halfStrokeWidth == 0.0f) {
// hairline - compensate for scale
totalOffset.x *= 0.5f * inverseScaleX;
totalOffset.y *= 0.5f * inverseScaleY;
} else {
totalOffset *= halfStrokeWidth;
}
Vertex::set(&buffer[currentIndex++],
current->position[0] + totalOffset.x,
current->position[1] + totalOffset.y);
Vertex::set(&buffer[currentIndex++],
current->position[0] - totalOffset.x,
current->position[1] - totalOffset.y);
last = current;
current = next;
lastNormal = nextNormal;
}
// wrap around to beginning
copyVertex(&buffer[currentIndex++], &buffer[0]);
copyVertex(&buffer[currentIndex++], &buffer[1]);
}
void getFillVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer,
float inverseScaleX, float inverseScaleY) {
AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2);
// generate alpha points - fill Alpha vertex gaps in between each point with
// alpha 0 vertex, offset by a scaled normal.
int currentIndex = 0;
const Vertex* last = &(perimeter[perimeter.size() - 1]);
const Vertex* current = &(perimeter[0]);
vec2 lastNormal(current->position[1] - last->position[1],
last->position[0] - current->position[0]);
lastNormal.normalize();
for (unsigned int i = 0; i < perimeter.size(); i++) {
const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
vec2 nextNormal(next->position[1] - current->position[1], vec2 nextNormal(next->position[1] - current->position[1],
current->position[0] - next->position[0]); current->position[0] - next->position[0]);
nextNormal.normalize(); nextNormal.normalize();
// AA point offset from original point is that point's normal, such that // AA point offset from original point is that point's normal, such that
// each side is offset by .5 pixels // each side is offset by .5 pixels
vec2 totalOffset = (lastNormal + nextNormal) / (2 * (1 + lastNormal.dot(nextNormal))); vec2 totalOffset = (lastNormal + nextNormal);
totalOffset.x *= inverseScaleX; totalOffset.normalize();
totalOffset.y *= inverseScaleY; totalOffset.x *= inverseScaleX * 0.5f;
totalOffset.y *= inverseScaleY * 0.5f;
AlphaVertex::set(&buffer[currentIndex++], AlphaVertex::set(&buffer[currentIndex++],
current->position[0] + totalOffset.x, current->position[0] + totalOffset.x,
@@ -129,48 +170,162 @@ void PathRenderer::convexPathFillVertices(const SkPath &path, const mat4 *transf
current->position[0] - totalOffset.x, current->position[0] - totalOffset.x,
current->position[1] - totalOffset.y, current->position[1] - totalOffset.y,
1.0f); 1.0f);
last = current; last = current;
current = next;
lastNormal = nextNormal;
} }
// wrap around to beginning // wrap around to beginning
AlphaVertex::set(&buffer[currentIndex++], copyAlphaVertex(&buffer[currentIndex++], &buffer[0]);
buffer[0].position[0], copyAlphaVertex(&buffer[currentIndex++], &buffer[1]);
buffer[0].position[1], 0.0f);
AlphaVertex::set(&buffer[currentIndex++],
buffer[1].position[0],
buffer[1].position[1], 1.0f);
// zig zag between all previous points on the inside of the hull to create a // zig zag between all previous points on the inside of the hull to create a
// triangle strip that fills the hull, repeating the first inner point to // triangle strip that fills the hull, repeating the first inner point to
// create degenerate tris to start inside path // create degenerate tris to start inside path
int srcAindex = 0; int srcAindex = 0;
int srcBindex = tempVertices.size() - 1; int srcBindex = perimeter.size() - 1;
while (srcAindex <= srcBindex) { while (srcAindex <= srcBindex) {
AlphaVertex::set(&buffer[currentIndex++], copyAlphaVertex(&buffer[currentIndex++], &buffer[srcAindex * 2 + 1]);
buffer[srcAindex * 2 + 1].position[0],
buffer[srcAindex * 2 + 1].position[1],
1.0f);
if (srcAindex == srcBindex) break; if (srcAindex == srcBindex) break;
AlphaVertex::set(&buffer[currentIndex++], copyAlphaVertex(&buffer[currentIndex++], &buffer[srcBindex * 2 + 1]);
buffer[srcBindex * 2 + 1].position[0],
buffer[srcBindex * 2 + 1].position[1],
1.0f);
srcAindex++; srcAindex++;
srcBindex--; srcBindex--;
} }
#if VERTEX_DEBUG #if VERTEX_DEBUG
for (unsigned int i = 0; i < vertexBuffer.mSize; i++) { for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
ALOGD("point at %f %f", ALOGD("point at %f %f", buffer[i].position[0], buffer[i].position[1]);
buffer[i].position[0],
buffer[i].position[1]);
} }
#endif #endif
} }
void getStrokeVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, float halfStrokeWidth,
VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * perimeter.size() + 8);
void PathRenderer::convexPathVertices(const SkPath &path, float thresholdx, float thresholdy, int offset = 2 * perimeter.size() + 3;
Vector<Vertex> &outputVertices) { int currentAAOuterIndex = 0;
int currentStrokeIndex = offset;
int currentAAInnerIndex = offset * 2;
const Vertex* last = &(perimeter[perimeter.size() - 1]);
const Vertex* current = &(perimeter[0]);
vec2 lastNormal(current->position[1] - last->position[1],
last->position[0] - current->position[0]);
lastNormal.normalize();
for (unsigned int i = 0; i < perimeter.size(); i++) {
const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
vec2 nextNormal(next->position[1] - current->position[1],
current->position[0] - next->position[0]);
nextNormal.normalize();
vec2 pointNormal = (lastNormal + nextNormal);
pointNormal.normalize();
vec2 AAOffset = pointNormal * 0.5f;
AAOffset.x *= inverseScaleX;
AAOffset.y *= inverseScaleY;
vec2 innerOffset = pointNormal;
if (halfStrokeWidth == 0.0f) {
// hairline! - compensate for scale
innerOffset.x *= 0.5f * inverseScaleX;
innerOffset.y *= 0.5f * inverseScaleY;
} else {
innerOffset *= halfStrokeWidth;
}
vec2 outerOffset = innerOffset + AAOffset;
innerOffset -= AAOffset;
AlphaVertex::set(&buffer[currentAAOuterIndex++],
current->position[0] + outerOffset.x,
current->position[1] + outerOffset.y,
0.0f);
AlphaVertex::set(&buffer[currentAAOuterIndex++],
current->position[0] + innerOffset.x,
current->position[1] + innerOffset.y,
1.0f);
AlphaVertex::set(&buffer[currentStrokeIndex++],
current->position[0] + innerOffset.x,
current->position[1] + innerOffset.y,
1.0f);
AlphaVertex::set(&buffer[currentStrokeIndex++],
current->position[0] - innerOffset.x,
current->position[1] - innerOffset.y,
1.0f);
AlphaVertex::set(&buffer[currentAAInnerIndex++],
current->position[0] - innerOffset.x,
current->position[1] - innerOffset.y,
1.0f);
AlphaVertex::set(&buffer[currentAAInnerIndex++],
current->position[0] - outerOffset.x,
current->position[1] - outerOffset.y,
0.0f);
// TODO: current = next, copy last normal instead of recalculate
last = current;
current = next;
lastNormal = nextNormal;
}
// wrap each strip around to beginning, creating degenerate tris to bridge strips
copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[0]);
copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]);
copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]);
copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset]);
copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]);
copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]);
copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset]);
copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset + 1]);
// don't need to create last degenerate tri
}
void PathRenderer::convexPathVertices(const SkPath &path, const SkPaint* paint,
const mat4 *transform, VertexBuffer& vertexBuffer) {
ATRACE_CALL();
SkPaint::Style style = paint->getStyle();
bool isAA = paint->isAntiAlias();
float inverseScaleX, inverseScaleY;
computeInverseScales(transform, inverseScaleX, inverseScaleY);
Vector<Vertex> tempVertices;
convexPathPerimeterVertices(path, inverseScaleX * inverseScaleX, inverseScaleY * inverseScaleY,
tempVertices);
#if VERTEX_DEBUG
for (unsigned int i = 0; i < tempVertices.size(); i++) {
ALOGD("orig path: point at %f %f", tempVertices[i].position[0], tempVertices[i].position[1]);
}
#endif
if (style == SkPaint::kStroke_Style) {
float halfStrokeWidth = paint->getStrokeWidth() * 0.5f;
if (!isAA) {
getStrokeVerticesFromPerimeter(tempVertices, halfStrokeWidth, vertexBuffer,
inverseScaleX, inverseScaleY);
} else {
getStrokeVerticesFromPerimeterAA(tempVertices, halfStrokeWidth, vertexBuffer,
inverseScaleX, inverseScaleY);
}
} else {
// For kStrokeAndFill style, the path should be adjusted externally, as it will be treated as a fill here.
if (!isAA) {
getFillVerticesFromPerimeter(tempVertices, vertexBuffer);
} else {
getFillVerticesFromPerimeterAA(tempVertices, vertexBuffer, inverseScaleX, inverseScaleY);
}
}
}
void PathRenderer::convexPathPerimeterVertices(const SkPath& path,
float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
ATRACE_CALL(); ATRACE_CALL();
SkPath::Iter iter(path, true); SkPath::Iter iter(path, true);
@@ -194,7 +349,7 @@ void PathRenderer::convexPathVertices(const SkPath &path, float thresholdx, floa
// TODO: make this not yuck // TODO: make this not yuck
outputVertices.push(); outputVertices.push();
newVertex = &(outputVertices.editArray()[outputVertices.size()-1]); newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]);
Vertex::set(newVertex, pts[1].x(), pts[1].y()); Vertex::set(newVertex, pts[1].x(), pts[1].y());
break; break;
case SkPath::kQuad_Verb: case SkPath::kQuad_Verb:
@@ -203,8 +358,7 @@ void PathRenderer::convexPathVertices(const SkPath &path, float thresholdx, floa
pts[0].x(), pts[0].y(), pts[0].x(), pts[0].y(),
pts[2].x(), pts[2].y(), pts[2].x(), pts[2].y(),
pts[1].x(), pts[1].y(), pts[1].x(), pts[1].y(),
thresholdx, thresholdy, sqrInvScaleX, sqrInvScaleY, outputVertices);
outputVertices);
break; break;
case SkPath::kCubic_Verb: case SkPath::kCubic_Verb:
ALOGV("kCubic_Verb"); ALOGV("kCubic_Verb");
@@ -213,7 +367,7 @@ void PathRenderer::convexPathVertices(const SkPath &path, float thresholdx, floa
pts[1].x(), pts[1].y(), pts[1].x(), pts[1].y(),
pts[3].x(), pts[3].y(), pts[3].x(), pts[3].y(),
pts[2].x(), pts[2].y(), pts[2].x(), pts[2].y(),
thresholdx, thresholdy, outputVertices); sqrInvScaleX, sqrInvScaleY, outputVertices);
break; break;
default: default:
break; break;
@@ -224,18 +378,20 @@ void PathRenderer::convexPathVertices(const SkPath &path, float thresholdx, floa
void PathRenderer::recursiveCubicBezierVertices( void PathRenderer::recursiveCubicBezierVertices(
float p1x, float p1y, float c1x, float c1y, float p1x, float p1y, float c1x, float c1y,
float p2x, float p2y, float c2x, float c2y, float p2x, float p2y, float c2x, float c2y,
float thresholdx, float thresholdy, Vector<Vertex> &outputVertices) { float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
float dx = p2x - p1x; float dx = p2x - p1x;
float dy = p2y - p1y; float dy = p2y - p1y;
float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx); float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx);
float d2 = fabs((c2x - p2x) * dy - (c2y - p2y) * dx); float d2 = fabs((c2x - p2x) * dy - (c2y - p2y) * dx);
float d = d1 + d2; float d = d1 + d2;
if (d * d < (thresholdx * (dx * dx) + thresholdy * (dy * dy))) { // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
// below thresh, draw line by adding endpoint // below thresh, draw line by adding endpoint
// TODO: make this not yuck // TODO: make this not yuck
outputVertices.push(); outputVertices.push();
Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size()-1]); Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]);
Vertex::set(newVertex, p2x, p2y); Vertex::set(newVertex, p2x, p2y);
} else { } else {
float p1c1x = (p1x + c1x) * 0.5f; float p1c1x = (p1x + c1x) * 0.5f;
@@ -258,13 +414,11 @@ void PathRenderer::recursiveCubicBezierVertices(
recursiveCubicBezierVertices( recursiveCubicBezierVertices(
p1x, p1y, p1c1x, p1c1y, p1x, p1y, p1c1x, p1c1y,
mx, my, p1c1c2x, p1c1c2y, mx, my, p1c1c2x, p1c1c2y,
thresholdx, thresholdy, sqrInvScaleX, sqrInvScaleY, outputVertices);
outputVertices);
recursiveCubicBezierVertices( recursiveCubicBezierVertices(
mx, my, p2c1c2x, p2c1c2y, mx, my, p2c1c2x, p2c1c2y,
p2x, p2y, p2c2x, p2c2y, p2x, p2y, p2c2x, p2c2y,
thresholdx, thresholdy, sqrInvScaleX, sqrInvScaleY, outputVertices);
outputVertices);
} }
} }
@@ -272,16 +426,16 @@ void PathRenderer::recursiveQuadraticBezierVertices(
float ax, float ay, float ax, float ay,
float bx, float by, float bx, float by,
float cx, float cy, float cx, float cy,
float thresholdx, float thresholdy, Vector<Vertex> &outputVertices) { float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
float dx = bx - ax; float dx = bx - ax;
float dy = by - ay; float dy = by - ay;
float d = (cx - bx) * dy - (cy - by) * dx; float d = (cx - bx) * dy - (cy - by) * dx;
if (d * d < (thresholdx * (dx * dx) + thresholdy * (dy * dy))) { if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
// below thresh, draw line by adding endpoint // below thresh, draw line by adding endpoint
// TODO: make this not yuck // TODO: make this not yuck
outputVertices.push(); outputVertices.push();
Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size()-1]); Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]);
Vertex::set(newVertex, bx, by); Vertex::set(newVertex, bx, by);
} else { } else {
float acx = (ax + cx) * 0.5f; float acx = (ax + cx) * 0.5f;
@@ -294,9 +448,9 @@ void PathRenderer::recursiveQuadraticBezierVertices(
float my = (acy + bcy) * 0.5f; float my = (acy + bcy) * 0.5f;
recursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy, recursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy,
thresholdx, thresholdy, outputVertices); sqrInvScaleX, sqrInvScaleY, outputVertices);
recursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy, recursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy,
thresholdx, thresholdy, outputVertices); sqrInvScaleX, sqrInvScaleY, outputVertices);
} }
} }

View File

@@ -35,15 +35,13 @@ public:
mCleanupMethod(0) mCleanupMethod(0)
{} {}
~VertexBuffer() ~VertexBuffer() {
{
if (mCleanupMethod) if (mCleanupMethod)
mCleanupMethod(mBuffer); mCleanupMethod(mBuffer);
} }
template <class TYPE> template <class TYPE>
TYPE* alloc(int size) TYPE* alloc(int size) {
{
mSize = size; mSize = size;
mBuffer = (void*)new TYPE[size]; mBuffer = (void*)new TYPE[size];
mCleanupMethod = &(cleanup<TYPE>); mCleanupMethod = &(cleanup<TYPE>);
@@ -56,8 +54,7 @@ public:
private: private:
template <class TYPE> template <class TYPE>
static void cleanup(void* buffer) static void cleanup(void* buffer) {
{
delete[] (TYPE*)buffer; delete[] (TYPE*)buffer;
} }
@@ -68,17 +65,15 @@ private:
class PathRenderer { class PathRenderer {
public: public:
static void computeInverseScales( static SkRect computePathBounds(const SkPath& path, const SkPaint* paint);
const mat4 *transform, float &inverseScaleX, float& inverseScaleY);
static void convexPathFillVertices( static void convexPathVertices(const SkPath& path, const SkPaint* paint,
const SkPath &path, const mat4 *transform, const mat4 *transform, VertexBuffer& vertexBuffer);
VertexBuffer &vertexBuffer, bool isAA);
private: private:
static void convexPathVertices( static void convexPathPerimeterVertices(
const SkPath &path, const SkPath &path,
float thresholdx, float thresholdy, float sqrInvScaleX, float sqrInvScaleY,
Vector<Vertex> &outputVertices); Vector<Vertex> &outputVertices);
/* /*
@@ -89,7 +84,7 @@ private:
float ax, float ay, float ax, float ay,
float bx, float by, float bx, float by,
float cx, float cy, float cx, float cy,
float thresholdx, float thresholdy, float sqrInvScaleX, float sqrInvScaleY,
Vector<Vertex> &outputVertices); Vector<Vertex> &outputVertices);
/* /*
@@ -101,7 +96,7 @@ private:
float c1x, float c1y, float c1x, float c1y,
float p2x, float p2y, float p2x, float p2y,
float c2x, float c2y, float c2x, float c2y,
float thresholdx, float thresholdy, float sqrInvScaleX, float sqrInvScaleY,
Vector<Vertex> &outputVertices); Vector<Vertex> &outputVertices);
}; };