Line endcaps for AA lines are now antialiased.
Also fixed other minor issues with AA and line rendering.
Change-Id: Icd4638d27c70e2ee0f28b5d9a2b97d8b29e8ac4d
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index faecadd..596781e 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -62,8 +62,10 @@
static const GLsizei gMeshStride = sizeof(TextureVertex);
static const GLsizei gVertexStride = sizeof(Vertex);
static const GLsizei gAlphaVertexStride = sizeof(AlphaVertex);
+static const GLsizei gAAVertexStride = sizeof(AAVertex);
static const GLsizei gMeshTextureOffset = 2 * sizeof(float);
-static const GLsizei gVertexAlphaOffset = 2 * sizeof(float);
+static const GLsizei gVertexAAWidthOffset = 2 * sizeof(float);
+static const GLsizei gVertexAALengthOffset = 3 * sizeof(float);
static const GLsizei gMeshCount = 4;
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 34d8fd3..049e9b7 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -915,7 +915,7 @@
}
void OpenGLRenderer::setupDrawAALine() {
- mDescription.hasWidth = true;
+ mDescription.isAA = true;
}
void OpenGLRenderer::setupDrawPoint(float pointSize) {
@@ -1121,25 +1121,30 @@
/**
* 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 width of the
- * core line primitive ("width") and the width of the fading boundary ("boundaryWidth"). The
- * "vtxDistance" attribute (one per vertex) is a value from zero to one that tells the fragment
- * shader where the fragment is in relation to the line width overall; this value is then used
- * to compute the proper color, based on whether the fragment lies in the fading AA region of
- * the line.
+ * 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* distanceCoords, float strokeWidth) {
+void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* widthCoords,
+ GLvoid* lengthCoords, float strokeWidth) {
mCaches.unbindMeshBuffer();
glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE,
- gAlphaVertexStride, vertices);
- int distanceSlot = mCaches.currentProgram->getAttrib("vtxDistance");
- glEnableVertexAttribArray(distanceSlot);
- glVertexAttribPointer(distanceSlot, 1, GL_FLOAT, GL_FALSE, gAlphaVertexStride, distanceCoords);
- int widthSlot = mCaches.currentProgram->getUniform("width");
+ gAAVertexStride, vertices);
+ int widthSlot = mCaches.currentProgram->getAttrib("vtxWidth");
+ glEnableVertexAttribArray(widthSlot);
+ glVertexAttribPointer(widthSlot, 1, GL_FLOAT, GL_FALSE, gAAVertexStride, widthCoords);
+ int lengthSlot = mCaches.currentProgram->getAttrib("vtxLength");
+ glEnableVertexAttribArray(lengthSlot);
+ glVertexAttribPointer(lengthSlot, 1, GL_FLOAT, GL_FALSE, gAAVertexStride, lengthCoords);
int boundaryWidthSlot = mCaches.currentProgram->getUniform("boundaryWidth");
+ // Setting the inverse value saves computations per-fragment in the shader
int inverseBoundaryWidthSlot = mCaches.currentProgram->getUniform("inverseBoundaryWidth");
float boundaryWidth = (1 - strokeWidth) / 2;
- glUniform1f(widthSlot, strokeWidth);
glUniform1f(boundaryWidthSlot, boundaryWidth);
glUniform1f(inverseBoundaryWidthSlot, (1 / boundaryWidth));
}
@@ -1480,20 +1485,21 @@
}
Vertex lines[verticesCount];
Vertex* vertices = &lines[0];
- AlphaVertex wLines[verticesCount];
- AlphaVertex* aaVertices = &wLines[0];
+ AAVertex wLines[verticesCount];
+ AAVertex* aaVertices = &wLines[0];
if (!isAA) {
setupDrawVertices(vertices);
} else {
- void* alphaCoords = ((GLbyte*) aaVertices) + gVertexAlphaOffset;
+ 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
// 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, alphaCoords, innerProportion);
+ setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords, innerProportion);
}
- AlphaVertex *prevAAVertex = NULL;
+ AAVertex *prevAAVertex = NULL;
Vertex *prevVertex = NULL;
float inverseScaleX = 1.0f;
float inverseScaleY = 1.0f;
@@ -1516,15 +1522,17 @@
}
}
+ int boundaryLengthSlot = -1;
+ int inverseBoundaryLengthSlot = -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;
// Find the normal to the line
vec2 n = (b - a).copyNormalized() * strokeWidth;
if (isHairLine) {
- n *= inverseScaleX;
if (isAA) {
float wideningFactor;
if (fabs(n.x) >= fabs(n.y)) {
@@ -1534,27 +1542,35 @@
}
n *= wideningFactor;
}
+ n.x *= inverseScaleX;
+ n.y *= inverseScaleY;
}
float x = n.x;
n.x = -n.y;
n.y = x;
+ // aa lines expand the endpoint vertices to encompass the AA boundary
+ if (isAA) {
+ vec2 abVector = (b - a);
+ length = abVector.length();
+ abVector.normalize();
+ 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 (!quickReject(left, top, right, bottom)) {
- // Draw the line as 2 triangles, could be optimized
- // by using only 4 vertices and the correct indices
- // Also we should probably used non textured vertices
- // when line AA is disabled to save on bandwidth
if (!isAA) {
if (prevVertex != NULL) {
// Issue two repeat vertices to create degenerate triangles to bridge
@@ -1572,24 +1588,36 @@
prevVertex = vertices - 1;
generatedVerticesCount += 4;
} else {
+ 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));
+
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.
- AlphaVertex::set(aaVertices++,prevAAVertex->position[0],
- prevAAVertex->position[1], prevAAVertex->alpha);
- AlphaVertex::set(aaVertices++, p4.x, p4.y, 1);
+ AAVertex::set(aaVertices++,prevAAVertex->position[0],
+ prevAAVertex->position[1], prevAAVertex->width, prevAAVertex->length);
+ AAVertex::set(aaVertices++, p4.x, p4.y, 1, 1);
generatedVerticesCount += 2;
}
- AlphaVertex::set(aaVertices++, p4.x, p4.y, 1);
- AlphaVertex::set(aaVertices++, p1.x, p1.y, 1);
- AlphaVertex::set(aaVertices++, p3.x, p3.y, 0);
- AlphaVertex::set(aaVertices++, p2.x, p2.y, 0);
+ 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(left, top, right, bottom, *mSnapshot->transform);
+ 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) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 0ffd70b..6ffd931 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -468,7 +468,8 @@
void setupDrawTextureTransform(mat4& transform);
void setupDrawMesh(GLvoid* vertices, GLvoid* texCoords = NULL, GLuint vbo = 0);
void setupDrawVertices(GLvoid* vertices);
- void setupDrawAALine(GLvoid* vertices, GLvoid* distanceCoords, float strokeWidth);
+ void setupDrawAALine(GLvoid* vertices, GLvoid* distanceCoords, GLvoid* lengthCoords,
+ float strokeWidth);
void finishDrawTexture();
void drawRegionRects(const Region& region);
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 62ac2ba..5bfe7a3 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -39,8 +39,9 @@
"attribute vec4 position;\n";
const char* gVS_Header_Attributes_TexCoords =
"attribute vec2 texCoords;\n";
-const char* gVS_Header_Attributes_Distance =
- "attribute float vtxDistance;\n";
+const char* gVS_Header_Attributes_AAParameters =
+ "attribute float vtxWidth;\n"
+ "attribute float vtxLength;\n";
const char* gVS_Header_Uniforms_TextureTransform =
"uniform mat4 mainTextureTransform;\n";
const char* gVS_Header_Uniforms =
@@ -60,8 +61,9 @@
"uniform mediump vec2 textureDimension;\n";
const char* gVS_Header_Varyings_HasTexture =
"varying vec2 outTexCoords;\n";
-const char* gVS_Header_Varyings_HasWidth =
- "varying float distance;\n";
+const char* gVS_Header_Varyings_IsAA =
+ "varying float widthProportion;\n"
+ "varying float lengthProportion;\n";
const char* gVS_Header_Varyings_HasBitmap =
"varying vec2 outBitmapTexCoords;\n";
const char* gVS_Header_Varyings_PointHasBitmap =
@@ -96,8 +98,9 @@
" gl_Position = transform * position;\n";
const char* gVS_Main_PointSize =
" gl_PointSize = pointSize;\n";
-const char* gVS_Main_Width =
- " distance = vtxDistance;\n";
+const char* gVS_Main_AA =
+ " widthProportion = vtxWidth;\n"
+ " lengthProportion = vtxLength;\n";
const char* gVS_Footer =
"}\n\n";
@@ -113,10 +116,11 @@
"precision mediump float;\n\n";
const char* gFS_Uniforms_Color =
"uniform vec4 color;\n";
-const char* gFS_Uniforms_Width =
- "uniform float width;\n"
+const char* gFS_Uniforms_AA =
"uniform float boundaryWidth;\n"
- "uniform float inverseBoundaryWidth;\n";
+ "uniform float inverseBoundaryWidth;\n"
+ "uniform float boundaryLength;\n"
+ "uniform float inverseBoundaryLength;\n";
const char* gFS_Header_Uniforms_PointHasBitmap =
"uniform vec2 textureDimension;\n"
"uniform float pointSize;\n";
@@ -189,11 +193,16 @@
" fragColor = color;\n";
const char* gFS_Main_ModulateColor =
" fragColor *= color.a;\n";
-const char* gFS_Main_AccountForWidth =
- " if (distance < boundaryWidth) {\n"
- " fragColor *= (distance * inverseBoundaryWidth);\n"
- " } else if (distance > (1.0 - boundaryWidth)) {\n"
- " fragColor *= ((1.0 - distance) * inverseBoundaryWidth);\n"
+const char* gFS_Main_AccountForAA =
+ " if (widthProportion < boundaryWidth) {\n"
+ " fragColor *= (widthProportion * inverseBoundaryWidth);\n"
+ " } else if (widthProportion > (1.0 - boundaryWidth)) {\n"
+ " fragColor *= ((1.0 - widthProportion) * inverseBoundaryWidth);\n"
+ " }\n"
+ " if (lengthProportion < boundaryLength) {\n"
+ " fragColor *= (lengthProportion * inverseBoundaryLength);\n"
+ " } else if (lengthProportion > (1.0 - boundaryLength)) {\n"
+ " fragColor *= ((1.0 - lengthProportion) * inverseBoundaryLength);\n"
" }\n";
const char* gFS_Main_FetchTexture[2] = {
// Don't modulate
@@ -380,8 +389,8 @@
if (description.hasTexture || description.hasExternalTexture) {
shader.append(gVS_Header_Attributes_TexCoords);
}
- if (description.hasWidth) {
- shader.append(gVS_Header_Attributes_Distance);
+ if (description.isAA) {
+ shader.append(gVS_Header_Attributes_AAParameters);
}
// Uniforms
shader.append(gVS_Header_Uniforms);
@@ -401,8 +410,8 @@
if (description.hasTexture || description.hasExternalTexture) {
shader.append(gVS_Header_Varyings_HasTexture);
}
- if (description.hasWidth) {
- shader.append(gVS_Header_Varyings_HasWidth);
+ if (description.isAA) {
+ shader.append(gVS_Header_Varyings_IsAA);
}
if (description.hasGradient) {
shader.append(gVS_Header_Varyings_HasGradient[description.gradientType]);
@@ -421,8 +430,8 @@
if (description.hasExternalTexture) {
shader.append(gVS_Main_OutTransformedTexCoords);
}
- if (description.hasWidth) {
- shader.append(gVS_Main_Width);
+ if (description.isAA) {
+ shader.append(gVS_Main_AA);
}
if (description.hasGradient) {
shader.append(gVS_Main_OutGradient[description.gradientType]);
@@ -464,8 +473,8 @@
if (description.hasTexture || description.hasExternalTexture) {
shader.append(gVS_Header_Varyings_HasTexture);
}
- if (description.hasWidth) {
- shader.append(gVS_Header_Varyings_HasWidth);
+ if (description.isAA) {
+ shader.append(gVS_Header_Varyings_IsAA);
}
if (description.hasGradient) {
shader.append(gVS_Header_Varyings_HasGradient[description.gradientType]);
@@ -491,8 +500,8 @@
if (description.hasExternalTexture) {
shader.append(gFS_Uniforms_ExternalTextureSampler);
}
- if (description.hasWidth) {
- shader.append(gFS_Uniforms_Width);
+ if (description.isAA) {
+ shader.append(gFS_Uniforms_AA);
}
if (description.hasGradient) {
shader.append(gFS_Uniforms_GradientSampler[description.gradientType]);
@@ -502,7 +511,7 @@
}
// Optimization for common cases
- if (!description.hasWidth && !blendFramebuffer &&
+ if (!description.isAA && !blendFramebuffer &&
description.colorOp == ProgramDescription::kColorNone && !description.isPoint) {
bool fast = false;
@@ -587,8 +596,8 @@
shader.append(gFS_Main_FetchColor);
}
}
- if (description.hasWidth) {
- shader.append(gFS_Main_AccountForWidth);
+ if (description.isAA) {
+ shader.append(gFS_Main_AccountForAA);
}
if (description.hasGradient) {
shader.append(gFS_Main_FetchGradient[description.gradientType]);
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index 70909fd..2586636 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -75,7 +75,7 @@
#define PROGRAM_IS_POINT_SHIFT 36
-#define PROGRAM_HAS_WIDTH_SHIFT 37
+#define PROGRAM_HAS_AA_SHIFT 37
#define PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT 38
@@ -124,7 +124,7 @@
bool hasBitmap;
bool isBitmapNpot;
- bool hasWidth;
+ bool isAA;
bool hasGradient;
Gradient gradientType;
@@ -156,7 +156,7 @@
hasAlpha8Texture = false;
hasExternalTexture = false;
- hasWidth = false;
+ isAA = false;
modulate = false;
@@ -243,7 +243,7 @@
if (swapSrcDst) key |= PROGRAM_KEY_SWAP_SRC_DST;
if (modulate) key |= programid(0x1) << PROGRAM_MODULATE_SHIFT;
if (isPoint) key |= programid(0x1) << PROGRAM_IS_POINT_SHIFT;
- if (hasWidth) key |= programid(0x1) << PROGRAM_HAS_WIDTH_SHIFT;
+ if (isAA) key |= programid(0x1) << PROGRAM_HAS_AA_SHIFT;
if (hasExternalTexture) key |= programid(0x1) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT;
return key;
}
diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h
index c120428..38455dc 100644
--- a/libs/hwui/Vertex.h
+++ b/libs/hwui/Vertex.h
@@ -68,6 +68,25 @@
}
}; // struct AlphaVertex
+/**
+ * Simple structure to describe a vertex with a position and an alpha value.
+ */
+struct AAVertex : Vertex {
+ float width;
+ float length;
+
+ static inline void set(AAVertex* vertex, float x, float y, float width, float length) {
+ Vertex::set(vertex, x, y);
+ vertex[0].width = width;
+ vertex[0].length = length;
+ }
+
+ static inline void setColor(AAVertex* vertex, float width, float length) {
+ vertex[0].width = width;
+ vertex[0].length = length;
+ }
+}; // struct AlphaVertex
+
}; // namespace uirenderer
}; // namespace android
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java
index 55fab3f..7173a85 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java
@@ -86,6 +86,14 @@
canvas.drawLines(copyPoints, 0, 12, p);
}
+ private void drawVerticalLine(Canvas canvas, Paint p, float length, float x, float y) {
+ canvas.drawLine(x, y, x, y + length, p);
+ }
+
+ private void drawDiagonalLine(Canvas canvas, Paint p, float length, float x, float y) {
+ canvas.drawLine(x, y, x + length, y + length, p);
+ }
+
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -145,6 +153,99 @@
canvas.translate(60, 0);
drawLines(canvas, p, mOffset/2, yOffset/2);
canvas.restore();
+
+ yOffset += 100;
+ canvas.save();
+ p.setStrokeWidth(1);
+ float x = 10 + mOffset;
+ for (float length = 1; length <= 10; length +=1 ) {
+ p.setAntiAlias(false);
+ drawVerticalLine(canvas, p, length, x, yOffset);
+ x += 5;
+ p.setAntiAlias(true);
+ drawVerticalLine(canvas, p, length, x, yOffset);
+ x += 5;
+ }
+ p.setStrokeWidth(5);
+ for (float length = 1; length <= 10; length +=1 ) {
+ p.setAntiAlias(false);
+ drawVerticalLine(canvas, p, length, x, yOffset);
+ x += 10;
+ p.setAntiAlias(true);
+ drawVerticalLine(canvas, p, length, x, yOffset);
+ x += 10;
+ }
+ canvas.restore();
+
+ yOffset += 20;
+ canvas.save();
+ p.setStrokeWidth(1);
+ x = 10 + mOffset;
+ for (float length = 1; length <= 10; length +=1 ) {
+ p.setAntiAlias(false);
+ drawDiagonalLine(canvas, p, length, x, yOffset);
+ x += 5;
+ p.setAntiAlias(true);
+ drawDiagonalLine(canvas, p, length, x, yOffset);
+ x += 5;
+ }
+ p.setStrokeWidth(2);
+ for (float length = 1; length <= 10; length +=1 ) {
+ p.setAntiAlias(false);
+ drawDiagonalLine(canvas, p, length, x, yOffset);
+ x += 10;
+ p.setAntiAlias(true);
+ drawDiagonalLine(canvas, p, length, x, yOffset);
+ x += 10;
+ }
+ canvas.restore();
+
+ yOffset += 20;
+ canvas.save();
+ p.setStrokeWidth(1);
+ x = 10 + mOffset;
+ for (float length = 1; length <= 10; length +=1 ) {
+ p.setAntiAlias(false);
+ canvas.drawLine(x, yOffset, x + 1, yOffset + length, p);
+ x += 5;
+ p.setAntiAlias(true);
+ canvas.drawLine(x, yOffset, x + 1, yOffset + length, p);
+ x += 5;
+ }
+ p.setStrokeWidth(2);
+ for (float length = 1; length <= 10; length +=1 ) {
+ p.setAntiAlias(false);
+ canvas.drawLine(x, yOffset, x + 1, yOffset + length, p);
+ x += 10;
+ p.setAntiAlias(true);
+ canvas.drawLine(x, yOffset, x + 1, yOffset + length, p);
+ x += 10;
+ }
+ canvas.restore();
+
+ yOffset += 20;
+ canvas.save();
+ p.setStrokeWidth(1);
+ x = 10 + mOffset;
+ for (float length = 1; length <= 10; length +=1 ) {
+ p.setAntiAlias(false);
+ canvas.drawLine(x, yOffset, x + length, yOffset + 1, p);
+ x += 5;
+ p.setAntiAlias(true);
+ canvas.drawLine(x, yOffset, x + length, yOffset + 1, p);
+ x += 5;
+ }
+ p.setStrokeWidth(2);
+ for (float length = 1; length <= 10; length +=1 ) {
+ p.setAntiAlias(false);
+ canvas.drawLine(x, yOffset, x + length, yOffset + 1, p);
+ x += 10;
+ p.setAntiAlias(true);
+ canvas.drawLine(x, yOffset, x + length, yOffset + 1, p);
+ x += 10;
+ }
+ canvas.restore();
+
}
}
}