void SkSVGDevice::AutoElement::addClipResources(const SkDraw& draw, Resources* resources) { SkASSERT(!draw.fClipStack->isWideOpen()); SkPath clipPath; (void) draw.fClipStack->asPath(&clipPath); SkString clipID = fResourceBucket->addClip(); const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ? "evenodd" : "nonzero"; { // clipPath is in device space, but since we're only pushing transform attributes // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space. AutoElement clipPathElement("clipPath", fWriter); clipPathElement.addAttribute("id", clipID); SkRect clipRect = SkRect::MakeEmpty(); if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) { AutoElement rectElement("rect", fWriter); rectElement.addRectAttributes(clipRect); rectElement.addAttribute("clip-rule", clipRule); } else { AutoElement pathElement("path", fWriter); pathElement.addPathAttributes(clipPath); pathElement.addAttribute("clip-rule", clipRule); } } resources->fClip.printf("url(#%s)", clipID.c_str()); }
sk_sp<SkPathEffect> SkPath1DPathEffect::Make(const SkPath& path, SkScalar advance, SkScalar phase, Style style) { if (advance <= 0 || path.isEmpty()) { return nullptr; } return sk_sp<SkPathEffect>(new SkPath1DPathEffect(path, advance, phase, style)); }
void makePath() { if (fPath.isEmpty()) { const SkScalar radius = SkIntToScalar(45); fPath.addCircle(SkIntToScalar(50), SkIntToScalar(50), radius); fPath.addCircle(SkIntToScalar(100), SkIntToScalar(100), radius); } }
GrGLPath::GrGLPath(GrGpuGL* gpu, const SkPath& path) : INHERITED(gpu, kIsWrapped) { #ifndef SK_SCALAR_IS_FLOAT GrCrash("Assumes scalar is float."); #endif SkASSERT(!path.isEmpty()); GL_CALL_RET(fPathID, GenPaths(1)); SkSTArray<16, GrGLubyte, true> pathCommands; SkSTArray<16, SkPoint, true> pathPoints; int verbCnt = path.countVerbs(); int pointCnt = path.countPoints(); pathCommands.resize_back(verbCnt); pathPoints.resize_back(pointCnt); // TODO: Direct access to path points since we could pass them on directly. path.getPoints(&pathPoints[0], pointCnt); path.getVerbs(&pathCommands[0], verbCnt); GR_DEBUGCODE(int numPts = 0); for (int i = 0; i < verbCnt; ++i) { SkPath::Verb v = static_cast<SkPath::Verb>(pathCommands[i]); pathCommands[i] = verb_to_gl_path_cmd(v); GR_DEBUGCODE(numPts += num_pts(v)); } GrAssert(pathPoints.count() == numPts); GL_CALL(PathCommands(fPathID, verbCnt, &pathCommands[0], 2 * pointCnt, GR_GL_FLOAT, &pathPoints[0])); fBounds = path.getBounds(); }
static void failOne(skiatest::Reporter* reporter, int index) { SkPath path; int i = (int) (index % nonFinitePtsCount); int f = (int) (index % finitePtsCount); int g = (int) ((f + 1) % finitePtsCount); switch (index % 13) { case 0: path.lineTo(nonFinitePts[i]); break; case 1: path.quadTo(nonFinitePts[i], nonFinitePts[i]); break; case 2: path.quadTo(nonFinitePts[i], finitePts[f]); break; case 3: path.quadTo(finitePts[f], nonFinitePts[i]); break; case 4: path.cubicTo(nonFinitePts[i], finitePts[f], finitePts[f]); break; case 5: path.cubicTo(finitePts[f], nonFinitePts[i], finitePts[f]); break; case 6: path.cubicTo(finitePts[f], finitePts[f], nonFinitePts[i]); break; case 7: path.cubicTo(nonFinitePts[i], nonFinitePts[i], finitePts[f]); break; case 8: path.cubicTo(nonFinitePts[i], finitePts[f], nonFinitePts[i]); break; case 9: path.cubicTo(finitePts[f], nonFinitePts[i], nonFinitePts[i]); break; case 10: path.cubicTo(nonFinitePts[i], nonFinitePts[i], nonFinitePts[i]); break; case 11: path.cubicTo(nonFinitePts[i], finitePts[f], finitePts[g]); break; case 12: path.moveTo(nonFinitePts[i]); break; } SkPath result; result.setFillType(SkPath::kWinding_FillType); bool success = Simplify(path, &result); REPORTER_ASSERT(reporter, !success); REPORTER_ASSERT(reporter, result.isEmpty()); REPORTER_ASSERT(reporter, result.getFillType() == SkPath::kWinding_FillType); reporter->bumpTestCount(); }
bool GrAAConvexPathRenderer::onDrawPath(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, GrColor color, const SkMatrix& vm, const SkPath& path, const SkStrokeRec&, bool antiAlias) { if (path.isEmpty()) { return true; } // We outset our vertices one pixel and add one more pixel for precision. // TODO create tighter bounds when we start reordering. SkRect devRect = path.getBounds(); vm.mapRect(&devRect); devRect.outset(2, 2); AAConvexPathBatch::Geometry geometry; geometry.fColor = color; geometry.fViewMatrix = vm; geometry.fPath = path; SkAutoTUnref<GrBatch> batch(AAConvexPathBatch::Create(geometry)); target->drawBatch(pipelineBuilder, batch, &devRect); return true; }
SkPath1DPathEffect::SkPath1DPathEffect(const SkPath& path, SkScalar advance, SkScalar phase, Style style) : fPath(path) { SkASSERT(advance > 0 && !path.isEmpty()); // cleanup their phase parameter, inverting it so that it becomes an // offset along the path (to match the interpretation in PostScript) if (phase < 0) { phase = -phase; if (phase > advance) { phase = SkScalarMod(phase, advance); } } else { if (phase > advance) { phase = SkScalarMod(phase, advance); } phase = advance - phase; } // now catch the edge case where phase == advance (within epsilon) if (phase >= advance) { phase = 0; } SkASSERT(phase >= 0); fAdvance = advance; fInitialOffset = phase; if ((unsigned)style > kMorph_Style) { SkDEBUGF(("SkPath1DPathEffect style enum out of range %d\n", style)); } fStyle = style; }
GrGLPath::GrGLPath(GrGLGpu* gpu, const SkPath& origSkPath, const GrStrokeInfo& origStroke) : INHERITED(gpu, origSkPath, origStroke), fPathID(gpu->glPathRendering()->genPaths(1)) { if (origSkPath.isEmpty()) { InitPathObjectEmptyPath(gpu, fPathID); fShouldStroke = false; fShouldFill = false; } else { const SkPath* skPath = &origSkPath; SkTLazy<SkPath> tmpPath; const GrStrokeInfo* stroke = &origStroke; GrStrokeInfo tmpStroke(SkStrokeRec::kFill_InitStyle); if (stroke->isDashed()) { // Skia stroking and NVPR stroking differ with respect to dashing // pattern. // Convert a dashing to either a stroke or a fill. if (stroke->applyDashToPath(tmpPath.init(), &tmpStroke, *skPath)) { skPath = tmpPath.get(); stroke = &tmpStroke; } } bool didInit = false; if (stroke->needToApply() && stroke->getCap() != SkPaint::kButt_Cap) { // Skia stroking and NVPR stroking differ with respect to stroking // end caps of empty subpaths. // Convert stroke to fill if path contains empty subpaths. didInit = InitPathObjectPathDataCheckingDegenerates(gpu, fPathID, *skPath); if (!didInit) { if (!tmpPath.isValid()) { tmpPath.init(); } SkAssertResult(stroke->applyToPath(tmpPath.get(), *skPath)); skPath = tmpPath.get(); tmpStroke.setFillStyle(); stroke = &tmpStroke; } } if (!didInit) { InitPathObjectPathData(gpu, fPathID, *skPath); } fShouldStroke = stroke->needToApply(); fShouldFill = stroke->isFillStyle() || stroke->getStyle() == SkStrokeRec::kStrokeAndFill_Style; if (fShouldStroke) { InitPathObjectStroke(gpu, fPathID, *stroke); // FIXME: try to account for stroking, without rasterizing the stroke. fBounds.outset(stroke->getWidth(), stroke->getWidth()); } } this->registerWithCache(); }
bool SkHitTestPath(const SkPath& path, SkRect& target, bool hires) { if (target.isEmpty()) { return false; } bool isInverse = path.isInverseFillType(); if (path.isEmpty()) { return isInverse; } SkRect bounds = path.getBounds(); bool sects = SkRect::Intersects(target, bounds); if (isInverse) { if (!sects) { return true; } } else { if (!sects) { return false; } if (target.contains(bounds)) { return true; } } SkPath devPath; const SkPath* pathPtr; SkRect devTarget; if (hires) { const SkScalar coordLimit = SkIntToScalar(16384); const SkRect limit = { 0, 0, coordLimit, coordLimit }; SkMatrix matrix; matrix.setRectToRect(bounds, limit, SkMatrix::kFill_ScaleToFit); path.transform(matrix, &devPath); matrix.mapRect(&devTarget, target); pathPtr = &devPath; } else { devTarget = target; pathPtr = &path; } SkIRect iTarget; devTarget.round(&iTarget); if (iTarget.isEmpty()) { iTarget.fLeft = SkScalarFloorToInt(devTarget.fLeft); iTarget.fTop = SkScalarFloorToInt(devTarget.fTop); iTarget.fRight = iTarget.fLeft + 1; iTarget.fBottom = iTarget.fTop + 1; } SkRegion clip(iTarget); SkRegion rgn; return rgn.setPath(*pathPtr, clip) ^ isInverse; }
static void PathOpsSimplifyFailTest(skiatest::Reporter* reporter) { for (int index = 0; index < (int) (13 * nonFinitePtsCount * finitePtsCount); ++index) { SkPath path; int i = (int) (index % nonFinitePtsCount); int f = (int) (index % finitePtsCount); int g = (int) ((f + 1) % finitePtsCount); switch (index % 13) { case 0: path.lineTo(nonFinitePts[i]); break; case 1: path.quadTo(nonFinitePts[i], nonFinitePts[i]); break; case 2: path.quadTo(nonFinitePts[i], finitePts[f]); break; case 3: path.quadTo(finitePts[f], nonFinitePts[i]); break; case 4: path.cubicTo(nonFinitePts[i], finitePts[f], finitePts[f]); break; case 5: path.cubicTo(finitePts[f], nonFinitePts[i], finitePts[f]); break; case 6: path.cubicTo(finitePts[f], finitePts[f], nonFinitePts[i]); break; case 7: path.cubicTo(nonFinitePts[i], nonFinitePts[i], finitePts[f]); break; case 8: path.cubicTo(nonFinitePts[i], finitePts[f], nonFinitePts[i]); break; case 9: path.cubicTo(finitePts[f], nonFinitePts[i], nonFinitePts[i]); break; case 10: path.cubicTo(nonFinitePts[i], nonFinitePts[i], nonFinitePts[i]); break; case 11: path.cubicTo(nonFinitePts[i], finitePts[f], finitePts[g]); break; case 12: path.moveTo(nonFinitePts[i]); break; } SkPath result; result.setFillType(SkPath::kWinding_FillType); bool success = Simplify(path, &result); REPORTER_ASSERT(reporter, !success); REPORTER_ASSERT(reporter, result.isEmpty()); REPORTER_ASSERT(reporter, result.getFillType() == SkPath::kWinding_FillType); reporter->bumpTestCount(); } if (sizeof(reporter) == 4) { return; } for (int index = 0; index < (int) (11 * finitePtsCount); ++index) { SkPath path; int f = (int) (index % finitePtsCount); int g = (int) ((f + 1) % finitePtsCount); switch (index % 11) { case 0: path.lineTo(finitePts[f]); break; case 1: path.quadTo(finitePts[f], finitePts[f]); break; case 2: path.quadTo(finitePts[f], finitePts[g]); break; case 3: path.quadTo(finitePts[g], finitePts[f]); break; case 4: path.cubicTo(finitePts[f], finitePts[f], finitePts[f]); break; case 5: path.cubicTo(finitePts[f], finitePts[f], finitePts[g]); break; case 6: path.cubicTo(finitePts[f], finitePts[g], finitePts[f]); break; case 7: path.cubicTo(finitePts[f], finitePts[g], finitePts[g]); break; case 8: path.cubicTo(finitePts[g], finitePts[f], finitePts[f]); break; case 9: path.cubicTo(finitePts[g], finitePts[f], finitePts[g]); break; case 10: path.moveTo(finitePts[f]); break; } SkPath result; result.setFillType(SkPath::kWinding_FillType); bool success = Simplify(path, &result); REPORTER_ASSERT(reporter, success); REPORTER_ASSERT(reporter, result.getFillType() != SkPath::kWinding_FillType); reporter->bumpTestCount(); } }
static void toString(const SkPath& path, SkString* str) { if (path.isEmpty()) { str->append("path:empty"); } else { toString(path.getBounds(), str); #if 1 SkString s; dumpVerbs(path, &s); str->append(s.c_str()); #endif str->append("]"); str->prepend("path:["); } }
// http://crbug.com/165432 // Limit extreme dash path effects to avoid exhausting the system memory. static void test_crbug_165432(skiatest::Reporter* reporter) { SkPath path; path.moveTo(0, 0); path.lineTo(10000000, 0); SkScalar intervals[] = { 0.5f, 0.5f }; SkDashPathEffect dash(intervals, 2, 0); SkPaint paint; paint.setStyle(SkPaint::kStroke_Style); paint.setPathEffect(&dash); SkPath filteredPath; SkStrokeRec rec(paint); REPORTER_ASSERT(reporter, !dash.filterPath(&filteredPath, path, &rec, NULL)); REPORTER_ASSERT(reporter, filteredPath.isEmpty()); }
bool SkHitTestPathEx(const SkPath& path, SkScalar x, SkScalar y) { bool isInverse = path.isInverseFillType(); if (path.isEmpty()) { return isInverse; } const SkRect& bounds = path.getBounds(); if (!bounds.contains(x, y)) { return isInverse; } SkPath::Iter iter(path, true); bool done = false; int w = 0; do { SkPoint pts[4]; switch (iter.next(pts, false)) { case SkPath::kMove_Verb: case SkPath::kClose_Verb: break; case SkPath::kLine_Verb: w += winding_line(pts, x, y); break; case SkPath::kQuad_Verb: w += winding_quad(pts, x, y); break; case SkPath::kCubic_Verb: w += winding_cubic(pts, x, y); break; case SkPath::kDone_Verb: done = true; break; } } while (!done); switch (path.getFillType()) { case SkPath::kEvenOdd_FillType: case SkPath::kInverseEvenOdd_FillType: w &= 1; break; default: break; } return SkToBool(w); }
void GrGLPath::InitPathObjectPathData(GrGLGpu* gpu, GrGLuint pathID, const SkPath& skPath) { SkASSERT(!skPath.isEmpty()); #ifdef SK_SCALAR_IS_FLOAT // This branch does type punning, converting SkPoint* to GrGLfloat*. if ((skPath.getSegmentMasks() & SkPath::kConic_SegmentMask) == 0) { int verbCnt = skPath.countVerbs(); int pointCnt = skPath.countPoints(); int coordCnt = pointCnt * 2; SkSTArray<16, GrGLubyte, true> pathCommands(verbCnt); SkSTArray<16, GrGLfloat, true> pathCoords(coordCnt); static_assert(sizeof(SkPoint) == sizeof(GrGLfloat) * 2, "sk_point_not_two_floats"); pathCommands.resize_back(verbCnt); pathCoords.resize_back(coordCnt); skPath.getPoints(reinterpret_cast<SkPoint*>(&pathCoords[0]), pointCnt); skPath.getVerbs(&pathCommands[0], verbCnt); SkDEBUGCODE(int verbCoordCnt = 0); for (int i = 0; i < verbCnt; ++i) { SkPath::Verb v = static_cast<SkPath::Verb>(pathCommands[i]); pathCommands[i] = verb_to_gl_path_cmd(v); SkDEBUGCODE(verbCoordCnt += num_coords(v)); } SkASSERT(verbCnt == pathCommands.count()); SkASSERT(verbCoordCnt == pathCoords.count()); SkDEBUGCODE(verify_floats(&pathCoords[0], pathCoords.count())); GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, pathCommands.count(), &pathCommands[0], pathCoords.count(), GR_GL_FLOAT, &pathCoords[0])); return; } #endif SkAssertResult(init_path_object_for_general_path<false>(gpu, pathID, skPath)); }
bool GrAALinearizingConvexPathRenderer::onDrawPath(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, GrColor color, const SkMatrix& vm, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) { if (path.isEmpty()) { return true; } AAFlatteningConvexPathBatch::Geometry geometry; geometry.fColor = color; geometry.fViewMatrix = vm; geometry.fPath = path; geometry.fStrokeWidth = stroke.isFillStyle() ? -1.0f : stroke.getWidth(); geometry.fJoin = stroke.isFillStyle() ? SkPaint::Join::kMiter_Join : stroke.getJoin(); geometry.fMiterLimit = stroke.getMiter(); SkAutoTUnref<GrBatch> batch(AAFlatteningConvexPathBatch::Create(geometry)); target->drawBatch(pipelineBuilder, batch); return true; }
DEF_TEST(PathOpsBuilder, reporter) { SkOpBuilder builder; SkPath result; REPORTER_ASSERT(reporter, builder.resolve(&result)); REPORTER_ASSERT(reporter, result.isEmpty()); builder.add(result, kDifference_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); REPORTER_ASSERT(reporter, result.isEmpty()); builder.add(result, kUnion_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); REPORTER_ASSERT(reporter, result.isEmpty()); SkPath rectPath; rectPath.setFillType(SkPath::kEvenOdd_FillType); rectPath.addRect(0, 1, 2, 3, SkPath::kCW_Direction); builder.add(rectPath, kUnion_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); bool closed; SkPath::Direction dir; REPORTER_ASSERT(reporter, result.isRect(nullptr, &closed, &dir)); REPORTER_ASSERT(reporter, closed); REPORTER_ASSERT(reporter, dir == SkPath::kCCW_Direction); int pixelDiff = comparePaths(reporter, __FUNCTION__, rectPath, result); REPORTER_ASSERT(reporter, pixelDiff == 0); rectPath.reset(); rectPath.setFillType(SkPath::kEvenOdd_FillType); rectPath.addRect(0, 1, 2, 3, SkPath::kCCW_Direction); builder.add(rectPath, kUnion_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); REPORTER_ASSERT(reporter, result.isRect(nullptr, &closed, &dir)); REPORTER_ASSERT(reporter, closed); REPORTER_ASSERT(reporter, dir == SkPath::kCCW_Direction); REPORTER_ASSERT(reporter, rectPath == result); builder.add(rectPath, kDifference_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); REPORTER_ASSERT(reporter, result.isEmpty()); SkPath rect2, rect3; rect2.addRect(2, 1, 4, 3, SkPath::kCW_Direction); rect3.addRect(4, 1, 5, 3, SkPath::kCCW_Direction); builder.add(rectPath, kUnion_SkPathOp); builder.add(rect2, kUnion_SkPathOp); builder.add(rect3, kUnion_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); REPORTER_ASSERT(reporter, result.isRect(nullptr, &closed, &dir)); REPORTER_ASSERT(reporter, closed); SkRect expected; expected.set(0, 1, 5, 3); REPORTER_ASSERT(reporter, result.getBounds() == expected); SkPath circle1, circle2, circle3; circle1.addCircle(5, 6, 4, SkPath::kCW_Direction); circle2.addCircle(7, 4, 8, SkPath::kCCW_Direction); circle3.addCircle(6, 5, 6, SkPath::kCW_Direction); SkPath opCompare; Op(circle1, circle2, kUnion_SkPathOp, &opCompare); Op(opCompare, circle3, kDifference_SkPathOp, &opCompare); builder.add(circle1, kUnion_SkPathOp); builder.add(circle2, kUnion_SkPathOp); builder.add(circle3, kDifference_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); pixelDiff = comparePaths(reporter, __FUNCTION__, opCompare, result); REPORTER_ASSERT(reporter, pixelDiff == 0); }
//////////////////////////////////////////////////////////////////////////////// // Create a 1-bit clip mask in the stencil buffer. 'devClipBounds' are in device // (as opposed to canvas) coordinates bool GrClipMaskManager::createStencilClipMask(int32_t elementsGenID, InitialState initialState, const ElementList& elements, const SkIRect& clipSpaceIBounds, const SkIPoint& clipSpaceToStencilOffset) { SkASSERT(kNone_ClipMaskType == fCurrClipMaskType); GrDrawState* drawState = fGpu->drawState(); SkASSERT(drawState->isClipState()); GrRenderTarget* rt = drawState->getRenderTarget(); SkASSERT(NULL != rt); // TODO: dynamically attach a SB when needed. GrStencilBuffer* stencilBuffer = rt->getStencilBuffer(); if (NULL == stencilBuffer) { return false; } if (stencilBuffer->mustRenderClip(elementsGenID, clipSpaceIBounds, clipSpaceToStencilOffset)) { stencilBuffer->setLastClip(elementsGenID, clipSpaceIBounds, clipSpaceToStencilOffset); // Set the matrix so that rendered clip elements are transformed from clip to stencil space. SkVector translate = { SkIntToScalar(clipSpaceToStencilOffset.fX), SkIntToScalar(clipSpaceToStencilOffset.fY) }; SkMatrix matrix; matrix.setTranslate(translate); GrDrawTarget::AutoGeometryAndStatePush agasp(fGpu, GrDrawTarget::kReset_ASRInit, &matrix); drawState = fGpu->drawState(); drawState->setRenderTarget(rt); // We set the current clip to the bounds so that our recursive draws are scissored to them. SkIRect stencilSpaceIBounds(clipSpaceIBounds); stencilSpaceIBounds.offset(clipSpaceToStencilOffset); GrDrawTarget::AutoClipRestore acr(fGpu, stencilSpaceIBounds); drawState->enableState(GrDrawState::kClip_StateBit); #if !VISUALIZE_COMPLEX_CLIP drawState->enableState(GrDrawState::kNoColorWrites_StateBit); #endif int clipBit = stencilBuffer->bits(); SkASSERT((clipBit <= 16) && "Ganesh only handles 16b or smaller stencil buffers"); clipBit = (1 << (clipBit-1)); fGpu->clearStencilClip(stencilSpaceIBounds, kAllIn_InitialState == initialState); // walk through each clip element and perform its set op // with the existing clip. for (ElementList::Iter iter(elements.headIter()); NULL != iter.get(); iter.next()) { const Element* element = iter.get(); bool fillInverted = false; // enabled at bottom of loop drawState->disableState(GrGpu::kModifyStencilClip_StateBit); // if the target is MSAA then we want MSAA enabled when the clip is soft if (rt->isMultisampled()) { drawState->setState(GrDrawState::kHWAntialias_StateBit, element->isAA()); } // This will be used to determine whether the clip shape can be rendered into the // stencil with arbitrary stencil settings. GrPathRenderer::StencilSupport stencilSupport; SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle); SkRegion::Op op = element->getOp(); GrPathRenderer* pr = NULL; SkPath clipPath; if (Element::kRect_Type == element->getType()) { stencilSupport = GrPathRenderer::kNoRestriction_StencilSupport; fillInverted = false; } else { element->asPath(&clipPath); fillInverted = clipPath.isInverseFillType(); if (fillInverted) { clipPath.toggleInverseFillType(); } pr = this->getContext()->getPathRenderer(clipPath, stroke, fGpu, false, GrPathRendererChain::kStencilOnly_DrawType, &stencilSupport); if (NULL == pr) { return false; } } int passes; GrStencilSettings stencilSettings[GrStencilSettings::kMaxStencilClipPasses]; bool canRenderDirectToStencil = GrPathRenderer::kNoRestriction_StencilSupport == stencilSupport; bool canDrawDirectToClip; // Given the renderer, the element, // fill rule, and set operation can // we render the element directly to // stencil bit used for clipping. canDrawDirectToClip = GrStencilSettings::GetClipPasses(op, canRenderDirectToStencil, clipBit, fillInverted, &passes, stencilSettings); // draw the element to the client stencil bits if necessary if (!canDrawDirectToClip) { GR_STATIC_CONST_SAME_STENCIL(gDrawToStencil, kIncClamp_StencilOp, kIncClamp_StencilOp, kAlways_StencilFunc, 0xffff, 0x0000, 0xffff); SET_RANDOM_COLOR if (Element::kRect_Type == element->getType()) { *drawState->stencil() = gDrawToStencil; fGpu->drawSimpleRect(element->getRect(), NULL); } else { if (!clipPath.isEmpty()) { if (canRenderDirectToStencil) { *drawState->stencil() = gDrawToStencil; pr->drawPath(clipPath, stroke, fGpu, false); } else { pr->stencilPath(clipPath, stroke, fGpu); } } } } // now we modify the clip bit by rendering either the clip // element directly or a bounding rect of the entire clip. drawState->enableState(GrGpu::kModifyStencilClip_StateBit); for (int p = 0; p < passes; ++p) { *drawState->stencil() = stencilSettings[p]; if (canDrawDirectToClip) { if (Element::kRect_Type == element->getType()) { SET_RANDOM_COLOR fGpu->drawSimpleRect(element->getRect(), NULL); } else { SET_RANDOM_COLOR pr->drawPath(clipPath, stroke, fGpu, false); } } else { SET_RANDOM_COLOR // The view matrix is setup to do clip space -> stencil space translation, so // draw rect in clip space. fGpu->drawSimpleRect(SkRect::Make(clipSpaceIBounds), NULL); } } }
void GrCCFiller::parseDeviceSpaceFill(const SkPath& path, const SkPoint* deviceSpacePts, GrScissorTest scissorTest, const SkIRect& clippedDevIBounds, const SkIVector& devToAtlasOffset) { SkASSERT(!fInstanceBuffer); // Can't call after prepareToDraw(). SkASSERT(!path.isEmpty()); int currPathPointsIdx = fGeometry.points().count(); int currPathVerbsIdx = fGeometry.verbs().count(); PrimitiveTallies currPathPrimitiveCounts = PrimitiveTallies(); fGeometry.beginPath(); const float* conicWeights = SkPathPriv::ConicWeightData(path); int ptsIdx = 0; int conicWeightsIdx = 0; bool insideContour = false; for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { switch (verb) { case SkPath::kMove_Verb: if (insideContour) { currPathPrimitiveCounts += fGeometry.endContour(); } fGeometry.beginContour(deviceSpacePts[ptsIdx]); ++ptsIdx; insideContour = true; continue; case SkPath::kClose_Verb: if (insideContour) { currPathPrimitiveCounts += fGeometry.endContour(); } insideContour = false; continue; case SkPath::kLine_Verb: fGeometry.lineTo(&deviceSpacePts[ptsIdx - 1]); ++ptsIdx; continue; case SkPath::kQuad_Verb: fGeometry.quadraticTo(&deviceSpacePts[ptsIdx - 1]); ptsIdx += 2; continue; case SkPath::kCubic_Verb: fGeometry.cubicTo(&deviceSpacePts[ptsIdx - 1]); ptsIdx += 3; continue; case SkPath::kConic_Verb: fGeometry.conicTo(&deviceSpacePts[ptsIdx - 1], conicWeights[conicWeightsIdx]); ptsIdx += 2; ++conicWeightsIdx; continue; default: SK_ABORT("Unexpected path verb."); } } SkASSERT(ptsIdx == path.countPoints()); SkASSERT(conicWeightsIdx == SkPathPriv::ConicWeightCnt(path)); if (insideContour) { currPathPrimitiveCounts += fGeometry.endContour(); } fPathInfos.emplace_back(scissorTest, devToAtlasOffset); // Tessellate fans from very large and/or simple paths, in order to reduce overdraw. int numVerbs = fGeometry.verbs().count() - currPathVerbsIdx - 1; int64_t tessellationWork = (int64_t)numVerbs * (32 - SkCLZ(numVerbs)); // N log N. int64_t fanningWork = (int64_t)clippedDevIBounds.height() * clippedDevIBounds.width(); if (tessellationWork * (50*50) + (100*100) < fanningWork) { // Don't tessellate under 100x100. fPathInfos.back().tessellateFan(fGeometry, currPathVerbsIdx, currPathPointsIdx, clippedDevIBounds, &currPathPrimitiveCounts); } fTotalPrimitiveCounts[(int)scissorTest] += currPathPrimitiveCounts; if (GrScissorTest::kEnabled == scissorTest) { fScissorSubBatches.push_back() = {fTotalPrimitiveCounts[(int)GrScissorTest::kEnabled], clippedDevIBounds.makeOffset(devToAtlasOffset.fX, devToAtlasOffset.fY)}; } }
void GrGLPath::InitPathObject(GrGLGpu* gpu, GrGLuint pathID, const SkPath& skPath, const GrStrokeInfo& stroke) { SkASSERT(!stroke.isDashed()); if (!skPath.isEmpty()) { int verbCnt = skPath.countVerbs(); int pointCnt = skPath.countPoints(); int minCoordCnt = pointCnt * 2; SkSTArray<16, GrGLubyte, true> pathCommands(verbCnt); SkSTArray<16, GrGLfloat, true> pathCoords(minCoordCnt); SkDEBUGCODE(int numCoords = 0); if ((skPath.getSegmentMasks() & SkPath::kConic_SegmentMask) == 0) { // This branch does type punning, converting SkPoint* to GrGLfloat*. SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(GrGLfloat) * 2, sk_point_not_two_floats); // This branch does not convert with SkScalarToFloat. #ifndef SK_SCALAR_IS_FLOAT #error Need SK_SCALAR_IS_FLOAT. #endif pathCommands.resize_back(verbCnt); pathCoords.resize_back(minCoordCnt); skPath.getPoints(reinterpret_cast<SkPoint*>(&pathCoords[0]), pointCnt); skPath.getVerbs(&pathCommands[0], verbCnt); for (int i = 0; i < verbCnt; ++i) { SkPath::Verb v = static_cast<SkPath::Verb>(pathCommands[i]); pathCommands[i] = verb_to_gl_path_cmd(v); SkDEBUGCODE(numCoords += num_coords(v)); } } else { SkPoint points[4]; SkPath::RawIter iter(skPath); SkPath::Verb verb; while ((verb = iter.next(points)) != SkPath::kDone_Verb) { pathCommands.push_back(verb_to_gl_path_cmd(verb)); GrGLfloat coords[6]; int coordsForVerb; switch (verb) { case SkPath::kMove_Verb: points_to_coords(points, 0, 1, coords); coordsForVerb = 2; break; case SkPath::kLine_Verb: points_to_coords(points, 1, 1, coords); coordsForVerb = 2; break; case SkPath::kConic_Verb: points_to_coords(points, 1, 2, coords); coords[4] = SkScalarToFloat(iter.conicWeight()); coordsForVerb = 5; break; case SkPath::kQuad_Verb: points_to_coords(points, 1, 2, coords); coordsForVerb = 4; break; case SkPath::kCubic_Verb: points_to_coords(points, 1, 3, coords); coordsForVerb = 6; break; case SkPath::kClose_Verb: continue; default: SkASSERT(false); // Not reached. continue; } SkDEBUGCODE(numCoords += num_coords(verb)); pathCoords.push_back_n(coordsForVerb, coords); } } SkASSERT(verbCnt == pathCommands.count()); SkASSERT(numCoords == pathCoords.count()); GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, pathCommands.count(), &pathCommands[0], pathCoords.count(), GR_GL_FLOAT, &pathCoords[0])); } else { GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, 0, NULL, 0, GR_GL_FLOAT, NULL)); } if (stroke.needToApply()) { SkASSERT(!stroke.isHairlineStyle()); GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_WIDTH, SkScalarToFloat(stroke.getWidth()))); GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_MITER_LIMIT, SkScalarToFloat(stroke.getMiter()))); GrGLenum join = join_to_gl_join(stroke.getJoin()); GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_JOIN_STYLE, join)); GrGLenum cap = cap_to_gl_cap(stroke.getCap()); GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_END_CAPS, cap)); GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_BOUND, 0.02f)); } }
static void test_zero_length_paths(skiatest::Reporter* reporter) { SkPath p; SkRect bounds; // Lone moveTo case p.moveTo(SK_Scalar1, SK_Scalar1); bounds.set(0, 0, 0, 0); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 1 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // MoveTo-MoveTo case p.moveTo(SK_Scalar1*2, SK_Scalar1); bounds.set(SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 2 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // moveTo-close case p.reset(); p.moveTo(SK_Scalar1, SK_Scalar1); p.close(); bounds.set(0, 0, 0, 0); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 1 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // moveTo-close-moveTo-close case p.moveTo(SK_Scalar1*2, SK_Scalar1); p.close(); bounds.set(SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 2 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // moveTo-line case p.reset(); p.moveTo(SK_Scalar1, SK_Scalar1); p.lineTo(SK_Scalar1, SK_Scalar1); bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 2 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // moveTo-lineTo-moveTo-lineTo case p.moveTo(SK_Scalar1*2, SK_Scalar1); p.lineTo(SK_Scalar1*2, SK_Scalar1); bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1*2, SK_Scalar1); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 4 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // moveTo-line-close case p.reset(); p.moveTo(SK_Scalar1, SK_Scalar1); p.lineTo(SK_Scalar1, SK_Scalar1); p.close(); bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 2 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // moveTo-line-close-moveTo-line-close case p.moveTo(SK_Scalar1*2, SK_Scalar1); p.lineTo(SK_Scalar1*2, SK_Scalar1); p.close(); bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1*2, SK_Scalar1); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 4 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // moveTo-quadTo case p.reset(); p.moveTo(SK_Scalar1, SK_Scalar1); p.quadTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1); bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 3 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // moveTo-quadTo-close case p.close(); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 3 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // moveTo-quadTo-moveTo-quadTo case p.reset(); p.moveTo(SK_Scalar1, SK_Scalar1); p.quadTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1); p.moveTo(SK_Scalar1*2, SK_Scalar1); p.quadTo(SK_Scalar1*2, SK_Scalar1, SK_Scalar1*2, SK_Scalar1); bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1*2, SK_Scalar1); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 6 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // moveTo-cubicTo case p.reset(); p.moveTo(SK_Scalar1, SK_Scalar1); p.cubicTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1); bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 4 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // moveTo-quadTo-close case p.close(); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 4 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); // moveTo-quadTo-moveTo-quadTo case p.reset(); p.moveTo(SK_Scalar1, SK_Scalar1); p.cubicTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1); p.moveTo(SK_Scalar1*2, SK_Scalar1); p.cubicTo(SK_Scalar1*2, SK_Scalar1, SK_Scalar1*2, SK_Scalar1, SK_Scalar1*2, SK_Scalar1); bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1*2, SK_Scalar1); REPORTER_ASSERT(reporter, !p.isEmpty()); REPORTER_ASSERT(reporter, 8 == p.countPoints()); REPORTER_ASSERT(reporter, bounds == p.getBounds()); }
//////////////////////////////////////////////////////////////////////////////// // Create a 1-bit clip mask in the stencil buffer. 'devClipBounds' are in device // (as opposed to canvas) coordinates bool GrClipMaskManager::CreateStencilClipMask(GrContext* context, GrDrawContext* drawContext, int32_t elementsGenID, GrReducedClip::InitialState initialState, const GrReducedClip::ElementList& elements, const SkIRect& clipSpaceIBounds, const SkIPoint& clipSpaceToStencilOffset) { SkASSERT(drawContext); GrStencilAttachment* stencilAttachment = context->resourceProvider()->attachStencilAttachment( drawContext->accessRenderTarget()); if (nullptr == stencilAttachment) { return false; } // TODO: these need to be swapped over to using a StencilAttachmentProxy if (stencilAttachment->mustRenderClip(elementsGenID, clipSpaceIBounds, clipSpaceToStencilOffset)) { stencilAttachment->setLastClip(elementsGenID, clipSpaceIBounds, clipSpaceToStencilOffset); // Set the matrix so that rendered clip elements are transformed from clip to stencil space. SkVector translate = { SkIntToScalar(clipSpaceToStencilOffset.fX), SkIntToScalar(clipSpaceToStencilOffset.fY) }; SkMatrix viewMatrix; viewMatrix.setTranslate(translate); // We set the current clip to the bounds so that our recursive draws are scissored to them. SkIRect stencilSpaceIBounds(clipSpaceIBounds); stencilSpaceIBounds.offset(clipSpaceToStencilOffset); GrFixedClip clip(stencilSpaceIBounds); drawContext->drawContextPriv().clearStencilClip( stencilSpaceIBounds, GrReducedClip::kAllIn_InitialState == initialState); // walk through each clip element and perform its set op // with the existing clip. for (GrReducedClip::ElementList::Iter iter(elements.headIter()); iter.get(); iter.next()) { const Element* element = iter.get(); bool useHWAA = element->isAA() && drawContext->isStencilBufferMultisampled(); bool fillInverted = false; // enabled at bottom of loop clip.enableStencilClip(false); // This will be used to determine whether the clip shape can be rendered into the // stencil with arbitrary stencil settings. GrPathRenderer::StencilSupport stencilSupport; SkRegion::Op op = element->getOp(); GrPathRenderer* pr = nullptr; SkPath clipPath; if (Element::kRect_Type == element->getType()) { stencilSupport = GrPathRenderer::kNoRestriction_StencilSupport; fillInverted = false; } else { element->asPath(&clipPath); fillInverted = clipPath.isInverseFillType(); if (fillInverted) { clipPath.toggleInverseFillType(); } GrShape shape(clipPath, GrStyle::SimpleFill()); GrPathRenderer::CanDrawPathArgs canDrawArgs; canDrawArgs.fShaderCaps = context->caps()->shaderCaps(); canDrawArgs.fViewMatrix = &viewMatrix; canDrawArgs.fShape = &shape; canDrawArgs.fAntiAlias = false; canDrawArgs.fHasUserStencilSettings = false; canDrawArgs.fIsStencilBufferMSAA = drawContext->isStencilBufferMultisampled(); pr = context->drawingManager()->getPathRenderer(canDrawArgs, false, GrPathRendererChain::kStencilOnly_DrawType, &stencilSupport); if (!pr) { return false; } } bool canRenderDirectToStencil = GrPathRenderer::kNoRestriction_StencilSupport == stencilSupport; bool drawDirectToClip; // Given the renderer, the element, // fill rule, and set operation should // we render the element directly to // stencil bit used for clipping. GrUserStencilSettings const* const* stencilPasses = GrStencilSettings::GetClipPasses(op, canRenderDirectToStencil, fillInverted, &drawDirectToClip); // draw the element to the client stencil bits if necessary if (!drawDirectToClip) { static constexpr GrUserStencilSettings kDrawToStencil( GrUserStencilSettings::StaticInit< 0x0000, GrUserStencilTest::kAlways, 0xffff, GrUserStencilOp::kIncMaybeClamp, GrUserStencilOp::kIncMaybeClamp, 0xffff>() ); if (Element::kRect_Type == element->getType()) { drawContext->drawContextPriv().stencilRect(clip, &kDrawToStencil, useHWAA, viewMatrix, element->getRect()); } else { if (!clipPath.isEmpty()) { GrShape shape(clipPath, GrStyle::SimpleFill()); if (canRenderDirectToStencil) { GrPaint paint; paint.setXPFactory(GrDisableColorXPFactory::Make()); paint.setAntiAlias(element->isAA()); GrPathRenderer::DrawPathArgs args; args.fResourceProvider = context->resourceProvider(); args.fPaint = &paint; args.fUserStencilSettings = &kDrawToStencil; args.fDrawContext = drawContext; args.fClip = &clip; args.fColor = GrColor_WHITE; args.fViewMatrix = &viewMatrix; args.fShape = &shape; args.fAntiAlias = false; args.fGammaCorrect = false; pr->drawPath(args); } else { GrPathRenderer::StencilPathArgs args; args.fResourceProvider = context->resourceProvider(); args.fDrawContext = drawContext; args.fClip = &clip; args.fViewMatrix = &viewMatrix; args.fIsAA = element->isAA(); args.fShape = &shape; pr->stencilPath(args); } } } } // now we modify the clip bit by rendering either the clip // element directly or a bounding rect of the entire clip. clip.enableStencilClip(true); for (GrUserStencilSettings const* const* pass = stencilPasses; *pass; ++pass) { if (drawDirectToClip) { if (Element::kRect_Type == element->getType()) { drawContext->drawContextPriv().stencilRect(clip, *pass, useHWAA, viewMatrix, element->getRect()); } else { GrShape shape(clipPath, GrStyle::SimpleFill()); GrPaint paint; paint.setXPFactory(GrDisableColorXPFactory::Make()); paint.setAntiAlias(element->isAA()); GrPathRenderer::DrawPathArgs args; args.fResourceProvider = context->resourceProvider(); args.fPaint = &paint; args.fUserStencilSettings = *pass; args.fDrawContext = drawContext; args.fClip = &clip; args.fColor = GrColor_WHITE; args.fViewMatrix = &viewMatrix; args.fShape = &shape; args.fAntiAlias = false; args.fGammaCorrect = false; pr->drawPath(args); } } else { // The view matrix is setup to do clip space -> stencil space translation, so // draw rect in clip space. drawContext->drawContextPriv().stencilRect(clip, *pass, false, viewMatrix, SkRect::Make(clipSpaceIBounds)); } } } } return true; }
void draw(SkCanvas* canvas) { SkPath path; SkDebugf("path is " "%s" "empty", path.isEmpty() ? "" : "not "); }
void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width, SkScalar scale, bool drawText) { if (path.isEmpty()) { return; } SkRect bounds = path.getBounds(); this->setWHZ(SkScalarCeilToInt(bounds.right()), drawText ? SkScalarRoundToInt(scale * 3 / 2) : SkScalarRoundToInt(scale), SkScalarRoundToInt(950.0f / scale)); erase(fMinSurface); SkPaint paint; paint.setColor(0x1f1f0f0f); paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(width * scale * scale); paint.setColor(0x3f0f1f3f); if (drawText) { fMinSurface->getCanvas()->drawPath(path, paint); this->copyMinToMax(); fMaxSurface->draw(canvas, 0, 0, NULL); } paint.setAntiAlias(true); paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(1); paint.setColor(SKELETON_COLOR); SkPath scaled; SkMatrix matrix; matrix.reset(); matrix.setScale(950 / scale, 950 / scale); if (drawText) { path.transform(matrix, &scaled); } else { scaled = path; } canvas->drawPath(scaled, paint); draw_points(canvas, scaled, SKELETON_COLOR, true); if (fDrawRibs) { draw_ribs(canvas, scaled, width, 0xFF00FF00); } SkPath fill; SkPaint p; p.setStyle(SkPaint::kStroke_Style); if (drawText) { p.setStrokeWidth(width * scale * scale); } else { p.setStrokeWidth(width); } p.getFillPath(path, &fill); SkPath scaledFill; if (drawText) { fill.transform(matrix, &scaledFill); } else { scaledFill = fill; } paint.setColor(WIREFRAME_COLOR); canvas->drawPath(scaledFill, paint); draw_points(canvas, scaledFill, WIREFRAME_COLOR, false); }
bool GrStrokePathRenderer::onDrawPath(const SkPath& origPath, const SkStrokeRec& stroke, GrDrawTarget* target, bool antiAlias) { if (origPath.isEmpty()) { return true; } SkScalar width = stroke.getWidth(); if (width <= 0) { return false; } // Get the join type SkPaint::Join join = stroke.getJoin(); SkScalar miterLimit = stroke.getMiter(); SkScalar sqMiterLimit = SkScalarMul(miterLimit, miterLimit); if ((join == SkPaint::kMiter_Join) && (miterLimit <= SK_Scalar1)) { // If the miter limit is small, treat it as a bevel join join = SkPaint::kBevel_Join; } const bool isMiter = (join == SkPaint::kMiter_Join); const bool isBevel = (join == SkPaint::kBevel_Join); SkScalar invMiterLimit = isMiter ? SK_Scalar1 / miterLimit : 0; SkScalar invMiterLimitSq = SkScalarMul(invMiterLimit, invMiterLimit); // Allocate vertices const int nbQuads = origPath.countPoints() + 1; // Could be "-1" if path is not closed const int extraVerts = isMiter || isBevel ? 1 : 0; const int maxVertexCount = nbQuads * (4 + extraVerts); const int maxIndexCount = nbQuads * (6 + extraVerts * 3); // Each extra vert adds a triangle target->drawState()->setDefaultVertexAttribs(); GrDrawTarget::AutoReleaseGeometry arg(target, maxVertexCount, maxIndexCount); if (!arg.succeeded()) { return false; } SkPoint* verts = reinterpret_cast<SkPoint*>(arg.vertices()); uint16_t* idxs = reinterpret_cast<uint16_t*>(arg.indices()); int vCount = 0, iCount = 0; // Transform the path into a list of triangles SkPath::Iter iter(origPath, false); SkPoint pts[4]; const SkScalar radius = SkScalarMul(width, 0.5f); SkPoint *firstPt = verts, *lastPt = NULL; SkVector firstDir, dir; firstDir.set(0, 0); dir.set(0, 0); bool isOpen = true; for(SkPath::Verb v = iter.next(pts); v != SkPath::kDone_Verb; v = iter.next(pts)) { switch(v) { case SkPath::kMove_Verb: // This will already be handled as pts[0] of the 1st line break; case SkPath::kClose_Verb: isOpen = (lastPt == NULL); break; case SkPath::kLine_Verb: { SkVector v0 = dir; dir = pts[1] - pts[0]; if (dir.setLength(radius)) { SkVector dirT; dirT.set(dir.fY, -dir.fX); // Get perpendicular direction SkPoint l1a = pts[0]+dirT, l1b = pts[1]+dirT, l2a = pts[0]-dirT, l2b = pts[1]-dirT; SkPoint miterPt[2]; bool useMiterPoint = false; int idx0(-1), idx1(-1); if (NULL == lastPt) { firstDir = dir; } else { SkVector v1 = dir; if (v0.normalize() && v1.normalize()) { SkScalar dotProd = v0.dot(v1); // No need for bevel or miter join if the angle // is either 0 or 180 degrees if (!SkScalarNearlyZero(dotProd + SK_Scalar1) && !SkScalarNearlyZero(dotProd - SK_Scalar1)) { bool ccw = !is_clockwise(v0, v1); int offset = ccw ? 1 : 0; idx0 = vCount-2+offset; idx1 = vCount+offset; const SkPoint* pt0 = &(lastPt[offset]); const SkPoint* pt1 = ccw ? &l2a : &l1a; switch(join) { case SkPaint::kMiter_Join: { // *Note : Logic is from MiterJoiner // FIXME : Special case if we have a right angle ? // if (SkScalarNearlyZero(dotProd)) {...} SkScalar sinHalfAngleSq = SkScalarHalf(SK_Scalar1 + dotProd); if (sinHalfAngleSq >= invMiterLimitSq) { // Find the miter point (or points if it is further // than the miter limit) const SkPoint pt2 = *pt0+v0, pt3 = *pt1+v1; if (intersection(*pt0, pt2, *pt1, pt3, miterPt[0]) != kNone_IntersectionType) { SkPoint miterPt0 = miterPt[0] - *pt0; SkPoint miterPt1 = miterPt[0] - *pt1; SkScalar sqDist0 = miterPt0.dot(miterPt0); SkScalar sqDist1 = miterPt1.dot(miterPt1); const SkScalar rSq = SkScalarDiv(SkScalarMul(radius, radius), sinHalfAngleSq); const SkScalar sqRLimit = SkScalarMul(sqMiterLimit, rSq); if (sqDist0 > sqRLimit || sqDist1 > sqRLimit) { if (sqDist1 > sqRLimit) { v1.setLength(SkScalarSqrt(sqRLimit)); miterPt[1] = *pt1+v1; } else { miterPt[1] = miterPt[0]; } if (sqDist0 > sqRLimit) { v0.setLength(SkScalarSqrt(sqRLimit)); miterPt[0] = *pt0+v0; } } else { miterPt[1] = miterPt[0]; } useMiterPoint = true; } } if (useMiterPoint && (miterPt[1] == miterPt[0])) { break; } } default: case SkPaint::kBevel_Join: { // Note : This currently causes some overdraw where both // lines initially intersect. We'd need to add // another line intersection check here if the // overdraw becomes an issue instead of using the // current point directly. // Add center point *verts++ = pts[0]; // Use current point directly // This idx is passed the current point so increment it ++idx1; // Add center triangle *idxs++ = idx0; *idxs++ = vCount; *idxs++ = idx1; vCount++; iCount += 3; } break; } } } } *verts++ = l1a; *verts++ = l2a; lastPt = verts; *verts++ = l1b; *verts++ = l2b; if (useMiterPoint && (idx0 >= 0) && (idx1 >= 0)) { firstPt[idx0] = miterPt[0]; firstPt[idx1] = miterPt[1]; } // 1st triangle *idxs++ = vCount+0; *idxs++ = vCount+2; *idxs++ = vCount+1; // 2nd triangle *idxs++ = vCount+1; *idxs++ = vCount+2; *idxs++ = vCount+3; vCount += 4; iCount += 6; } } break; case SkPath::kQuad_Verb: case SkPath::kCubic_Verb: SkDEBUGFAIL("Curves not supported!"); default: // Unhandled cases SkASSERT(false); } } if (isOpen) { // Add caps switch (stroke.getCap()) { case SkPaint::kSquare_Cap: firstPt[0] -= firstDir; firstPt[1] -= firstDir; lastPt [0] += dir; lastPt [1] += dir; break; case SkPaint::kRound_Cap: SkDEBUGFAIL("Round caps not supported!"); default: // No cap break; } } SkASSERT(vCount <= maxVertexCount); SkASSERT(iCount <= maxIndexCount); if (vCount > 0) { target->drawIndexed(kTriangles_GrPrimitiveType, 0, // start vertex 0, // start index vCount, iCount); } return true; }
static void dump(const SkPath& path) { const SkRect& r = path.getBounds(); SkDebugf("isEmpty %d, bounds [%g %g %g %g]\n", path.isEmpty(), r.fLeft, r.fTop, r.fRight, r.fBottom); }
//////////////////////////////////////////////////////////////////////////////// // Create a 1-bit clip mask in the stencil buffer. 'devClipBounds' are in device // (as opposed to canvas) coordinates bool GrClipMaskManager::createStencilClipMask(GrRenderTarget* rt, int32_t elementsGenID, GrReducedClip::InitialState initialState, const GrReducedClip::ElementList& elements, const SkIRect& clipSpaceIBounds, const SkIPoint& clipSpaceToStencilOffset) { SkASSERT(rt); GrStencilAttachment* stencilAttachment = fDrawTarget->cmmAccess().resourceProvider()->attachStencilAttachment(rt); if (nullptr == stencilAttachment) { return false; } if (stencilAttachment->mustRenderClip(elementsGenID, clipSpaceIBounds, clipSpaceToStencilOffset)) { stencilAttachment->setLastClip(elementsGenID, clipSpaceIBounds, clipSpaceToStencilOffset); // Set the matrix so that rendered clip elements are transformed from clip to stencil space. SkVector translate = { SkIntToScalar(clipSpaceToStencilOffset.fX), SkIntToScalar(clipSpaceToStencilOffset.fY) }; SkMatrix viewMatrix; viewMatrix.setTranslate(translate); // We set the current clip to the bounds so that our recursive draws are scissored to them. SkIRect stencilSpaceIBounds(clipSpaceIBounds); stencilSpaceIBounds.offset(clipSpaceToStencilOffset); GrClip clip(stencilSpaceIBounds); int clipBit = stencilAttachment->bits(); SkASSERT((clipBit <= 16) && "Ganesh only handles 16b or smaller stencil buffers"); clipBit = (1 << (clipBit-1)); fDrawTarget->cmmAccess().clearStencilClip(stencilSpaceIBounds, GrReducedClip::kAllIn_InitialState == initialState, rt); // walk through each clip element and perform its set op // with the existing clip. for (GrReducedClip::ElementList::Iter iter(elements.headIter()); iter.get(); iter.next()) { const Element* element = iter.get(); GrPipelineBuilder pipelineBuilder; pipelineBuilder.setClip(clip); pipelineBuilder.setRenderTarget(rt); pipelineBuilder.setDisableColorXPFactory(); // if the target is MSAA then we want MSAA enabled when the clip is soft if (rt->isStencilBufferMultisampled()) { pipelineBuilder.setState(GrPipelineBuilder::kHWAntialias_Flag, element->isAA()); } bool fillInverted = false; // enabled at bottom of loop fClipMode = kIgnoreClip_StencilClipMode; // This will be used to determine whether the clip shape can be rendered into the // stencil with arbitrary stencil settings. GrPathRenderer::StencilSupport stencilSupport; GrStrokeInfo stroke(SkStrokeRec::kFill_InitStyle); SkRegion::Op op = element->getOp(); GrPathRenderer* pr = nullptr; SkPath clipPath; if (Element::kRect_Type == element->getType()) { stencilSupport = GrPathRenderer::kNoRestriction_StencilSupport; fillInverted = false; } else { element->asPath(&clipPath); fillInverted = clipPath.isInverseFillType(); if (fillInverted) { clipPath.toggleInverseFillType(); } pr = this->getContext()->getPathRenderer(fDrawTarget, &pipelineBuilder, viewMatrix, clipPath, stroke, false, GrPathRendererChain::kStencilOnly_DrawType, &stencilSupport); if (nullptr == pr) { return false; } } int passes; GrStencilSettings stencilSettings[GrStencilSettings::kMaxStencilClipPasses]; bool canRenderDirectToStencil = GrPathRenderer::kNoRestriction_StencilSupport == stencilSupport; bool canDrawDirectToClip; // Given the renderer, the element, // fill rule, and set operation can // we render the element directly to // stencil bit used for clipping. canDrawDirectToClip = GrStencilSettings::GetClipPasses(op, canRenderDirectToStencil, clipBit, fillInverted, &passes, stencilSettings); // draw the element to the client stencil bits if necessary if (!canDrawDirectToClip) { GR_STATIC_CONST_SAME_STENCIL(gDrawToStencil, kIncClamp_StencilOp, kIncClamp_StencilOp, kAlways_StencilFunc, 0xffff, 0x0000, 0xffff); if (Element::kRect_Type == element->getType()) { *pipelineBuilder.stencil() = gDrawToStencil; // We need this AGP until everything is in GrBatch fDrawTarget->drawNonAARect(pipelineBuilder, GrColor_WHITE, viewMatrix, element->getRect()); } else { if (!clipPath.isEmpty()) { if (canRenderDirectToStencil) { *pipelineBuilder.stencil() = gDrawToStencil; GrPathRenderer::DrawPathArgs args; args.fTarget = fDrawTarget; args.fResourceProvider = this->getContext()->resourceProvider(); args.fPipelineBuilder = &pipelineBuilder; args.fColor = GrColor_WHITE; args.fViewMatrix = &viewMatrix; args.fPath = &clipPath; args.fStroke = &stroke; args.fAntiAlias = false; pr->drawPath(args); } else { GrPathRenderer::StencilPathArgs args; args.fTarget = fDrawTarget; args.fResourceProvider = this->getContext()->resourceProvider(); args.fPipelineBuilder = &pipelineBuilder; args.fViewMatrix = &viewMatrix; args.fPath = &clipPath; args.fStroke = &stroke; pr->stencilPath(args); } } } } // now we modify the clip bit by rendering either the clip // element directly or a bounding rect of the entire clip. fClipMode = kModifyClip_StencilClipMode; for (int p = 0; p < passes; ++p) { *pipelineBuilder.stencil() = stencilSettings[p]; if (canDrawDirectToClip) { if (Element::kRect_Type == element->getType()) { // We need this AGP until everything is in GrBatch fDrawTarget->drawNonAARect(pipelineBuilder, GrColor_WHITE, viewMatrix, element->getRect()); } else { GrPathRenderer::DrawPathArgs args; args.fTarget = fDrawTarget; args.fResourceProvider = this->getContext()->resourceProvider(); args.fPipelineBuilder = &pipelineBuilder; args.fColor = GrColor_WHITE; args.fViewMatrix = &viewMatrix; args.fPath = &clipPath; args.fStroke = &stroke; args.fAntiAlias = false; pr->drawPath(args); } } else { // The view matrix is setup to do clip space -> stencil space translation, so // draw rect in clip space. fDrawTarget->drawNonAARect(pipelineBuilder, GrColor_WHITE, viewMatrix, SkRect::Make(clipSpaceIBounds)); } } } } fClipMode = kRespectClip_StencilClipMode; return true; }