Merge "Line endcaps for AA lines are now antialiased."

This commit is contained in:
Chet Haase
2011-05-02 15:20:15 -07:00
committed by Android (Google) Code Review
7 changed files with 225 additions and 65 deletions

View File

@ -62,8 +62,10 @@ static const TextureVertex gMeshVertices[] = {
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;
///////////////////////////////////////////////////////////////////////////////

View File

@ -915,7 +915,7 @@ void OpenGLRenderer::setupDrawWithExternalTexture() {
}
void OpenGLRenderer::setupDrawAALine() {
mDescription.hasWidth = true;
mDescription.isAA = true;
}
void OpenGLRenderer::setupDrawPoint(float pointSize) {
@ -1121,25 +1121,30 @@ void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) {
/**
* 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 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
}
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 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
}
}
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 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
}
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 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
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) {

View File

@ -468,7 +468,8 @@ private:
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);

View File

@ -39,8 +39,9 @@ const char* gVS_Header_Attributes =
"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 @@ const char* gVS_Header_Uniforms_HasBitmap =
"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 @@ const char* gVS_Main_Position =
" 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 @@ const char* gFS_Header =
"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 @@ const char* gFS_Main_FetchColor =
" 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 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
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 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
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 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
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 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
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 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
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 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
}
// 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 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
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]);

View File

@ -75,7 +75,7 @@ namespace uirenderer {
#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 @@ struct ProgramDescription {
bool hasBitmap;
bool isBitmapNpot;
bool hasWidth;
bool isAA;
bool hasGradient;
Gradient gradientType;
@ -156,7 +156,7 @@ struct ProgramDescription {
hasAlpha8Texture = false;
hasExternalTexture = false;
hasWidth = false;
isAA = false;
modulate = false;
@ -243,7 +243,7 @@ struct ProgramDescription {
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;
}

View File

@ -68,6 +68,25 @@ struct AlphaVertex : Vertex {
}
}; // 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

View File

@ -86,6 +86,14 @@ public class Lines2Activity extends Activity {
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 @@ public class Lines2Activity extends Activity {
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();
}
}
}