diff options
Diffstat (limited to 'libs/hwui/OpenGLRenderer.cpp')
-rw-r--r-- | libs/hwui/OpenGLRenderer.cpp | 707 |
1 files changed, 576 insertions, 131 deletions
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index d9d7d23..6c9c0eb 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -63,7 +63,7 @@ struct Blender { // In this array, the index of each Blender equals the value of the first // entry. For instance, gBlends[1] == gBlends[SkXfermode::kSrc_Mode] static const Blender gBlends[] = { - { SkXfermode::kClear_Mode, GL_ZERO, GL_ZERO }, + { SkXfermode::kClear_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA }, { SkXfermode::kSrc_Mode, GL_ONE, GL_ZERO }, { SkXfermode::kDst_Mode, GL_ZERO, GL_ONE }, { SkXfermode::kSrcOver_Mode, GL_ONE, GL_ONE_MINUS_SRC_ALPHA }, @@ -81,7 +81,7 @@ static const Blender gBlends[] = { // this array's SrcOver blending mode is actually DstOver. You can refer to // createLayer() for more information on the purpose of this array. static const Blender gBlendsSwap[] = { - { SkXfermode::kClear_Mode, GL_ZERO, GL_ZERO }, + { SkXfermode::kClear_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO }, { SkXfermode::kSrc_Mode, GL_ZERO, GL_ONE }, { SkXfermode::kDst_Mode, GL_ONE, GL_ZERO }, { SkXfermode::kSrcOver_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE }, @@ -633,26 +633,64 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) { } } +void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) { + float alpha = layer->alpha / 255.0f; + + setupDraw(); + if (layer->renderTarget == GL_TEXTURE_2D) { + setupDrawWithTexture(); + } else { + setupDrawWithExternalTexture(); + } + setupDrawTextureTransform(); + setupDrawColor(alpha, alpha, alpha, alpha); + setupDrawColorFilter(); + setupDrawBlending(layer->blend, layer->mode); + setupDrawProgram(); + setupDrawModelView(rect.left, rect.top, rect.right, rect.bottom); + setupDrawPureColorUniforms(); + setupDrawColorFilterUniforms(); + if (layer->renderTarget == GL_TEXTURE_2D) { + setupDrawTexture(layer->texture); + } else { + setupDrawExternalTexture(layer->texture); + } + setupDrawTextureTransformUniforms(layer->texTransform); + setupDrawMesh(&mMeshVertices[0].position[0], &mMeshVertices[0].texture[0]); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); + + finishDrawTexture(); +} + void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) { - const Rect& texCoords = layer->texCoords; - resetDrawTextureTexCoords(texCoords.left, texCoords.top, texCoords.right, texCoords.bottom); + if (!layer->isTextureLayer) { + const Rect& texCoords = layer->texCoords; + resetDrawTextureTexCoords(texCoords.left, texCoords.top, + texCoords.right, texCoords.bottom); - drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture, - layer->alpha / 255.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0], - &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, swap, swap); + drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture, + layer->alpha / 255.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0], + &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, swap, swap); - resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f); + resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f); + } else { + resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f); + drawTextureLayer(layer, rect); + resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f); + } } void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { #if RENDER_LAYERS_AS_REGIONS -#if RENDER_LAYERS_RECT_AS_RECT if (layer->region.isRect()) { - composeLayerRect(layer, rect); + layer->setRegionAsRect(); + + composeLayerRect(layer, layer->regionRect); + layer->region.clear(); return; } -#endif if (!layer->region.isEmpty()) { size_t count; @@ -881,12 +919,27 @@ void OpenGLRenderer::setupDrawWithTexture(bool isAlpha8) { mDescription.hasAlpha8Texture = isAlpha8; } +void OpenGLRenderer::setupDrawWithExternalTexture() { + mDescription.hasExternalTexture = true; +} + +void OpenGLRenderer::setupDrawAALine() { + mDescription.isAA = true; +} + +void OpenGLRenderer::setupDrawPoint(float pointSize) { + mDescription.isPoint = true; + mDescription.pointSize = pointSize; +} + void OpenGLRenderer::setupDrawColor(int color) { setupDrawColor(color, (color >> 24) & 0xFF); } void OpenGLRenderer::setupDrawColor(int color, int alpha) { mColorA = alpha / 255.0f; + // Second divide of a by 255 is an optimization, allowing us to simply multiply + // the rgb values by a instead of also dividing by 255 const float a = mColorA / 255.0f; mColorR = a * ((color >> 16) & 0xFF); mColorG = a * ((color >> 8) & 0xFF); @@ -897,6 +950,8 @@ void OpenGLRenderer::setupDrawColor(int color, int alpha) { void OpenGLRenderer::setupDrawAlpha8Color(int color, int alpha) { mColorA = alpha / 255.0f; + // Double-divide of a by 255 is an optimization, allowing us to simply multiply + // the rgb values by a instead of also dividing by 255 const float a = mColorA / 255.0f; mColorR = a * ((color >> 16) & 0xFF); mColorG = a * ((color >> 8) & 0xFF); @@ -935,12 +990,26 @@ void OpenGLRenderer::setupDrawColorFilter() { } } +void OpenGLRenderer::accountForClear(SkXfermode::Mode mode) { + if (mColorSet && mode == SkXfermode::kClear_Mode) { + mColorA = 1.0f; + mColorR = mColorG = mColorB = 0.0f; + mSetShaderColor = mDescription.setAlpha8Color(mColorR, mColorG, mColorB, mColorA); + } +} + void OpenGLRenderer::setupDrawBlending(SkXfermode::Mode mode, bool swapSrcDst) { + // When the blending mode is kClear_Mode, we need to use a modulate color + // argb=1,0,0,0 + accountForClear(mode); chooseBlending((mColorSet && mColorA < 1.0f) || (mShader && mShader->blend()), mode, mDescription, swapSrcDst); } void OpenGLRenderer::setupDrawBlending(bool blend, SkXfermode::Mode mode, bool swapSrcDst) { + // When the blending mode is kClear_Mode, we need to use a modulate color + // argb=1,0,0,0 + accountForClear(mode); chooseBlending(blend || (mColorSet && mColorA < 1.0f) || (mShader && mShader->blend()), mode, mDescription, swapSrcDst); } @@ -965,8 +1034,8 @@ void OpenGLRenderer::setupDrawModelViewTranslate(float left, float top, float ri } } -void OpenGLRenderer::setupDrawModelViewIdentity() { - mCaches.currentProgram->set(mOrthoMatrix, mIdentity, *mSnapshot->transform); +void OpenGLRenderer::setupDrawModelViewIdentity(bool offset) { + mCaches.currentProgram->set(mOrthoMatrix, mIdentity, *mSnapshot->transform, offset); } void OpenGLRenderer::setupDrawModelView(float left, float top, float right, float bottom, @@ -989,6 +1058,11 @@ void OpenGLRenderer::setupDrawModelView(float left, float top, float right, floa } } +void OpenGLRenderer::setupDrawPointUniforms() { + int slot = mCaches.currentProgram->getUniform("pointSize"); + glUniform1f(slot, mDescription.pointSize); +} + void OpenGLRenderer::setupDrawColorUniforms() { if (mColorSet || (mShader && mSetShaderColor)) { mCaches.currentProgram->setColor(mColorR, mColorG, mColorB, mColorA); @@ -1036,6 +1110,23 @@ void OpenGLRenderer::setupDrawTexture(GLuint texture) { glEnableVertexAttribArray(mTexCoordsSlot); } +void OpenGLRenderer::setupDrawExternalTexture(GLuint texture) { + bindExternalTexture(texture); + glUniform1i(mCaches.currentProgram->getUniform("sampler"), mTextureUnit++); + + mTexCoordsSlot = mCaches.currentProgram->getAttrib("texCoords"); + glEnableVertexAttribArray(mTexCoordsSlot); +} + +void OpenGLRenderer::setupDrawTextureTransform() { + mDescription.hasTextureTransform = true; +} + +void OpenGLRenderer::setupDrawTextureTransformUniforms(mat4& transform) { + glUniformMatrix4fv(mCaches.currentProgram->getUniform("mainTextureTransform"), 1, + GL_FALSE, &transform.data[0]); +} + void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint vbo) { if (!vertices) { mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo); @@ -1049,6 +1140,41 @@ void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint v } } +void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) { + mCaches.unbindMeshBuffer(); + glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE, + gVertexStride, 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 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) { + mCaches.unbindMeshBuffer(); + glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE, + 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"); + glUniform1f(boundaryWidthSlot, boundaryWidthProportion); + // Setting the inverse value saves computations per-fragment in the shader + int inverseBoundaryWidthSlot = mCaches.currentProgram->getUniform("inverseBoundaryWidth"); + glUniform1f(inverseBoundaryWidthSlot, (1 / boundaryWidthProportion)); +} + void OpenGLRenderer::finishDrawTexture() { glDisableVertexAttribArray(mTexCoordsSlot); } @@ -1072,6 +1198,50 @@ bool OpenGLRenderer::drawDisplayList(DisplayList* displayList, uint32_t width, u return false; } +void OpenGLRenderer::outputDisplayList(DisplayList* displayList, uint32_t level) { + if (displayList) { + displayList->output(*this, level); + } +} + +void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, SkPaint* paint) { + int alpha; + SkXfermode::Mode mode; + getAlphaAndMode(paint, &alpha, &mode); + + setTextureWrapModes(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + + float x = left; + float y = top; + + bool ignoreTransform = false; + if (mSnapshot->transform->isPureTranslate()) { + x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f); + y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f); + ignoreTransform = true; + } + + setupDraw(); + setupDrawWithTexture(true); + if (paint) { + setupDrawAlpha8Color(paint->getColor(), alpha); + } + setupDrawColorFilter(); + setupDrawShader(); + setupDrawBlending(true, mode); + setupDrawProgram(); + setupDrawModelView(x, y, x + texture->width, y + texture->height, ignoreTransform); + setupDrawTexture(texture->id); + setupDrawPureColorUniforms(); + setupDrawColorFilterUniforms(); + setupDrawShaderUniforms(); + setupDrawMesh(NULL, (GLvoid*) gMeshTextureOffset); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); + + finishDrawTexture(); +} + void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint) { const float right = left + bitmap->width(); const float bottom = top + bitmap->height(); @@ -1085,7 +1255,11 @@ void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint if (!texture) return; const AutoTexture autoCleanup(texture); - drawTextureRect(left, top, right, bottom, texture, paint); + if (bitmap->getConfig() == SkBitmap::kA8_Config) { + drawAlphaBitmap(texture, left, top, paint); + } else { + drawTextureRect(left, top, right, bottom, texture, paint); + } } void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint) { @@ -1209,10 +1383,10 @@ void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, const float width = texture->width; const float height = texture->height; - const float u1 = srcLeft / width; - const float v1 = srcTop / height; - const float u2 = srcRight / width; - const float v2 = srcBottom / height; + const float u1 = (srcLeft + 0.5f) / width; + const float v1 = (srcTop + 0.5f) / height; + const float u2 = (srcRight - 0.5f) / width; + const float v2 = (srcBottom - 0.5f) / height; mCaches.unbindMeshBuffer(); resetDrawTextureTexCoords(u1, v1, u2, v2); @@ -1297,117 +1471,375 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int } } +/** + * 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, and use a fragment + * shader to compute the translucency of the color, determined by whether a given pixel is + * within that boundary region and how far into the region it is. + */ +void OpenGLRenderer::drawAARect(float left, float top, float right, float bottom, + int color, SkXfermode::Mode mode) { + float inverseScaleX = 1.0f; + float inverseScaleY = 1.0f; + // The quad that we use needs to account for scaling. + 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; + } + + setupDraw(); + setupDrawAALine(); + setupDrawColor(color); + setupDrawColorFilter(); + setupDrawShader(); + setupDrawBlending(true, mode); + setupDrawProgram(); + setupDrawModelViewIdentity(true); + setupDrawColorUniforms(); + setupDrawColorFilterUniforms(); + setupDrawShaderIdentityUniforms(); + + AAVertex rects[4]; + AAVertex* aaVertices = &rects[0]; + void* widthCoords = ((GLbyte*) aaVertices) + gVertexAAWidthOffset; + void* lengthCoords = ((GLbyte*) aaVertices) + gVertexAALengthOffset; + + float boundarySizeX = .5 * inverseScaleX; + float boundarySizeY = .5 * inverseScaleY; + + // Adjust the rect by the AA boundary padding + left -= boundarySizeX; + right += boundarySizeX; + top -= boundarySizeY; + bottom += boundarySizeY; + + float width = right - left; + float height = bottom - top; + + float boundaryWidthProportion = (width != 0) ? (2 * boundarySizeX) / width : 0; + float boundaryHeightProportion = (height != 0) ? (2 * boundarySizeY) / height : 0; + setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords, boundaryWidthProportion); + int boundaryLengthSlot = mCaches.currentProgram->getUniform("boundaryLength"); + int inverseBoundaryLengthSlot = mCaches.currentProgram->getUniform("inverseBoundaryLength"); + glUniform1f(boundaryLengthSlot, boundaryHeightProportion); + glUniform1f(inverseBoundaryLengthSlot, (1 / boundaryHeightProportion)); + + if (!quickReject(left, top, right, bottom)) { + AAVertex::set(aaVertices++, left, bottom, 1, 1); + AAVertex::set(aaVertices++, left, top, 1, 0); + AAVertex::set(aaVertices++, right, bottom, 0, 1); + AAVertex::set(aaVertices++, right, top, 0, 0); + dirtyLayer(left, top, right, bottom, *mSnapshot->transform); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } +} + +/** + * 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(); - const float strokeWidth = paint->getStrokeWidth() * 0.5f; - // A stroke width of 0 has a special meaningin Skia: - // it draws an unscaled 1px wide line - const bool isHairLine = paint->getStrokeWidth() == 0.0f; - + // 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; - getAlphaAndMode(paint, &alpha, &mode); - - int verticesCount = count >> 2; int generatedVerticesCount = 0; - if (!isHairLine) { - // TODO: AA needs more vertices - verticesCount *= 6; - } else { - // TODO: AA will be different - verticesCount *= 2; + 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 (!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; + } + } } - TextureVertex lines[verticesCount]; - TextureVertex* vertex = &lines[0]; - + getAlphaAndMode(paint, &alpha, &mode); setupDraw(); + if (isAA) { + setupDrawAALine(); + } setupDrawColor(paint->getColor(), alpha); setupDrawColorFilter(); setupDrawShader(); - setupDrawBlending(mode); + if (isAA) { + setupDrawBlending(true, mode); + } else { + setupDrawBlending(mode); + } setupDrawProgram(); - setupDrawModelViewIdentity(); + setupDrawModelViewIdentity(true); setupDrawColorUniforms(); setupDrawColorFilterUniforms(); setupDrawShaderIdentityUniforms(); - setupDrawMesh(vertex); - if (!isHairLine) { - // TODO: Handle the AA case - 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]); - - // Bias to snap to the same pixels as Skia - a += 0.375; - b += 0.375; - - // Find the normal to the line - vec2 n = (b - a).copyNormalized() * strokeWidth; - float x = n.x; - n.x = -n.y; - n.y = x; - - // 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 - TextureVertex::set(vertex++, p1.x, p1.y, 0.0f, 0.0f); - TextureVertex::set(vertex++, p2.x, p2.y, 0.0f, 0.0f); - TextureVertex::set(vertex++, p3.x, p3.y, 0.0f, 0.0f); - TextureVertex::set(vertex++, p1.x, p1.y, 0.0f, 0.0f); - TextureVertex::set(vertex++, p3.x, p3.y, 0.0f, 0.0f); - TextureVertex::set(vertex++, p4.x, p4.y, 0.0f, 0.0f); - - generatedVerticesCount += 6; - - dirtyLayer(left, top, right, bottom, *mSnapshot->transform); + 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; + } + Vertex lines[verticesCount]; + Vertex* vertices = &lines[0]; + AAVertex wLines[verticesCount]; + AAVertex* aaVertices = &wLines[0]; + if (!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 = 1 / (2 * halfStrokeWidth); + setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords, boundaryWidthProportion); + } + + 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() * halfStrokeWidth; + 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 = extendedNLength / (halfStrokeWidth + extendedNLength); + n += extendedN; + } + 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(); + 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; } - if (generatedVerticesCount > 0) { - // GL_LINE does not give the result we want to match Skia - glDrawArrays(GL_TRIANGLES, 0, generatedVerticesCount); + // 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)) { + 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"); + 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"); + } + glUniform1f(boundaryLengthSlot, boundaryLengthProportion); + glUniform1f(inverseBoundaryLengthSlot, (1 / 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); } - } else { - // TODO: Handle the AA case - for (int i = 0; i < count; i += 4) { - const float left = fmin(points[i], points[i + 1]); - const float right = fmax(points[i], points[i + 1]); - const float top = fmin(points[i + 2], points[i + 3]); - const float bottom = fmax(points[i + 2], points[i + 3]); + } + if (generatedVerticesCount > 0) { + glDrawArrays(GL_TRIANGLE_STRIP, 0, generatedVerticesCount); + } +} + +void OpenGLRenderer::drawPoints(float* points, int count, SkPaint* paint) { + if (mSnapshot->isIgnored()) return; - if (!quickReject(left, top, right, bottom)) { - TextureVertex::set(vertex++, points[i], points[i + 1], 0.0f, 0.0f); - TextureVertex::set(vertex++, points[i + 2], points[i + 3], 0.0f, 0.0f); + // TODO: The paint's cap style defines whether the points are square or circular + // TODO: Handle AA for round points - generatedVerticesCount += 2; + // A stroke width of 0 has a special meaning in Skia: + // it draws an unscaled 1px point + float strokeWidth = paint->getStrokeWidth(); + const bool isHairLine = paint->getStrokeWidth() == 0.0f; + if (isHairLine) { + // Now that we know it's hairline, we can set the effective width, to be used later + strokeWidth = 1.0f; + } + const float halfWidth = strokeWidth / 2; + int alpha; + SkXfermode::Mode mode; + getAlphaAndMode(paint, &alpha, &mode); - dirtyLayer(left, top, right, bottom, *mSnapshot->transform); - } - } + int verticesCount = count >> 1; + int generatedVerticesCount = 0; - if (generatedVerticesCount > 0) { - glLineWidth(1.0f); - glDrawArrays(GL_LINES, 0, generatedVerticesCount); - } + TextureVertex pointsData[verticesCount]; + TextureVertex* vertex = &pointsData[0]; + + setupDraw(); + setupDrawPoint(strokeWidth); + setupDrawColor(paint->getColor(), alpha); + setupDrawColorFilter(); + setupDrawShader(); + setupDrawBlending(mode); + setupDrawProgram(); + setupDrawModelViewIdentity(true); + setupDrawColorUniforms(); + setupDrawColorFilterUniforms(); + setupDrawPointUniforms(); + setupDrawShaderIdentityUniforms(); + setupDrawMesh(vertex); + + for (int i = 0; i < count; i += 2) { + TextureVertex::set(vertex++, points[i], points[i + 1], 0.0f, 0.0f); + generatedVerticesCount++; + float left = points[i] - halfWidth; + float right = points[i] + halfWidth; + float top = points[i + 1] - halfWidth; + float bottom = points [i + 1] + halfWidth; + dirtyLayer(left, top, right, bottom, *mSnapshot->transform); } + + glDrawArrays(GL_POINTS, 0, generatedVerticesCount); } void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) { @@ -1502,7 +1934,11 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, } int color = p->getColor(); - drawColorRect(left, top, right, bottom, color, mode); + if (p->isAntiAlias() && !mSnapshot->transform->isSimple()) { + drawAARect(left, top, right, bottom, color, mode); + } else { + drawColorRect(left, top, right, bottom, color, mode); + } } void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, @@ -1512,7 +1948,16 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, } if (mSnapshot->isIgnored()) return; + // TODO: We should probably make a copy of the paint instead of modifying + // it; modifying the paint will change its generationID the first + // time, which might impact caches. More investigation needed to + // see if it matters. + // If we make a copy, then drawTextDecorations() should *not* make + // its own copy as it does right now. paint->setAntiAlias(true); +#if RENDER_TEXT_AS_GLYPHS + paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); +#endif float length = -1.0f; switch (paint->getTextAlign()) { @@ -1546,27 +1991,36 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, if (mHasShadow) { mCaches.dropShadowCache.setFontRenderer(fontRenderer); - const ShadowTexture* shadow = mCaches.dropShadowCache.get(paint, text, bytesCount, - count, mShadowRadius); + const ShadowTexture* shadow = mCaches.dropShadowCache.get( + paint, text, bytesCount, count, mShadowRadius); const AutoTexture autoCleanup(shadow); - const float sx = x - shadow->left + mShadowDx; - const float sy = y - shadow->top + mShadowDy; + const float sx = oldX - shadow->left + mShadowDx; + const float sy = oldY - shadow->top + mShadowDy; const int shadowAlpha = ((mShadowColor >> 24) & 0xFF); + int shadowColor = mShadowColor; + if (mShader) { + shadowColor = 0xffffffff; + } glActiveTexture(gTextureUnits[0]); setupDraw(); setupDrawWithTexture(true); - setupDrawAlpha8Color(mShadowColor, shadowAlpha < 255 ? shadowAlpha : alpha); + setupDrawAlpha8Color(shadowColor, shadowAlpha < 255 ? shadowAlpha : alpha); + setupDrawColorFilter(); + setupDrawShader(); setupDrawBlending(true, mode); setupDrawProgram(); - setupDrawModelView(sx, sy, sx + shadow->width, sy + shadow->height, pureTranslate); + setupDrawModelView(sx, sy, sx + shadow->width, sy + shadow->height); setupDrawTexture(shadow->id); setupDrawPureColorUniforms(); + setupDrawColorFilterUniforms(); + setupDrawShaderUniforms(); setupDrawMesh(NULL, (GLvoid*) gMeshTextureOffset); glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); + finishDrawTexture(); } @@ -1658,14 +2112,9 @@ void OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* paint) { #if RENDER_LAYERS_AS_REGIONS if (!layer->region.isEmpty()) { -#if RENDER_LAYERS_RECT_AS_RECT if (layer->region.isRect()) { - const Rect r(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight()); - composeLayerRect(layer, r); + composeLayerRect(layer, layer->regionRect); } else if (layer->mesh) { -#else - if (layer->mesh) { -#endif const float a = alpha / 255.0f; const Rect& rect = layer->layer; @@ -1675,13 +2124,11 @@ void OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* paint) { setupDrawColorFilter(); setupDrawBlending(layer->blend || layer->alpha < 255, layer->mode, false); setupDrawProgram(); + setupDrawModelViewTranslate(x, y, + x + layer->layer.getWidth(), y + layer->layer.getHeight()); setupDrawPureColorUniforms(); setupDrawColorFilterUniforms(); setupDrawTexture(layer->texture); - // TODO: The current layer, if any, will be dirtied with the bounding box - // of the layer we are drawing. Since the layer we are drawing has - // a mesh, we know the dirty region, we should use it instead - setupDrawModelViewTranslate(rect.left, rect.top, rect.right, rect.bottom); setupDrawMesh(&layer->mesh[0].position[0], &layer->mesh[0].texture[0]); glDrawElements(GL_TRIANGLES, layer->meshElementCount, @@ -1786,14 +2233,15 @@ void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float // Handle underline and strike-through uint32_t flags = paint->getFlags(); if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { + SkPaint paintCopy(*paint); float underlineWidth = length; // If length is > 0.0f, we already measured the text for the text alignment if (length <= 0.0f) { - underlineWidth = paint->measureText(text, bytesCount); + underlineWidth = paintCopy.measureText(text, bytesCount); } float offsetX = 0; - switch (paint->getTextAlign()) { + switch (paintCopy.getTextAlign()) { case SkPaint::kCenter_Align: offsetX = underlineWidth * 0.5f; break; @@ -1805,8 +2253,7 @@ void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float } if (underlineWidth > 0.0f) { - const float textSize = paint->getTextSize(); - // TODO: Support stroke width < 1.0f when we have AA lines + const float textSize = paintCopy.getTextSize(); const float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f); const float left = x - offsetX; @@ -1836,10 +2283,9 @@ void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float points[currentPoint++] = top; } - SkPaint linesPaint(*paint); - linesPaint.setStrokeWidth(strokeWidth); + paintCopy.setStrokeWidth(strokeWidth); - drawLines(&points[0], pointsCount, &linesPaint); + drawLines(&points[0], pointsCount, &paintCopy); } } } @@ -2004,12 +2450,11 @@ void OpenGLRenderer::getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mod } SkXfermode::Mode OpenGLRenderer::getXfermode(SkXfermode* mode) { - // In the future we should look at unifying the Porter-Duff modes and - // SkXferModes so that we can use SkXfermode::IsMode(xfer, &mode). - if (mode == NULL) { - return SkXfermode::kSrcOver_Mode; + SkXfermode::Mode resultMode; + if (!SkXfermode::AsMode(mode, &resultMode)) { + resultMode = SkXfermode::kSrcOver_Mode; } - return mode->fMode; + return resultMode; } void OpenGLRenderer::setTextureWrapModes(Texture* texture, GLenum wrapS, GLenum wrapT) { |