diff options
author | Chris Craik <ccraik@google.com> | 2012-09-25 12:00:29 -0700 |
---|---|---|
committer | Chris Craik <ccraik@google.com> | 2012-09-26 14:38:11 -0700 |
commit | cb4d6009576cf08195dc23f341a3f4939c0878bb (patch) | |
tree | 234201781d7baaba35fc3baab178007ab03ea2df /libs | |
parent | fd3398c8bf25d8ec7ce813ebda431d67d734f268 (diff) | |
download | frameworks_base-cb4d6009576cf08195dc23f341a3f4939c0878bb.zip frameworks_base-cb4d6009576cf08195dc23f341a3f4939c0878bb.tar.gz frameworks_base-cb4d6009576cf08195dc23f341a3f4939c0878bb.tar.bz2 |
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
Diffstat (limited to 'libs')
-rw-r--r-- | libs/hwui/Caches.cpp | 10 | ||||
-rw-r--r-- | libs/hwui/Caches.h | 6 | ||||
-rw-r--r-- | libs/hwui/FontRenderer.cpp | 5 | ||||
-rw-r--r-- | libs/hwui/OpenGLRenderer.cpp | 94 | ||||
-rw-r--r-- | libs/hwui/OpenGLRenderer.h | 11 | ||||
-rw-r--r-- | libs/hwui/PathRenderer.cpp | 372 | ||||
-rw-r--r-- | libs/hwui/PathRenderer.h | 43 |
7 files changed, 362 insertions, 179 deletions
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index f0f72f9..2883f37 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -68,6 +68,7 @@ void Caches::init() { mCurrentBuffer = meshBuffer; mCurrentIndicesBuffer = 0; mCurrentPositionPointer = this; + mCurrentPositionStride = 0; mCurrentTexCoordsPointer = this; mTexCoordsArrayEnabled = false; @@ -340,15 +341,18 @@ bool Caches::unbindIndicesBuffer() { // Meshes and textures /////////////////////////////////////////////////////////////////////////////// -void Caches::bindPositionVertexPointer(bool force, GLuint slot, GLvoid* vertices, GLsizei stride) { - if (force || vertices != mCurrentPositionPointer) { +void Caches::bindPositionVertexPointer(bool force, GLvoid* vertices, GLsizei stride) { + if (force || vertices != mCurrentPositionPointer || stride != mCurrentPositionStride) { + GLuint slot = currentProgram->position; glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, stride, 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) { + GLuint slot = currentProgram->texCoords; glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, gMeshStride, vertices); mCurrentTexCoordsPointer = vertices; } diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 48efd10..9f28409 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -173,14 +173,13 @@ public: * Binds an attrib to the specified float vertex pointer. * Assumes a stride of gMeshStride and a size of 2. */ - void bindPositionVertexPointer(bool force, GLuint slot, GLvoid* vertices, - GLsizei stride = gMeshStride); + void bindPositionVertexPointer(bool force, GLvoid* vertices, GLsizei stride = gMeshStride); /** * Binds an attrib to the specified float vertex pointer. * 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. @@ -295,6 +294,7 @@ private: GLuint mCurrentBuffer; GLuint mCurrentIndicesBuffer; void* mCurrentPositionPointer; + GLuint mCurrentPositionStride; void* mCurrentTexCoordsPointer; bool mTexCoordsArrayEnabled; diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index cab68f0..4e97c88 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -374,9 +374,8 @@ void FontRenderer::issueDrawCommand() { int offset = 2; bool force = caches.unbindMeshBuffer(); - caches.bindPositionVertexPointer(force, caches.currentProgram->position, buffer); - caches.bindTexCoordsVertexPointer(force, caches.currentProgram->texCoords, - buffer + offset); + caches.bindPositionVertexPointer(force, buffer); + caches.bindTexCoordsVertexPointer(force, buffer + offset); } glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL); diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 87c3a47..b0328f5 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -1248,6 +1248,15 @@ bool OpenGLRenderer::quickRejectNoScissor(float left, float top, float right, fl 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) { if (mSnapshot->isIgnored()) { return true; @@ -1490,7 +1499,7 @@ void OpenGLRenderer::setupDrawTextGammaUniforms() { void OpenGLRenderer::setupDrawSimpleMesh() { bool force = mCaches.bindMeshBuffer(); - mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, 0); + mCaches.bindPositionVertexPointer(force, 0); mCaches.unbindIndicesBuffer(); } @@ -1523,9 +1532,9 @@ void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint v force = mCaches.unbindMeshBuffer(); } - mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, vertices); + mCaches.bindPositionVertexPointer(force, vertices); if (mCaches.currentProgram->texCoords >= 0) { - mCaches.bindTexCoordsVertexPointer(force, mCaches.currentProgram->texCoords, texCoords); + mCaches.bindTexCoordsVertexPointer(force, texCoords); } mCaches.unbindIndicesBuffer(); @@ -1533,16 +1542,15 @@ void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint v void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords) { bool force = mCaches.unbindMeshBuffer(); - mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, vertices); + mCaches.bindPositionVertexPointer(force, vertices); if (mCaches.currentProgram->texCoords >= 0) { - mCaches.bindTexCoordsVertexPointer(force, mCaches.currentProgram->texCoords, texCoords); + mCaches.bindTexCoordsVertexPointer(force, texCoords); } } void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) { bool force = mCaches.unbindMeshBuffer(); - mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, - vertices, gVertexStride); + mCaches.bindPositionVertexPointer(force, vertices, gVertexStride); mCaches.unbindIndicesBuffer(); } @@ -1560,8 +1568,7 @@ void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) { void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* widthCoords, GLvoid* lengthCoords, float boundaryWidthProportion, int& widthSlot, int& lengthSlot) { bool force = mCaches.unbindMeshBuffer(); - mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, - vertices, gAAVertexStride); + mCaches.bindPositionVertexPointer(force, vertices, gAAVertexStride); mCaches.resetTexCoordsVertexPointer(); 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. - * We expand the rectangle by a half pixel in screen space on all sides. However, instead of using - * a fragment shader to compute the translucency of the color from its position, we simply use a - * varying parameter to define how far a given pixel is into the region. + * Renders a convex path via tessellation. For AA paths, this function uses a similar approach to + * that of AA lines in the drawLines() function. We expand the convex path by a half pixel in + * screen space in all directions. However, instead of using a fragment shader to compute the + * 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; // TODO: try clipping large paths to viewport - PathRenderer::convexPathFillVertices(path, mSnapshot->transform, vertexBuffer, isAA); + PathRenderer::convexPathVertices(path, paint, mSnapshot->transform, vertexBuffer); setupDraw(); setupDrawNoTexture(); @@ -1938,15 +1953,14 @@ void OpenGLRenderer::drawConvexPath(const SkPath& path, int color, SkXfermode::M setupDrawShader(); setupDrawBlending(isAA, mode); setupDrawProgram(); - setupDrawModelViewIdentity(true); + setupDrawModelViewIdentity(); setupDrawColorUniforms(); setupDrawColorFilterUniforms(); setupDrawShaderIdentityUniforms(); void* vertices = vertexBuffer.getBuffer(); bool force = mCaches.unbindMeshBuffer(); - mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, - vertices, isAA ? gAlphaVertexStride : gVertexStride); + mCaches.bindPositionVertexPointer(true, vertices, isAA ? gAlphaVertexStride : gVertexStride); mCaches.resetTexCoordsVertexPointer(); 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); } - SkRect bounds = path.getBounds(); + SkRect bounds = PathRenderer::computePathBounds(path, paint); dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, *mSnapshot->transform); glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getSize()); @@ -2050,7 +2064,7 @@ status_t OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { setupDrawShader(); setupDrawBlending(isAA, mode); setupDrawProgram(); - setupDrawModelViewIdentity(true); + setupDrawModelViewIdentity(); setupDrawColorUniforms(); setupDrawColorFilterUniforms(); 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, 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; } - if (p->getStyle() != SkPaint::kFill_Style) { + if (p->getPathEffect() != 0) { mCaches.activeTexture(0); const PathTexture* texture = mCaches.roundRectShapeCache.getRoundRect( right - left, bottom - top, rx, ry, p); @@ -2343,37 +2357,47 @@ status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float SkPath path; 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); - drawConvexPath(path, p->getColor(), getXfermode(p->getXfermode()), p->isAntiAlias()); + drawConvexPath(path, p); return DrawGlInfo::kStatusDrew; } 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; } - - if (p->getStyle() != SkPaint::kFill_Style) { + if (p->getPathEffect() != 0) { mCaches.activeTexture(0); const PathTexture* texture = mCaches.circleShapeCache.getCircle(radius, p); return drawShape(x - radius, y - radius, texture, p); } SkPath path; - path.addCircle(x, y, radius); - drawConvexPath(path, p->getColor(), getXfermode(p->getXfermode()), p->isAntiAlias()); + if (p->getStyle() == SkPaint::kStrokeAndFill_Style) { + path.addCircle(x, y, radius + p->getStrokeWidth() / 2); + } else { + path.addCircle(x, y, radius); + } + drawConvexPath(path, p); return DrawGlInfo::kStatusDrew; } status_t OpenGLRenderer::drawOval(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; } - if (p->getStyle() != SkPaint::kFill_Style) { + if (p->getPathEffect() != 0) { mCaches.activeTexture(0); const PathTexture* texture = mCaches.ovalShapeCache.getOval(right - left, bottom - top, 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; 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); - drawConvexPath(path, p->getColor(), getXfermode(p->getXfermode()), p->isAntiAlias()); + drawConvexPath(path, p); 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) { - if (mSnapshot->isIgnored() || quickReject(left, top, right, bottom)) { + if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) { return DrawGlInfo::kStatusDone; } + // only fill style is supported by drawConvexPath, since others have to handle joins if (p->getStyle() != SkPaint::kFill_Style) { mCaches.activeTexture(0); 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()) { SkPath path; path.addRect(left, top, right, bottom); - drawConvexPath(path, p->getColor(), getXfermode(p->getXfermode()), true); + drawConvexPath(path, p); } else { drawColorRect(left, top, right, bottom, p->getColor(), getXfermode(p->getXfermode())); } diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index c29e3fb..bc9b693 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -407,6 +407,11 @@ private: 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. * * @param snapshot The snapshot associated with the new layer @@ -513,11 +518,9 @@ private: * Renders the convex hull defined by the specified path as a strip of polygons. * * @param path The hull of the path to draw - * @param color The color of the rect - * @param mode The blending mode to draw the path - * @param isAA True if the drawing should be anti-aliased + * @param paint The paint to render with */ - 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 diff --git a/libs/hwui/PathRenderer.cpp b/libs/hwui/PathRenderer.cpp index d222009..6893f9d 100644 --- a/libs/hwui/PathRenderer.cpp +++ b/libs/hwui/PathRenderer.cpp @@ -21,6 +21,7 @@ #define VERTEX_DEBUG 0 #include <SkPath.h> +#include <SkPaint.h> #include <stdlib.h> #include <stdint.h> @@ -39,10 +40,16 @@ namespace uirenderer { #define THRESHOLD 0.5f -void PathRenderer::computeInverseScales(const mat4 *transform, - float &inverseScaleX, float& inverseScaleY) { - inverseScaleX = 1.0f; - inverseScaleY = 1.0f; +SkRect PathRenderer::computePathBounds(const SkPath& path, const SkPaint* paint) { + SkRect bounds = path.getBounds(); + if (paint->getStyle() != SkPaint::kFill_Style) { + 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())) { float m00 = transform->data[Matrix4::kScaleX]; float m01 = transform->data[Matrix4::kSkewY]; @@ -50,127 +57,275 @@ void PathRenderer::computeInverseScales(const mat4 *transform, float m11 = transform->data[Matrix4::kScaleY]; float scaleX = sqrt(m00 * m00 + m01 * m01); float scaleY = sqrt(m10 * m10 + m11 * m11); - inverseScaleX = (scaleX != 0) ? (inverseScaleX / scaleX) : 0; - inverseScaleY = (scaleY != 0) ? (inverseScaleY / scaleY) : 0; + inverseScaleX = (scaleX != 0) ? (1.0f / scaleX) : 0; + inverseScaleY = (scaleY != 0) ? (1.0f / scaleY) : 0; + } else { + inverseScaleX = 1.0f; + inverseScaleY = 1.0f; } } -void PathRenderer::convexPathFillVertices(const SkPath &path, const mat4 *transform, - VertexBuffer &vertexBuffer, bool isAA) { - ATRACE_CALL(); - float inverseScaleX; - float inverseScaleY; - computeInverseScales(transform, inverseScaleX, inverseScaleY); +inline void copyVertex(Vertex* destPtr, const Vertex* srcPtr) +{ + Vertex::set(destPtr, srcPtr->position[0], srcPtr->position[1]); +} - Vector<Vertex> tempVertices; - float thresholdx = THRESHOLD * inverseScaleX; - float thresholdy = THRESHOLD * inverseScaleY; - convexPathVertices(path, - thresholdx * thresholdx, - thresholdy * thresholdy, - tempVertices); +inline void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) +{ + AlphaVertex::set(destPtr, srcPtr->position[0], srcPtr->position[1], srcPtr->alpha); +} -#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]); +void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) { + Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size()); + + int currentIndex = 0; + // zig zag between all previous points on the inside of the hull to create a + // triangle strip that fills the hull + int srcAindex = 0; + int srcBindex = perimeter.size() - 1; + while (srcAindex <= srcBindex) { + copyVertex(&buffer[currentIndex++], &perimeter[srcAindex]); + if (srcAindex == srcBindex) break; + copyVertex(&buffer[currentIndex++], &perimeter[srcBindex]); + srcAindex++; + srcBindex--; } -#endif +} + +void getStrokeVerticesFromPerimeter(const Vector<Vertex>& perimeter, float halfStrokeWidth, + VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) { + Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size() * 2 + 2); + 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 - // triangle strip that fills the hull - int srcAindex = 0; - int srcBindex = tempVertices.size() - 1; - while (srcAindex <= srcBindex) { - Vertex::set(&buffer[currentIndex++], - tempVertices.editArray()[srcAindex].position[0], - tempVertices.editArray()[srcAindex].position[1]); - if (srcAindex == srcBindex) break; - Vertex::set(&buffer[currentIndex++], - tempVertices.editArray()[srcBindex].position[0], - tempVertices.editArray()[srcBindex].position[1]); - srcAindex++; - srcBindex--; + 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(); + + // 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; } - return; + + 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; } - AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(tempVertices.size() * 3 + 2); - // generate alpha points - fill Alpha vertex gaps in between each point with - // alpha 0 vertex, offset by a scaled normal. - Vertex* last = &(tempVertices.editArray()[tempVertices.size()-1]); + // wrap around to beginning + copyVertex(&buffer[currentIndex++], &buffer[0]); + copyVertex(&buffer[currentIndex++], &buffer[1]); +} - 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]); +void getFillVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer, + float inverseScaleX, float inverseScaleY) { + AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2); - vec2 lastNormal(current->position[1] - last->position[1], - last->position[0] - current->position[0]); - lastNormal.normalize(); + // 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], - current->position[0] - next->position[0]); + current->position[0] - next->position[0]); nextNormal.normalize(); // AA point offset from original point is that point's normal, such that // each side is offset by .5 pixels - vec2 totalOffset = (lastNormal + nextNormal) / (2 * (1 + lastNormal.dot(nextNormal))); - totalOffset.x *= inverseScaleX; - totalOffset.y *= inverseScaleY; + vec2 totalOffset = (lastNormal + nextNormal); + totalOffset.normalize(); + totalOffset.x *= inverseScaleX * 0.5f; + totalOffset.y *= inverseScaleY * 0.5f; AlphaVertex::set(&buffer[currentIndex++], - current->position[0] + totalOffset.x, - current->position[1] + totalOffset.y, - 0.0f); + current->position[0] + totalOffset.x, + current->position[1] + totalOffset.y, + 0.0f); AlphaVertex::set(&buffer[currentIndex++], - current->position[0] - totalOffset.x, - current->position[1] - totalOffset.y, - 1.0f); + current->position[0] - totalOffset.x, + current->position[1] - totalOffset.y, + 1.0f); + last = current; + current = next; + lastNormal = nextNormal; } // wrap around to beginning - AlphaVertex::set(&buffer[currentIndex++], - buffer[0].position[0], - buffer[0].position[1], 0.0f); - AlphaVertex::set(&buffer[currentIndex++], - buffer[1].position[0], - buffer[1].position[1], 1.0f); + copyAlphaVertex(&buffer[currentIndex++], &buffer[0]); + copyAlphaVertex(&buffer[currentIndex++], &buffer[1]); // 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 // create degenerate tris to start inside path int srcAindex = 0; - int srcBindex = tempVertices.size() - 1; + int srcBindex = perimeter.size() - 1; while (srcAindex <= srcBindex) { - AlphaVertex::set(&buffer[currentIndex++], - buffer[srcAindex * 2 + 1].position[0], - buffer[srcAindex * 2 + 1].position[1], - 1.0f); + copyAlphaVertex(&buffer[currentIndex++], &buffer[srcAindex * 2 + 1]); if (srcAindex == srcBindex) break; - AlphaVertex::set(&buffer[currentIndex++], - buffer[srcBindex * 2 + 1].position[0], - buffer[srcBindex * 2 + 1].position[1], - 1.0f); + copyAlphaVertex(&buffer[currentIndex++], &buffer[srcBindex * 2 + 1]); srcAindex++; srcBindex--; } #if VERTEX_DEBUG - for (unsigned int i = 0; i < vertexBuffer.mSize; i++) { - ALOGD("point at %f %f", - buffer[i].position[0], - buffer[i].position[1]); + for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) { + ALOGD("point at %f %f", buffer[i].position[0], buffer[i].position[1]); } #endif } +void getStrokeVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, float halfStrokeWidth, + VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) { + AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * perimeter.size() + 8); + + int offset = 2 * perimeter.size() + 3; + 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]); -void PathRenderer::convexPathVertices(const SkPath &path, float thresholdx, float thresholdy, - Vector<Vertex> &outputVertices) { + 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(); SkPath::Iter iter(path, true); @@ -189,31 +344,30 @@ void PathRenderer::convexPathVertices(const SkPath &path, float thresholdx, floa break; case SkPath::kLine_Verb: ALOGV("kLine_Verb %f %f -> %f %f", - pts[0].x(), pts[0].y(), - pts[1].x(), pts[1].y()); + pts[0].x(), pts[0].y(), + pts[1].x(), pts[1].y()); // TODO: make this not yuck outputVertices.push(); - newVertex = &(outputVertices.editArray()[outputVertices.size()-1]); + newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]); Vertex::set(newVertex, pts[1].x(), pts[1].y()); break; case SkPath::kQuad_Verb: ALOGV("kQuad_Verb"); recursiveQuadraticBezierVertices( - pts[0].x(), pts[0].y(), - pts[2].x(), pts[2].y(), - pts[1].x(), pts[1].y(), - thresholdx, thresholdy, - outputVertices); + pts[0].x(), pts[0].y(), + pts[2].x(), pts[2].y(), + pts[1].x(), pts[1].y(), + sqrInvScaleX, sqrInvScaleY, outputVertices); break; case SkPath::kCubic_Verb: ALOGV("kCubic_Verb"); recursiveCubicBezierVertices( - pts[0].x(), pts[0].y(), - pts[1].x(), pts[1].y(), - pts[3].x(), pts[3].y(), - pts[2].x(), pts[2].y(), - thresholdx, thresholdy, outputVertices); + pts[0].x(), pts[0].y(), + pts[1].x(), pts[1].y(), + pts[3].x(), pts[3].y(), + pts[2].x(), pts[2].y(), + sqrInvScaleX, sqrInvScaleY, outputVertices); break; default: break; @@ -224,18 +378,20 @@ void PathRenderer::convexPathVertices(const SkPath &path, float thresholdx, floa void PathRenderer::recursiveCubicBezierVertices( float p1x, float p1y, float c1x, float c1y, 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 dy = p2y - p1y; float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx); float d2 = fabs((c2x - p2x) * dy - (c2y - p2y) * dx); 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 // TODO: make this not yuck outputVertices.push(); - Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size()-1]); + Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]); Vertex::set(newVertex, p2x, p2y); } else { float p1c1x = (p1x + c1x) * 0.5f; @@ -258,13 +414,11 @@ void PathRenderer::recursiveCubicBezierVertices( recursiveCubicBezierVertices( p1x, p1y, p1c1x, p1c1y, mx, my, p1c1c2x, p1c1c2y, - thresholdx, thresholdy, - outputVertices); + sqrInvScaleX, sqrInvScaleY, outputVertices); recursiveCubicBezierVertices( mx, my, p2c1c2x, p2c1c2y, p2x, p2y, p2c2x, p2c2y, - thresholdx, thresholdy, - outputVertices); + sqrInvScaleX, sqrInvScaleY, outputVertices); } } @@ -272,16 +426,16 @@ void PathRenderer::recursiveQuadraticBezierVertices( float ax, float ay, float bx, float by, float cx, float cy, - float thresholdx, float thresholdy, Vector<Vertex> &outputVertices) { + float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) { float dx = bx - ax; float dy = by - ay; 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 // TODO: make this not yuck outputVertices.push(); - Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size()-1]); + Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]); Vertex::set(newVertex, bx, by); } else { float acx = (ax + cx) * 0.5f; @@ -294,9 +448,9 @@ void PathRenderer::recursiveQuadraticBezierVertices( float my = (acy + bcy) * 0.5f; recursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy, - thresholdx, thresholdy, outputVertices); + sqrInvScaleX, sqrInvScaleY, outputVertices); recursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy, - thresholdx, thresholdy, outputVertices); + sqrInvScaleX, sqrInvScaleY, outputVertices); } } diff --git a/libs/hwui/PathRenderer.h b/libs/hwui/PathRenderer.h index 1354f16..28a5b90 100644 --- a/libs/hwui/PathRenderer.h +++ b/libs/hwui/PathRenderer.h @@ -35,15 +35,13 @@ public: mCleanupMethod(0) {} - ~VertexBuffer() - { + ~VertexBuffer() { if (mCleanupMethod) mCleanupMethod(mBuffer); } template <class TYPE> - TYPE* alloc(int size) - { + TYPE* alloc(int size) { mSize = size; mBuffer = (void*)new TYPE[size]; mCleanupMethod = &(cleanup<TYPE>); @@ -56,8 +54,7 @@ public: private: template <class TYPE> - static void cleanup(void* buffer) - { + static void cleanup(void* buffer) { delete[] (TYPE*)buffer; } @@ -68,17 +65,15 @@ private: class PathRenderer { public: - static void computeInverseScales( - const mat4 *transform, float &inverseScaleX, float& inverseScaleY); + static SkRect computePathBounds(const SkPath& path, const SkPaint* paint); - static void convexPathFillVertices( - const SkPath &path, const mat4 *transform, - VertexBuffer &vertexBuffer, bool isAA); + static void convexPathVertices(const SkPath& path, const SkPaint* paint, + const mat4 *transform, VertexBuffer& vertexBuffer); private: - static void convexPathVertices( + static void convexPathPerimeterVertices( const SkPath &path, - float thresholdx, float thresholdy, + float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex> &outputVertices); /* @@ -86,23 +81,23 @@ private: control c */ static void recursiveQuadraticBezierVertices( - float ax, float ay, - float bx, float by, - float cx, float cy, - float thresholdx, float thresholdy, - Vector<Vertex> &outputVertices); + float ax, float ay, + float bx, float by, + float cx, float cy, + float sqrInvScaleX, float sqrInvScaleY, + Vector<Vertex> &outputVertices); /* endpoints p1, p2 control c1, c2 */ static void recursiveCubicBezierVertices( - float p1x, float p1y, - float c1x, float c1y, - float p2x, float p2y, - float c2x, float c2y, - float thresholdx, float thresholdy, - Vector<Vertex> &outputVertices); + float p1x, float p1y, + float c1x, float c1y, + float p2x, float p2y, + float c2x, float c2y, + float sqrInvScaleX, float sqrInvScaleY, + Vector<Vertex> &outputVertices); }; }; // namespace uirenderer |