/* * Copyright 2011 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/gpu/ganesh/Device.h" #include "include/core/SkAlphaType.h" #include "include/core/SkBitmap.h" #include "include/core/SkBlendMode.h" #include "include/core/SkCanvas.h" #include "include/core/SkClipOp.h" #include "include/core/SkColor.h" #include "include/core/SkColorSpace.h" #include "include/core/SkColorType.h" #include "include/core/SkDrawable.h" #include "include/core/SkImage.h" #include "include/core/SkImageInfo.h" #include "include/core/SkM44.h" #include "include/core/SkMatrix.h" #include "include/core/SkMesh.h" #include "include/core/SkPaint.h" #include "include/core/SkPath.h" #include "include/core/SkPathTypes.h" #include "include/core/SkPixmap.h" #include "include/core/SkPoint.h" #include "include/core/SkRRect.h" #include "include/core/SkRSXform.h" #include "include/core/SkRect.h" #include "include/core/SkRefCnt.h" #include "include/core/SkRegion.h" #include "include/core/SkScalar.h" #include "include/core/SkSize.h" #include "include/core/SkSpan.h" #include "include/core/SkStrokeRec.h" #include "include/core/SkSurface.h" #include "include/core/SkSurfaceProps.h" #include "include/core/SkVertices.h" #include "include/effects/SkRuntimeEffect.h" #include "include/gpu/GpuTypes.h" #include "include/gpu/ganesh/GrBackendSurface.h" #include "include/gpu/ganesh/GrContextOptions.h" #include "include/gpu/ganesh/GrDirectContext.h" #include "include/gpu/ganesh/GrRecordingContext.h" #include "include/gpu/ganesh/GrTypes.h" #include "include/gpu/ganesh/SkSurfaceGanesh.h" #include "include/private/SkColorData.h" #include "include/private/base/SingleOwner.h" #include "include/private/base/SkAssert.h" #include "include/private/base/SkDebug.h" #include "include/private/base/SkTArray.h" #include "include/private/base/SkTo.h" #include "include/private/chromium/Slug.h" // IWYU pragma: keep #include "include/private/gpu/ganesh/GrTypesPriv.h" #include "src/base/SkTLazy.h" #include "src/core/SkDevice.h" #include "src/core/SkDrawBase.h" #include "src/core/SkImageFilterTypes.h" // IWYU pragma: keep #include "src/core/SkImageInfoPriv.h" #include "src/core/SkLatticeIter.h" #include "src/core/SkMeshPriv.h" #include "src/core/SkRasterClip.h" #include "src/core/SkSpecialImage.h" #include "src/core/SkStrikeCache.h" #include "src/core/SkTraceEvent.h" #include "src/core/SkVerticesPriv.h" #include "src/gpu/SkBackingFit.h" #include "src/gpu/Swizzle.h" #include "src/gpu/TiledTextureUtils.h" #include "src/gpu/ganesh/ClipStack.h" #include "src/gpu/ganesh/GrAuditTrail.h" #include "src/gpu/ganesh/GrBlurUtils.h" #include "src/gpu/ganesh/GrCaps.h" #include "src/gpu/ganesh/GrColorInfo.h" #include "src/gpu/ganesh/GrColorSpaceXform.h" #include "src/gpu/ganesh/GrFPArgs.h" #include "src/gpu/ganesh/GrFragmentProcessor.h" #include "src/gpu/ganesh/GrFragmentProcessors.h" #include "src/gpu/ganesh/GrImageInfo.h" #include "src/gpu/ganesh/GrPaint.h" #include "src/gpu/ganesh/GrProxyProvider.h" #include "src/gpu/ganesh/GrRecordingContextPriv.h" #include "src/gpu/ganesh/GrRenderTargetProxy.h" #include "src/gpu/ganesh/GrStyle.h" #include "src/gpu/ganesh/GrSurfaceProxy.h" #include "src/gpu/ganesh/GrSurfaceProxyPriv.h" #include "src/gpu/ganesh/GrSurfaceProxyView.h" #include "src/gpu/ganesh/GrTextureProxy.h" #include "src/gpu/ganesh/GrTracing.h" #include "src/gpu/ganesh/GrUserStencilSettings.h" #include "src/gpu/ganesh/GrXferProcessor.h" #include "src/gpu/ganesh/SkGr.h" #include "src/gpu/ganesh/SurfaceContext.h" #include "src/gpu/ganesh/SurfaceDrawContext.h" #include "src/gpu/ganesh/SurfaceFillContext.h" #include "src/gpu/ganesh/effects/GrDisableColorXP.h" #include "src/gpu/ganesh/effects/GrRRectEffect.h" #include "src/gpu/ganesh/geometry/GrShape.h" #include "src/gpu/ganesh/geometry/GrStyledShape.h" #include "src/gpu/ganesh/image/GrImageUtils.h" #include "src/gpu/ganesh/image/SkSpecialImage_Ganesh.h" #include "src/text/GlyphRun.h" #include "src/text/gpu/SlugImpl.h" #include "src/text/gpu/SubRunContainer.h" #include #include #include #include #include #include #include class GrBackendSemaphore; struct GrShaderCaps; struct SkDrawShadowRec; using namespace skia_private; #define ASSERT_SINGLE_OWNER SKGPU_ASSERT_SINGLE_OWNER(fContext->priv().singleOwner()) #if defined(GPU_TEST_UTILS) // GrContextOptions::fMaxTextureSizeOverride exists but doesn't allow for changing the // maxTextureSize on the fly. int gOverrideMaxTextureSizeGanesh = 0; // Allows tests to check how many tiles were drawn on the most recent call to // Device::drawAsTiledImageRect. This is an atomic because we can write to it from // multiple threads during "normal" operations. However, the tests that actually // read from it are done single-threaded. std::atomic gNumTilesDrawnGanesh{0}; #endif /////////////////////////////////////////////////////////////////////////////// namespace { bool force_aa_clip(const skgpu::ganesh::SurfaceDrawContext* sdc) { return sdc->numSamples() > 1 || sdc->alwaysAntialias(); } inline GrPrimitiveType point_mode_to_primitive_type(SkCanvas::PointMode mode) { switch (mode) { case SkCanvas::kPoints_PointMode: return GrPrimitiveType::kPoints; case SkCanvas::kLines_PointMode: return GrPrimitiveType::kLines; case SkCanvas::kPolygon_PointMode: return GrPrimitiveType::kLineStrip; } SK_ABORT("Unexpected mode"); } std::unique_ptr make_inverse_rrect_fp(const SkMatrix& viewMatrix, const SkRRect& rrect, GrAA aa, const GrShaderCaps& shaderCaps) { SkTCopyOnFirstWrite devRRect(rrect); if (viewMatrix.isIdentity() || rrect.transform(viewMatrix, devRRect.writable())) { auto edgeType = (aa == GrAA::kYes) ? GrClipEdgeType::kInverseFillAA : GrClipEdgeType::kInverseFillBW; auto [success, fp] = GrRRectEffect::Make(/*inputFP=*/nullptr, edgeType, *devRRect, shaderCaps); return (success) ? std::move(fp) : nullptr; } return nullptr; } bool init_vertices_paint(GrRecordingContext* rContext, const GrColorInfo& colorInfo, const SkPaint& skPaint, const SkMatrix& ctm, SkBlender* blender, bool hasColors, const SkSurfaceProps& props, GrPaint* grPaint) { if (hasColors) { return SkPaintToGrPaintWithBlend(rContext, colorInfo, skPaint, ctm, blender, props, grPaint); } else { return SkPaintToGrPaint(rContext, colorInfo, skPaint, ctm, props, grPaint); } } bool init_mesh_child_effects(GrRecordingContext* rContext, const GrColorInfo& colorInfo, const SkSurfaceProps& surfaceProps, const SkMesh& mesh, TArray>* meshChildFPs) { // We use `Scope::kRuntimeEffect` here to ensure that mesh shaders get the same "raw" sampling // behavior from alpha-only image shaders as a Runtime Effect would, rather than the unexpected // tinting-by-input-color. GrFPArgs fpArgs(rContext, &colorInfo, surfaceProps, GrFPArgs::Scope::kRuntimeEffect); for (const SkRuntimeEffect::ChildPtr& child : mesh.children()) { auto [success, childFP] = GrFragmentProcessors::MakeChildFP(child, fpArgs); if (!success) { return false; } meshChildFPs->push_back(std::move(childFP)); } return true; } } // anonymous namespace namespace skgpu::ganesh { sk_sp Device::Make(GrRecordingContext* rContext, GrColorType colorType, sk_sp proxy, sk_sp colorSpace, GrSurfaceOrigin origin, const SkSurfaceProps& surfaceProps, InitContents init) { auto sdc = SurfaceDrawContext::Make(rContext, colorType, std::move(proxy), std::move(colorSpace), origin, surfaceProps); return Device::Make(std::move(sdc), kPremul_SkAlphaType, init); } SkImageInfo Device::MakeInfo(SurfaceContext* sc, DeviceFlags flags) { SkColorType colorType = GrColorTypeToSkColorType(sc->colorInfo().colorType()); return SkImageInfo::Make(sc->width(), sc->height(), colorType, flags & DeviceFlags::kIsOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType, sc->colorInfo().refColorSpace()); } /** Checks that the alpha type is legal and gets constructor flags. Returns false if device creation should fail. */ bool Device::CheckAlphaTypeAndGetFlags(SkAlphaType alphaType, InitContents init, DeviceFlags* flags) { *flags = DeviceFlags::kNone; switch (alphaType) { case kPremul_SkAlphaType: break; case kOpaque_SkAlphaType: *flags |= DeviceFlags::kIsOpaque; break; default: // If it is unpremul or unknown don't try to render return false; } if (InitContents::kClear == init) { *flags |= DeviceFlags::kNeedClear; } return true; } sk_sp Device::Make(std::unique_ptr sdc, SkAlphaType alphaType, InitContents init) { if (!sdc) { return nullptr; } GrRecordingContext* rContext = sdc->recordingContext(); if (rContext->abandoned()) { return nullptr; } SkColorType ct = GrColorTypeToSkColorType(sdc->colorInfo().colorType()); DeviceFlags flags; if (!rContext->colorTypeSupportedAsSurface(ct) || !CheckAlphaTypeAndGetFlags(alphaType, init, &flags)) { return nullptr; } return sk_sp(new Device(std::move(sdc), flags)); } sk_sp Device::Make(GrRecordingContext* rContext, skgpu::Budgeted budgeted, const SkImageInfo& ii, SkBackingFit fit, int sampleCount, skgpu::Mipmapped mipmapped, GrProtected isProtected, GrSurfaceOrigin origin, const SkSurfaceProps& props, InitContents init) { if (!rContext) { return nullptr; } auto sdc = SurfaceDrawContext::Make(rContext, SkColorTypeToGrColorType(ii.colorType()), ii.refColorSpace(), fit, ii.dimensions(), props, /*label=*/"MakeDevice", sampleCount, mipmapped, isProtected, origin, budgeted); return Device::Make(std::move(sdc), ii.alphaType(), init); } Device::Device(std::unique_ptr sdc, DeviceFlags flags) : SkDevice(MakeInfo(sdc.get(), flags), sdc->surfaceProps()) , fContext(sk_ref_sp(sdc->recordingContext())) , fSubRunControl(sdc->recordingContext()->priv().getSubRunControl( sdc->surfaceProps().isUseDeviceIndependentFonts())) , fSurfaceDrawContext(std::move(sdc)) , fClip(SkIRect::MakeSize(fSurfaceDrawContext->dimensions()), &this->localToDevice(), force_aa_clip(fSurfaceDrawContext.get())) { if (flags & DeviceFlags::kNeedClear) { this->clearAll(); } } Device::~Device() = default; /////////////////////////////////////////////////////////////////////////////// bool Device::onReadPixels(const SkPixmap& pm, int x, int y) { ASSERT_SINGLE_OWNER // Context TODO: Elevate direct context requirement to public API auto dContext = fContext->asDirectContext(); if (!dContext || !SkImageInfoValidConversion(pm.info(), this->imageInfo())) { return false; } return fSurfaceDrawContext->readPixels(dContext, pm, {x, y}); } bool Device::onWritePixels(const SkPixmap& pm, int x, int y) { ASSERT_SINGLE_OWNER // Context TODO: Elevate direct context requirement to public API auto dContext = fContext->asDirectContext(); if (!dContext || !SkImageInfoValidConversion(this->imageInfo(), pm.info())) { return false; } return fSurfaceDrawContext->writePixels(dContext, pm, {x, y}); } bool Device::onAccessPixels(SkPixmap* pmap) { ASSERT_SINGLE_OWNER return false; } SurfaceDrawContext* Device::surfaceDrawContext() { ASSERT_SINGLE_OWNER return fSurfaceDrawContext.get(); } const SurfaceDrawContext* Device::surfaceDrawContext() const { ASSERT_SINGLE_OWNER return fSurfaceDrawContext.get(); } SurfaceFillContext* Device::surfaceFillContext() { ASSERT_SINGLE_OWNER return fSurfaceDrawContext.get(); } void Device::clearAll() { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "clearAll", fContext.get()); SkIRect rect = SkIRect::MakeWH(this->width(), this->height()); fSurfaceDrawContext->clearAtLeast(rect, SK_PMColor4fTRANSPARENT); } /////////////////////////////////////////////////////////////////////////////// void Device::clipPath(const SkPath& path, SkClipOp op, bool aa) { #if defined(GPU_TEST_UTILS) if (fContext->priv().options().fAllPathsVolatile && !path.isVolatile()) { this->clipPath(SkPath(path).setIsVolatile(true), op, aa); return; } #endif SkASSERT(op == SkClipOp::kIntersect || op == SkClipOp::kDifference); fClip.clipPath(this->localToDevice(), path, GrAA(aa), op); } void Device::clipRegion(const SkRegion& globalRgn, SkClipOp op) { SkASSERT(op == SkClipOp::kIntersect || op == SkClipOp::kDifference); // Regions don't actually need AA, but in DMSAA mode every clip element is antialiased. GrAA aa = GrAA(fSurfaceDrawContext->alwaysAntialias()); if (globalRgn.isEmpty()) { fClip.clipRect(SkMatrix::I(), SkRect::MakeEmpty(), aa, op); } else if (globalRgn.isRect()) { fClip.clipRect(this->globalToDevice().asM33(), SkRect::Make(globalRgn.getBounds()), aa, op); } else { SkPath path; globalRgn.getBoundaryPath(&path); fClip.clipPath(this->globalToDevice().asM33(), path, aa, op); } } void Device::android_utils_clipAsRgn(SkRegion* region) const { SkIRect bounds = fClip.getConservativeBounds(); // Assume wide open and then perform intersect/difference operations reducing the region region->setRect(bounds); const SkRegion deviceBounds(bounds); for (const ClipStack::Element& e : fClip) { SkRegion tmp; if (e.fShape.isRect() && e.fLocalToDevice.isIdentity()) { tmp.setRect(e.fShape.rect().roundOut()); } else { SkPath tmpPath; e.fShape.asPath(&tmpPath); tmpPath.transform(e.fLocalToDevice); tmp.setPath(tmpPath, deviceBounds); } region->op(tmp, (SkRegion::Op) e.fOp); } } bool Device::isClipAntiAliased() const { for (const ClipStack::Element& e : fClip) { if (e.fAA == GrAA::kYes) { return true; } SkASSERT(!fSurfaceDrawContext->alwaysAntialias()); } return false; } /////////////////////////////////////////////////////////////////////////////// void Device::drawPaint(const SkPaint& paint) { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawPaint", fContext.get()); GrPaint grPaint; if (!SkPaintToGrPaint(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } fSurfaceDrawContext->drawPaint(this->clip(), std::move(grPaint), this->localToDevice()); } void Device::drawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawPoints", fContext.get()); SkScalar width = paint.getStrokeWidth(); if (width < 0) { return; } GrAA aa = fSurfaceDrawContext->chooseAA(paint); if (count == 2 && mode == SkCanvas::kLines_PointMode) { if (paint.getPathEffect()) { // Probably a dashed line. Draw as a path. GrPaint grPaint; if (SkPaintToGrPaint(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { SkPath path; path.setIsVolatile(true); path.moveTo(pts[0]); path.lineTo(pts[1]); fSurfaceDrawContext->drawPath(this->clip(), std::move(grPaint), aa, this->localToDevice(), path, GrStyle(paint, SkPaint::kStroke_Style)); } return; } if (!paint.getMaskFilter() && paint.getStrokeWidth() > 0 && // drawStrokedLine doesn't support hairlines. paint.getStrokeCap() != SkPaint::kRound_Cap) { // drawStrokedLine doesn't do round caps. // Simple stroked line. Bypass path rendering. GrPaint grPaint; if (SkPaintToGrPaint(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { fSurfaceDrawContext->drawStrokedLine(this->clip(), std::move(grPaint), aa, this->localToDevice(), pts, SkStrokeRec(paint, SkPaint::kStroke_Style)); } return; } } const GrCaps* caps = fContext->priv().caps(); SkScalar scales[2]; bool isHairline = ((0 == width) || (1 == width && this->localToDevice().getMinMaxScales(scales) && SkScalarNearlyEqual(scales[0], 1.f) && SkScalarNearlyEqual(scales[1], 1.f))) && // Don't do this as a hairline draw, which will emit line primitives, if // lines are not permitted by caps. !((mode == SkCanvas::kLines_PointMode || mode == SkCanvas::kPolygon_PointMode) && caps->avoidLineDraws()); // we only handle non-coverage-aa hairlines and paints without path effects or mask filters, // else we let the SkDraw call our drawPath() if (!isHairline || paint.getPathEffect() || paint.getMaskFilter() || fSurfaceDrawContext->chooseAAType(aa) == GrAAType::kCoverage) { SkRasterClip rc(this->devClipBounds()); SkDrawBase draw; // don't need to set fBlitterChoose, as it should never get used draw.fDst = SkPixmap(SkImageInfo::MakeUnknown(this->width(), this->height()), nullptr, 0); draw.fCTM = &this->localToDevice(); draw.fRC = &rc; draw.drawDevicePoints(mode, count, pts, paint, this); return; } GrPaint grPaint; if (!SkPaintToGrPaint(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } static constexpr SkVertices::VertexMode kIgnoredMode = SkVertices::kTriangles_VertexMode; sk_sp vertices = SkVertices::MakeCopy(kIgnoredMode, SkToS32(count), pts, nullptr, nullptr); GrPrimitiveType primitiveType = point_mode_to_primitive_type(mode); fSurfaceDrawContext->drawVertices(this->clip(), std::move(grPaint), this->localToDevice(), std::move(vertices), &primitiveType); } /////////////////////////////////////////////////////////////////////////////// void Device::drawRect(const SkRect& rect, const SkPaint& paint) { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawRect", fContext.get()); GrStyle style(paint); // A couple reasons we might need to call drawPath. if (paint.getMaskFilter() || paint.getPathEffect()) { GrStyledShape shape(rect, style); GrBlurUtils::DrawShapeWithMaskFilter(fContext.get(), fSurfaceDrawContext.get(), this->clip(), paint, this->localToDevice(), shape); return; } GrPaint grPaint; if (!SkPaintToGrPaint(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } fSurfaceDrawContext->drawRect(this->clip(), std::move(grPaint), fSurfaceDrawContext->chooseAA(paint), this->localToDevice(), rect, &style); } void Device::drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], SkCanvas::QuadAAFlags aaFlags, const SkColor4f& color, SkBlendMode mode) { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawEdgeAAQuad", fContext.get()); SkPMColor4f dstColor = SkColor4fPrepForDst(color, fSurfaceDrawContext->colorInfo()).premul(); GrPaint grPaint; grPaint.setColor4f(dstColor); if (mode != SkBlendMode::kSrcOver) { grPaint.setXPFactory(GrXPFactory::FromBlendMode(mode)); } if (clip) { // Use fillQuadWithEdgeAA fSurfaceDrawContext->fillQuadWithEdgeAA(this->clip(), std::move(grPaint), SkToGrQuadAAFlags(aaFlags), this->localToDevice(), clip, nullptr); } else { // Use fillRectWithEdgeAA to preserve mathematical properties of dst being rectangular fSurfaceDrawContext->fillRectWithEdgeAA(this->clip(), std::move(grPaint), SkToGrQuadAAFlags(aaFlags), this->localToDevice(), rect); } } /////////////////////////////////////////////////////////////////////////////// void Device::drawRRect(const SkRRect& rrect, const SkPaint& paint) { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawRRect", fContext.get()); auto mf = paint.getMaskFilter(); if (mf) { if (GrFragmentProcessors::IsSupported(mf)) { mf = nullptr; // already handled in SkPaintToGrPaint } } GrStyle style(paint); if (mf || style.pathEffect()) { // A path effect will presumably transform this rrect into something else. GrStyledShape shape(rrect, style); GrBlurUtils::DrawShapeWithMaskFilter(fContext.get(), fSurfaceDrawContext.get(), this->clip(), paint, this->localToDevice(), shape); return; } SkASSERT(!style.pathEffect()); GrPaint grPaint; if (!SkPaintToGrPaint(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } fSurfaceDrawContext->drawRRect(this->clip(), std::move(grPaint), fSurfaceDrawContext->chooseAA(paint), this->localToDevice(), rrect, style); } void Device::drawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawDRRect", fContext.get()); if (outer.isEmpty()) { return; } if (inner.isEmpty()) { return this->drawRRect(outer, paint); } SkStrokeRec stroke(paint); if (stroke.isFillStyle() && !paint.getMaskFilter() && !paint.getPathEffect()) { // For axis-aligned filled DRRects, just draw a regular rrect with inner clipped out using a // coverage FP instead of using path rendering. if (auto fp = make_inverse_rrect_fp(this->localToDevice(), inner, fSurfaceDrawContext->chooseAA(paint), *fSurfaceDrawContext->caps()->shaderCaps())) { GrPaint grPaint; if (!SkPaintToGrPaint(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } SkASSERT(!grPaint.hasCoverageFragmentProcessor()); grPaint.setCoverageFragmentProcessor(std::move(fp)); fSurfaceDrawContext->drawRRect(this->clip(), std::move(grPaint), fSurfaceDrawContext->chooseAA(paint), this->localToDevice(), outer, GrStyle()); return; } } SkPath path; path.setIsVolatile(true); path.addRRect(outer); path.addRRect(inner); path.setFillType(SkPathFillType::kEvenOdd); // TODO: We are losing the possible mutability of the path here but this should probably be // fixed by upgrading GrStyledShape to handle DRRects. GrStyledShape shape(path, paint); GrBlurUtils::DrawShapeWithMaskFilter(fContext.get(), fSurfaceDrawContext.get(), this->clip(), paint, this->localToDevice(), shape); } ///////////////////////////////////////////////////////////////////////////// void Device::drawRegion(const SkRegion& region, const SkPaint& paint) { ASSERT_SINGLE_OWNER if (paint.getMaskFilter()) { SkPath path; region.getBoundaryPath(&path); path.setIsVolatile(true); return this->drawPath(path, paint, true); } GrPaint grPaint; if (!SkPaintToGrPaint(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } fSurfaceDrawContext->drawRegion(this->clip(), std::move(grPaint), fSurfaceDrawContext->chooseAA(paint), this->localToDevice(), region, GrStyle(paint)); } void Device::drawOval(const SkRect& oval, const SkPaint& paint) { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawOval", fContext.get()); if (paint.getMaskFilter()) { // The RRect path can handle special case blurring SkRRect rr = SkRRect::MakeOval(oval); return this->drawRRect(rr, paint); } GrPaint grPaint; if (!SkPaintToGrPaint(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } fSurfaceDrawContext->drawOval(this->clip(), std::move(grPaint), fSurfaceDrawContext->chooseAA(paint), this->localToDevice(), oval, GrStyle(paint)); } void Device::drawArc(const SkArc& arc, const SkPaint& paint) { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawArc", fContext.get()); if (paint.getMaskFilter()) { this->SkDevice::drawArc(arc, paint); return; } GrPaint grPaint; if (!SkPaintToGrPaint(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } fSurfaceDrawContext->drawArc(this->clip(), std::move(grPaint), fSurfaceDrawContext->chooseAA(paint), this->localToDevice(), arc, GrStyle(paint)); } /////////////////////////////////////////////////////////////////////////////// void Device::drawPath(const SkPath& origSrcPath, const SkPaint& paint, bool pathIsMutable) { #if defined(GPU_TEST_UTILS) if (fContext->priv().options().fAllPathsVolatile && !origSrcPath.isVolatile()) { this->drawPath(SkPath(origSrcPath).setIsVolatile(true), paint, true); return; } #endif ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawPath", fContext.get()); if (!paint.getMaskFilter()) { GrPaint grPaint; if (!SkPaintToGrPaint(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } fSurfaceDrawContext->drawPath(this->clip(), std::move(grPaint), fSurfaceDrawContext->chooseAA(paint), this->localToDevice(), origSrcPath, GrStyle(paint)); return; } // TODO: losing possible mutability of 'origSrcPath' here GrStyledShape shape(origSrcPath, paint); GrBlurUtils::DrawShapeWithMaskFilter(fContext.get(), fSurfaceDrawContext.get(), this->clip(), paint, this->localToDevice(), shape); } sk_sp Device::createImageFilteringBackend(const SkSurfaceProps& surfaceProps, SkColorType colorType) const { return skif::MakeGaneshBackend( fContext, fSurfaceDrawContext->origin(), surfaceProps, colorType); } sk_sp Device::makeSpecial(const SkBitmap& bitmap) { ASSERT_SINGLE_OWNER // TODO: this makes a tight copy of 'bitmap' but it doesn't have to be (given SkSpecialImage's // semantics). Since this is cached we would have to bake the fit into the cache key though. auto view = std::get<0>( GrMakeCachedBitmapProxyView(fContext.get(), bitmap, /*label=*/"Device_MakeSpecial")); if (!view) { return nullptr; } const SkIRect rect = SkIRect::MakeSize(view.proxy()->dimensions()); // GrMakeCachedBitmapProxyView creates a tight copy of 'bitmap' so we don't have to subset // the special image return SkSpecialImages::MakeDeferredFromGpu(fContext.get(), rect, bitmap.getGenerationID(), std::move(view), {SkColorTypeToGrColorType(bitmap.colorType()), kPremul_SkAlphaType, bitmap.refColorSpace()}, this->surfaceProps()); } sk_sp Device::makeSpecial(const SkImage* image) { ASSERT_SINGLE_OWNER SkPixmap pm; if (image->isTextureBacked()) { auto [view, ct] = skgpu::ganesh::AsView(this->recordingContext(), image, skgpu::Mipmapped::kNo); SkASSERT(view); return SkSpecialImages::MakeDeferredFromGpu( fContext.get(), SkIRect::MakeWH(image->width(), image->height()), image->uniqueID(), std::move(view), {ct, kPremul_SkAlphaType, image->refColorSpace()}, this->surfaceProps()); } else if (image->peekPixels(&pm)) { SkBitmap bm; bm.installPixels(pm); return this->makeSpecial(bm); } else { return nullptr; } } sk_sp Device::snapSpecial(const SkIRect& subset, bool forceCopy) { ASSERT_SINGLE_OWNER auto sdc = fSurfaceDrawContext.get(); // If we are wrapping a vulkan secondary command buffer, then we can't snap off a special image // since it would require us to make a copy of the underlying VkImage which we don't have access // to. Additionaly we can't stop and start the render pass that is used with the secondary // command buffer. if (sdc->wrapsVkSecondaryCB()) { return nullptr; } SkASSERT(sdc->asSurfaceProxy()); SkIRect finalSubset = subset; GrSurfaceProxyView view = sdc->readSurfaceView(); if (forceCopy || !view.asTextureProxy()) { // When the device doesn't have a texture, or a copy is requested, we create a temporary // texture that matches the device contents view = GrSurfaceProxyView::Copy(fContext.get(), std::move(view), skgpu::Mipmapped::kNo, // Don't auto generate mips subset, SkBackingFit::kApprox, skgpu::Budgeted::kYes, /*label=*/"Device_SnapSpecial"); // Always budgeted if (!view) { return nullptr; } // Since this copied only the requested subset, the special image wrapping the proxy no // longer needs the original subset. finalSubset = SkIRect::MakeSize(view.dimensions()); } return SkSpecialImages::MakeDeferredFromGpu(fContext.get(), finalSubset, kNeedNewImageUniqueID_SpecialImage, std::move(view), GrColorInfo(this->imageInfo().colorInfo()), this->surfaceProps()); } sk_sp Device::snapSpecialScaled(const SkIRect& subset, const SkISize& dstDims) { ASSERT_SINGLE_OWNER auto sdc = fSurfaceDrawContext.get(); // If we are wrapping a vulkan secondary command buffer, then we can't snap off a special image // since it would require us to make a copy of the underlying VkImage which we don't have access // to. Additionaly we can't stop and start the render pass that is used with the secondary // command buffer. if (sdc->wrapsVkSecondaryCB()) { return nullptr; } SkASSERT(sdc->asSurfaceProxy()); auto scaledContext = sdc->rescale(sdc->imageInfo().makeDimensions(dstDims), sdc->origin(), subset, RescaleGamma::kSrc, RescaleMode::kLinear); if (!scaledContext) { return nullptr; } return SkSpecialImages::MakeDeferredFromGpu(fContext.get(), SkIRect::MakeSize(dstDims), kNeedNewImageUniqueID_SpecialImage, scaledContext->readSurfaceView(), GrColorInfo(this->imageInfo().colorInfo()), this->surfaceProps()); } void Device::drawDevice(SkDevice* device, const SkSamplingOptions& sampling, const SkPaint& paint) { ASSERT_SINGLE_OWNER // clear of the source device must occur before CHECK_SHOULD_DRAW GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawDevice", fContext.get()); this->SkDevice::drawDevice(device, sampling, paint); } void Device::drawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) { ASSERT_SINGLE_OWNER GrAA aa = fSurfaceDrawContext->chooseAA(paint); SkCanvas::QuadAAFlags aaFlags = (aa == GrAA::kYes) ? SkCanvas::kAll_QuadAAFlags : SkCanvas::kNone_QuadAAFlags; this->drawImageQuadDirect(image, src ? *src : SkRect::MakeIWH(image->width(), image->height()), dst, /* dstClip= */ nullptr, aaFlags, /* preViewMatrix= */ nullptr, sampling, paint, constraint); } bool Device::drawAsTiledImageRect(SkCanvas* canvas, const SkImage* image, const SkRect* src, const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) { GrRecordingContext* rCtx = canvas->recordingContext(); if (!rCtx) { return false; } ASSERT_SINGLE_OWNER GrAA aa = fSurfaceDrawContext->chooseAA(paint); SkCanvas::QuadAAFlags aaFlags = (aa == GrAA::kYes) ? SkCanvas::kAll_QuadAAFlags : SkCanvas::kNone_QuadAAFlags; // NOTE: if the context is not a direct context, it doesn't have access to the resource // cache, and theoretically, the resource cache's limits could be being changed on // another thread, so even having access to just the limit wouldn't be a reliable // test during recording here. size_t cacheSize = 0; if (auto dCtx = GrAsDirectContext(rCtx)) { cacheSize = dCtx->getResourceCacheLimit(); } size_t maxTextureSize = rCtx->maxTextureSize(); #if defined(GPU_TEST_UTILS) if (gOverrideMaxTextureSizeGanesh) { maxTextureSize = gOverrideMaxTextureSizeGanesh; } gNumTilesDrawnGanesh.store(0, std::memory_order_relaxed); #endif [[maybe_unused]] auto [wasTiled, numTiles] = TiledTextureUtils::DrawAsTiledImageRect( canvas, image, src ? *src : SkRect::MakeIWH(image->width(), image->height()), dst, aaFlags, sampling, &paint, constraint, rCtx->priv().options().fSharpenMipmappedTextures, cacheSize, maxTextureSize); #if defined(GPU_TEST_UTILS) gNumTilesDrawnGanesh.store(numTiles, std::memory_order_relaxed); #endif return wasTiled; } void Device::drawViewLattice(GrSurfaceProxyView view, const GrColorInfo& info, std::unique_ptr iter, const SkRect& dst, SkFilterMode filter, const SkPaint& origPaint) { GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawViewLattice", fContext.get()); SkASSERT(view); SkTCopyOnFirstWrite paint(&origPaint); if (!info.isAlphaOnly() && (paint->getColor() & 0x00FFFFFF) != 0x00FFFFFF) { paint.writable()->setColor(SkColorSetARGB(origPaint.getAlpha(), 0xFF, 0xFF, 0xFF)); } GrPaint grPaint; // Passing null as shaderFP indicates that the GP will provide the shader. if (!SkPaintToGrPaintReplaceShader(this->recordingContext(), fSurfaceDrawContext->colorInfo(), *paint, this->localToDevice(), /*shaderFP=*/nullptr, fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } if (info.isAlphaOnly()) { // If we were doing this with an FP graph we'd use a kDstIn blend between the texture // and the paint color. view.concatSwizzle(skgpu::Swizzle("aaaa")); } auto csxf = GrColorSpaceXform::Make(info, fSurfaceDrawContext->colorInfo()); fSurfaceDrawContext->drawImageLattice(this->clip(), std::move(grPaint), this->localToDevice(), std::move(view), info.alphaType(), std::move(csxf), filter, std::move(iter), dst); } void Device::drawImageLattice(const SkImage* image, const SkCanvas::Lattice& lattice, const SkRect& dst, SkFilterMode filter, const SkPaint& paint) { ASSERT_SINGLE_OWNER auto iter = std::make_unique(lattice, dst); auto [view, ct] = skgpu::ganesh::AsView(this->recordingContext(), image, skgpu::Mipmapped::kNo); if (view) { GrColorInfo colorInfo(ct, image->alphaType(), image->refColorSpace()); this->drawViewLattice( std::move(view), std::move(colorInfo), std::move(iter), dst, filter, paint); } } void Device::drawVertices(const SkVertices* vertices, sk_sp blender, const SkPaint& paint, bool skipColorXform) { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawVertices", fContext.get()); SkASSERT(vertices); #ifdef SK_LEGACY_IGNORE_DRAW_VERTICES_BLEND_WITH_NO_SHADER if (!paint.getShader()) { blender = SkBlender::Mode(SkBlendMode::kDst); } #endif SkVerticesPriv info(vertices->priv()); GrPaint grPaint; if (!init_vertices_paint(fContext.get(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), blender.get(), info.hasColors(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } fSurfaceDrawContext->drawVertices(this->clip(), std::move(grPaint), this->localToDevice(), sk_ref_sp(const_cast(vertices)), nullptr, skipColorXform); } void Device::drawMesh(const SkMesh& mesh, sk_sp blender, const SkPaint& paint) { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawMesh", fContext.get()); if (!mesh.isValid()) { return; } GrPaint grPaint; if (!init_vertices_paint(fContext.get(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), blender.get(), SkMeshSpecificationPriv::HasColors(*mesh.spec()), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } TArray> meshChildFPs; if (!init_mesh_child_effects(fContext.get(), fSurfaceDrawContext->colorInfo(), fSurfaceDrawContext->surfaceProps(), mesh, &meshChildFPs)) { return; } fSurfaceDrawContext->drawMesh(this->clip(), std::move(grPaint), this->localToDevice(), mesh, std::move(meshChildFPs)); } /////////////////////////////////////////////////////////////////////////////// #if !defined(SK_ENABLE_OPTIMIZE_SIZE) void Device::drawShadow(const SkPath& path, const SkDrawShadowRec& rec) { #if defined(GPU_TEST_UTILS) if (fContext->priv().options().fAllPathsVolatile && !path.isVolatile()) { this->drawShadow(SkPath(path).setIsVolatile(true), rec); return; } #endif ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawShadow", fContext.get()); if (!fSurfaceDrawContext->drawFastShadow(this->clip(), this->localToDevice(), path, rec)) { // failed to find an accelerated case this->SkDevice::drawShadow(path, rec); } } #endif // SK_ENABLE_OPTIMIZE_SIZE /////////////////////////////////////////////////////////////////////////////// void Device::drawAtlas(const SkRSXform xform[], const SkRect texRect[], const SkColor colors[], int count, sk_sp blender, const SkPaint& paint) { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawAtlas", fContext.get()); GrPaint grPaint; if (colors) { if (!SkPaintToGrPaintWithBlend(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), blender.get(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } } else { if (!SkPaintToGrPaint(this->recordingContext(), fSurfaceDrawContext->colorInfo(), paint, this->localToDevice(), fSurfaceDrawContext->surfaceProps(), &grPaint)) { return; } } fSurfaceDrawContext->drawAtlas(this->clip(), std::move(grPaint), this->localToDevice(), count, xform, texRect, colors); } /////////////////////////////////////////////////////////////////////////////// void Device::onDrawGlyphRunList(SkCanvas* canvas, const sktext::GlyphRunList& glyphRunList, const SkPaint& paint) { ASSERT_SINGLE_OWNER GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawGlyphRunList", fContext.get()); SkASSERT(!glyphRunList.hasRSXForm()); if (glyphRunList.blob() == nullptr) { // If the glyphRunList does not have an associated text blob, then it was created by one of // the direct draw APIs (drawGlyphs, etc.). Use a Slug to draw the glyphs. auto slug = this->convertGlyphRunListToSlug(glyphRunList, paint); if (slug != nullptr) { this->drawSlug(canvas, slug.get(), paint); } } else { fSurfaceDrawContext->drawGlyphRunList(canvas, this->clip(), this->localToDevice(), glyphRunList, this->strikeDeviceInfo(), paint); } } /////////////////////////////////////////////////////////////////////////////// void Device::drawDrawable(SkCanvas* canvas, SkDrawable* drawable, const SkMatrix* matrix) { ASSERT_SINGLE_OWNER GrBackendApi api = this->recordingContext()->backend(); if (GrBackendApi::kVulkan == api) { const SkMatrix& ctm = this->localToDevice(); const SkMatrix& combinedMatrix = matrix ? SkMatrix::Concat(ctm, *matrix) : ctm; std::unique_ptr gpuDraw = drawable->snapGpuDrawHandler(api, combinedMatrix, this->devClipBounds(), this->imageInfo()); if (gpuDraw) { fSurfaceDrawContext->drawDrawable( std::move(gpuDraw), combinedMatrix.mapRect(drawable->getBounds())); return; } } this->SkDevice::drawDrawable(canvas, drawable, matrix); } /////////////////////////////////////////////////////////////////////////////// GrSurfaceProxyView Device::readSurfaceView() { return this->surfaceFillContext()->readSurfaceView(); } GrRenderTargetProxy* Device::targetProxy() { return this->readSurfaceView().asRenderTargetProxy(); } bool Device::wait(int numSemaphores, const GrBackendSemaphore* waitSemaphores, bool deleteSemaphoresAfterWait) { ASSERT_SINGLE_OWNER return fSurfaceDrawContext->waitOnSemaphores(numSemaphores, waitSemaphores, deleteSemaphoresAfterWait); } void Device::discard() { fSurfaceDrawContext->discard(); } void Device::resolveMSAA() { fSurfaceDrawContext->resolveMSAA(); } bool Device::replaceBackingProxy(SkSurface::ContentChangeMode mode, sk_sp newRTP, GrColorType grColorType, sk_sp colorSpace, GrSurfaceOrigin origin, const SkSurfaceProps& props) { auto sdc = SurfaceDrawContext::Make(fContext.get(), grColorType, std::move(newRTP), std::move(colorSpace), origin, props); if (!sdc) { return false; } SkASSERT(sdc->dimensions() == fSurfaceDrawContext->dimensions()); SkASSERT(sdc->numSamples() == fSurfaceDrawContext->numSamples()); SkASSERT(sdc->asSurfaceProxy()->priv().isExact()); if (mode == SkSurface::kRetain_ContentChangeMode) { if (fContext->abandoned()) { return false; } SkASSERT(fSurfaceDrawContext->asTextureProxy()); SkAssertResult(sdc->blitTexture(fSurfaceDrawContext->readSurfaceView(), SkIRect::MakeWH(this->width(), this->height()), SkIPoint::Make(0, 0))); } fSurfaceDrawContext = std::move(sdc); return true; } bool Device::replaceBackingProxy(SkSurface::ContentChangeMode mode) { ASSERT_SINGLE_OWNER const SkImageInfo& ii = this->imageInfo(); GrRenderTargetProxy* oldRTP = this->targetProxy(); GrSurfaceProxyView oldView = this->readSurfaceView(); auto grColorType = SkColorTypeToGrColorType(ii.colorType()); auto format = fContext->priv().caps()->getDefaultBackendFormat(grColorType, GrRenderable::kYes); if (!format.isValid()) { return false; } GrProxyProvider* proxyProvider = fContext->priv().proxyProvider(); // This entry point is used by SkSurface_Ganesh::onCopyOnWrite so it must create a // kExact-backed render target proxy sk_sp proxy = proxyProvider->createProxy(format, ii.dimensions(), GrRenderable::kYes, oldRTP->numSamples(), oldView.mipmapped(), SkBackingFit::kExact, oldRTP->isBudgeted(), oldRTP->isProtected(), /*label=*/"BaseDevice_ReplaceBackingProxy"); if (!proxy) { return false; } return this->replaceBackingProxy(mode, sk_ref_sp(proxy->asRenderTargetProxy()), grColorType, ii.refColorSpace(), oldView.origin(), this->surfaceProps()); } void Device::asyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect, RescaleGamma rescaleGamma, RescaleMode rescaleMode, ReadPixelsCallback callback, ReadPixelsContext context) { auto* sdc = fSurfaceDrawContext.get(); // Context TODO: Elevate direct context requirement to public API. auto dContext = sdc->recordingContext()->asDirectContext(); if (!dContext) { return; } sdc->asyncRescaleAndReadPixels(dContext, info, srcRect, rescaleGamma, rescaleMode, callback, context); } void Device::asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace, bool readAlpha, sk_sp dstColorSpace, const SkIRect& srcRect, SkISize dstSize, RescaleGamma rescaleGamma, RescaleMode rescaleMode, ReadPixelsCallback callback, ReadPixelsContext context) { auto* sdc = fSurfaceDrawContext.get(); // Context TODO: Elevate direct context requirement to public API. auto dContext = sdc->recordingContext()->asDirectContext(); if (!dContext) { return; } sdc->asyncRescaleAndReadPixelsYUV420(dContext, yuvColorSpace, readAlpha, std::move(dstColorSpace), srcRect, dstSize, rescaleGamma, rescaleMode, callback, context); } /////////////////////////////////////////////////////////////////////////////// sk_sp Device::createDevice(const CreateInfo& cinfo, const SkPaint*) { ASSERT_SINGLE_OWNER SkSurfaceProps props = this->surfaceProps().cloneWithPixelGeometry(cinfo.fPixelGeometry); SkASSERT(cinfo.fInfo.colorType() != kRGBA_1010102_SkColorType); auto sdc = SurfaceDrawContext::MakeWithFallback( fContext.get(), SkColorTypeToGrColorType(cinfo.fInfo.colorType()), cinfo.fInfo.refColorSpace(), SkBackingFit::kApprox, cinfo.fInfo.dimensions(), props, fSurfaceDrawContext->numSamples(), skgpu::Mipmapped::kNo, fSurfaceDrawContext->asSurfaceProxy()->isProtected(), fSurfaceDrawContext->origin(), skgpu::Budgeted::kYes); if (!sdc) { return nullptr; } // Skia's convention is to only clear a device if it is non-opaque. InitContents init = cinfo.fInfo.isOpaque() ? InitContents::kUninit : InitContents::kClear; return Device::Make(std::move(sdc), cinfo.fInfo.alphaType(), init); } sk_sp Device::makeSurface(const SkImageInfo& info, const SkSurfaceProps& props) { ASSERT_SINGLE_OWNER // TODO: Change the signature of newSurface to take a budgeted parameter. static const skgpu::Budgeted kBudgeted = skgpu::Budgeted::kNo; bool isProtected = this->targetProxy()->isProtected() == GrProtected::kYes; return SkSurfaces::RenderTarget(fContext.get(), kBudgeted, info, fSurfaceDrawContext->numSamples(), fSurfaceDrawContext->origin(), &props, /* shouldCreateWithMips= */ false, isProtected); } //////////////////////////////////////////////////////////////////////////////////// bool Device::android_utils_clipWithStencil() { SkRegion clipRegion; this->android_utils_clipAsRgn(&clipRegion); if (clipRegion.isEmpty()) { return false; } auto sdc = fSurfaceDrawContext.get(); SkASSERT(sdc); GrPaint grPaint; grPaint.setXPFactory(GrDisableColorXPFactory::Get()); static constexpr GrUserStencilSettings kDrawToStencil( GrUserStencilSettings::StaticInit< 0x1, GrUserStencilTest::kAlways, 0x1, GrUserStencilOp::kReplace, GrUserStencilOp::kReplace, 0x1>() ); // Regions don't actually need AA, but in DMSAA mode everything is antialiased. GrAA aa = GrAA(fSurfaceDrawContext->alwaysAntialias()); sdc->drawRegion(nullptr, std::move(grPaint), aa, SkMatrix::I(), clipRegion, GrStyle::SimpleFill(), &kDrawToStencil); return true; } SkStrikeDeviceInfo Device::strikeDeviceInfo() const { return {this->surfaceProps(), this->scalerContextFlags(), &fSubRunControl}; } sk_sp Device::convertGlyphRunListToSlug(const sktext::GlyphRunList& glyphRunList, const SkPaint& paint) { return sktext::gpu::SlugImpl::Make(this->localToDevice(), glyphRunList, paint, this->strikeDeviceInfo(), SkStrikeCache::GlobalStrikeCache()); } #if defined(SK_DEBUG) static bool valid_slug_matrices(const SkMatrix& creationMatrix, const SkMatrix& positionMatrix) { // A Slug can be drawn with: // The exact same matrix that was used for creation // - OR - // A non-perspective matrix that has the same 2x2 and a relative integer translation // // For the second case, calculate the translation in source space to a translation in // device space by mapping (0, 0) through both the creationMatrix and the positionMatrix; // take the difference. if (creationMatrix == positionMatrix) { return true; } SkVector translation = positionMatrix.mapOrigin() - creationMatrix.mapOrigin(); return creationMatrix.getScaleX() == positionMatrix.getScaleX() && creationMatrix.getScaleY() == positionMatrix.getScaleY() && creationMatrix.getSkewX() == positionMatrix.getSkewX() && creationMatrix.getSkewY() == positionMatrix.getSkewY() && !positionMatrix.hasPerspective() && !creationMatrix.hasPerspective() && SkScalarIsInt(translation.x()) && SkScalarIsInt(translation.y()); } #endif void Device::drawSlug(SkCanvas* canvas, const sktext::gpu::Slug* slug, const SkPaint& paint) { SkASSERT(canvas); SkASSERT(slug); const sktext::gpu::SlugImpl* slugImpl = static_cast(slug); #if defined(SK_DEBUG) if (!fContext->priv().options().fSupportBilerpFromGlyphAtlas) { // We can draw a slug if the atlas has padding or if the creation matrix and the // drawing matrix are the same (up to integer translation). // If they are the same, then the Slug will use the direct drawing code and not use bi-lerp. SkMatrix slugMatrix = slugImpl->initialPositionMatrix(); SkMatrix positionMatrix = this->localToDevice(); positionMatrix.preTranslate(slugImpl->origin().x(), slugImpl->origin().y()); SkASSERT(valid_slug_matrices(slugMatrix, positionMatrix)); } #endif auto atlasDelegate = [&](const sktext::gpu::AtlasSubRun* subRun, SkPoint drawOrigin, const SkPaint& paint, sk_sp subRunStorage, sktext::gpu::RendererData) { auto[drawingClip, op] = subRun->makeAtlasTextOp( this->clip(), this->localToDevice(), drawOrigin, paint, std::move(subRunStorage), fSurfaceDrawContext.get()); if (op != nullptr) { fSurfaceDrawContext->addDrawOp(drawingClip, std::move(op)); } }; slugImpl->subRuns()->draw(canvas, slugImpl->origin(), paint, slugImpl, atlasDelegate); } } // namespace skgpu::ganesh