Esempio n. 1
0
bool GrDistanceFieldTextContext::canDraw(const SkPaint& paint, const SkMatrix& viewMatrix) {
    // TODO: support perspective (need getMaxScale replacement)
    if (viewMatrix.hasPerspective()) {
        return false;
    }

    SkScalar maxScale = viewMatrix.getMaxScale();
    SkScalar scaledTextSize = maxScale*paint.getTextSize();
    // Scaling up beyond 2x yields undesireable artifacts
    if (scaledTextSize > 2*kLargeDFFontSize) {
        return false;
    }

    if (!fEnableDFRendering && !paint.isDistanceFieldTextTEMP() &&
        scaledTextSize < kLargeDFFontSize) {
        return false;
    }

    // rasterizers and mask filters modify alpha, which doesn't
    // translate well to distance
    if (paint.getRasterizer() || paint.getMaskFilter() ||
        !fContext->getTextTarget()->caps()->shaderDerivativeSupport()) {
        return false;
    }

    // TODO: add some stroking support
    if (paint.getStyle() != SkPaint::kFill_Style) {
        return false;
    }

    return true;
}
bool GrAADistanceFieldPathRenderer::canDrawPath(const GrDrawTarget* target,
                                                const GrPipelineBuilder* pipelineBuilder,
                                                const SkMatrix& viewMatrix,
                                                const SkPath& path,
                                                const GrStrokeInfo& stroke,
                                                bool antiAlias) const {
    
    // TODO: Support inverse fill
    // TODO: Support strokes
    if (!target->caps()->shaderCaps()->shaderDerivativeSupport() || !antiAlias 
        || path.isInverseFillType() || path.isVolatile() || !stroke.isFillStyle()) {
        return false;
    }

    // currently don't support perspective
    if (viewMatrix.hasPerspective()) {
        return false;
    }
    
    // only support paths smaller than 64x64, scaled to less than 256x256
    // the goal is to accelerate rendering of lots of small paths that may be scaling
    SkScalar maxScale = viewMatrix.getMaxScale();
    const SkRect& bounds = path.getBounds();
    SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
    return maxDim < 64.f && maxDim * maxScale < 256.f;
}
bool GrStencilAndCoverTextContext::canDraw(const GrRenderTarget* rt,
                                           const GrClip& clip,
                                           const GrPaint& paint,
                                           const SkPaint& skPaint,
                                           const SkMatrix& viewMatrix) {
    if (skPaint.getRasterizer()) {
        return false;
    }
    if (skPaint.getMaskFilter()) {
        return false;
    }
    if (SkPathEffect* pe = skPaint.getPathEffect()) {
        if (pe->asADash(NULL) != SkPathEffect::kDash_DashType) {
            return false;
        }
    }

    // No hairlines unless we can map the 1 px width to the object space.
    if (skPaint.getStyle() == SkPaint::kStroke_Style
        && skPaint.getStrokeWidth() == 0
        && viewMatrix.hasPerspective()) {
        return false;
    }

    // No color bitmap fonts.
    SkScalerContext::Rec    rec;
    SkScalerContext::MakeRec(skPaint, &fDeviceProperties, NULL, &rec);
    return rec.getFormat() != SkMask::kARGB32_Format;
}
Esempio n. 4
0
static void tesselate(intptr_t vertices,
                      size_t vertexStride,
                      GrColor color,
                      const SkMatrix& viewMatrix,
                      const SkRect& rect,
                      const GrQuad* localQuad) {
    SkPoint* positions = reinterpret_cast<SkPoint*>(vertices);

    positions->setRectFan(rect.fLeft, rect.fTop,
                          rect.fRight, rect.fBottom, vertexStride);

    if (!viewMatrix.hasPerspective()) {
        viewMatrix.mapPointsWithStride(positions, vertexStride,
                                       NonAAFillRectBatchBase::kVertsPerInstance);
    }

    // Setup local coords
    // TODO we should only do this if local coords are being read
    if (localQuad) {
        static const int kLocalOffset = sizeof(SkPoint) + sizeof(GrColor);
        for (int i = 0; i < NonAAFillRectBatchBase::kVertsPerInstance; i++) {
            SkPoint* coords = reinterpret_cast<SkPoint*>(vertices + kLocalOffset +
                              i * vertexStride);
            *coords = localQuad->point(i);
        }
    }

    static const int kColorOffset = sizeof(SkPoint);
    GrColor* vertColor = reinterpret_cast<GrColor*>(vertices + kColorOffset);
    for (int j = 0; j < 4; ++j) {
        *vertColor = color;
        vertColor = (GrColor*) ((intptr_t) vertColor + vertexStride);
    }
}
Esempio n. 5
0
bool Append(GrBatch* origBatch,
            GrColor color,
            const SkMatrix& viewMatrix,
            const SkRect& rect,
            const SkRect* localRect,
            const SkMatrix* localMatrix) {
    bool usePerspective = viewMatrix.hasPerspective() ||
                          (localMatrix && localMatrix->hasPerspective());

    if (usePerspective && origBatch->classID() != NonAAFillRectBatchPerspective::ClassID()) {
        return false;
    }

    if (!usePerspective) {
        NonAAFillRectBatchSimple* batch = origBatch->cast<NonAAFillRectBatchSimple>();
        append_to_batch(batch, color, viewMatrix, rect, localRect, localMatrix);
        batch->updateBoundsAfterAppend();
    } else {
        NonAAFillRectBatchPerspective* batch = origBatch->cast<NonAAFillRectBatchPerspective>();
        const NonAAFillRectBatchPerspective::Geometry& geo = batch->geoData()->back();

        if (!geo.fViewMatrix.cheapEqualTo(viewMatrix) ||
                geo.fHasLocalRect != SkToBool(localRect) ||
                geo.fHasLocalMatrix != SkToBool(localMatrix) ||
                (geo.fHasLocalMatrix && !geo.fLocalMatrix.cheapEqualTo(*localMatrix))) {
            return false;
        }

        append_to_batch(batch, color, viewMatrix, rect, localRect, localMatrix);
        batch->updateBoundsAfterAppend();
    }

    return true;
}
bool check_bounds(const SkMatrix& viewMatrix, const SkRect& devBounds, void* vertices, int vCount)
{
    SkRect tolDevBounds = devBounds;
    // The bounds ought to be tight, but in perspective the below code runs the verts
    // through the view matrix to get back to dev coords, which can introduce imprecision.
    if (viewMatrix.hasPerspective()) {
        tolDevBounds.outset(SK_Scalar1 / 1000, SK_Scalar1 / 1000);
    } else {
        // Non-persp matrices cause this path renderer to draw in device space.
        SkASSERT(viewMatrix.isIdentity());
    }
    SkRect actualBounds;

    VertexType* verts = reinterpret_cast<VertexType*>(vertices);
    bool first = true;
    for (int i = 0; i < vCount; ++i) {
        SkPoint pos = verts[i].fPos;
        // This is a hack to workaround the fact that we move some degenerate segments offscreen.
        if (SK_ScalarMax == pos.fX) {
            continue;
        }
        viewMatrix.mapPoints(&pos, 1);
        if (first) {
            actualBounds.set(pos.fX, pos.fY, pos.fX, pos.fY);
            first = false;
        } else {
            actualBounds.growToInclude(pos.fX, pos.fY);
        }
    }
    if (!first) {
        return tolDevBounds.contains(actualBounds);
    }

    return true;
}
Esempio n. 7
0
GrFillRRectOp::GrFillRRectOp(
        GrAAType aaType, const SkRRect& rrect, Flags flags,
        const SkMatrix& totalShapeMatrix, GrPaint&& paint, const SkRect& devBounds)
        : GrDrawOp(ClassID())
        , fAAType(aaType)
        , fOriginalColor(paint.getColor4f())
        , fLocalRect(rrect.rect())
        , fFlags(flags)
        , fProcessors(std::move(paint)) {
    SkASSERT((fFlags & Flags::kHasPerspective) == totalShapeMatrix.hasPerspective());
    this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);

    // Write the matrix attribs.
    const SkMatrix& m = totalShapeMatrix;
    if (!(fFlags & Flags::kHasPerspective)) {
        // Affine 2D transformation (float2x2 plus float2 translate).
        SkASSERT(!m.hasPerspective());
        this->writeInstanceData(m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY());
        this->writeInstanceData(m.getTranslateX(), m.getTranslateY());
    } else {
        // Perspective float3x3 transformation matrix.
        SkASSERT(m.hasPerspective());
        m.get9(this->appendInstanceData<float>(9));
    }

    // Convert the radii to [-1, -1, +1, +1] space and write their attribs.
    Sk4f radiiX, radiiY;
    Sk4f::Load2(SkRRectPriv::GetRadiiArray(rrect), &radiiX, &radiiY);
    (radiiX * (2/rrect.width())).store(this->appendInstanceData<float>(4));
    (radiiY * (2/rrect.height())).store(this->appendInstanceData<float>(4));

    // We will write the color and local rect attribs during finalize().
}
bool GrAAHairLinePathRenderer::createGeom(
            const SkPath& path,
            GrDrawTarget* target,
            int* lineCnt,
            int* quadCnt,
            GrDrawTarget::AutoReleaseGeometry* arg) {
    const GrDrawState& drawState = target->getDrawState();
    int rtHeight = drawState.getRenderTarget()->height();

    GrIRect devClipBounds;
    target->getClip()->getConservativeBounds(drawState.getRenderTarget(),
                                             &devClipBounds);

    GrVertexLayout layout = GrDrawState::kEdge_VertexLayoutBit;
    SkMatrix viewM = drawState.getViewMatrix();

    PREALLOC_PTARRAY(128) lines;
    PREALLOC_PTARRAY(128) quads;
    IntArray qSubdivs;
    *quadCnt = generate_lines_and_quads(path, viewM, devClipBounds,
                                        &lines, &quads, &qSubdivs);

    *lineCnt = lines.count() / 2;
    int vertCnt = kVertsPerLineSeg * *lineCnt + kVertsPerQuad * *quadCnt;

    GrAssert(sizeof(Vertex) == GrDrawState::VertexSize(layout));

    if (!arg->set(target, layout, vertCnt, 0)) {
        return false;
    }

    Vertex* verts = reinterpret_cast<Vertex*>(arg->vertices());

    const SkMatrix* toDevice = NULL;
    const SkMatrix* toSrc = NULL;
    SkMatrix ivm;

    if (viewM.hasPerspective()) {
        if (viewM.invert(&ivm)) {
            toDevice = &viewM;
            toSrc = &ivm;
        }
    }

    for (int i = 0; i < *lineCnt; ++i) {
        add_line(&lines[2*i], rtHeight, toSrc, &verts);
    }

    int unsubdivQuadCnt = quads.count() / 3;
    for (int i = 0; i < unsubdivQuadCnt; ++i) {
        GrAssert(qSubdivs[i] >= 0);
        add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts);
    }

    return true;
}
Esempio n. 9
0
SkAxisAlignment SkComputeAxisAlignmentForHText(const SkMatrix& matrix) {
    SkASSERT(!matrix.hasPerspective());
    
    if (0 == matrix[SkMatrix::kMSkewY]) {
        return kX_SkAxisAlignment;
    }
    if (0 == matrix[SkMatrix::kMScaleX]) {
        return kY_SkAxisAlignment;
    }
    return kNone_SkAxisAlignment;
}
Esempio n. 10
0
SkShader::Context::MatrixClass SkShader::Context::ComputeMatrixClass(const SkMatrix& mat) {
    MatrixClass mc = kLinear_MatrixClass;

    if (mat.hasPerspective()) {
        if (mat.isFixedStepInX()) {
            mc = kFixedStepInX_MatrixClass;
        } else {
            mc = kPerspective_MatrixClass;
        }
    }
    return mc;
}
Esempio n. 11
0
void GrDrawContext::drawPaint(GrRenderTarget* rt,
                              const GrClip& clip,
                              const GrPaint& origPaint,
                              const SkMatrix& viewMatrix) {
    RETURN_IF_ABANDONED
    // set rect to be big enough to fill the space, but not super-huge, so we
    // don't overflow fixed-point implementations
    SkRect r;
    r.setLTRB(0, 0,
              SkIntToScalar(rt->width()),
              SkIntToScalar(rt->height()));
    SkTCopyOnFirstWrite<GrPaint> paint(origPaint);

    // by definition this fills the entire clip, no need for AA
    if (paint->isAntiAlias()) {
        paint.writable()->setAntiAlias(false);
    }

    bool isPerspective = viewMatrix.hasPerspective();

    // We attempt to map r by the inverse matrix and draw that. mapRect will
    // map the four corners and bound them with a new rect. This will not
    // produce a correct result for some perspective matrices.
    if (!isPerspective) {
        SkMatrix inverse;
        if (!viewMatrix.invert(&inverse)) {
            SkDebugf("Could not invert matrix\n");
            return;
        }
        inverse.mapRect(&r);
        this->drawRect(rt, clip, *paint, viewMatrix, r);
    } else {
        SkMatrix localMatrix;
        if (!viewMatrix.invert(&localMatrix)) {
            SkDebugf("Could not invert matrix\n");
            return;
        }

        AutoCheckFlush acf(fContext);
        if (!this->prepareToDraw(rt)) {
            return;
        }

        GrPipelineBuilder pipelineBuilder(*paint, rt, clip);
        fDrawTarget->drawBWRect(pipelineBuilder,
                                paint->getColor(),
                                SkMatrix::I(),
                                r,
                                NULL,
                                &localMatrix);
    }
}
Esempio n. 12
0
void GrTextUtils::InitDistanceFieldPaint(GrAtlasTextBlob* blob,
                                         SkPaint* skPaint,
                                         SkScalar* textRatio,
                                         const SkMatrix& viewMatrix) {
    // getMaxScale doesn't support perspective, so neither do we at the moment
    SkASSERT(!viewMatrix.hasPerspective());
    SkScalar maxScale = viewMatrix.getMaxScale();
    SkScalar textSize = skPaint->getTextSize();
    SkScalar scaledTextSize = textSize;
    // if we have non-unity scale, we need to choose our base text size
    // based on the SkPaint's text size multiplied by the max scale factor
    // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)?
    if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) {
        scaledTextSize *= maxScale;
    }

    // We have three sizes of distance field text, and within each size 'bucket' there is a floor
    // and ceiling.  A scale outside of this range would require regenerating the distance fields
    SkScalar dfMaskScaleFloor;
    SkScalar dfMaskScaleCeil;
    if (scaledTextSize <= kSmallDFFontLimit) {
        dfMaskScaleFloor = kMinDFFontSize;
        dfMaskScaleCeil = kSmallDFFontLimit;
        *textRatio = textSize / kSmallDFFontSize;
        skPaint->setTextSize(SkIntToScalar(kSmallDFFontSize));
    } else if (scaledTextSize <= kMediumDFFontLimit) {
        dfMaskScaleFloor = kSmallDFFontLimit;
        dfMaskScaleCeil = kMediumDFFontLimit;
        *textRatio = textSize / kMediumDFFontSize;
        skPaint->setTextSize(SkIntToScalar(kMediumDFFontSize));
    } else {
        dfMaskScaleFloor = kMediumDFFontLimit;
        dfMaskScaleCeil = kLargeDFFontLimit;
        *textRatio = textSize / kLargeDFFontSize;
        skPaint->setTextSize(SkIntToScalar(kLargeDFFontSize));
    }

    // Because there can be multiple runs in the blob, we want the overall maxMinScale, and
    // minMaxScale to make regeneration decisions.  Specifically, we want the maximum minimum scale
    // we can tolerate before we'd drop to a lower mip size, and the minimum maximum scale we can
    // tolerate before we'd have to move to a large mip size.  When we actually test these values
    // we look at the delta in scale between the new viewmatrix and the old viewmatrix, and test
    // against these values to decide if we can reuse or not(ie, will a given scale change our mip
    // level)
    SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScaleCeil);
    blob->setMinAndMaxScale(dfMaskScaleFloor / scaledTextSize, dfMaskScaleCeil / scaledTextSize);

    skPaint->setLCDRenderText(false);
    skPaint->setAutohinted(false);
    skPaint->setHinting(SkPaint::kNormal_Hinting);
    skPaint->setSubpixelText(true);
}
Esempio n. 13
0
static inline bool get_direction(const SkPath& path, const SkMatrix& m, SkPath::Direction* dir) {
    if (!path.cheapComputeDirection(dir)) {
        return false;
    }
    // check whether m reverses the orientation
    SkASSERT(!m.hasPerspective());
    SkScalar det2x2 = SkScalarMul(m.get(SkMatrix::kMScaleX), m.get(SkMatrix::kMScaleY)) -
                      SkScalarMul(m.get(SkMatrix::kMSkewX), m.get(SkMatrix::kMSkewY));
    if (det2x2 < 0) {
        *dir = SkPath::OppositeDirection(*dir);
    }
    return true;
}
Esempio n. 14
0
GrCCPathCache::MaskTransform::MaskTransform(const SkMatrix& m, SkIVector* shift)
        : fMatrix2x2{m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY()} {
    SkASSERT(!m.hasPerspective());
    Sk2f translate = Sk2f(m.getTranslateX(), m.getTranslateY());
    Sk2f transFloor;
#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
    // On Android framework we pre-round view matrix translates to integers for better caching.
    transFloor = translate;
#else
    transFloor = translate.floor();
    (translate - transFloor).store(fSubpixelTranslate);
#endif
    shift->set((int)transFloor[0], (int)transFloor[1]);
    SkASSERT((float)shift->fX == transFloor[0]);  // Make sure transFloor had integer values.
    SkASSERT((float)shift->fY == transFloor[1]);
}
Esempio n. 15
0
SkScalar scaled_text_size(const SkScalar textSize, const SkMatrix& viewMatrix) {
    SkScalar scaledTextSize = textSize;

    if (viewMatrix.hasPerspective()) {
        // for perspective, we simply force to the medium size
        // TODO: compute a size based on approximate screen area
        scaledTextSize = kMediumDFFontLimit;
    } else {
        SkScalar maxScale = viewMatrix.getMaxScale();
        // if we have non-unity scale, we need to choose our base text size
        // based on the SkPaint's text size multiplied by the max scale factor
        // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)?
        if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) {
            scaledTextSize *= maxScale;
        }
    }

    return scaledTextSize;
}
Esempio n. 16
0
inline static void append_to_batch(NonAAFillRectBatchPerspective* batch, GrColor color,
                                   const SkMatrix& viewMatrix, const SkRect& rect,
                                   const SkRect* localRect, const SkMatrix* localMatrix) {
    SkASSERT(viewMatrix.hasPerspective() || (localMatrix && localMatrix->hasPerspective()));
    NonAAFillRectBatchPerspective::Geometry& geo = batch->geoData()->push_back();

    geo.fColor = color;
    geo.fViewMatrix = viewMatrix;
    geo.fRect = rect;
    geo.fHasLocalRect = SkToBool(localRect);
    geo.fHasLocalMatrix = SkToBool(localMatrix);
    if (localMatrix) {
        geo.fLocalMatrix = *localMatrix;
    }
    if (localRect) {
        geo.fLocalRect = *localRect;
    }

}
Esempio n. 17
0
inline static void append_to_batch(NonAAFillRectBatchSimple* batch, GrColor color,
                                   const SkMatrix& viewMatrix, const SkRect& rect,
                                   const SkRect* localRect, const SkMatrix* localMatrix) {
    SkASSERT(!viewMatrix.hasPerspective() && (!localMatrix || !localMatrix->hasPerspective()));
    NonAAFillRectBatchSimple::Geometry& geo = batch->geoData()->push_back();

    geo.fColor = color;
    geo.fViewMatrix = viewMatrix;
    geo.fRect = rect;

    if (localRect && localMatrix) {
        geo.fLocalQuad.setFromMappedRect(*localRect, *localMatrix);
    } else if (localRect) {
        geo.fLocalQuad.set(*localRect);
    } else if (localMatrix) {
        geo.fLocalQuad.setFromMappedRect(rect, *localMatrix);
    } else {
        geo.fLocalQuad.set(rect);
    }
}
Esempio n. 18
0
bool GrTextUtils::CanDrawAsDistanceFields(const SkPaint& skPaint, const SkMatrix& viewMatrix,
                                          const SkSurfaceProps& props, const GrShaderCaps& caps) {
    // TODO: support perspective (need getMaxScale replacement)
    if (viewMatrix.hasPerspective()) {
        return false;
    }

    SkScalar maxScale = viewMatrix.getMaxScale();
    SkScalar scaledTextSize = maxScale*skPaint.getTextSize();
    // Hinted text looks far better at small resolutions
    // Scaling up beyond 2x yields undesireable artifacts
    if (scaledTextSize < kMinDFFontSize ||
        scaledTextSize > kLargeDFFontLimit) {
        return false;
    }

    bool useDFT = props.isUseDeviceIndependentFonts();
#if SK_FORCE_DISTANCE_FIELD_TEXT
    useDFT = true;
#endif

    if (!useDFT && scaledTextSize < kLargeDFFontSize) {
        return false;
    }

    // rasterizers and mask filters modify alpha, which doesn't
    // translate well to distance
    if (skPaint.getRasterizer() || skPaint.getMaskFilter() || !caps.shaderDerivativeSupport()) {
        return false;
    }

    // TODO: add some stroking support
    if (skPaint.getStyle() != SkPaint::kFill_Style) {
        return false;
    }

    return true;
}
Esempio n. 19
0
/**
 *  Returns PS function code that applies inverse perspective
 *  to a x, y point.
 *  The function assumes that the stack has at least two elements,
 *  and that the top 2 elements are numeric values.
 *  After executing this code on a PS stack, the last 2 elements are updated
 *  while the rest of the stack is preserved intact.
 *  inversePerspectiveMatrix is the inverse perspective matrix.
 */
static SkString apply_perspective_to_coordinates(
        const SkMatrix& inversePerspectiveMatrix) {
    SkString code;
    if (!inversePerspectiveMatrix.hasPerspective()) {
        return code;
    }

    // Perspective matrix should be:
    // 1   0  0
    // 0   1  0
    // p0 p1 p2

    const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0];
    const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1];
    const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2];

    // y = y / (p2 + p0 x + p1 y)
    // x = x / (p2 + p0 x + p1 y)

    // Input on stack: x y
    code.append(" dup ");               // x y y
    code.appendScalar(p1);              // x y y p1
    code.append(" mul "                 // x y y*p1
                " 2 index ");           // x y y*p1 x
    code.appendScalar(p0);              // x y y p1 x p0
    code.append(" mul ");               // x y y*p1 x*p0
    code.appendScalar(p2);              // x y y p1 x*p0 p2
    code.append(" add "                 // x y y*p1 x*p0+p2
                "add "                  // x y y*p1+x*p0+p2
                "3 1 roll "             // y*p1+x*p0+p2 x y
                "2 index "              // z x y y*p1+x*p0+p2
                "div "                  // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2)
                "3 1 roll "             // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x
                "exch "                 // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2
                "div "                  // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2)
                "exch\n");              // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2)
    return code;
}
Esempio n. 20
0
void GrGLGeometryProcessor::setupPosition(GrGLGPBuilder* pb,
                                          GrGPArgs* gpArgs,
                                          const char* posName,
                                          const SkMatrix& mat) {
    GrGLVertexBuilder* vsBuilder = pb->getVertexShaderBuilder();
    if (mat.isIdentity()) {
        gpArgs->fPositionVar.set(kVec2f_GrSLType, "pos2");

        vsBuilder->codeAppendf("vec2 %s = %s;", gpArgs->fPositionVar.c_str(), posName);
    } else if (!mat.hasPerspective()) {
        this->addUniformViewMatrix(pb);
        gpArgs->fPositionVar.set(kVec2f_GrSLType, "pos2");

        vsBuilder->codeAppendf("vec2 %s = vec2(%s * vec3(%s, 1));",
                               gpArgs->fPositionVar.c_str(), this->uViewM(), posName);
    } else {
        this->addUniformViewMatrix(pb);
        gpArgs->fPositionVar.set(kVec3f_GrSLType, "pos3");

        vsBuilder->codeAppendf("vec3 %s = %s * vec3(%s, 1);",
                               gpArgs->fPositionVar.c_str(), this->uViewM(), posName);
    }
}
Esempio n. 21
0
bool GrTextContext::CanDrawAsDistanceFields(const SkPaint& paint, const SkFont& font,
                                            const SkMatrix& viewMatrix,
                                            const SkSurfaceProps& props,
                                            bool contextSupportsDistanceFieldText,
                                            const Options& options) {
    if (!viewMatrix.hasPerspective()) {
        SkScalar maxScale = viewMatrix.getMaxScale();
        SkScalar scaledTextSize = maxScale * font.getSize();
        // Hinted text looks far better at small resolutions
        // Scaling up beyond 2x yields undesireable artifacts
        if (scaledTextSize < options.fMinDistanceFieldFontSize ||
            scaledTextSize > options.fMaxDistanceFieldFontSize) {
            return false;
        }

        bool useDFT = props.isUseDeviceIndependentFonts();
#if SK_FORCE_DISTANCE_FIELD_TEXT
        useDFT = true;
#endif

        if (!useDFT && scaledTextSize < kLargeDFFontSize) {
            return false;
        }
    }

    // mask filters modify alpha, which doesn't translate well to distance
    if (paint.getMaskFilter() || !contextSupportsDistanceFieldText) {
        return false;
    }

    // TODO: add some stroking support
    if (paint.getStyle() != SkPaint::kFill_Style) {
        return false;
    }

    return true;
}
Esempio n. 22
0
/**
 *  Returns PS function code that applies inverse perspective
 *  to a x, y point.
 *  The function assumes that the stack has at least two elements,
 *  and that the top 2 elements are numeric values.
 *  After executing this code on a PS stack, the last 2 elements are updated
 *  while the rest of the stack is preserved intact.
 *  inversePerspectiveMatrix is the inverse perspective matrix.
 */
static void apply_perspective_to_coordinates(const SkMatrix& inversePerspectiveMatrix,
                                             SkDynamicMemoryWStream* code) {
    if (!inversePerspectiveMatrix.hasPerspective()) {
        return;
    }

    // Perspective matrix should be:
    // 1   0  0
    // 0   1  0
    // p0 p1 p2

    const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0];
    const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1];
    const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2];

    // y = y / (p2 + p0 x + p1 y)
    // x = x / (p2 + p0 x + p1 y)

    // Input on stack: x y
    code->writeText(" dup ");             // x y y
    SkPDFUtils::AppendScalar(p1, code);   // x y y p1
    code->writeText(" mul "               // x y y*p1
                    " 2 index ");         // x y y*p1 x
    SkPDFUtils::AppendScalar(p0, code);   // x y y p1 x p0
    code->writeText(" mul ");             // x y y*p1 x*p0
    SkPDFUtils::AppendScalar(p2, code);   // x y y p1 x*p0 p2
    code->writeText(" add "               // x y y*p1 x*p0+p2
                    "add "                // x y y*p1+x*p0+p2
                    "3 1 roll "           // y*p1+x*p0+p2 x y
                    "2 index "            // z x y y*p1+x*p0+p2
                    "div "                // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2)
                    "3 1 roll "           // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x
                    "exch "               // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2
                    "div "                // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2)
                    "exch\n");            // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2)
}
Esempio n. 23
0
SkPDFFunctionShader* SkPDFFunctionShader::Create(
        SkPDFCanon* canon, SkAutoTDelete<SkPDFShader::State>* autoState) {
    const SkPDFShader::State& state = **autoState;

    SkString (*codeFunction)(const SkShader::GradientInfo& info,
                             const SkMatrix& perspectiveRemover) = NULL;
    SkPoint transformPoints[2];

    // Depending on the type of the gradient, we want to transform the
    // coordinate space in different ways.
    const SkShader::GradientInfo* info = &state.fInfo;
    transformPoints[0] = info->fPoint[0];
    transformPoints[1] = info->fPoint[1];
    switch (state.fType) {
        case SkShader::kLinear_GradientType:
            codeFunction = &linearCode;
            break;
        case SkShader::kRadial_GradientType:
            transformPoints[1] = transformPoints[0];
            transformPoints[1].fX += info->fRadius[0];
            codeFunction = &radialCode;
            break;
        case SkShader::kRadial2_GradientType: {
            // Bail out if the radii are the same.
            if (info->fRadius[0] == info->fRadius[1]) {
                return NULL;
            }
            transformPoints[1] = transformPoints[0];
            SkScalar dr = info->fRadius[1] - info->fRadius[0];
            transformPoints[1].fX += dr;
            codeFunction = &twoPointRadialCode;
            break;
        }
        case SkShader::kConical_GradientType: {
            transformPoints[1] = transformPoints[0];
            transformPoints[1].fX += SK_Scalar1;
            codeFunction = &twoPointConicalCode;
            break;
        }
        case SkShader::kSweep_GradientType:
            transformPoints[1] = transformPoints[0];
            transformPoints[1].fX += SK_Scalar1;
            codeFunction = &sweepCode;
            break;
        case SkShader::kColor_GradientType:
        case SkShader::kNone_GradientType:
        default:
            return NULL;
    }

    // Move any scaling (assuming a unit gradient) or translation
    // (and rotation for linear gradient), of the final gradient from
    // info->fPoints to the matrix (updating bbox appropriately).  Now
    // the gradient can be drawn on on the unit segment.
    SkMatrix mapperMatrix;
    unitToPointsMatrix(transformPoints, &mapperMatrix);

    SkMatrix finalMatrix = state.fCanvasTransform;
    finalMatrix.preConcat(state.fShaderTransform);
    finalMatrix.preConcat(mapperMatrix);

    // Preserves as much as posible in the final matrix, and only removes
    // the perspective. The inverse of the perspective is stored in
    // perspectiveInverseOnly matrix and has 3 useful numbers
    // (p0, p1, p2), while everything else is either 0 or 1.
    // In this way the shader will handle it eficiently, with minimal code.
    SkMatrix perspectiveInverseOnly = SkMatrix::I();
    if (finalMatrix.hasPerspective()) {
        if (!split_perspective(finalMatrix,
                               &finalMatrix, &perspectiveInverseOnly)) {
            return NULL;
        }
    }

    SkRect bbox;
    bbox.set(state.fBBox);
    if (!inverse_transform_bbox(finalMatrix, &bbox)) {
        return NULL;
    }

    SkAutoTUnref<SkPDFArray> domain(new SkPDFArray);
    domain->reserve(4);
    domain->appendScalar(bbox.fLeft);
    domain->appendScalar(bbox.fRight);
    domain->appendScalar(bbox.fTop);
    domain->appendScalar(bbox.fBottom);

    SkString functionCode;
    // The two point radial gradient further references
    // state.fInfo
    // in translating from x, y coordinates to the t parameter. So, we have
    // to transform the points and radii according to the calculated matrix.
    if (state.fType == SkShader::kRadial2_GradientType) {
        SkShader::GradientInfo twoPointRadialInfo = *info;
        SkMatrix inverseMapperMatrix;
        if (!mapperMatrix.invert(&inverseMapperMatrix)) {
            return NULL;
        }
        inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2);
        twoPointRadialInfo.fRadius[0] =
            inverseMapperMatrix.mapRadius(info->fRadius[0]);
        twoPointRadialInfo.fRadius[1] =
            inverseMapperMatrix.mapRadius(info->fRadius[1]);
        functionCode = codeFunction(twoPointRadialInfo, perspectiveInverseOnly);
    } else {
        functionCode = codeFunction(*info, perspectiveInverseOnly);
    }

    SkAutoTUnref<SkPDFDict> pdfShader(new SkPDFDict);
    pdfShader->insertInt("ShadingType", 1);
    pdfShader->insertName("ColorSpace", "DeviceRGB");
    pdfShader->insert("Domain", domain.get());

    SkAutoTUnref<SkPDFStream> function(
            make_ps_function(functionCode, domain.get()));
    pdfShader->insert("Function", new SkPDFObjRef(function))->unref();

    SkAutoTUnref<SkPDFArray> matrixArray(
            SkPDFUtils::MatrixToArray(finalMatrix));

    SkPDFFunctionShader* pdfFunctionShader =
            SkNEW_ARGS(SkPDFFunctionShader, (autoState->detach()));

    pdfFunctionShader->insertInt("PatternType", 2);
    pdfFunctionShader->insert("Matrix", matrixArray.get());
    pdfFunctionShader->insert("Shading", pdfShader.get());

    canon->addFunctionShader(pdfFunctionShader);
    return pdfFunctionShader;
}
Esempio n. 24
0
bool GrAtlasTextBlob::mustRegenerate(const SkPaint& paint,
                                     GrColor color, const SkMaskFilter::BlurRec& blurRec,
                                     const SkMatrix& viewMatrix, SkScalar x, SkScalar y) {
    // If we have LCD text then our canonical color will be set to transparent, in this case we have
    // to regenerate the blob on any color change
    // We use the grPaint to get any color filter effects
    if (fKey.fCanonicalColor == SK_ColorTRANSPARENT &&
        fPaintColor != color) {
        return true;
    }

    if (fInitialViewMatrix.hasPerspective() != viewMatrix.hasPerspective()) {
        return true;
    }

    if (fInitialViewMatrix.hasPerspective() && !fInitialViewMatrix.cheapEqualTo(viewMatrix)) {
        return true;
    }

    // We only cache one masked version
    if (fKey.fHasBlur &&
        (fBlurRec.fSigma != blurRec.fSigma ||
         fBlurRec.fStyle != blurRec.fStyle ||
         fBlurRec.fQuality != blurRec.fQuality)) {
        return true;
    }

    // Similarly, we only cache one version for each style
    if (fKey.fStyle != SkPaint::kFill_Style &&
        (fStrokeInfo.fFrameWidth != paint.getStrokeWidth() ||
         fStrokeInfo.fMiterLimit != paint.getStrokeMiter() ||
         fStrokeInfo.fJoin != paint.getStrokeJoin())) {
        return true;
    }

    // Mixed blobs must be regenerated.  We could probably figure out a way to do integer scrolls
    // for mixed blobs if this becomes an issue.
    if (this->hasBitmap() && this->hasDistanceField()) {
        // Identical viewmatrices and we can reuse in all cases
        if (fInitialViewMatrix.cheapEqualTo(viewMatrix) && x == fInitialX && y == fInitialY) {
            return false;
        }
        return true;
    }

    if (this->hasBitmap()) {
        if (fInitialViewMatrix.getScaleX() != viewMatrix.getScaleX() ||
            fInitialViewMatrix.getScaleY() != viewMatrix.getScaleY() ||
            fInitialViewMatrix.getSkewX() != viewMatrix.getSkewX() ||
            fInitialViewMatrix.getSkewY() != viewMatrix.getSkewY()) {
            return true;
        }

        // We can update the positions in the cachedtextblobs without regenerating the whole blob,
        // but only for integer translations.
        // This cool bit of math will determine the necessary translation to apply to the already
        // generated vertex coordinates to move them to the correct position
        SkScalar transX = viewMatrix.getTranslateX() +
                          viewMatrix.getScaleX() * (x - fInitialX) +
                          viewMatrix.getSkewX() * (y - fInitialY) -
                          fInitialViewMatrix.getTranslateX();
        SkScalar transY = viewMatrix.getTranslateY() +
                          viewMatrix.getSkewY() * (x - fInitialX) +
                          viewMatrix.getScaleY() * (y - fInitialY) -
                          fInitialViewMatrix.getTranslateY();
        if (!SkScalarIsInt(transX) || !SkScalarIsInt(transY)) {
            return true;
        }
    } else if (this->hasDistanceField()) {
        // A scale outside of [blob.fMaxMinScale, blob.fMinMaxScale] would result in a different
        // distance field being generated, so we have to regenerate in those cases
        SkScalar newMaxScale = viewMatrix.getMaxScale();
        SkScalar oldMaxScale = fInitialViewMatrix.getMaxScale();
        SkScalar scaleAdjust = newMaxScale / oldMaxScale;
        if (scaleAdjust < fMaxMinScale || scaleAdjust > fMinMaxScale) {
            return true;
        }
    }

    // It is possible that a blob has neither distanceField nor bitmaptext.  This is in the case
    // when all of the runs inside the blob are drawn as paths.  In this case, we always regenerate
    // the blob anyways at flush time, so no need to regenerate explicitly
    return false;
}
Esempio n. 25
0
/**
 * Generates the lines and quads to be rendered. Lines are always recorded in
 * device space. We will do a device space bloat to account for the 1pixel
 * thickness.
 * Quads are recorded in device space unless m contains
 * perspective, then in they are in src space. We do this because we will
 * subdivide large quads to reduce over-fill. This subdivision has to be
 * performed before applying the perspective matrix.
 */
static int gather_lines_and_quads(const SkPath& path,
                                  const SkMatrix& m,
                                  const SkIRect& devClipBounds,
                                  GrAAHairLinePathRenderer::PtArray* lines,
                                  GrAAHairLinePathRenderer::PtArray* quads,
                                  GrAAHairLinePathRenderer::PtArray* conics,
                                  GrAAHairLinePathRenderer::IntArray* quadSubdivCnts,
                                  GrAAHairLinePathRenderer::FloatArray* conicWeights) {
    SkPath::Iter iter(path, false);

    int totalQuadCount = 0;
    SkRect bounds;
    SkIRect ibounds;

    bool persp = m.hasPerspective();

    for (;;) {
        SkPoint pathPts[4];
        SkPoint devPts[4];
        SkPath::Verb verb = iter.next(pathPts);
        switch (verb) {
            case SkPath::kConic_Verb: {
                SkConic dst[4];
                // We chop the conics to create tighter clipping to hide error
                // that appears near max curvature of very thin conics. Thin
                // hyperbolas with high weight still show error.
                int conicCnt = chop_conic(pathPts, dst, iter.conicWeight());
                for (int i = 0; i < conicCnt; ++i) {
                    SkPoint* chopPnts = dst[i].fPts;
                    m.mapPoints(devPts, chopPnts, 3);
                    bounds.setBounds(devPts, 3);
                    bounds.outset(SK_Scalar1, SK_Scalar1);
                    bounds.roundOut(&ibounds);
                    if (SkIRect::Intersects(devClipBounds, ibounds)) {
                        if (is_degen_quad_or_conic(devPts)) {
                            SkPoint* pts = lines->push_back_n(4);
                            pts[0] = devPts[0];
                            pts[1] = devPts[1];
                            pts[2] = devPts[1];
                            pts[3] = devPts[2];
                        } else {
                            // when in perspective keep conics in src space
                            SkPoint* cPts = persp ? chopPnts : devPts;
                            SkPoint* pts = conics->push_back_n(3);
                            pts[0] = cPts[0];
                            pts[1] = cPts[1];
                            pts[2] = cPts[2];
                            conicWeights->push_back() = dst[i].fW;
                        }
                    }
                }
                break;
            }
            case SkPath::kMove_Verb:
                break;
            case SkPath::kLine_Verb:
                m.mapPoints(devPts, pathPts, 2);
                bounds.setBounds(devPts, 2);
                bounds.outset(SK_Scalar1, SK_Scalar1);
                bounds.roundOut(&ibounds);
                if (SkIRect::Intersects(devClipBounds, ibounds)) {
                    SkPoint* pts = lines->push_back_n(2);
                    pts[0] = devPts[0];
                    pts[1] = devPts[1];
                }
                break;
            case SkPath::kQuad_Verb: {
                SkPoint choppedPts[5];
                // Chopping the quad helps when the quad is either degenerate or nearly degenerate.
                // When it is degenerate it allows the approximation with lines to work since the
                // chop point (if there is one) will be at the parabola's vertex. In the nearly
                // degenerate the QuadUVMatrix computed for the points is almost singular which
                // can cause rendering artifacts.
                int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts);
                for (int i = 0; i < n; ++i) {
                    SkPoint* quadPts = choppedPts + i * 2;
                    m.mapPoints(devPts, quadPts, 3);
                    bounds.setBounds(devPts, 3);
                    bounds.outset(SK_Scalar1, SK_Scalar1);
                    bounds.roundOut(&ibounds);

                    if (SkIRect::Intersects(devClipBounds, ibounds)) {
                        int subdiv = num_quad_subdivs(devPts);
                        SkASSERT(subdiv >= -1);
                        if (-1 == subdiv) {
                            SkPoint* pts = lines->push_back_n(4);
                            pts[0] = devPts[0];
                            pts[1] = devPts[1];
                            pts[2] = devPts[1];
                            pts[3] = devPts[2];
                        } else {
                            // when in perspective keep quads in src space
                            SkPoint* qPts = persp ? quadPts : devPts;
                            SkPoint* pts = quads->push_back_n(3);
                            pts[0] = qPts[0];
                            pts[1] = qPts[1];
                            pts[2] = qPts[2];
                            quadSubdivCnts->push_back() = subdiv;
                            totalQuadCount += 1 << subdiv;
                        }
                    }
                }
                break;
            }
            case SkPath::kCubic_Verb:
                m.mapPoints(devPts, pathPts, 4);
                bounds.setBounds(devPts, 4);
                bounds.outset(SK_Scalar1, SK_Scalar1);
                bounds.roundOut(&ibounds);
                if (SkIRect::Intersects(devClipBounds, ibounds)) {
                    PREALLOC_PTARRAY(32) q;
                    // we don't need a direction if we aren't constraining the subdivision
                    const SkPathPriv::FirstDirection kDummyDir = SkPathPriv::kCCW_FirstDirection;
                    // We convert cubics to quadratics (for now).
                    // In perspective have to do conversion in src space.
                    if (persp) {
                        SkScalar tolScale =
                            GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m,
                                                             path.getBounds());
                        GrPathUtils::convertCubicToQuads(pathPts, tolScale, false, kDummyDir, &q);
                    } else {
                        GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, false, kDummyDir, &q);
                    }
                    for (int i = 0; i < q.count(); i += 3) {
                        SkPoint* qInDevSpace;
                        // bounds has to be calculated in device space, but q is
                        // in src space when there is perspective.
                        if (persp) {
                            m.mapPoints(devPts, &q[i], 3);
                            bounds.setBounds(devPts, 3);
                            qInDevSpace = devPts;
                        } else {
                            bounds.setBounds(&q[i], 3);
                            qInDevSpace = &q[i];
                        }
                        bounds.outset(SK_Scalar1, SK_Scalar1);
                        bounds.roundOut(&ibounds);
                        if (SkIRect::Intersects(devClipBounds, ibounds)) {
                            int subdiv = num_quad_subdivs(qInDevSpace);
                            SkASSERT(subdiv >= -1);
                            if (-1 == subdiv) {
                                SkPoint* pts = lines->push_back_n(4);
                                // lines should always be in device coords
                                pts[0] = qInDevSpace[0];
                                pts[1] = qInDevSpace[1];
                                pts[2] = qInDevSpace[1];
                                pts[3] = qInDevSpace[2];
                            } else {
                                SkPoint* pts = quads->push_back_n(3);
                                // q is already in src space when there is no
                                // perspective and dev coords otherwise.
                                pts[0] = q[0 + i];
                                pts[1] = q[1 + i];
                                pts[2] = q[2 + i];
                                quadSubdivCnts->push_back() = subdiv;
                                totalQuadCount += 1 << subdiv;
                            }
                        }
                    }
                }
                break;
            case SkPath::kClose_Verb:
                break;
            case SkPath::kDone_Verb:
                return totalQuadCount;
        }
    }
}
Esempio n. 26
0
static void test_matrix_min_max_scale(skiatest::Reporter* reporter) {
    SkScalar scales[2];
    bool success;

    SkMatrix identity;
    identity.reset();
    REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMinScale());
    REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxScale());
    success = identity.getMinMaxScales(scales);
    REPORTER_ASSERT(reporter, success && SK_Scalar1 == scales[0] && SK_Scalar1 == scales[1]);

    SkMatrix scale;
    scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
    REPORTER_ASSERT(reporter, SK_Scalar1 * 2 == scale.getMinScale());
    REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxScale());
    success = scale.getMinMaxScales(scales);
    REPORTER_ASSERT(reporter, success && SK_Scalar1 * 2 == scales[0] && SK_Scalar1 * 4 == scales[1]);

    SkMatrix rot90Scale;
    rot90Scale.setRotate(90 * SK_Scalar1);
    rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
    REPORTER_ASSERT(reporter, SK_Scalar1 / 4 == rot90Scale.getMinScale());
    REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxScale());
    success = rot90Scale.getMinMaxScales(scales);
    REPORTER_ASSERT(reporter, success && SK_Scalar1 / 4  == scales[0] && SK_Scalar1 / 2 == scales[1]);

    SkMatrix rotate;
    rotate.setRotate(128 * SK_Scalar1);
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMinScale(), SK_ScalarNearlyZero));
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMaxScale(), SK_ScalarNearlyZero));
    success = rotate.getMinMaxScales(scales);
    REPORTER_ASSERT(reporter, success);
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, scales[0], SK_ScalarNearlyZero));
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, scales[1], SK_ScalarNearlyZero));

    SkMatrix translate;
    translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
    REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMinScale());
    REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxScale());
    success = translate.getMinMaxScales(scales);
    REPORTER_ASSERT(reporter, success && SK_Scalar1 == scales[0] && SK_Scalar1 == scales[1]);

    SkMatrix perspX;
    perspX.reset();
    perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000));
    REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMinScale());
    REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxScale());
    // Verify that getMinMaxScales() doesn't update the scales array on failure.
    scales[0] = -5;
    scales[1] = -5;
    success = perspX.getMinMaxScales(scales);
    REPORTER_ASSERT(reporter, !success && -5 * SK_Scalar1 == scales[0] && -5 * SK_Scalar1  == scales[1]);

    SkMatrix perspY;
    perspY.reset();
    perspY.setPerspY(SkScalarToPersp(-SK_Scalar1 / 500));
    REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMinScale());
    REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxScale());
    scales[0] = -5;
    scales[1] = -5;
    success = perspY.getMinMaxScales(scales);
    REPORTER_ASSERT(reporter, !success && -5 * SK_Scalar1 == scales[0] && -5 * SK_Scalar1  == scales[1]);

    SkMatrix baseMats[] = {scale, rot90Scale, rotate,
                           translate, perspX, perspY};
    SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)];
    for (size_t i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) {
        mats[i] = baseMats[i];
        bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]);
        REPORTER_ASSERT(reporter, invertable);
    }
    SkRandom rand;
    for (int m = 0; m < 1000; ++m) {
        SkMatrix mat;
        mat.reset();
        for (int i = 0; i < 4; ++i) {
            int x = rand.nextU() % SK_ARRAY_COUNT(mats);
            mat.postConcat(mats[x]);
        }

        SkScalar minScale = mat.getMinScale();
        SkScalar maxScale = mat.getMaxScale();
        REPORTER_ASSERT(reporter, (minScale < 0) == (maxScale < 0));
        REPORTER_ASSERT(reporter, (maxScale < 0) == mat.hasPerspective());

        SkScalar scales[2];
        bool success = mat.getMinMaxScales(scales);
        REPORTER_ASSERT(reporter, success == !mat.hasPerspective());
        REPORTER_ASSERT(reporter, !success || (scales[0] == minScale && scales[1] == maxScale));

        if (mat.hasPerspective()) {
            m -= 1; // try another non-persp matrix
            continue;
        }

        // test a bunch of vectors. All should be scaled by between minScale and maxScale
        // (modulo some error) and we should find a vector that is scaled by almost each.
        static const SkScalar gVectorScaleTol = (105 * SK_Scalar1) / 100;
        static const SkScalar gCloseScaleTol = (97 * SK_Scalar1) / 100;
        SkScalar max = 0, min = SK_ScalarMax;
        SkVector vectors[1000];
        for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
            vectors[i].fX = rand.nextSScalar1();
            vectors[i].fY = rand.nextSScalar1();
            if (!vectors[i].normalize()) {
                i -= 1;
                continue;
            }
        }
        mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors));
        for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
            SkScalar d = vectors[i].length();
            REPORTER_ASSERT(reporter, SkScalarDiv(d, maxScale) < gVectorScaleTol);
            REPORTER_ASSERT(reporter, SkScalarDiv(minScale, d) < gVectorScaleTol);
            if (max < d) {
                max = d;
            }
            if (min > d) {
                min = d;
            }
        }
        REPORTER_ASSERT(reporter, SkScalarDiv(max, maxScale) >= gCloseScaleTol);
        REPORTER_ASSERT(reporter, SkScalarDiv(minScale, min) >= gCloseScaleTol);
    }
}
static void test_matrix_max_stretch(skiatest::Reporter* reporter) {
    SkMatrix identity;
    identity.reset();
    REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch());

    SkMatrix scale;
    scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
    REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch());

    SkMatrix rot90Scale;
    rot90Scale.setRotate(90 * SK_Scalar1);
    rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
    REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch());

    SkMatrix rotate;
    rotate.setRotate(128 * SK_Scalar1);
    REPORTER_ASSERT(reporter, SkScalarAbs(SK_Scalar1 - rotate.getMaxStretch()) <= SK_ScalarNearlyZero);

    SkMatrix translate;
    translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
    REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch());

    SkMatrix perspX;
    perspX.reset();
    perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000));
    REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());

    SkMatrix perspY;
    perspY.reset();
    perspY.setPerspX(SkScalarToPersp(-SK_Scalar1 / 500));
    REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch());

    SkMatrix baseMats[] = {scale, rot90Scale, rotate,
                           translate, perspX, perspY};
    SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)];
    for (size_t i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) {
        mats[i] = baseMats[i];
        bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]);
        REPORTER_ASSERT(reporter, invertable);
    }
    SkMWCRandom rand;
    for (int m = 0; m < 1000; ++m) {
        SkMatrix mat;
        mat.reset();
        for (int i = 0; i < 4; ++i) {
            int x = rand.nextU() % SK_ARRAY_COUNT(mats);
            mat.postConcat(mats[x]);
        }
        SkScalar stretch = mat.getMaxStretch();

        if ((stretch < 0) != mat.hasPerspective()) {
            stretch = mat.getMaxStretch();
        }

        REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective());

        if (mat.hasPerspective()) {
            m -= 1; // try another non-persp matrix
            continue;
        }

        // test a bunch of vectors. None should be scaled by more than stretch
        // (modulo some error) and we should find a vector that is scaled by
        // almost stretch.
        static const SkScalar gStretchTol = (105 * SK_Scalar1) / 100;
        static const SkScalar gMaxStretchTol = (97 * SK_Scalar1) / 100;
        SkScalar max = 0;
        SkVector vectors[1000];
        for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
            vectors[i].fX = rand.nextSScalar1();
            vectors[i].fY = rand.nextSScalar1();
            if (!vectors[i].normalize()) {
                i -= 1;
                continue;
            }
        }
        mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors));
        for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
            SkScalar d = vectors[i].length();
            REPORTER_ASSERT(reporter, SkScalarDiv(d, stretch) < gStretchTol);
            if (max < d) {
                max = d;
            }
        }
        REPORTER_ASSERT(reporter, SkScalarDiv(max, stretch) >= gMaxStretchTol);
    }
}
Esempio n. 28
0
/**
 * Generates the lines and quads to be rendered. Lines are always recorded in
 * device space. We will do a device space bloat to account for the 1pixel
 * thickness.
 * Quads are recorded in device space unless m contains
 * perspective, then in they are in src space. We do this because we will
 * subdivide large quads to reduce over-fill. This subdivision has to be
 * performed before applying the perspective matrix.
 */
static int gather_lines_and_quads(const SkPath& path,
                                  const SkMatrix& m,
                                  const SkIRect& devClipBounds,
                                  SkScalar capLength,
                                  bool convertConicsToQuads,
                                  GrAAHairLinePathRenderer::PtArray* lines,
                                  GrAAHairLinePathRenderer::PtArray* quads,
                                  GrAAHairLinePathRenderer::PtArray* conics,
                                  GrAAHairLinePathRenderer::IntArray* quadSubdivCnts,
                                  GrAAHairLinePathRenderer::FloatArray* conicWeights) {
    SkPath::Iter iter(path, false);

    int totalQuadCount = 0;
    SkRect bounds;
    SkIRect ibounds;

    bool persp = m.hasPerspective();

    // Whenever a degenerate, zero-length contour is encountered, this code will insert a
    // 'capLength' x-aligned line segment. Since this is rendering hairlines it is hoped this will
    // suffice for AA square & circle capping.
    int verbsInContour = 0; // Does not count moves
    bool seenZeroLengthVerb = false;
    SkPoint zeroVerbPt;

    // Adds a quad that has already been chopped to the list and checks for quads that are close to
    // lines. Also does a bounding box check. It takes points that are in src space and device
    // space. The src points are only required if the view matrix has perspective.
    auto addChoppedQuad = [&](const SkPoint srcPts[3], const SkPoint devPts[4],
                              bool isContourStart) {
        SkRect bounds;
        SkIRect ibounds;
        bounds.setBounds(devPts, 3);
        bounds.outset(SK_Scalar1, SK_Scalar1);
        bounds.roundOut(&ibounds);
        // We only need the src space space pts when not in perspective.
        SkASSERT(srcPts || !persp);
        if (SkIRect::Intersects(devClipBounds, ibounds)) {
            int subdiv = num_quad_subdivs(devPts);
            SkASSERT(subdiv >= -1);
            if (-1 == subdiv) {
                SkPoint* pts = lines->push_back_n(4);
                pts[0] = devPts[0];
                pts[1] = devPts[1];
                pts[2] = devPts[1];
                pts[3] = devPts[2];
                if (isContourStart && pts[0] == pts[1] && pts[2] == pts[3]) {
                    seenZeroLengthVerb = true;
                    zeroVerbPt = pts[0];
                }
            } else {
                // when in perspective keep quads in src space
                const SkPoint* qPts = persp ? srcPts : devPts;
                SkPoint* pts = quads->push_back_n(3);
                pts[0] = qPts[0];
                pts[1] = qPts[1];
                pts[2] = qPts[2];
                quadSubdivCnts->push_back() = subdiv;
                totalQuadCount += 1 << subdiv;
            }
        }
    };

    // Applies the view matrix to quad src points and calls the above helper.
    auto addSrcChoppedQuad = [&](const SkPoint srcSpaceQuadPts[3], bool isContourStart) {
        SkPoint devPts[3];
        m.mapPoints(devPts, srcSpaceQuadPts, 3);
        addChoppedQuad(srcSpaceQuadPts, devPts, isContourStart);
    };

    for (;;) {
        SkPoint pathPts[4];
        SkPath::Verb verb = iter.next(pathPts, false);
        switch (verb) {
            case SkPath::kConic_Verb:
                if (convertConicsToQuads) {
                    SkScalar weight = iter.conicWeight();
                    SkAutoConicToQuads converter;
                    const SkPoint* quadPts = converter.computeQuads(pathPts, weight, 0.25f);
                    for (int i = 0; i < converter.countQuads(); ++i) {
                        addSrcChoppedQuad(quadPts + 2 * i, !verbsInContour && 0 == i);
                    }
                } else {
                    SkConic dst[4];
                    // We chop the conics to create tighter clipping to hide error
                    // that appears near max curvature of very thin conics. Thin
                    // hyperbolas with high weight still show error.
                    int conicCnt = chop_conic(pathPts, dst, iter.conicWeight());
                    for (int i = 0; i < conicCnt; ++i) {
                        SkPoint devPts[4];
                        SkPoint* chopPnts = dst[i].fPts;
                        m.mapPoints(devPts, chopPnts, 3);
                        bounds.setBounds(devPts, 3);
                        bounds.outset(SK_Scalar1, SK_Scalar1);
                        bounds.roundOut(&ibounds);
                        if (SkIRect::Intersects(devClipBounds, ibounds)) {
                            if (is_degen_quad_or_conic(devPts)) {
                                SkPoint* pts = lines->push_back_n(4);
                                pts[0] = devPts[0];
                                pts[1] = devPts[1];
                                pts[2] = devPts[1];
                                pts[3] = devPts[2];
                                if (verbsInContour == 0 && i == 0 && pts[0] == pts[1] &&
                                    pts[2] == pts[3]) {
                                    seenZeroLengthVerb = true;
                                    zeroVerbPt = pts[0];
                                }
                            } else {
                                // when in perspective keep conics in src space
                                SkPoint* cPts = persp ? chopPnts : devPts;
                                SkPoint* pts = conics->push_back_n(3);
                                pts[0] = cPts[0];
                                pts[1] = cPts[1];
                                pts[2] = cPts[2];
                                conicWeights->push_back() = dst[i].fW;
                            }
                        }
                    }
                }
                verbsInContour++;
                break;
            case SkPath::kMove_Verb:
                // New contour (and last one was unclosed). If it was just a zero length drawing
                // operation, and we're supposed to draw caps, then add a tiny line.
                if (seenZeroLengthVerb && verbsInContour == 1 && capLength > 0) {
                    SkPoint* pts = lines->push_back_n(2);
                    pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY);
                    pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY);
                }
                verbsInContour = 0;
                seenZeroLengthVerb = false;
                break;
            case SkPath::kLine_Verb: {
                SkPoint devPts[2];
                m.mapPoints(devPts, pathPts, 2);
                bounds.setBounds(devPts, 2);
                bounds.outset(SK_Scalar1, SK_Scalar1);
                bounds.roundOut(&ibounds);
                if (SkIRect::Intersects(devClipBounds, ibounds)) {
                    SkPoint* pts = lines->push_back_n(2);
                    pts[0] = devPts[0];
                    pts[1] = devPts[1];
                    if (verbsInContour == 0 && pts[0] == pts[1]) {
                        seenZeroLengthVerb = true;
                        zeroVerbPt = pts[0];
                    }
                }
                verbsInContour++;
                break;
            }
            case SkPath::kQuad_Verb: {
                SkPoint choppedPts[5];
                // Chopping the quad helps when the quad is either degenerate or nearly degenerate.
                // When it is degenerate it allows the approximation with lines to work since the
                // chop point (if there is one) will be at the parabola's vertex. In the nearly
                // degenerate the QuadUVMatrix computed for the points is almost singular which
                // can cause rendering artifacts.
                int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts);
                for (int i = 0; i < n; ++i) {
                    addSrcChoppedQuad(choppedPts + i * 2, !verbsInContour && 0 == i);
                }
                verbsInContour++;
                break;
            }
            case SkPath::kCubic_Verb: {
                SkPoint devPts[4];
                m.mapPoints(devPts, pathPts, 4);
                bounds.setBounds(devPts, 4);
                bounds.outset(SK_Scalar1, SK_Scalar1);
                bounds.roundOut(&ibounds);
                if (SkIRect::Intersects(devClipBounds, ibounds)) {
                    PREALLOC_PTARRAY(32) q;
                    // We convert cubics to quadratics (for now).
                    // In perspective have to do conversion in src space.
                    if (persp) {
                        SkScalar tolScale =
                            GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m, path.getBounds());
                        GrPathUtils::convertCubicToQuads(pathPts, tolScale, &q);
                    } else {
                        GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, &q);
                    }
                    for (int i = 0; i < q.count(); i += 3) {
                        if (persp) {
                            addSrcChoppedQuad(&q[i], !verbsInContour && 0 == i);
                        } else {
                            addChoppedQuad(nullptr, &q[i], !verbsInContour && 0 == i);
                        }
                    }
                }
                verbsInContour++;
                break;
            }
            case SkPath::kClose_Verb:
                // Contour is closed, so we don't need to grow the starting line, unless it's
                // *just* a zero length subpath. (SVG Spec 11.4, 'stroke').
                if (capLength > 0) {
                    if (seenZeroLengthVerb && verbsInContour == 1) {
                        SkPoint* pts = lines->push_back_n(2);
                        pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY);
                        pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY);
                    } else if (verbsInContour == 0) {
                        // Contour was (moveTo, close). Add a line.
                        SkPoint devPts[2];
                        m.mapPoints(devPts, pathPts, 1);
                        devPts[1] = devPts[0];
                        bounds.setBounds(devPts, 2);
                        bounds.outset(SK_Scalar1, SK_Scalar1);
                        bounds.roundOut(&ibounds);
                        if (SkIRect::Intersects(devClipBounds, ibounds)) {
                            SkPoint* pts = lines->push_back_n(2);
                            pts[0] = SkPoint::Make(devPts[0].fX - capLength, devPts[0].fY);
                            pts[1] = SkPoint::Make(devPts[1].fX + capLength, devPts[1].fY);
                        }
                    }
                }
                break;
            case SkPath::kDone_Verb:
                if (seenZeroLengthVerb && verbsInContour == 1 && capLength > 0) {
                    // Path ended with a dangling (moveTo, line|quad|etc). If the final verb is
                    // degenerate, we need to draw a line.
                    SkPoint* pts = lines->push_back_n(2);
                    pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY);
                    pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY);
                }
                return totalQuadCount;
        }
    }
}
bool GrStencilAndCoverPathRenderer::onDrawPath(GrDrawTarget* target,
                                               GrPipelineBuilder* pipelineBuilder,
                                               GrColor color,
                                               const SkMatrix& viewMatrix,
                                               const SkPath& path,
                                               const GrStrokeInfo& stroke,
                                               bool antiAlias) {
    SkASSERT(!antiAlias);
    SkASSERT(!stroke.getStrokeRec().isHairlineStyle());
    SkASSERT(!stroke.isDashed());
    SkASSERT(pipelineBuilder->getStencil().isDisabled());

    SkAutoTUnref<GrPath> p(get_gr_path(fGpu, path, stroke.getStrokeRec()));

    if (path.isInverseFillType()) {
        GR_STATIC_CONST_SAME_STENCIL(kInvertedStencilPass,
            kZero_StencilOp,
            kZero_StencilOp,
            // We know our rect will hit pixels outside the clip and the user bits will be 0
            // outside the clip. So we can't just fill where the user bits are 0. We also need to
            // check that the clip bit is set.
            kEqualIfInClip_StencilFunc,
            0xffff,
            0x0000,
            0xffff);

        pipelineBuilder->setStencil(kInvertedStencilPass);

        // fake inverse with a stencil and cover
        SkAutoTUnref<GrPathProcessor> pp(GrPathProcessor::Create(GrColor_WHITE, viewMatrix));
        target->stencilPath(pipelineBuilder, pp, p, convert_skpath_filltype(path.getFillType()));

        SkMatrix invert = SkMatrix::I();
        SkRect bounds =
            SkRect::MakeLTRB(0, 0, SkIntToScalar(pipelineBuilder->getRenderTarget()->width()),
                             SkIntToScalar(pipelineBuilder->getRenderTarget()->height()));
        SkMatrix vmi;
        // mapRect through persp matrix may not be correct
        if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) {
            vmi.mapRect(&bounds);
            // theoretically could set bloat = 0, instead leave it because of matrix inversion
            // precision.
            SkScalar bloat = viewMatrix.getMaxScale() * SK_ScalarHalf;
            bounds.outset(bloat, bloat);
        } else {
            if (!viewMatrix.invert(&invert)) {
                return false;
            }
        }
        const SkMatrix& viewM = viewMatrix.hasPerspective() ? SkMatrix::I() : viewMatrix;
        target->drawRect(pipelineBuilder, color, viewM, bounds, NULL, &invert);
    } else {
        GR_STATIC_CONST_SAME_STENCIL(kStencilPass,
            kZero_StencilOp,
            kZero_StencilOp,
            kNotEqual_StencilFunc,
            0xffff,
            0x0000,
            0xffff);

        pipelineBuilder->setStencil(kStencilPass);
        SkAutoTUnref<GrPathProcessor> pp(GrPathProcessor::Create(color, viewMatrix));
        target->drawPath(pipelineBuilder, pp, p, convert_skpath_filltype(path.getFillType()));
    }

    pipelineBuilder->stencil()->setDisabled();
    return true;
}
Esempio n. 30
0
bool GrDefaultPathRenderer::internalDrawPath(GrDrawContext* drawContext,
                                             const GrPaint& paint,
                                             const GrUserStencilSettings& userStencilSettings,
                                             const GrClip& clip,
                                             const SkMatrix& viewMatrix,
                                             const GrShape& shape,
                                             bool stencilOnly) {
    SkPath path;
    shape.asPath(&path);

    SkScalar hairlineCoverage;
    uint8_t newCoverage = 0xff;
    bool isHairline = false;
    if (IsStrokeHairlineOrEquivalent(shape.style(), viewMatrix, &hairlineCoverage)) {
        newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff);
        isHairline = true;
    } else {
        SkASSERT(shape.style().isSimpleFill());
    }

    int                          passCount = 0;
    const GrUserStencilSettings* passes[3];
    GrDrawFace                   drawFace[3];
    bool                         reverse = false;
    bool                         lastPassIsBounds;

    if (isHairline) {
        passCount = 1;
        if (stencilOnly) {
            passes[0] = &gDirectToStencil;
        } else {
            passes[0] = &userStencilSettings;
        }
        lastPassIsBounds = false;
        drawFace[0] = GrDrawFace::kBoth;
    } else {
        if (single_pass_shape(shape)) {
            passCount = 1;
            if (stencilOnly) {
                passes[0] = &gDirectToStencil;
            } else {
                passes[0] = &userStencilSettings;
            }
            drawFace[0] = GrDrawFace::kBoth;
            lastPassIsBounds = false;
        } else {
            switch (path.getFillType()) {
                case SkPath::kInverseEvenOdd_FillType:
                    reverse = true;
                    // fallthrough
                case SkPath::kEvenOdd_FillType:
                    passes[0] = &gEOStencilPass;
                    if (stencilOnly) {
                        passCount = 1;
                        lastPassIsBounds = false;
                    } else {
                        passCount = 2;
                        lastPassIsBounds = true;
                        if (reverse) {
                            passes[1] = &gInvEOColorPass;
                        } else {
                            passes[1] = &gEOColorPass;
                        }
                    }
                    drawFace[0] = drawFace[1] = GrDrawFace::kBoth;
                    break;

                case SkPath::kInverseWinding_FillType:
                    reverse = true;
                    // fallthrough
                case SkPath::kWinding_FillType:
                    if (fSeparateStencil) {
                        if (fStencilWrapOps) {
                            passes[0] = &gWindStencilSeparateWithWrap;
                        } else {
                            passes[0] = &gWindStencilSeparateNoWrap;
                        }
                        passCount = 2;
                        drawFace[0] = GrDrawFace::kBoth;
                    } else {
                        if (fStencilWrapOps) {
                            passes[0] = &gWindSingleStencilWithWrapInc;
                            passes[1] = &gWindSingleStencilWithWrapDec;
                        } else {
                            passes[0] = &gWindSingleStencilNoWrapInc;
                            passes[1] = &gWindSingleStencilNoWrapDec;
                        }
                        // which is cw and which is ccw is arbitrary.
                        drawFace[0] = GrDrawFace::kCW;
                        drawFace[1] = GrDrawFace::kCCW;
                        passCount = 3;
                    }
                    if (stencilOnly) {
                        lastPassIsBounds = false;
                        --passCount;
                    } else {
                        lastPassIsBounds = true;
                        drawFace[passCount-1] = GrDrawFace::kBoth;
                        if (reverse) {
                            passes[passCount-1] = &gInvWindColorPass;
                        } else {
                            passes[passCount-1] = &gWindColorPass;
                        }
                    }
                    break;
                default:
                    SkDEBUGFAIL("Unknown path fFill!");
                    return false;
            }
        }
    }

    SkScalar tol = GrPathUtils::kDefaultTolerance;
    SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, path.getBounds());

    SkRect devBounds;
    GetPathDevBounds(path, drawContext->width(), drawContext->height(), viewMatrix, &devBounds);

    for (int p = 0; p < passCount; ++p) {
        if (lastPassIsBounds && (p == passCount-1)) {
            SkRect bounds;
            SkMatrix localMatrix = SkMatrix::I();
            if (reverse) {
                // draw over the dev bounds (which will be the whole dst surface for inv fill).
                bounds = devBounds;
                SkMatrix vmi;
                // mapRect through persp matrix may not be correct
                if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) {
                    vmi.mapRect(&bounds);
                } else {
                    if (!viewMatrix.invert(&localMatrix)) {
                        return false;
                    }
                }
            } else {
                bounds = path.getBounds();
            }
            const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() :
                                                                               viewMatrix;
            SkAutoTUnref<GrDrawBatch> batch(
                    GrRectBatchFactory::CreateNonAAFill(paint.getColor(), viewM, bounds, nullptr,
                                                        &localMatrix));

            SkASSERT(GrDrawFace::kBoth == drawFace[p]);
            GrPipelineBuilder pipelineBuilder(paint, drawContext->mustUseHWAA(paint));
            pipelineBuilder.setDrawFace(drawFace[p]);
            pipelineBuilder.setUserStencil(passes[p]);

            drawContext->drawBatch(pipelineBuilder, clip, batch);
        } else {
            SkAutoTUnref<GrDrawBatch> batch(new DefaultPathBatch(paint.getColor(), path,
                                                                 srcSpaceTol,
                                                                 newCoverage, viewMatrix,
                                                                 isHairline, devBounds));

            GrPipelineBuilder pipelineBuilder(paint, drawContext->mustUseHWAA(paint));
            pipelineBuilder.setDrawFace(drawFace[p]);
            pipelineBuilder.setUserStencil(passes[p]);
            if (passCount > 1) {
                pipelineBuilder.setDisableColorXPFactory();
            }

            drawContext->drawBatch(pipelineBuilder, clip, batch);
        }
    }
    return true;
}