Add cap tessellation support
bug:7117155
bug:8114304
Currently used for lines (with and without AA) and arcs with useCenter=false
Also removes 0.375, 0.375 offset for AA lines
Change-Id: Ic8ace418739344db1e2814edf65253fe7448b0b0
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 7772f3a..06d1784 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -33,7 +33,7 @@
#include "OpenGLRenderer.h"
#include "DisplayListRenderer.h"
-#include "PathRenderer.h"
+#include "PathTessellator.h"
#include "Properties.h"
#include "Vector.h"
@@ -1469,10 +1469,6 @@
mDescription.isAA = true;
}
-void OpenGLRenderer::setupDrawVertexShape() {
- mDescription.isVertexShape = true;
-}
-
void OpenGLRenderer::setupDrawPoint(float pointSize) {
mDescription.isPoint = true;
mDescription.pointSize = pointSize;
@@ -1688,41 +1684,6 @@
mCaches.unbindIndicesBuffer();
}
-/**
- * Sets up the shader to draw an AA line. We draw AA lines with quads, where there is an
- * outer boundary that fades out to 0. The variables set in the shader define the proportion of
- * the width and length of the primitive occupied by the AA region. The vtxWidth and vtxLength
- * attributes (one per vertex) are values from zero to one that tells the fragment
- * shader where the fragment is in relation to the line width/length overall; these values are
- * then used to compute the proper color, based on whether the fragment lies in the fading AA
- * region of the line.
- * Note that we only pass down the width values in this setup function. The length coordinates
- * are set up for each individual segment.
- */
-void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* widthCoords,
- GLvoid* lengthCoords, float boundaryWidthProportion, int& widthSlot, int& lengthSlot) {
- bool force = mCaches.unbindMeshBuffer();
- mCaches.bindPositionVertexPointer(force, vertices, gAAVertexStride);
- mCaches.resetTexCoordsVertexPointer();
- mCaches.unbindIndicesBuffer();
-
- widthSlot = mCaches.currentProgram->getAttrib("vtxWidth");
- glEnableVertexAttribArray(widthSlot);
- glVertexAttribPointer(widthSlot, 1, GL_FLOAT, GL_FALSE, gAAVertexStride, widthCoords);
-
- lengthSlot = mCaches.currentProgram->getAttrib("vtxLength");
- glEnableVertexAttribArray(lengthSlot);
- glVertexAttribPointer(lengthSlot, 1, GL_FLOAT, GL_FALSE, gAAVertexStride, lengthCoords);
-
- int boundaryWidthSlot = mCaches.currentProgram->getUniform("boundaryWidth");
- glUniform1f(boundaryWidthSlot, boundaryWidthProportion);
-}
-
-void OpenGLRenderer::finishDrawAALine(const int widthSlot, const int lengthSlot) {
- glDisableVertexAttribArray(widthSlot);
- glDisableVertexAttribArray(lengthSlot);
-}
-
void OpenGLRenderer::finishDrawTexture() {
}
@@ -2083,39 +2044,26 @@
return DrawGlInfo::kStatusDrew;
}
-/**
- * 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, SkPaint* paint) {
+status_t OpenGLRenderer::drawVertexBuffer(const VertexBuffer& vertexBuffer, SkPaint* paint,
+ bool useOffset) {
+ if (!vertexBuffer.getSize()) {
+ // no vertices to draw
+ return DrawGlInfo::kStatusDone;
+ }
+
int color = paint->getColor();
SkXfermode::Mode mode = getXfermode(paint->getXfermode());
bool isAA = paint->isAntiAlias();
- VertexBuffer vertexBuffer;
- // TODO: try clipping large paths to viewport
- PathRenderer::convexPathVertices(path, paint, mSnapshot->transform, vertexBuffer);
-
- if (!vertexBuffer.getSize()) {
- // no vertices to draw
- return;
- }
-
setupDraw();
setupDrawNoTexture();
if (isAA) setupDrawAA();
- setupDrawVertexShape();
setupDrawColor(color, ((color >> 24) & 0xFF) * mSnapshot->alpha);
setupDrawColorFilter();
setupDrawShader();
setupDrawBlending(isAA, mode);
setupDrawProgram();
- setupDrawModelViewIdentity();
+ setupDrawModelViewIdentity(useOffset);
setupDrawColorUniforms();
setupDrawColorFilterUniforms();
setupDrawShaderIdentityUniforms();
@@ -2136,286 +2084,59 @@
glVertexAttribPointer(alphaSlot, 1, GL_FLOAT, GL_FALSE, gAlphaVertexStride, alphaCoords);
}
- SkRect bounds = PathRenderer::computePathBounds(path, paint);
- dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, *mSnapshot->transform);
-
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getSize());
if (isAA) {
glDisableVertexAttribArray(alphaSlot);
}
+
+ return DrawGlInfo::kStatusDrew;
}
/**
- * We draw lines as quads (tristrips). Using GL_LINES can be difficult because the rasterization
- * rules for those lines produces some unexpected results, and may vary between hardware devices.
- * The basics of lines-as-quads is easy; we simply find the normal to the line and position the
- * corners of the quads on either side of each line endpoint, separated by the strokeWidth
- * of the line. Hairlines are more involved because we need to account for transform scaling
- * to end up with a one-pixel-wide line in screen space..
- * Anti-aliased lines add another factor to the approach. We use a specialized fragment shader
- * in combination with values that we calculate and pass down in this method. The basic approach
- * is that the quad we create contains both the core line area plus a bounding area in which
- * the translucent/AA pixels are drawn. The values we calculate tell the shader what
- * proportion of the width and the length of a given segment is represented by the boundary
- * region. The quad ends up being exactly .5 pixel larger in all directions than the non-AA quad.
- * The bounding region is actually 1 pixel wide on all sides (half pixel on the outside, half pixel
- * on the inside). This ends up giving the result we want, with pixels that are completely
- * 'inside' the line area being filled opaquely and the other pixels being filled according to
- * how far into the boundary region they are, which is determined by shader interpolation.
+ * 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.
+ */
+status_t OpenGLRenderer::drawConvexPath(const SkPath& path, SkPaint* paint) {
+ VertexBuffer vertexBuffer;
+ // TODO: try clipping large paths to viewport
+ PathTessellator::tessellatePath(path, paint, mSnapshot->transform, vertexBuffer);
+
+ SkRect bounds = path.getBounds();
+ PathTessellator::expandBoundsForStroke(bounds, paint, false);
+ dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, *mSnapshot->transform);
+
+ return drawVertexBuffer(vertexBuffer, paint);
+}
+
+/**
+ * We create tristrips for the lines much like shape stroke tessellation, using a per-vertex alpha
+ * and additional geometry for defining an alpha slope perimeter.
+ *
+ * Using GL_LINES can be difficult because the rasterization rules for those lines produces some
+ * unexpected results, and may vary between hardware devices. Previously we used a varying-base
+ * in-shader alpha region, but found it to be taxing on some GPUs.
+ *
+ * TODO: try using a fixed input buffer for non-capped lines as in text rendering. this may reduce
+ * memory transfer by removing need for degenerate vertices.
*/
status_t OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
- if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;
+ if (mSnapshot->isIgnored() || count < 4) return DrawGlInfo::kStatusDone;
- const bool isAA = paint->isAntiAlias();
- // We use half the stroke width here because we're going to position the quad
- // corner vertices half of the width away from the line endpoints
- float halfStrokeWidth = paint->getStrokeWidth() * 0.5f;
- // A stroke width of 0 has a special meaning in Skia:
- // it draws a line 1 px wide regardless of current transform
- bool isHairLine = paint->getStrokeWidth() == 0.0f;
+ count &= ~0x3; // round down to nearest four
- float inverseScaleX = 1.0f;
- float inverseScaleY = 1.0f;
- bool scaled = false;
+ VertexBuffer buffer;
+ SkRect bounds;
+ PathTessellator::tessellateLines(points, count, paint, mSnapshot->transform, bounds, buffer);
+ dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, *mSnapshot->transform);
- int alpha;
- SkXfermode::Mode mode;
-
- int generatedVerticesCount = 0;
- int verticesCount = count;
- if (count > 4) {
- // Polyline: account for extra vertices needed for continuous tri-strip
- verticesCount += (count - 4);
- }
-
- if (isHairLine || isAA) {
- // The quad that we use for AA and hairlines needs to account for scaling. For hairlines
- // the line on the screen should always be one pixel wide regardless of scale. For
- // AA lines, we only want one pixel of translucent boundary around the quad.
- if (CC_UNLIKELY(!mSnapshot->transform->isPureTranslate())) {
- Matrix4 *mat = mSnapshot->transform;
- float m00 = mat->data[Matrix4::kScaleX];
- float m01 = mat->data[Matrix4::kSkewY];
- float m10 = mat->data[Matrix4::kSkewX];
- float m11 = mat->data[Matrix4::kScaleY];
-
- float scaleX = sqrtf(m00 * m00 + m01 * m01);
- float scaleY = sqrtf(m10 * m10 + m11 * m11);
-
- inverseScaleX = (scaleX != 0) ? (inverseScaleX / scaleX) : 0;
- inverseScaleY = (scaleY != 0) ? (inverseScaleY / scaleY) : 0;
-
- if (inverseScaleX != 1.0f || inverseScaleY != 1.0f) {
- scaled = true;
- }
- }
- }
-
- getAlphaAndMode(paint, &alpha, &mode);
-
- mCaches.enableScissor();
-
- setupDraw();
- setupDrawNoTexture();
- if (isAA) {
- setupDrawAA();
- }
- setupDrawColor(paint->getColor(), alpha);
- setupDrawColorFilter();
- setupDrawShader();
- setupDrawBlending(isAA, mode);
- setupDrawProgram();
- setupDrawModelViewIdentity(true);
- setupDrawColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawShaderIdentityUniforms();
-
- if (isHairLine) {
- // Set a real stroke width to be used in quad construction
- halfStrokeWidth = isAA? 1 : .5;
- } else if (isAA && !scaled) {
- // Expand boundary to enable AA calculations on the quad border
- halfStrokeWidth += .5f;
- }
-
- int widthSlot;
- int lengthSlot;
-
- Vertex lines[verticesCount];
- Vertex* vertices = &lines[0];
-
- AAVertex wLines[verticesCount];
- AAVertex* aaVertices = &wLines[0];
-
- if (CC_UNLIKELY(!isAA)) {
- setupDrawVertices(vertices);
- } else {
- void* widthCoords = ((GLbyte*) aaVertices) + gVertexAAWidthOffset;
- void* lengthCoords = ((GLbyte*) aaVertices) + gVertexAALengthOffset;
- // innerProportion is the ratio of the inner (non-AA) part of the line to the total
- // AA stroke width (the base stroke width expanded by a half pixel on either side).
- // This value is used in the fragment shader to determine how to fill fragments.
- // We will need to calculate the actual width proportion on each segment for
- // scaled non-hairlines, since the boundary proportion may differ per-axis when scaled.
- float boundaryWidthProportion = .5 - 1 / (2 * halfStrokeWidth);
- setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords,
- boundaryWidthProportion, widthSlot, lengthSlot);
- }
-
- AAVertex* prevAAVertex = NULL;
- Vertex* prevVertex = NULL;
-
- int boundaryLengthSlot = -1;
- int boundaryWidthSlot = -1;
-
- for (int i = 0; i < count; i += 4) {
- // a = start point, b = end point
- vec2 a(points[i], points[i + 1]);
- vec2 b(points[i + 2], points[i + 3]);
-
- float length = 0;
- float boundaryLengthProportion = 0;
- float boundaryWidthProportion = 0;
-
- // Find the normal to the line
- vec2 n = (b - a).copyNormalized() * halfStrokeWidth;
- float x = n.x;
- n.x = -n.y;
- n.y = x;
-
- if (isHairLine) {
- if (isAA) {
- float wideningFactor;
- if (fabs(n.x) >= fabs(n.y)) {
- wideningFactor = fabs(1.0f / n.x);
- } else {
- wideningFactor = fabs(1.0f / n.y);
- }
- n *= wideningFactor;
- }
-
- if (scaled) {
- n.x *= inverseScaleX;
- n.y *= inverseScaleY;
- }
- } else if (scaled) {
- // Extend n by .5 pixel on each side, post-transform
- vec2 extendedN = n.copyNormalized();
- extendedN /= 2;
- extendedN.x *= inverseScaleX;
- extendedN.y *= inverseScaleY;
-
- float extendedNLength = extendedN.length();
- // We need to set this value on the shader prior to drawing
- boundaryWidthProportion = .5 - extendedNLength / (halfStrokeWidth + extendedNLength);
- n += extendedN;
- }
-
- // aa lines expand the endpoint vertices to encompass the AA boundary
- if (isAA) {
- vec2 abVector = (b - a);
- length = abVector.length();
- abVector.normalize();
-
- if (scaled) {
- abVector.x *= inverseScaleX;
- abVector.y *= inverseScaleY;
- float abLength = abVector.length();
- boundaryLengthProportion = .5 - abLength / (length + abLength);
- } else {
- boundaryLengthProportion = .5 - .5 / (length + 1);
- }
-
- abVector /= 2;
- a -= abVector;
- b += abVector;
- }
-
- // Four corners of the rectangle defining a thick line
- vec2 p1 = a - n;
- vec2 p2 = a + n;
- vec2 p3 = b + n;
- vec2 p4 = b - n;
-
-
- const float left = fmin(p1.x, fmin(p2.x, fmin(p3.x, p4.x)));
- const float right = fmax(p1.x, fmax(p2.x, fmax(p3.x, p4.x)));
- const float top = fmin(p1.y, fmin(p2.y, fmin(p3.y, p4.y)));
- const float bottom = fmax(p1.y, fmax(p2.y, fmax(p3.y, p4.y)));
-
- if (!quickRejectNoScissor(left, top, right, bottom)) {
- if (!isAA) {
- if (prevVertex != NULL) {
- // Issue two repeat vertices to create degenerate triangles to bridge
- // between the previous line and the new one. This is necessary because
- // we are creating a single triangle_strip which will contain
- // potentially discontinuous line segments.
- Vertex::set(vertices++, prevVertex->position[0], prevVertex->position[1]);
- Vertex::set(vertices++, p1.x, p1.y);
- generatedVerticesCount += 2;
- }
-
- Vertex::set(vertices++, p1.x, p1.y);
- Vertex::set(vertices++, p2.x, p2.y);
- Vertex::set(vertices++, p4.x, p4.y);
- Vertex::set(vertices++, p3.x, p3.y);
-
- prevVertex = vertices - 1;
- generatedVerticesCount += 4;
- } else {
- if (!isHairLine && scaled) {
- // Must set width proportions per-segment for scaled non-hairlines to use the
- // correct AA boundary dimensions
- if (boundaryWidthSlot < 0) {
- boundaryWidthSlot =
- mCaches.currentProgram->getUniform("boundaryWidth");
- }
-
- glUniform1f(boundaryWidthSlot, boundaryWidthProportion);
- }
-
- if (boundaryLengthSlot < 0) {
- boundaryLengthSlot = mCaches.currentProgram->getUniform("boundaryLength");
- }
-
- glUniform1f(boundaryLengthSlot, boundaryLengthProportion);
-
- if (prevAAVertex != NULL) {
- // Issue two repeat vertices to create degenerate triangles to bridge
- // between the previous line and the new one. This is necessary because
- // we are creating a single triangle_strip which will contain
- // potentially discontinuous line segments.
- AAVertex::set(aaVertices++,prevAAVertex->position[0],
- prevAAVertex->position[1], prevAAVertex->width, prevAAVertex->length);
- AAVertex::set(aaVertices++, p4.x, p4.y, 1, 1);
- generatedVerticesCount += 2;
- }
-
- AAVertex::set(aaVertices++, p4.x, p4.y, 1, 1);
- AAVertex::set(aaVertices++, p1.x, p1.y, 1, 0);
- AAVertex::set(aaVertices++, p3.x, p3.y, 0, 1);
- AAVertex::set(aaVertices++, p2.x, p2.y, 0, 0);
-
- prevAAVertex = aaVertices - 1;
- generatedVerticesCount += 4;
- }
-
- dirtyLayer(a.x == b.x ? left - 1 : left, a.y == b.y ? top - 1 : top,
- a.x == b.x ? right: right, a.y == b.y ? bottom: bottom,
- *mSnapshot->transform);
- }
- }
-
- if (generatedVerticesCount > 0) {
- glDrawArrays(GL_TRIANGLE_STRIP, 0, generatedVerticesCount);
- }
-
- if (isAA) {
- finishDrawAALine(widthSlot, lengthSlot);
- }
-
- return DrawGlInfo::kStatusDrew;
+ bool useOffset = !paint->isAntiAlias();
+ return drawVertexBuffer(buffer, paint, useOffset);
}
status_t OpenGLRenderer::drawPoints(float* points, int count, SkPaint* paint) {
@@ -2526,9 +2247,7 @@
ry += outset;
}
path.addRoundRect(rect, rx, ry);
- drawConvexPath(path, p);
-
- return DrawGlInfo::kStatusDrew;
+ return drawConvexPath(path, p);
}
status_t OpenGLRenderer::drawCircle(float x, float y, float radius, SkPaint* p) {
@@ -2548,9 +2267,7 @@
} else {
path.addCircle(x, y, radius);
}
- drawConvexPath(path, p);
-
- return DrawGlInfo::kStatusDrew;
+ return drawConvexPath(path, p);
}
status_t OpenGLRenderer::drawOval(float left, float top, float right, float bottom,
@@ -2571,9 +2288,7 @@
rect.outset(p->getStrokeWidth() / 2, p->getStrokeWidth() / 2);
}
path.addOval(rect);
- drawConvexPath(path, p);
-
- return DrawGlInfo::kStatusDrew;
+ return drawConvexPath(path, p);
}
status_t OpenGLRenderer::drawArc(float left, float top, float right, float bottom,
@@ -2587,8 +2302,7 @@
}
// TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180)
- if (p->getStyle() != SkPaint::kStroke_Style || p->getPathEffect() != 0 ||
- p->getStrokeCap() != SkPaint::kButt_Cap || useCenter) {
+ if (p->getStyle() != SkPaint::kStroke_Style || p->getPathEffect() != 0 || useCenter) {
mCaches.activeTexture(0);
const PathTexture* texture = mCaches.arcShapeCache.getArc(right - left, bottom - top,
startAngle, sweepAngle, useCenter, p);
@@ -2608,9 +2322,7 @@
if (useCenter) {
path.close();
}
- drawConvexPath(path, p);
-
- return DrawGlInfo::kStatusDrew;
+ return drawConvexPath(path, p);
}
// See SkPaintDefaults.h
@@ -2637,20 +2349,17 @@
rect.outset(p->getStrokeWidth() / 2, p->getStrokeWidth() / 2);
}
path.addRect(rect);
- drawConvexPath(path, p);
-
- return DrawGlInfo::kStatusDrew;
+ return drawConvexPath(path, p);
}
if (p->isAntiAlias() && !mSnapshot->transform->isSimple()) {
SkPath path;
path.addRect(left, top, right, bottom);
- drawConvexPath(path, p);
+ return drawConvexPath(path, p);
} else {
drawColorRect(left, top, right, bottom, p->getColor(), getXfermode(p->getXfermode()));
+ return DrawGlInfo::kStatusDrew;
}
-
- return DrawGlInfo::kStatusDrew;
}
void OpenGLRenderer::drawTextShadow(SkPaint* paint, const char* text, int bytesCount, int count,