| /* | |
| * 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/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/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/SkColorData.h" | |
| #include "src/core/SkDevice.h" | |
| #include "src/core/SkDraw.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/GrShaderCaps.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/gpu/ganesh/ops/AtlasTextOp.h" | |
| #include "src/text/GlyphRun.h" | |
| #include "src/text/gpu/SlugImpl.h" | |
| #include "src/text/gpu/SubRunContainer.h" | |
| #include <atomic> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <functional> | |
| #include <memory> | |
| #include <optional> | |
| #include <utility> | |
| class GrBackendSemaphore; | |
| 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<int> 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<GrFragmentProcessor> make_inverse_rrect_fp(const SkMatrix& viewMatrix, | |
| const SkRRect& rrect, GrAA aa, | |
| const GrShaderCaps& shaderCaps) { | |
| if (auto rr = rrect.transform(viewMatrix)) { | |
| auto edgeType = (aa == GrAA::kYes) ? GrClipEdgeType::kInverseFillAA | |
| : GrClipEdgeType::kInverseFillBW; | |
| auto [success, fp] = GrRRectEffect::Make(/*inputFP=*/nullptr, edgeType, *rr, shaderCaps); | |
| return (success) ? std::move(fp) : nullptr; | |
| } | |
| return nullptr; | |
| } | |
| bool init_vertices_paint(skgpu::ganesh::SurfaceDrawContext* sdc, | |
| const SkPaint& skPaint, | |
| const SkMatrix& ctm, | |
| SkBlender* blender, | |
| bool hasColors, | |
| GrPaint* grPaint) { | |
| if (hasColors) { | |
| return SkPaintToGrPaintWithBlend(sdc, skPaint, ctm, blender, grPaint); | |
| } else { | |
| return SkPaintToGrPaint(sdc, skPaint, ctm, grPaint); | |
| } | |
| } | |
| bool init_mesh_child_effects(skgpu::ganesh::SurfaceDrawContext* sdc, | |
| const SkMesh& mesh, | |
| TArray<std::unique_ptr<GrFragmentProcessor>>* 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(sdc, &sdc->colorInfo(), sdc->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> Device::Make(GrRecordingContext* rContext, | |
| GrColorType colorType, | |
| sk_sp<GrSurfaceProxy> proxy, | |
| sk_sp<SkColorSpace> 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> Device::Make(std::unique_ptr<SurfaceDrawContext> 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<Device>(new Device(std::move(sdc), flags)); | |
| } | |
| sk_sp<Device> 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<SurfaceDrawContext> 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(); | |
| 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().makeTransform(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(fSurfaceDrawContext.get(), | |
| paint, | |
| this->localToDevice(), | |
| &grPaint)) { | |
| return; | |
| } | |
| fSurfaceDrawContext->drawPaint(this->clip(), std::move(grPaint), this->localToDevice()); | |
| } | |
| void Device::drawPoints(SkCanvas::PointMode mode, SkSpan<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; | |
| } | |
| const size_t count = pts.size(); | |
| // If there is an image filter or mask filter these bounds were already checked in | |
| // the canvas. | |
| if (!paint.getImageFilter() && !paint.getMaskFilter()) { | |
| auto bounds = SkRect::Bounds(pts); | |
| if (!bounds || paint.nothingToDraw()) { | |
| return; | |
| } | |
| SkRect devBounds = SkMatrixPriv::MapRect(this->localToDevice44(), bounds.value()); | |
| if (!devBounds.isFinite()) { | |
| 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(fSurfaceDrawContext.get(), | |
| paint, | |
| this->localToDevice(), | |
| &grPaint)) { | |
| SkPath path = SkPath::Line(pts[0], pts[1]); | |
| path.setIsVolatile(true); | |
| 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(fSurfaceDrawContext.get(), | |
| paint, | |
| this->localToDevice(), | |
| &grPaint)) { | |
| fSurfaceDrawContext->drawStrokedLine(this->clip(), | |
| std::move(grPaint), | |
| aa, | |
| this->localToDevice(), | |
| pts.data(), | |
| 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()); | |
| skcpu::Draw 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, pts, paint, this); | |
| return; | |
| } | |
| GrPaint grPaint; | |
| if (!SkPaintToGrPaint(fSurfaceDrawContext.get(), paint, this->localToDevice(), &grPaint)) { | |
| return; | |
| } | |
| static constexpr SkVertices::VertexMode kIgnoredMode = SkVertices::kTriangles_VertexMode; | |
| sk_sp<SkVertices> vertices = SkVertices::MakeCopy(kIgnoredMode, SkToS32(count), pts.data(), | |
| 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(fSurfaceDrawContext.get(), paint, this->localToDevice(), &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(fSurfaceDrawContext.get(), | |
| paint, | |
| this->localToDevice(), | |
| &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(fSurfaceDrawContext.get(), | |
| paint, | |
| this->localToDevice(), | |
| &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; | |
| } | |
| } | |
| auto path = SkPathBuilder(SkPathFillType::kEvenOdd) | |
| .addRRect(outer) | |
| .addRRect(inner) | |
| .detach(); | |
| path.setIsVolatile(true); | |
| // 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.setIsVolatile(true); | |
| return this->drawPath(path, paint); | |
| } | |
| GrPaint grPaint; | |
| if (!SkPaintToGrPaint(fSurfaceDrawContext.get(), | |
| paint, | |
| this->localToDevice(), | |
| &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(fSurfaceDrawContext.get(), paint, this->localToDevice(), &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(fSurfaceDrawContext.get(), paint, this->localToDevice(), &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) { | |
| #if defined(GPU_TEST_UTILS) | |
| if (fContext->priv().options().fAllPathsVolatile && !origSrcPath.isVolatile()) { | |
| this->drawPath(SkPath(origSrcPath).setIsVolatile(true), paint); | |
| return; | |
| } | |
| #endif | |
| ASSERT_SINGLE_OWNER | |
| GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawPath", fContext.get()); | |
| if (!paint.getMaskFilter()) { | |
| GrPaint grPaint; | |
| if (!SkPaintToGrPaint(fSurfaceDrawContext.get(), | |
| paint, | |
| this->localToDevice(), | |
| &grPaint)) { | |
| return; | |
| } | |
| fSurfaceDrawContext->drawPath(this->clip(), std::move(grPaint), | |
| fSurfaceDrawContext->chooseAA(paint), this->localToDevice(), | |
| origSrcPath, GrStyle(paint)); | |
| return; | |
| } | |
| GrStyledShape shape(origSrcPath, paint); | |
| GrBlurUtils::DrawShapeWithMaskFilter(fContext.get(), fSurfaceDrawContext.get(), this->clip(), | |
| paint, this->localToDevice(), shape); | |
| } | |
| sk_sp<skif::Backend> Device::createImageFilteringBackend(const SkSurfaceProps& surfaceProps, | |
| SkColorType colorType) const { | |
| return skif::MakeGaneshBackend( | |
| fContext, fSurfaceDrawContext->origin(), surfaceProps, colorType); | |
| } | |
| sk_sp<SkSpecialImage> 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<SkSpecialImage> 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, | |
| !rCtx->priv().caps()->shaderCaps()->fHasLowFragmentPrecision); | |
| #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<SkLatticeIter> iter, | |
| const SkRect& dst, | |
| SkFilterMode filter, | |
| const SkPaint& origPaint) { | |
| GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawViewLattice", fContext.get()); | |
| SkASSERT(view); | |
| SkTCopyOnFirstWrite<SkPaint> 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(fSurfaceDrawContext.get(), | |
| *paint, | |
| this->localToDevice(), | |
| /*shaderFP=*/nullptr, | |
| &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<SkLatticeIter>(lattice, dst); | |
| auto [view, ct] = skgpu::ganesh::AsView(this->recordingContext(), | |
| image, | |
| skgpu::Mipmapped::kNo, | |
| this->targetProxy()); | |
| 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<SkBlender> 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(fSurfaceDrawContext.get(), | |
| paint, | |
| this->localToDevice(), | |
| blender.get(), | |
| info.hasColors(), | |
| &grPaint)) { | |
| return; | |
| } | |
| fSurfaceDrawContext->drawVertices(this->clip(), | |
| std::move(grPaint), | |
| this->localToDevice(), | |
| sk_ref_sp(const_cast<SkVertices*>(vertices)), | |
| nullptr, | |
| skipColorXform); | |
| } | |
| void Device::drawMesh(const SkMesh& mesh, sk_sp<SkBlender> 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(fSurfaceDrawContext.get(), | |
| paint, | |
| this->localToDevice(), | |
| blender.get(), | |
| SkMeshSpecificationPriv::HasColors(*mesh.spec()), | |
| &grPaint)) { | |
| return; | |
| } | |
| TArray<std::unique_ptr<GrFragmentProcessor>> meshChildFPs; | |
| if (!init_mesh_child_effects(fSurfaceDrawContext.get(), 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(SkCanvas* canvas, const SkPath& path, const SkDrawShadowRec& rec) { | |
| #if defined(GPU_TEST_UTILS) | |
| if (fContext->priv().options().fAllPathsVolatile && !path.isVolatile()) { | |
| this->drawShadow(canvas, 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(canvas, path, rec); | |
| } | |
| } | |
| #endif // SK_ENABLE_OPTIMIZE_SIZE | |
| /////////////////////////////////////////////////////////////////////////////// | |
| void Device::drawAtlas(SkSpan<const SkRSXform> xform, | |
| SkSpan<const SkRect> texRect, | |
| SkSpan<const SkColor> colors, | |
| sk_sp<SkBlender> blender, | |
| const SkPaint& paint) { | |
| ASSERT_SINGLE_OWNER | |
| GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawAtlas", fContext.get()); | |
| GrPaint grPaint; | |
| if (!colors.empty()) { | |
| if (!SkPaintToGrPaintWithBlend(fSurfaceDrawContext.get(), | |
| paint, | |
| this->localToDevice(), | |
| blender.get(), | |
| &grPaint)) { | |
| return; | |
| } | |
| } else { | |
| if (!SkPaintToGrPaint(fSurfaceDrawContext.get(), | |
| paint, | |
| this->localToDevice(), | |
| &grPaint)) { | |
| return; | |
| } | |
| } | |
| fSurfaceDrawContext->drawAtlas(this->clip(), std::move(grPaint), this->localToDevice(), | |
| 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<SkDrawable::GpuDrawHandler> 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<GrRenderTargetProxy> newRTP, | |
| GrColorType grColorType, | |
| sk_sp<SkColorSpace> 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<GrTextureProxy> 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<SkColorSpace> 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<SkDevice> 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<SkSurface> 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<sktext::gpu::Slug> 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<const sktext::gpu::SlugImpl*>(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<SkRefCnt> subRunStorage, | |
| sktext::gpu::RendererData) { | |
| auto[drawingClip, op] = AtlasTextOp::Make( | |
| fSurfaceDrawContext.get(), subRun, | |
| this->clip(), this->localToDevice(), drawOrigin, paint, | |
| std::move(subRunStorage)); | |
| if (op != nullptr) { | |
| fSurfaceDrawContext->addDrawOp(drawingClip, std::move(op)); | |
| } | |
| }; | |
| slugImpl->subRuns()->draw(canvas, slugImpl->origin(), paint, slugImpl, atlasDelegate); | |
| } | |
| } // namespace skgpu::ganesh |