GrGLPath::GrGLPath(GrGLGpu* gpu, const SkPath& origSkPath, const GrStrokeInfo& origStroke) : INHERITED(gpu, origSkPath, origStroke), fPathID(gpu->glPathRendering()->genPaths(1)) { // Convert a dashing to either a stroke or a fill. const SkPath* skPath = &origSkPath; SkTLazy<SkPath> tmpPath; const GrStrokeInfo* stroke = &origStroke; GrStrokeInfo tmpStroke(SkStrokeRec::kFill_InitStyle); if (stroke->isDashed()) { if (stroke->applyDashToPath(tmpPath.init(), &tmpStroke, *skPath)) { skPath = tmpPath.get(); stroke = &tmpStroke; } } InitPathObject(gpu, fPathID, *skPath, *stroke); fShouldStroke = stroke->needToApply(); fShouldFill = stroke->isFillStyle() || stroke->getStyle() == SkStrokeRec::kStrokeAndFill_Style; if (fShouldStroke) { // FIXME: try to account for stroking, without rasterizing the stroke. fBounds.outset(stroke->getWidth(), stroke->getWidth()); } this->registerWithCache(); }
void GrBlurUtils::drawPathWithMaskFilter(GrContext* context, GrDrawContext* drawContext, const GrClip& clip, const SkPath& origPath, GrPaint* paint, const SkMatrix& viewMatrix, const SkMaskFilter* mf, const SkPathEffect* pathEffect, const GrStrokeInfo& origStrokeInfo, bool pathIsMutable) { SkPath* pathPtr = const_cast<SkPath*>(&origPath); SkTLazy<SkPath> tmpPath; GrStrokeInfo strokeInfo(origStrokeInfo); if (!strokeInfo.isDashed() && pathEffect && pathEffect->filterPath(tmpPath.init(), *pathPtr, &strokeInfo, nullptr)) { pathPtr = tmpPath.get(); pathPtr->setIsVolatile(true); pathIsMutable = true; pathEffect = nullptr; } draw_path_with_mask_filter(context, drawContext, clip, paint, viewMatrix, mf, pathEffect, strokeInfo, pathPtr, pathIsMutable); }
void GrBlurUtils::drawPathWithMaskFilter(GrContext* context, GrDrawContext* drawContext, const GrClip& clip, const SkPath& origPath, const SkPaint& paint, const SkMatrix& origViewMatrix, const SkMatrix* prePathMatrix, const SkIRect& clipBounds, bool pathIsMutable) { SkASSERT(!pathIsMutable || origPath.isVolatile()); GrStyle style(paint); // If we have a prematrix, apply it to the path, optimizing for the case // where the original path can in fact be modified in place (even though // its parameter type is const). const SkPath* path = &origPath; SkTLazy<SkPath> tmpPath; SkMatrix viewMatrix = origViewMatrix; if (prePathMatrix) { // Styling, blurs, and shading are supposed to be applied *after* the prePathMatrix. if (!paint.getMaskFilter() && !paint.getShader() && !style.applies()) { viewMatrix.preConcat(*prePathMatrix); } else { SkPath* result = pathIsMutable ? const_cast<SkPath*>(path) : tmpPath.init(); pathIsMutable = true; path->transform(*prePathMatrix, result); path = result; result->setIsVolatile(true); } } // at this point we're done with prePathMatrix SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;)
int SkiaCanvas::saveLayerAlpha(float left, float top, float right, float bottom, int alpha, SaveFlags::Flags flags) { SkTLazy<SkPaint> alphaPaint; if (static_cast<unsigned>(alpha) < 0xFF) { alphaPaint.init()->setAlpha(alpha); } return this->saveLayer(left, top, right, bottom, alphaPaint.getMaybeNull(), flags); }
bool SkMiniRecorder::drawBitmapRect(const SkBitmap& bm, const SkRect* src, const SkRect& dst, const SkPaint* p, SkCanvas::SrcRectConstraint constraint) { SkRect bounds; if (!src) { bm.getBounds(&bounds); src = &bounds; } SkTLazy<SkPaint> defaultPaint; if (!p) { p = defaultPaint.init(); } TRY_TO_STORE(DrawBitmapRectFixedSize, *p, bm, *src, dst, constraint); }
void GrBlurUtils::drawPathWithMaskFilter(GrContext* context, GrDrawContext* drawContext, const GrClip& clip, const SkPath& origSrcPath, const SkPaint& paint, const SkMatrix& origViewMatrix, const SkMatrix* prePathMatrix, const SkIRect& clipBounds, bool pathIsMutable) { SkASSERT(!pathIsMutable || origSrcPath.isVolatile()); GrStrokeInfo strokeInfo(paint); // comment out the line below to determine if it is the reason that the chrome mac perf bot // has begun crashing // strokeInfo.setResScale(SkDraw::ComputeResScaleForStroking(origViewMatrix)); // If we have a prematrix, apply it to the path, optimizing for the case // where the original path can in fact be modified in place (even though // its parameter type is const). SkPath* pathPtr = const_cast<SkPath*>(&origSrcPath); SkTLazy<SkPath> tmpPath; SkTLazy<SkPath> effectPath; SkPathEffect* pathEffect = paint.getPathEffect(); SkMatrix viewMatrix = origViewMatrix; if (prePathMatrix) { // stroking, path effects, and blurs are supposed to be applied *after* the prePathMatrix. // The pre-path-matrix also should not affect shading. if (!paint.getMaskFilter() && !pathEffect && !paint.getShader() && (strokeInfo.isFillStyle() || strokeInfo.isHairlineStyle())) { viewMatrix.preConcat(*prePathMatrix); } else { SkPath* result = pathPtr; if (!pathIsMutable) { result = tmpPath.init(); result->setIsVolatile(true); pathIsMutable = true; } // should I push prePathMatrix on our MV stack temporarily, instead // of applying it here? See SkDraw.cpp pathPtr->transform(*prePathMatrix, result); pathPtr = result; } } // at this point we're done with prePathMatrix SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;)
GrTexture* SkPictureImageGenerator::onGenerateTexture(GrContext* ctx, SkImageUsageType usage, const SkIRect* subset) { const SkImageInfo& info = this->getInfo(); SkImageInfo surfaceInfo = subset ? info.makeWH(subset->width(), subset->height()) : info; // // TODO: respect the usage, by possibly creating a different (pow2) surface // SkAutoTUnref<SkSurface> surface(SkSurface::NewRenderTarget(ctx, SkSurface::kYes_Budgeted, surfaceInfo)); if (!surface.get()) { return nullptr; } SkMatrix matrix = fMatrix; if (subset) { matrix.postTranslate(-subset->x(), -subset->y()); } surface->getCanvas()->clear(0); // does NewRenderTarget promise to do this for us? surface->getCanvas()->drawPicture(fPicture, &matrix, fPaint.getMaybeNull()); SkAutoTUnref<SkImage> image(surface->newImageSnapshot()); if (!image.get()) { return nullptr; } return SkSafeRef(image->getTexture()); }
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(); }
void GrGLPathRange::onInitPath(int index, const SkPath& origSkPath) const { GrGLGpu* gpu = static_cast<GrGLGpu*>(this->getGpu()); if (NULL == gpu) { return; } // Make sure the path at this index hasn't been initted already. SkDEBUGCODE( GrGLboolean isPath; GR_GL_CALL_RET(gpu->glInterface(), isPath, IsPath(fBasePathID + index))); SkASSERT(GR_GL_FALSE == isPath); const SkPath* skPath = &origSkPath; SkTLazy<SkPath> tmpPath; const GrStrokeInfo* stroke = &fStroke; GrStrokeInfo tmpStroke(SkStrokeRec::kFill_InitStyle); // Dashing must be applied to the path. However, if dashing is present, // we must convert all the paths to fills. The GrStrokeInfo::applyDash leaves // simple paths as strokes but converts other paths to fills. // Thus we must stroke the strokes here, so that all paths in the // path range are using the same style. if (fStroke.isDashed()) { if (!stroke->applyDashToPath(tmpPath.init(), &tmpStroke, *skPath)) { return; } skPath = tmpPath.get(); stroke = &tmpStroke; if (tmpStroke.needToApply()) { if (!tmpStroke.applyToPath(tmpPath.get(), *tmpPath.get())) { return; } tmpStroke.setFillStyle(); } } GrGLPath::InitPathObject(gpu, fBasePathID + index, *skPath, *stroke); // TODO: Use a better approximation for the individual path sizes. fGpuMemorySize += 100; }
void GrDrawTarget::onDrawRect(const SkRect& rect, const SkMatrix* matrix, const SkRect* localRect, const SkMatrix* localMatrix) { GrDrawState::AutoViewMatrixRestore avmr; if (NULL != matrix) { avmr.set(this->drawState(), *matrix); } set_vertex_attributes(this->drawState(), NULL != localRect); AutoReleaseGeometry geo(this, 4, 0); if (!geo.succeeded()) { GrPrintf("Failed to get space for vertices!\n"); return; } size_t vsize = this->drawState()->getVertexSize(); geo.positions()->setRectFan(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, vsize); if (NULL != localRect) { GrPoint* coords = GrTCast<GrPoint*>(GrTCast<intptr_t>(geo.vertices()) + sizeof(GrPoint)); coords->setRectFan(localRect->fLeft, localRect->fTop, localRect->fRight, localRect->fBottom, vsize); if (NULL != localMatrix) { localMatrix->mapPointsWithStride(coords, vsize, 4); } } SkTLazy<SkRect> bounds; if (this->getDrawState().willEffectReadDstColor()) { bounds.init(); this->getDrawState().getViewMatrix().mapRect(bounds.get(), rect); } this->drawNonIndexed(kTriangleFan_GrPrimitiveType, 0, 4, bounds.getMaybeNull()); }
bool SkPictureImageGenerator::onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, SkPMColor ctable[], int* ctableCount) { if (info != getInfo() || ctable || ctableCount) { return false; } SkBitmap bitmap; if (!bitmap.installPixels(info, pixels, rowBytes)) { return false; } bitmap.eraseColor(SK_ColorTRANSPARENT); SkCanvas canvas(bitmap, SkSurfaceProps(0, kUnknown_SkPixelGeometry)); canvas.drawPicture(fPicture, &fMatrix, fPaint.getMaybeNull()); return true; }
bool SkPictureImageGenerator::onGenerateScaledPixels(const SkISize& scaledSize, const SkIPoint& scaledOrigin, const SkPixmap& scaledPixels) { int w = scaledSize.width(); int h = scaledSize.height(); const SkScalar scaleX = SkIntToScalar(w) / this->getInfo().width(); const SkScalar scaleY = SkIntToScalar(h) / this->getInfo().height(); SkMatrix matrix = SkMatrix::MakeScale(scaleX, scaleY); matrix.postTranslate(-SkIntToScalar(scaledOrigin.x()), -SkIntToScalar(scaledOrigin.y())); SkBitmap bitmap; if (!bitmap.installPixels(scaledPixels)) { return false; } bitmap.eraseColor(SK_ColorTRANSPARENT); SkCanvas canvas(bitmap, SkSurfaceProps(0, kUnknown_SkPixelGeometry)); matrix.preConcat(fMatrix); canvas.drawPicture(fPicture, &matrix, fPaint.getMaybeNull()); return true; }
GrContext* GpuTest::GetContext() { #if SK_SUPPORT_GPU // preserve this order, we want gGrContext destroyed after gEGLContext static SkTLazy<SkNativeGLContext> gGLContext; static SkAutoTUnref<GrContext> gGrContext; if (NULL == gGrContext.get()) { gGLContext.init(); if (gGLContext.get()->init(800, 600)) { GrPlatform3DContext ctx = reinterpret_cast<GrPlatform3DContext>(gGLContext.get()->gl()); gGrContext.reset(GrContext::Create(kOpenGL_Shaders_GrEngine, ctx)); } } if (gGLContext.get()) { gGLContext.get()->makeCurrent(); } return gGrContext.get(); #else return NULL; #endif }
// "Interesting" fuzzer values. static void test_linear_fuzzer(skiatest::Reporter*) { static const SkColor gColors0[] = { 0x30303030, 0x30303030 }; static const SkColor gColors1[] = { 0x30303030, 0x30303030, 0x30303030 }; static const SkScalar gPos1[] = { 0, 0, 1 }; static const SkScalar gMatrix0[9] = { 6.40969056e-10f, 0 , 6.40969056e-10f, 0 , 4.42539023e-39f, 6.40969056e-10f, 0 , 0 , 1 }; static const SkScalar gMatrix1[9] = { -2.75294113f , 6.40969056e-10f, 6.40969056e-10f, 6.40969056e-10f, 6.40969056e-10f, -3.32810161e+24f, 6.40969056e-10f, 6.40969056e-10f, 0 }; static const SkScalar gMatrix2[9] = { 7.93481258e+17f, 6.40969056e-10f, 6.40969056e-10f, 6.40969056e-10f, 6.40969056e-10f, 6.40969056e-10f, 6.40969056e-10f, 6.40969056e-10f, 0.688235283f }; static const SkScalar gMatrix3[9] = { 1.89180674e+11f, 6.40969056e-10f, 6.40969056e-10f, 6.40969056e-10f, 6.40969056e-10f, 6.40969056e-10f, 6.40969056e-10f, 11276.0469f , 8.12524808e+20f }; static const struct { SkPoint fPts[2]; const SkColor* fColors; const SkScalar* fPos; int fCount; SkTileMode fTileMode; uint32_t fFlags; const SkScalar* fLocalMatrix; const SkScalar* fGlobalMatrix; } gConfigs[] = { { {{0, -2.752941f}, {0, 0}}, gColors0, nullptr, SK_ARRAY_COUNT(gColors0), SkTileMode::kClamp, 0, gMatrix0, nullptr }, { {{4.42539023e-39f, -4.42539023e-39f}, {9.78041162e-15f, 4.42539023e-39f}}, gColors1, gPos1, SK_ARRAY_COUNT(gColors1), SkTileMode::kClamp, 0, nullptr, gMatrix1 }, { {{4.42539023e-39f, 6.40969056e-10f}, {6.40969056e-10f, 1.49237238e-19f}}, gColors1, gPos1, SK_ARRAY_COUNT(gColors1), SkTileMode::kClamp, 0, nullptr, gMatrix2 }, { {{6.40969056e-10f, 6.40969056e-10f}, {6.40969056e-10f, -0.688235283f}}, gColors0, nullptr, SK_ARRAY_COUNT(gColors0), SkTileMode::kClamp, 0, gMatrix3, nullptr }, }; sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB(); SkColorSpace* colorSpaces[] = { nullptr, // hits the legacy gradient impl srgb.get(), // triggers 4f/raster-pipeline }; SkPaint paint; for (auto colorSpace : colorSpaces) { sk_sp<SkSurface> surface = SkSurface::MakeRaster(SkImageInfo::Make(100, 100, kN32_SkColorType, kPremul_SkAlphaType, sk_ref_sp(colorSpace))); SkCanvas* canvas = surface->getCanvas(); for (const auto& config : gConfigs) { SkAutoCanvasRestore acr(canvas, false); SkTLazy<SkMatrix> localMatrix; if (config.fLocalMatrix) { localMatrix.init(); localMatrix.get()->set9(config.fLocalMatrix); } paint.setShader(SkGradientShader::MakeLinear(config.fPts, config.fColors, config.fPos, config.fCount, config.fTileMode, config.fFlags, localMatrix.getMaybeNull())); if (config.fGlobalMatrix) { SkMatrix m; m.set9(config.fGlobalMatrix); canvas->save(); canvas->concat(m); } canvas->drawPaint(paint); } } }
void onPrepareDraws(Target* target) const override { #ifndef SK_IGNORE_LINEONLY_AA_CONVEX_PATH_OPTS if (this->linesOnly()) { this->prepareLinesOnlyDraws(target); return; } #endif int instanceCount = fGeoData.count(); SkMatrix invert; if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) { SkDebugf("Could not invert viewmatrix\n"); return; } // Setup GrGeometryProcessor sk_sp<GrGeometryProcessor> quadProcessor( QuadEdgeEffect::Make(this->color(), invert, this->usesLocalCoords())); // TODO generate all segments for all paths and use one vertex buffer for (int i = 0; i < instanceCount; i++) { const Geometry& args = fGeoData[i]; // We use the fact that SkPath::transform path does subdivision based on // perspective. Otherwise, we apply the view matrix when copying to the // segment representation. const SkMatrix* viewMatrix = &args.fViewMatrix; // We avoid initializing the path unless we have to const SkPath* pathPtr = &args.fPath; SkTLazy<SkPath> tmpPath; if (viewMatrix->hasPerspective()) { SkPath* tmpPathPtr = tmpPath.init(*pathPtr); tmpPathPtr->setIsVolatile(true); tmpPathPtr->transform(*viewMatrix); viewMatrix = &SkMatrix::I(); pathPtr = tmpPathPtr; } int vertexCount; int indexCount; enum { kPreallocSegmentCnt = 512 / sizeof(Segment), kPreallocDrawCnt = 4, }; SkSTArray<kPreallocSegmentCnt, Segment, true> segments; SkPoint fanPt; if (!get_segments(*pathPtr, *viewMatrix, &segments, &fanPt, &vertexCount, &indexCount)) { continue; } const GrBuffer* vertexBuffer; int firstVertex; size_t vertexStride = quadProcessor->getVertexStride(); QuadVertex* verts = reinterpret_cast<QuadVertex*>(target->makeVertexSpace( vertexStride, vertexCount, &vertexBuffer, &firstVertex)); if (!verts) { SkDebugf("Could not allocate vertices\n"); return; } const GrBuffer* indexBuffer; int firstIndex; uint16_t *idxs = target->makeIndexSpace(indexCount, &indexBuffer, &firstIndex); if (!idxs) { SkDebugf("Could not allocate indices\n"); return; } SkSTArray<kPreallocDrawCnt, Draw, true> draws; create_vertices(segments, fanPt, &draws, verts, idxs); GrMesh mesh; for (int j = 0; j < draws.count(); ++j) { const Draw& draw = draws[j]; mesh.initIndexed(kTriangles_GrPrimitiveType, vertexBuffer, indexBuffer, firstVertex, firstIndex, draw.fVertexCnt, draw.fIndexCnt); target->draw(quadProcessor.get(), mesh); firstVertex += draw.fVertexCnt; firstIndex += draw.fIndexCnt; } } }
DEF_TEST(CanvasState_test_complex_layers, reporter) { const int WIDTH = 400; const int HEIGHT = 400; const int SPACER = 10; SkRect rect = SkRect::MakeXYWH(SkIntToScalar(SPACER), SkIntToScalar(SPACER), SkIntToScalar(WIDTH-(2*SPACER)), SkIntToScalar((HEIGHT-(2*SPACER)) / 7)); const SkColorType colorTypes[] = { kRGB_565_SkColorType, kN32_SkColorType }; const int layerAlpha[] = { 255, 255, 0 }; const SkCanvas::SaveLayerFlags flags[] = { static_cast<SkCanvas::SaveLayerFlags>(SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag), 0, static_cast<SkCanvas::SaveLayerFlags>(SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag), }; REPORTER_ASSERT(reporter, sizeof(layerAlpha) == sizeof(flags)); bool (*drawFn)(SkCanvasState* state, float l, float t, float r, float b, int32_t s); OpenLibResult openLibResult(reporter); if (openLibResult.handle() != nullptr) { *(void**) (&drawFn) = dlsym(openLibResult.handle(), "complex_layers_draw_from_canvas_state"); } else { drawFn = complex_layers_draw_from_canvas_state; } REPORTER_ASSERT(reporter, drawFn); if (!drawFn) { return; } for (size_t i = 0; i < SK_ARRAY_COUNT(colorTypes); ++i) { SkBitmap bitmaps[2]; for (int j = 0; j < 2; ++j) { bitmaps[j].allocPixels(SkImageInfo::Make(WIDTH, HEIGHT, colorTypes[i], kPremul_SkAlphaType)); SkCanvas canvas(bitmaps[j]); canvas.drawColor(SK_ColorRED); for (size_t k = 0; k < SK_ARRAY_COUNT(layerAlpha); ++k) { SkTLazy<SkPaint> paint; if (layerAlpha[k] != 0xFF) { paint.init()->setAlpha(layerAlpha[k]); } // draw a rect within the layer's bounds and again outside the layer's bounds canvas.saveLayer(SkCanvas::SaveLayerRec(&rect, paint.getMaybeNull(), flags[k])); if (j) { // Capture from the first Skia. SkCanvasState* state = SkCanvasStateUtils::CaptureCanvasState(&canvas); REPORTER_ASSERT(reporter, state); // And draw to it in the second Skia. bool success = complex_layers_draw_from_canvas_state(state, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, SPACER); REPORTER_ASSERT(reporter, success); // And release it in the *first* Skia. SkCanvasStateUtils::ReleaseCanvasState(state); } else { // Draw in the first Skia. complex_layers_draw(&canvas, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, SPACER); } canvas.restore(); // translate the canvas for the next iteration canvas.translate(0, 2*(rect.height() + SPACER)); } } // now we memcmp the two bitmaps REPORTER_ASSERT(reporter, bitmaps[0].getSize() == bitmaps[1].getSize()); REPORTER_ASSERT(reporter, !memcmp(bitmaps[0].getPixels(), bitmaps[1].getPixels(), bitmaps[0].getSize())); } }
void onPrepareDraws(Target* target) const override { int instanceCount = fGeoData.count(); SkMatrix invert; if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) { SkDebugf("Could not invert viewmatrix\n"); return; } // Setup GrGeometryProcessors SkAutoTUnref<GrPLSGeometryProcessor> triangleProcessor( PLSAATriangleEffect::Create(invert, this->usesLocalCoords())); SkAutoTUnref<GrPLSGeometryProcessor> quadProcessor( PLSQuadEdgeEffect::Create(invert, this->usesLocalCoords())); GrResourceProvider* rp = target->resourceProvider(); for (int i = 0; i < instanceCount; ++i) { const Geometry& args = fGeoData[i]; SkRect bounds = args.fPath.getBounds(); args.fViewMatrix.mapRect(&bounds); bounds.fLeft = SkScalarFloorToScalar(bounds.fLeft); bounds.fTop = SkScalarFloorToScalar(bounds.fTop); bounds.fRight = SkScalarCeilToScalar(bounds.fRight); bounds.fBottom = SkScalarCeilToScalar(bounds.fBottom); triangleProcessor->setBounds(bounds); quadProcessor->setBounds(bounds); // We use the fact that SkPath::transform path does subdivision based on // perspective. Otherwise, we apply the view matrix when copying to the // segment representation. const SkMatrix* viewMatrix = &args.fViewMatrix; // We avoid initializing the path unless we have to const SkPath* pathPtr = &args.fPath; SkTLazy<SkPath> tmpPath; if (viewMatrix->hasPerspective()) { SkPath* tmpPathPtr = tmpPath.init(*pathPtr); tmpPathPtr->setIsVolatile(true); tmpPathPtr->transform(*viewMatrix); viewMatrix = &SkMatrix::I(); pathPtr = tmpPathPtr; } GrVertices grVertices; PLSVertices triVertices; PLSVertices quadVertices; if (!get_geometry(*pathPtr, *viewMatrix, triVertices, quadVertices, rp, bounds)) { continue; } if (triVertices.count()) { const GrVertexBuffer* triVertexBuffer; int firstTriVertex; size_t triStride = triangleProcessor->getVertexStride(); PLSVertex* triVerts = reinterpret_cast<PLSVertex*>(target->makeVertexSpace( triStride, triVertices.count(), &triVertexBuffer, &firstTriVertex)); if (!triVerts) { SkDebugf("Could not allocate vertices\n"); return; } for (int i = 0; i < triVertices.count(); ++i) { triVerts[i] = triVertices[i]; } grVertices.init(kTriangles_GrPrimitiveType, triVertexBuffer, firstTriVertex, triVertices.count()); target->initDraw(triangleProcessor, this->pipeline()); target->draw(grVertices); } if (quadVertices.count()) { const GrVertexBuffer* quadVertexBuffer; int firstQuadVertex; size_t quadStride = quadProcessor->getVertexStride(); PLSVertex* quadVerts = reinterpret_cast<PLSVertex*>(target->makeVertexSpace( quadStride, quadVertices.count(), &quadVertexBuffer, &firstQuadVertex)); if (!quadVerts) { SkDebugf("Could not allocate vertices\n"); return; } for (int i = 0; i < quadVertices.count(); ++i) { quadVerts[i] = quadVertices[i]; } grVertices.init(kTriangles_GrPrimitiveType, quadVertexBuffer, firstQuadVertex, quadVertices.count()); target->initDraw(quadProcessor, this->pipeline()); target->draw(grVertices); } SkAutoTUnref<GrGeometryProcessor> finishProcessor( PLSFinishEffect::Create(this->color(), pathPtr->getFillType() == SkPath::FillType::kEvenOdd_FillType, invert, this->usesLocalCoords())); const GrVertexBuffer* rectVertexBuffer; size_t finishStride = finishProcessor->getVertexStride(); int firstRectVertex; static const int kRectVertexCount = 6; SkPoint* rectVerts = reinterpret_cast<SkPoint*>(target->makeVertexSpace( finishStride, kRectVertexCount, &rectVertexBuffer, &firstRectVertex)); if (!rectVerts) { SkDebugf("Could not allocate vertices\n"); return; } rectVerts[0] = { bounds.fLeft, bounds.fTop }; rectVerts[1] = { bounds.fLeft, bounds.fBottom }; rectVerts[2] = { bounds.fRight, bounds.fBottom }; rectVerts[3] = { bounds.fLeft, bounds.fTop }; rectVerts[4] = { bounds.fRight, bounds.fTop }; rectVerts[5] = { bounds.fRight, bounds.fBottom }; grVertices.init(kTriangles_GrPrimitiveType, rectVertexBuffer, firstRectVertex, kRectVertexCount); target->initDraw(finishProcessor, this->pipeline()); target->draw(grVertices); } }
void GrTextBlob::flush(GrTextTarget* target, const SkSurfaceProps& props, const GrDistanceFieldAdjustTable* distanceAdjustTable, const SkPaint& paint, GrColor filteredColor, const GrClip& clip, const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { // GrTextBlob::makeOp only takes uint16_t values for run and subRun indices. // Encountering something larger than this is highly unlikely, so we'll just not draw it. int lastRun = SkTMin(fRunCount, (1 << 16)) - 1; // For each run in the GrTextBlob we're going to churn through all the glyphs. // Each run is broken into a path part and a Mask / DFT / ARGB part. for (int runIndex = 0; runIndex <= lastRun; runIndex++) { Run& run = fRuns[runIndex]; // first flush any path glyphs if (run.fPathGlyphs.count()) { SkPaint runPaint{paint}; runPaint.setFlags((runPaint.getFlags() & ~Run::kPaintFlagsMask) | run.fPaintFlags); for (int i = 0; i < run.fPathGlyphs.count(); i++) { GrTextBlob::Run::PathGlyph& pathGlyph = run.fPathGlyphs[i]; SkMatrix ctm; const SkPath* path = &pathGlyph.fPath; // TmpPath must be in the same scope as GrShape shape below. SkTLazy<SkPath> tmpPath; // The glyph positions and glyph outlines are either in device space or in source // space based on fPreTransformed. if (!pathGlyph.fPreTransformed) { // Positions and outlines are in source space. ctm = viewMatrix; SkMatrix pathMatrix = SkMatrix::MakeScale(pathGlyph.fScale, pathGlyph.fScale); // The origin for the blob may have changed, so figure out the delta. SkVector originShift = SkPoint{x, y} - SkPoint{fInitialX, fInitialY}; // Shift the original glyph location in source space to the position of the new // blob. pathMatrix.postTranslate(originShift.x() + pathGlyph.fX, originShift.y() + pathGlyph.fY); // If there are shaders, blurs or styles, the path must be scaled into source // space independently of the CTM. This allows the CTM to be correct for the // different effects. GrStyle style(runPaint); bool scalePath = runPaint.getShader() || style.applies() || runPaint.getMaskFilter(); if (!scalePath) { // Scale can be applied to CTM -- no effects. ctm.preConcat(pathMatrix); } else { // Scale the outline into source space. // Transform the path form the normalized outline to source space. This // way the CTM will remain the same so it can be used by the effects. SkPath* sourceOutline = tmpPath.init(); path->transform(pathMatrix, sourceOutline); sourceOutline->setIsVolatile(true); path = sourceOutline; } } else { // Positions and outlines are in device space. SkPoint originalOrigin = {fInitialX, fInitialY}; fInitialViewMatrix.mapPoints(&originalOrigin, 1); SkPoint newOrigin = {x, y}; viewMatrix.mapPoints(&newOrigin, 1); // The origin shift in device space. SkPoint originShift = newOrigin - originalOrigin; // Shift the original glyph location in device space to the position of the // new blob. ctm = SkMatrix::MakeTrans(originShift.x() + pathGlyph.fX, originShift.y() + pathGlyph.fY); } // TODO: we are losing the mutability of the path here GrShape shape(*path, paint); target->drawShape(clip, runPaint, ctm, shape); } } // then flush each subrun, if any if (!run.fInitialized) { continue; } int lastSubRun = SkTMin(run.fSubRunInfo.count(), 1 << 16) - 1; for (int subRun = 0; subRun <= lastSubRun; subRun++) { const Run::SubRunInfo& info = run.fSubRunInfo[subRun]; int glyphCount = info.glyphCount(); if (0 == glyphCount) { continue; } bool skipClip = false; bool submitOp = true; SkIRect clipRect = SkIRect::MakeEmpty(); SkRect rtBounds = SkRect::MakeWH(target->width(), target->height()); SkRRect clipRRect; GrAA aa; // We can clip geometrically if we're not using SDFs or transformed glyphs, // and we have an axis-aligned rectangular non-AA clip if (!info.drawAsDistanceFields() && !info.needsTransform() && clip.isRRect(rtBounds, &clipRRect, &aa) && clipRRect.isRect() && GrAA::kNo == aa) { skipClip = true; // We only need to do clipping work if the subrun isn't contained by the clip SkRect subRunBounds; this->computeSubRunBounds(&subRunBounds, runIndex, subRun, viewMatrix, x, y, false); if (!clipRRect.getBounds().contains(subRunBounds)) { // If the subrun is completely outside, don't add an op for it if (!clipRRect.getBounds().intersects(subRunBounds)) { submitOp = false; } else { clipRRect.getBounds().round(&clipRect); } } } if (submitOp) { auto op = this->makeOp(info, glyphCount, runIndex, subRun, viewMatrix, x, y, clipRect, paint, filteredColor, props, distanceAdjustTable, target); if (op) { if (skipClip) { target->addDrawOp(GrNoClip(), std::move(op)); } else { target->addDrawOp(clip, std::move(op)); } } } } } }
static void draw_path_with_mask_filter(GrContext* context, GrDrawContext* drawContext, const GrClip& clip, GrPaint* paint, const SkMatrix& viewMatrix, const SkMaskFilter* maskFilter, const GrStyle& style, const SkPath* path, bool pathIsMutable) { SkASSERT(maskFilter); SkIRect clipBounds; clip.getConservativeBounds(drawContext->width(), drawContext->height(), &clipBounds); SkTLazy<SkPath> tmpPath; SkStrokeRec::InitStyle fillOrHairline; // We just fully apply the style here. if (style.applies()) { if (!style.applyToPath(tmpPath.init(), &fillOrHairline, *path, GrStyle::MatrixToScaleFactor(viewMatrix))) { return; } pathIsMutable = true; path = tmpPath.get(); } else if (style.isSimpleHairline()) { fillOrHairline = SkStrokeRec::kHairline_InitStyle; } else { SkASSERT(style.isSimpleFill()); fillOrHairline = SkStrokeRec::kFill_InitStyle; } // transform the path into device space if (!viewMatrix.isIdentity()) { SkPath* result; if (pathIsMutable) { result = const_cast<SkPath*>(path); } else { if (!tmpPath.isValid()) { tmpPath.init(); } result = tmpPath.get(); } path->transform(viewMatrix, result); path = result; result->setIsVolatile(true); pathIsMutable = true; } SkRect maskRect; if (maskFilter->canFilterMaskGPU(SkRRect::MakeRect(path->getBounds()), clipBounds, viewMatrix, &maskRect)) { // This mask will ultimately be drawn as a non-AA rect (see draw_mask). // Non-AA rects have a bad habit of snapping arbitrarily. Integerize here // so the mask draws in a reproducible manner. SkIRect finalIRect; maskRect.roundOut(&finalIRect); if (clip_bounds_quick_reject(clipBounds, finalIRect)) { // clipped out return; } if (maskFilter->directFilterMaskGPU(context->textureProvider(), drawContext, paint, clip, viewMatrix, SkStrokeRec(fillOrHairline), *path)) { // the mask filter was able to draw itself directly, so there's nothing // left to do. return; } sk_sp<GrTexture> mask(create_mask_GPU(context, finalIRect, *path, fillOrHairline, paint->isAntiAlias(), drawContext->numColorSamples())); if (mask) { GrTexture* filtered; if (maskFilter->filterMaskGPU(mask.get(), viewMatrix, finalIRect, &filtered, true)) { // filterMaskGPU gives us ownership of a ref to the result SkAutoTUnref<GrTexture> atu(filtered); if (draw_mask(drawContext, clip, viewMatrix, finalIRect, paint, filtered)) { // This path is completely drawn return; } } } } sw_draw_with_mask_filter(drawContext, context->textureProvider(), clip, viewMatrix, *path, maskFilter, clipBounds, paint, fillOrHairline); }
static void draw_path_with_mask_filter(GrContext* context, GrDrawContext* drawContext, const GrClip& clip, GrPaint* paint, const SkMatrix& viewMatrix, const SkMaskFilter* maskFilter, const SkPathEffect* pathEffect, const GrStrokeInfo& origStrokeInfo, SkPath* pathPtr, bool pathIsMutable) { SkASSERT(maskFilter); SkIRect clipBounds; clip.getConservativeBounds(drawContext->width(), drawContext->height(), &clipBounds); SkTLazy<SkPath> tmpPath; GrStrokeInfo strokeInfo(origStrokeInfo); static const SkRect* cullRect = nullptr; // TODO: what is our bounds? SkASSERT(strokeInfo.isDashed() || !pathEffect); if (!strokeInfo.isHairlineStyle()) { SkPath* strokedPath = pathIsMutable ? pathPtr : tmpPath.init(); if (strokeInfo.isDashed()) { if (pathEffect->filterPath(strokedPath, *pathPtr, &strokeInfo, cullRect)) { pathPtr = strokedPath; pathPtr->setIsVolatile(true); pathIsMutable = true; } strokeInfo.removeDash(); } if (strokeInfo.applyToPath(strokedPath, *pathPtr)) { // Apply the stroke to the path if there is one pathPtr = strokedPath; pathPtr->setIsVolatile(true); pathIsMutable = true; strokeInfo.setFillStyle(); } } // avoid possibly allocating a new path in transform if we can SkPath* devPathPtr = pathIsMutable ? pathPtr : tmpPath.init(); if (!pathIsMutable) { devPathPtr->setIsVolatile(true); } // transform the path into device space pathPtr->transform(viewMatrix, devPathPtr); SkRect maskRect; if (maskFilter->canFilterMaskGPU(SkRRect::MakeRect(devPathPtr->getBounds()), clipBounds, viewMatrix, &maskRect)) { SkIRect finalIRect; maskRect.roundOut(&finalIRect); if (clip_bounds_quick_reject(clipBounds, finalIRect)) { // clipped out return; } if (maskFilter->directFilterMaskGPU(context->textureProvider(), drawContext, paint, clip, viewMatrix, strokeInfo, *devPathPtr)) { // the mask filter was able to draw itself directly, so there's nothing // left to do. return; } SkAutoTUnref<GrTexture> mask(create_mask_GPU(context, &maskRect, *devPathPtr, strokeInfo, paint->isAntiAlias(), drawContext->numColorSamples())); if (mask) { GrTexture* filtered; if (maskFilter->filterMaskGPU(mask, viewMatrix, maskRect, &filtered, true)) { // filterMaskGPU gives us ownership of a ref to the result SkAutoTUnref<GrTexture> atu(filtered); if (draw_mask(drawContext, clip, viewMatrix, maskRect, paint, filtered)) { // This path is completely drawn return; } } } } // draw the mask on the CPU - this is a fallthrough path in case the // GPU path fails SkPaint::Style style = strokeInfo.isHairlineStyle() ? SkPaint::kStroke_Style : SkPaint::kFill_Style; sw_draw_with_mask_filter(drawContext, context->textureProvider(), clip, viewMatrix, *devPathPtr, maskFilter, clipBounds, paint, style); }