Merge "Fix for scaled AA lines"
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index f6a21d4..ea40bf4 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1144,7 +1144,7 @@
* are set up for each individual segment.
*/
void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* widthCoords,
- GLvoid* lengthCoords, float strokeWidth) {
+ GLvoid* lengthCoords, float boundaryWidthProportion) {
mCaches.unbindMeshBuffer();
glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE,
gAAVertexStride, vertices);
@@ -1155,11 +1155,10 @@
glEnableVertexAttribArray(lengthSlot);
glVertexAttribPointer(lengthSlot, 1, GL_FLOAT, GL_FALSE, gAAVertexStride, lengthCoords);
int boundaryWidthSlot = mCaches.currentProgram->getUniform("boundaryWidth");
+ glUniform1f(boundaryWidthSlot, boundaryWidthProportion);
// Setting the inverse value saves computations per-fragment in the shader
int inverseBoundaryWidthSlot = mCaches.currentProgram->getUniform("inverseBoundaryWidth");
- float boundaryWidth = (1 - strokeWidth) / 2;
- glUniform1f(boundaryWidthSlot, boundaryWidth);
- glUniform1f(inverseBoundaryWidthSlot, (1 / boundaryWidth));
+ glUniform1f(inverseBoundaryWidthSlot, (1 / boundaryWidthProportion));
}
void OpenGLRenderer::finishDrawTexture() {
@@ -1458,21 +1457,66 @@
}
}
+/**
+ * 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.
+ */
void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
if (mSnapshot->isIgnored()) return;
const bool isAA = paint->isAntiAlias();
- float strokeWidth = paint->getStrokeWidth() * 0.5f;
+ // 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;
+ float inverseScaleX = 1.0f;
+ float inverseScaleY = 1.0f;
+ bool scaled = false;
int alpha;
SkXfermode::Mode mode;
int generatedVerticesCount = 0;
int verticesCount = count;
if (count > 4) {
// Polyline: account for extra vertices needed for continous tri-strip
- verticesCount += (count -4);
+ 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 (!mSnapshot->transform->isPureTranslate()) {
+ Matrix4 *mat = mSnapshot->transform;
+ float m00 = mat->data[Matrix4::kScaleX];
+ float m01 = mat->data[Matrix4::kSkewY];
+ float m02 = mat->data[2];
+ float m10 = mat->data[Matrix4::kSkewX];
+ float m11 = mat->data[Matrix4::kScaleX];
+ float m12 = mat->data[6];
+ 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;
+ if (inverseScaleX != 1.0f || inverseScaleY != 1.0f) {
+ scaled = true;
+ }
+ }
}
getAlphaAndMode(paint, &alpha, &mode);
@@ -1496,11 +1540,10 @@
if (isHairLine) {
// Set a real stroke width to be used in quad construction
- strokeWidth = .5;
- }
- if (isAA) {
+ halfStrokeWidth = isAA? 1 : .5;
+ } else if (isAA && !scaled) {
// Expand boundary to enable AA calculations on the quad border
- strokeWidth += .5f;
+ halfStrokeWidth += .5f;
}
Vertex lines[verticesCount];
Vertex* vertices = &lines[0];
@@ -1511,46 +1554,32 @@
} else {
void* widthCoords = ((GLbyte*) aaVertices) + gVertexAAWidthOffset;
void* lengthCoords = ((GLbyte*) aaVertices) + gVertexAALengthOffset;
- // innerProportion is the ratio of the inner (non-AA) port of the line to the total
+ // 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.
- float innerProportion = fmax(strokeWidth - 1.0f, 0) / (strokeWidth + .5f);
- setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords, innerProportion);
+ // 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 = 1 / (2 * halfStrokeWidth);
+ setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords, boundaryWidthProportion);
}
- AAVertex *prevAAVertex = NULL;
- Vertex *prevVertex = NULL;
- float inverseScaleX = 1.0f;
- float inverseScaleY = 1.0f;
-
- if (isHairLine) {
- // The quad that we use for AA hairlines needs to account for scaling because the line
- // should always be one pixel wide regardless of scale.
- if (!mSnapshot->transform->isPureTranslate()) {
- Matrix4 *mat = mSnapshot->transform;
- float m00 = mat->data[Matrix4::kScaleX];
- float m01 = mat->data[Matrix4::kSkewY];
- float m02 = mat->data[2];
- float m10 = mat->data[Matrix4::kSkewX];
- float m11 = mat->data[Matrix4::kScaleX];
- float m12 = mat->data[6];
- 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;
- }
- }
+ AAVertex* prevAAVertex = NULL;
+ Vertex* prevVertex = NULL;
int boundaryLengthSlot = -1;
int inverseBoundaryLengthSlot = -1;
+ int boundaryWidthSlot = -1;
+ int inverseBoundaryWidthSlot = -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() * strokeWidth;
+ vec2 n = (b - a).copyNormalized() * halfStrokeWidth;
if (isHairLine) {
if (isAA) {
float wideningFactor;
@@ -1561,8 +1590,20 @@
}
n *= wideningFactor;
}
- n.x *= inverseScaleX;
- n.y *= inverseScaleY;
+ 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 = extendedNLength / (halfStrokeWidth + extendedNLength);
+ n += extendedN;
}
float x = n.x;
n.x = -n.y;
@@ -1573,6 +1614,15 @@
vec2 abVector = (b - a);
length = abVector.length();
abVector.normalize();
+ if (scaled) {
+ abVector.x *= inverseScaleX;
+ abVector.y *= inverseScaleY;
+ float abLength = abVector.length();
+ boundaryLengthProportion = abLength / (length + abLength);
+ } else {
+ boundaryLengthProportion = .5 / (length + 1);
+ }
+ abVector /= 2;
a -= abVector;
b += abVector;
}
@@ -1607,15 +1657,25 @@
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");
+ inverseBoundaryWidthSlot =
+ mCaches.currentProgram->getUniform("inverseBoundaryWidth");
+ }
+ glUniform1f(boundaryWidthSlot, boundaryWidthProportion);
+ glUniform1f(inverseBoundaryWidthSlot, (1 / boundaryWidthProportion));
+ }
if (boundaryLengthSlot < 0) {
boundaryLengthSlot = mCaches.currentProgram->getUniform("boundaryLength");
inverseBoundaryLengthSlot =
mCaches.currentProgram->getUniform("inverseBoundaryLength");
}
- float innerProportion = (length) / (length + 2);
- float boundaryLength = (1 - innerProportion) / 2;
- glUniform1f(boundaryLengthSlot, boundaryLength);
- glUniform1f(inverseBoundaryLengthSlot, (1 / boundaryLength));
+ glUniform1f(boundaryLengthSlot, boundaryLengthProportion);
+ glUniform1f(inverseBoundaryLengthSlot, (1 / boundaryLengthProportion));
if (prevAAVertex != NULL) {
// Issue two repeat vertices to create degenerate triangles to bridge
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
index c3a91ce..f0abb50 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
@@ -150,7 +150,7 @@
canvas.save();
canvas.scale(10.0f, 10.0f);
canvas.drawLine(50.0f, 40.0f, 10.0f, 40.0f, mSmallPaint);
- canvas.drawLine(10.0f, 50.0f, 50.0f, 50.0f, mSmallPaint);
+ canvas.drawLine(10.0f, 45.0f, 20.0f, 55.0f, mSmallPaint);
canvas.drawLine(10.0f, 60.0f, 50.0f, 60.0f, mHairLinePaint);
canvas.restore();
}