Flutter Engine
The Flutter Engine
Device.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2021 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
9
14#include "src/gpu/AtlasTypes.h"
15#include "src/gpu/BlurUtils.h"
45
47#include "include/core/SkPath.h"
50
66#include "src/text/GlyphRun.h"
72
73#include <functional>
74#include <tuple>
75#include <unordered_map>
76#include <vector>
77
82
83#if defined(GRAPHITE_TEST_UTILS)
84int gOverrideMaxTextureSizeGraphite = 0;
85// Allows tests to check how many tiles were drawn on the most recent call to
86// Device::drawAsTiledImageRect. This is an atomic because we can write to it from
87// multiple threads during "normal" operations. However, the tests that actually
88// read from it are done single-threaded.
89std::atomic<int> gNumTilesDrawnGraphite{0};
90#endif
91
92namespace skgpu::graphite {
93
94#define ASSERT_SINGLE_OWNER SkASSERT(fRecorder); SKGPU_ASSERT_SINGLE_OWNER(fRecorder->singleOwner())
95
96namespace {
97
98const SkStrokeRec& DefaultFillStyle() {
99 static const SkStrokeRec kFillStyle(SkStrokeRec::kFill_InitStyle);
100 return kFillStyle;
101}
102
103bool blender_depends_on_dst(const SkBlender* blender, bool srcIsTransparent) {
104 std::optional<SkBlendMode> bm = blender ? as_BB(blender)->asBlendMode() : SkBlendMode::kSrcOver;
105 if (!bm.has_value()) {
106 return true;
107 }
108 if (bm.value() == SkBlendMode::kSrc || bm.value() == SkBlendMode::kClear) {
109 // src and clear blending never depends on dst
110 return false;
111 }
112 if (bm.value() == SkBlendMode::kSrcOver) {
113 // src-over depends on dst if src is transparent (a != 1)
114 return srcIsTransparent;
115 }
116 // TODO: Are their other modes that don't depend on dst that can be trivially detected?
117 return true;
118}
119
120bool paint_depends_on_dst(SkColor4f color,
121 const SkShader* shader,
122 const SkColorFilter* colorFilter,
123 const SkBlender* finalBlender,
124 const SkBlender* primitiveBlender) {
125 const bool srcIsTransparent = !color.isOpaque() || (shader && !shader->isOpaque()) ||
126 (colorFilter && !colorFilter->isAlphaUnchanged());
127
128 if (primitiveBlender && blender_depends_on_dst(primitiveBlender, srcIsTransparent)) {
129 return true;
130 }
131
132 return blender_depends_on_dst(finalBlender, srcIsTransparent);
133}
134
135bool paint_depends_on_dst(const PaintParams& paintParams) {
136 return paint_depends_on_dst(paintParams.color(),
137 paintParams.shader(),
138 paintParams.colorFilter(),
139 paintParams.finalBlender(),
140 paintParams.primitiveBlender());
141}
142
143bool paint_depends_on_dst(const SkPaint& paint) {
144 // CAUTION: getMaskFilter is intentionally ignored here.
145 SkASSERT(!paint.getImageFilter()); // no paints in SkDevice should have an image filter
146 return paint_depends_on_dst(paint.getColor4f(),
147 paint.getShader(),
148 paint.getColorFilter(),
149 paint.getBlender(),
150 /*primitiveBlender=*/nullptr);
151}
152
153/** If the paint can be reduced to a solid flood-fill, determine the correct color to fill with. */
154std::optional<SkColor4f> extract_paint_color(const SkPaint& paint,
155 const SkColorInfo& dstColorInfo) {
156 SkASSERT(!paint_depends_on_dst(paint));
157 if (paint.getShader()) {
158 return std::nullopt;
159 }
160
161 SkColor4f dstPaintColor = PaintParams::Color4fPrepForDst(paint.getColor4f(), dstColorInfo);
162
163 if (SkColorFilter* filter = paint.getColorFilter()) {
164 SkColorSpace* dstCS = dstColorInfo.colorSpace();
165 return filter->filterColor4f(dstPaintColor, dstCS, dstCS);
166 }
167 return dstPaintColor;
168}
169
170SkIRect rect_to_pixelbounds(const Rect& r) {
171 return r.makeRoundOut().asSkIRect();
172}
173
174bool is_simple_shape(const Shape& shape, SkStrokeRec::Style type) {
175 // We send regular filled and hairline [round] rectangles, stroked/hairline lines, and stroked
176 // [r]rects with circular corners to a single Renderer that does not trigger MSAA.
177 // Per-edge AA quadrilaterals also use the same Renderer but those are not "Shapes".
178 return !shape.inverted() && type != SkStrokeRec::kStrokeAndFill_Style &&
179 (shape.isRect() ||
180 (shape.isLine() && type != SkStrokeRec::kFill_Style) ||
181 (shape.isRRect() && (type != SkStrokeRec::kStroke_Style ||
182 SkRRectPriv::AllCornersCircular(shape.rrect()))));
183}
184
185bool use_compute_atlas_when_available(PathRendererStrategy strategy) {
186 return strategy == PathRendererStrategy::kComputeAnalyticAA ||
190}
191
192} // anonymous namespace
193
194/**
195 * IntersectionTreeSet controls multiple IntersectionTrees to organize all add rectangles into
196 * disjoint sets. For a given CompressedPaintersOrder and bounds, it returns the smallest
197 * DisjointStencilIndex that guarantees the bounds are disjoint from all other draws that use the
198 * same painters order and stencil index.
199 */
201public:
203
205 auto& trees = fTrees[drawOrder];
207 for (auto&& tree : trees) {
208 if (tree->add(rect)) {
209 return stencil;
210 }
211 stencil = stencil.next(); // advance to the next tree's index
212 }
213
214 // If here, no existing intersection tree can hold the rect so add a new one
215 IntersectionTree* newTree = this->makeTree();
216 SkAssertResult(newTree->add(rect));
217 trees.push_back(newTree);
218 return stencil;
219 }
220
221 void reset() {
222 fTrees.clear();
223 fTreeStore.reset();
224 }
225
226private:
227 struct Hash {
228 size_t operator()(const CompressedPaintersOrder& o) const noexcept { return o.bits(); }
229 };
230
231 IntersectionTree* makeTree() {
232 return fTreeStore.make<IntersectionTree>();
233 }
234
235 // Each compressed painters order defines a barrier around draws so each order's set of draws
236 // are independent, even if they may intersect. Within each order, the list of trees holds the
237 // IntersectionTrees representing each disjoint set.
238 // TODO: This organization of trees is logically convenient but may need to be optimized based
239 // on real world data (e.g. how sparse is the map, how long is each vector of trees,...)
240 std::unordered_map<CompressedPaintersOrder, std::vector<IntersectionTree*>, Hash> fTrees;
241 SkSTArenaAllocWithReset<4 * sizeof(IntersectionTree)> fTreeStore;
242};
243
245 const SkImageInfo& ii,
246 skgpu::Budgeted budgeted,
247 Mipmapped mipmapped,
248 SkBackingFit backingFit,
249 const SkSurfaceProps& props,
250 LoadOp initialLoadOp,
251 std::string_view label,
252 bool registerWithRecorder) {
253 SkASSERT(!(mipmapped == Mipmapped::kYes && backingFit == SkBackingFit::kApprox));
254 if (!recorder) {
255 return nullptr;
256 }
257
258 const Caps* caps = recorder->priv().caps();
259 SkISize backingDimensions = backingFit == SkBackingFit::kApprox ? GetApproxSize(ii.dimensions())
260 : ii.dimensions();
261 auto textureInfo = caps->getDefaultSampledTextureInfo(ii.colorType(),
262 mipmapped,
265
266 return Make(recorder,
268 backingDimensions, textureInfo, std::move(label), budgeted),
269 ii.dimensions(),
270 ii.colorInfo(),
271 props,
272 initialLoadOp,
273 registerWithRecorder);
274}
275
278 SkISize deviceSize,
279 const SkColorInfo& colorInfo,
280 const SkSurfaceProps& props,
281 LoadOp initialLoadOp,
282 bool registerWithRecorder) {
283 if (!recorder) {
284 return nullptr;
285 }
286
288 std::move(target),
289 deviceSize,
290 colorInfo,
291 props);
292 if (!dc) {
293 return nullptr;
294 } else if (initialLoadOp == LoadOp::kClear) {
295 dc->clear(SkColors::kTransparent);
296 } else if (initialLoadOp == LoadOp::kDiscard) {
297 dc->discard();
298 } // else kLoad is the default initial op for a DrawContext
299
300 sk_sp<Device> device{new Device(recorder, std::move(dc))};
301 if (registerWithRecorder) {
302 // We don't register the device with the recorder until after the constructor has returned.
303 recorder->registerDevice(device);
304 } else {
305 // Since it's not registered, it should go out of scope before nextRecordingID() changes
306 // from what is saved to fScopedRecordingID.
307 SkDEBUGCODE(device->fScopedRecordingID = recorder->priv().nextRecordingID();)
308 }
309 return device;
310}
311
312// These default tuning numbers for the HybridBoundsManager were chosen from looking at performance
313// and accuracy curves produced by the BoundsManagerBench for random draw bounding boxes. This
314// config will use brute force for the first 64 draw calls to the Device and then switch to a grid
315// that is dynamically sized to produce cells that are 16x16, up to a grid that's 32x32 cells.
316// This seemed like a sweet spot balancing accuracy for low-draw count surfaces and overhead for
317// high-draw count and high-resolution surfaces. With the 32x32 grid limit, cell size will increase
318// above 16px when the surface dimension goes above 512px.
319// TODO: These could be exposed as context options or surface options, and we may want to have
320// different strategies in place for a base device vs. a layer's device.
321static constexpr int kGridCellSize = 16;
322static constexpr int kMaxBruteForceN = 64;
323static constexpr int kMaxGridSize = 32;
324
325Device::Device(Recorder* recorder, sk_sp<DrawContext> dc)
326 : SkDevice(dc->imageInfo(), dc->surfaceProps())
327 , fRecorder(recorder)
328 , fDC(std::move(dc))
329 , fClip(this)
330 , fColorDepthBoundsManager(std::make_unique<HybridBoundsManager>(
331 fDC->imageInfo().dimensions(), kGridCellSize, kMaxBruteForceN, kMaxGridSize))
332 , fDisjointStencilSet(std::make_unique<IntersectionTreeSet>())
333 , fCachedLocalToDevice(SkM44())
334 , fCurrentDepth(DrawOrder::kClearDepth)
335 , fSDFTControl(recorder->priv().caps()->getSDFTControl(
336 fDC->surfaceProps().isUseDeviceIndependentFonts())) {
337 SkASSERT(SkToBool(fDC) && SkToBool(fRecorder));
338 if (fRecorder->priv().caps()->defaultMSAASamplesCount() > 1) {
339 if (fRecorder->priv().caps()->msaaRenderToSingleSampledSupport()) {
340 fMSAASupported = true;
341 } else {
342 TextureInfo msaaTexInfo =
343 fRecorder->priv().caps()->getDefaultMSAATextureInfo(fDC->target()->textureInfo(),
345 fMSAASupported = msaaTexInfo.isValid();
346 }
347 }
348}
349
350Device::~Device() {
351 // The Device should have been marked immutable before it's destroyed, or the Recorder was the
352 // last holder of a reference to it and de-registered the device as part of its cleanup.
353 // However, if the Device was not registered with the recorder (i.e. a scratch device) we don't
354 // require that its recorder be adandoned. Scratch devices must either have been marked
355 // immutable or be destroyed before the recorder has been snapped.
356 SkASSERT(!fRecorder || fScopedRecordingID != 0);
357#if defined(SK_DEBUG)
358 if (fScopedRecordingID != 0 && fRecorder) {
359 SkASSERT(fScopedRecordingID == fRecorder->priv().nextRecordingID());
360 }
361 // else it wasn't a scratch device, or it was a scratch device that was marked immutable so its
362 // lifetime was validated when setImmutable() was called.
363#endif
364}
365
366void Device::setImmutable() {
367 if (fRecorder) {
368 // Push any pending work to the Recorder now. setImmutable() is only called by the
369 // destructor of a client-owned Surface, or explicitly in layer/filtering workflows. In
370 // both cases this is restricted to the Recorder's thread. This is in contrast to ~Device(),
371 // which might be called from another thread if it was linked to an Image used in multiple
372 // recorders.
373 this->flushPendingWorkToRecorder();
374 fRecorder->deregisterDevice(this);
375 // Abandoning the recorder ensures that there are no further operations that can be recorded
376 // and is relied on by Image::notifyInUse() to detect when it can unlink from a Device.
377 this->abandonRecorder();
378 }
379}
380
381const Transform& Device::localToDeviceTransform() {
382 if (this->checkLocalToDeviceDirty()) {
383 fCachedLocalToDevice = Transform{this->localToDevice44()};
384 }
385 return fCachedLocalToDevice;
386}
387
388SkStrikeDeviceInfo Device::strikeDeviceInfo() const {
389 return {this->surfaceProps(), this->scalerContextFlags(), &fSDFTControl};
390}
391
392sk_sp<SkDevice> Device::createDevice(const CreateInfo& info, const SkPaint*) {
393 // TODO: Inspect the paint and create info to determine if there's anything that has to be
394 // modified to support inline subpasses.
395 SkSurfaceProps props =
396 this->surfaceProps().cloneWithPixelGeometry(info.fPixelGeometry);
397
398 // Skia's convention is to only clear a device if it is non-opaque.
399 LoadOp initialLoadOp = info.fInfo.isOpaque() ? LoadOp::kDiscard : LoadOp::kClear;
400
401 std::string label = this->target()->label();
402 if (label.empty()) {
403 label = "ChildDevice";
404 } else {
405 label += "_ChildDevice";
406 }
407
408 return Make(fRecorder,
409 info.fInfo,
413 props,
414 initialLoadOp,
415 label);
416}
417
419 return SkSurfaces::RenderTarget(fRecorder, ii, Mipmapped::kNo, &props);
420}
421
422sk_sp<Image> Device::makeImageCopy(const SkIRect& subset,
423 Budgeted budgeted,
424 Mipmapped mipmapped,
425 SkBackingFit backingFit) {
427 this->flushPendingWorkToRecorder();
428
429 const SkColorInfo& colorInfo = this->imageInfo().colorInfo();
430 TextureProxyView srcView = this->readSurfaceView();
431 if (!srcView) {
432 // readSurfaceView() returns an empty view when the target is not texturable. Create an
433 // equivalent view for the blitting operation.
434 Swizzle readSwizzle = fRecorder->priv().caps()->getReadSwizzle(
435 colorInfo.colorType(), this->target()->textureInfo());
436 srcView = {sk_ref_sp(this->target()), readSwizzle};
437 }
438 std::string label = this->target()->label();
439 if (label.empty()) {
440 label = "CopyDeviceTexture";
441 } else {
442 label += "_DeviceCopy";
443 }
444
445 return Image::Copy(fRecorder, srcView, colorInfo, subset, budgeted, mipmapped, backingFit,
446 label);
447}
448
449bool Device::onReadPixels(const SkPixmap& pm, int srcX, int srcY) {
450#if defined(GRAPHITE_TEST_UTILS)
451 // This testing-only function should only be called before the Device has detached from its
452 // Recorder, since it's accessed via the test-held Surface.
454 if (Context* context = fRecorder->priv().context()) {
455 // Add all previous commands generated to the command buffer.
456 // If the client snaps later they'll only get post-read commands in their Recording,
457 // but since they're doing a readPixels in the middle that shouldn't be unexpected.
458 std::unique_ptr<Recording> recording = fRecorder->snap();
459 if (!recording) {
460 return false;
461 }
462 InsertRecordingInfo info;
463 info.fRecording = recording.get();
464 if (!context->insertRecording(info)) {
465 return false;
466 }
467 return context->priv().readPixels(pm, fDC->target(), this->imageInfo(), srcX, srcY);
468 }
469#endif
470 // We have no access to a context to do a read pixels here.
471 return false;
472}
473
474bool Device::onWritePixels(const SkPixmap& src, int x, int y) {
476 // TODO: we may need to share this in a more central place to handle uploads
477 // to backend textures
478
479 const TextureProxy* target = fDC->target();
480
481 // TODO: add mipmap support for createBackendTexture
482
483 if (src.colorType() == kUnknown_SkColorType) {
484 return false;
485 }
486
487 // If one alpha type is unknown and the other isn't, it's too underspecified.
488 if ((src.alphaType() == kUnknown_SkAlphaType) !=
489 (this->imageInfo().alphaType() == kUnknown_SkAlphaType)) {
490 return false;
491 }
492
493 // TODO: canvas2DFastPath?
494
495 if (!fRecorder->priv().caps()->supportsWritePixels(target->textureInfo())) {
496 auto image = SkImages::RasterFromPixmap(src, nullptr, nullptr);
498 if (!image) {
499 return false;
500 }
501
503 paint.setBlendMode(SkBlendMode::kSrc);
504 this->drawImageRect(image.get(),
505 /*src=*/nullptr,
506 SkRect::MakeXYWH(x, y, src.width(), src.height()),
508 paint,
510 return true;
511 }
512
513 // TODO: check for flips and either handle here or pass info to UploadTask
514
515 // Determine rect to copy
516 SkIRect dstRect = SkIRect::MakePtSize({x, y}, src.dimensions());
517 if (!target->isFullyLazy() && !dstRect.intersect(SkIRect::MakeSize(target->dimensions()))) {
518 return false;
519 }
520
521 // Set up copy location
522 const void* addr = src.addr(dstRect.fLeft - x, dstRect.fTop - y);
523 std::vector<MipLevel> levels;
524 levels.push_back({addr, src.rowBytes()});
525
526 // The writePixels() still respects painter's order, so flush everything to tasks before this
527 // recording the upload for the pixel data.
528 this->internalFlush();
529 // The new upload will be executed before any new draws are recorded and also ensures that
530 // the next call to flushDeviceToRecorder() will produce a non-null DrawTask. If this Device's
531 // target is mipmapped, mipmap generation tasks will be added automatically at that point.
532 return fDC->recordUpload(fRecorder, fDC->refTarget(), src.info().colorInfo(),
533 this->imageInfo().colorInfo(), levels, dstRect, nullptr);
534}
535
536
537///////////////////////////////////////////////////////////////////////////////
538
539bool Device::isClipAntiAliased() const {
540 // All clips are AA'ed unless it's wide-open, empty, or a device-rect with integer coordinates
541 ClipStack::ClipState type = fClip.clipState();
542 if (type == ClipStack::ClipState::kWideOpen || type == ClipStack::ClipState::kEmpty) {
543 return false;
545 const ClipStack::Element rect = *fClip.begin();
546 SkASSERT(rect.fShape.isRect() && rect.fLocalToDevice.type() == Transform::Type::kIdentity);
547 return rect.fShape.rect() != rect.fShape.rect().makeRoundOut();
548 } else {
549 return true;
550 }
551}
552
553SkIRect Device::devClipBounds() const {
554 return rect_to_pixelbounds(fClip.conservativeBounds());
555}
556
557// TODO: This is easy enough to support, but do we still need this API in Skia at all?
558void Device::android_utils_clipAsRgn(SkRegion* region) const {
559 SkIRect bounds = this->devClipBounds();
560 // Assume wide open and then perform intersect/difference operations reducing the region
562 const SkRegion deviceBounds(bounds);
563 for (const ClipStack::Element& e : fClip) {
564 SkRegion tmp;
565 if (e.fShape.isRect() && e.fLocalToDevice.type() == Transform::Type::kIdentity) {
566 tmp.setRect(rect_to_pixelbounds(e.fShape.rect()));
567 } else {
568 SkPath tmpPath = e.fShape.asPath();
569 tmpPath.transform(e.fLocalToDevice);
570 tmp.setPath(tmpPath, deviceBounds);
571 }
572
573 region->op(tmp, (SkRegion::Op) e.fOp);
574 }
575}
576
577void Device::clipRect(const SkRect& rect, SkClipOp op, bool aa) {
579 // TODO: Snap rect edges to pixel bounds if non-AA and axis-aligned?
580 fClip.clipShape(this->localToDeviceTransform(), Shape{rect}, op);
581}
582
583void Device::clipRRect(const SkRRect& rrect, SkClipOp op, bool aa) {
585 // TODO: Snap rrect edges to pixel bounds if non-AA and axis-aligned? Is that worth doing to
586 // seam with non-AA rects even if the curves themselves are AA'ed?
587 fClip.clipShape(this->localToDeviceTransform(), Shape{rrect}, op);
588}
589
590void Device::clipPath(const SkPath& path, SkClipOp op, bool aa) {
592 // TODO: Ensure all path inspection is handled here or in SkCanvas, and that non-AA rects as
593 // paths are routed appropriately.
594 // TODO: Must also detect paths that are lines so the clip stack can be set to empty
595 fClip.clipShape(this->localToDeviceTransform(), Shape{path}, op);
596}
597
598void Device::onClipShader(sk_sp<SkShader> shader) {
599 fClip.clipShader(std::move(shader));
600}
601
602// TODO: Is clipRegion() on the deprecation chopping block. If not it should be...
603void Device::clipRegion(const SkRegion& globalRgn, SkClipOp op) {
605
606 Transform globalToDevice{this->globalToDevice()};
607
608 if (globalRgn.isEmpty()) {
609 fClip.clipShape(globalToDevice, Shape{}, op);
610 } else if (globalRgn.isRect()) {
611 // TODO: Region clips are non-AA so this should match non-AA onClipRect(), but we use a
612 // different transform so can't just call that instead.
613 fClip.clipShape(globalToDevice, Shape{SkRect::Make(globalRgn.getBounds())}, op);
614 } else {
615 // TODO: Can we just iterate the region and do non-AA rects for each chunk?
616 SkPath path;
617 globalRgn.getBoundaryPath(&path);
618 fClip.clipShape(globalToDevice, Shape{path}, op);
619 }
620}
621
622void Device::replaceClip(const SkIRect& rect) {
623 // ReplaceClip() is currently not intended to be supported in Graphite since it's only used
624 // for emulating legacy clip ops in Android Framework, and apps/devices that require that
625 // should not use Graphite. However, if it needs to be supported, we could probably implement
626 // it by:
627 // 1. Flush all pending clip element depth draws.
628 // 2. Draw a fullscreen rect to the depth attachment using a Z value greater than what's
629 // been used so far.
630 // 3. Make sure all future "unclipped" draws use this Z value instead of 0 so they aren't
631 // sorted before the depth reset.
632 // 4. Make sure all prior elements are inactive so they can't affect subsequent draws.
633 //
634 // For now, just ignore it.
635}
636
637///////////////////////////////////////////////////////////////////////////////
638
639void Device::drawPaint(const SkPaint& paint) {
641 // We never want to do a fullscreen clear on a fully-lazy render target, because the device size
642 // may be smaller than the final surface we draw to, in which case we don't want to fill the
643 // entire final surface.
644 if (this->isClipWideOpen() && !fDC->target()->isFullyLazy()) {
645 if (!paint_depends_on_dst(paint)) {
646 if (std::optional<SkColor4f> color = extract_paint_color(paint, fDC->colorInfo())) {
647 // do fullscreen clear
648 fDC->clear(*color);
649 return;
650 } else {
651 // This paint does not depend on the destination and covers the entire surface, so
652 // discard everything previously recorded and proceed with the draw.
653 fDC->discard();
654 }
655 }
656 }
657
658 const Transform& localToDevice = this->localToDeviceTransform();
659 if (!localToDevice.valid()) {
660 // TBD: This matches legacy behavior for drawPaint() that requires local coords, although
661 // v1 handles arbitrary transforms when the paint is solid color because it just fills the
662 // device bounds directly. In the new world it might be nice to have non-invertible
663 // transforms formalized (i.e. no drawing ever, handled at SkCanvas level possibly?)
664 return;
665 }
666 Rect localCoveringBounds = localToDevice.inverseMapRect(fClip.conservativeBounds());
667 this->drawGeometry(localToDevice,
668 Geometry(Shape(localCoveringBounds)),
669 paint,
670 DefaultFillStyle(),
671 DrawFlags::kIgnorePathEffect);
672}
673
674void Device::drawRect(const SkRect& r, const SkPaint& paint) {
675 this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(r)),
677}
678
679void Device::drawVertices(const SkVertices* vertices, sk_sp<SkBlender> blender,
680 const SkPaint& paint, bool skipColorXform) {
681 // TODO - Add GPU handling of skipColorXform once Graphite has its color system more fleshed out.
682 this->drawGeometry(this->localToDeviceTransform(),
683 Geometry(sk_ref_sp(vertices)),
684 paint,
685 DefaultFillStyle(),
686 DrawFlags::kIgnorePathEffect,
687 std::move(blender),
688 skipColorXform);
689}
690
691bool Device::drawAsTiledImageRect(SkCanvas* canvas,
692 const SkImage* image,
693 const SkRect* src,
694 const SkRect& dst,
696 const SkPaint& paint,
697 SkCanvas::SrcRectConstraint constraint) {
698 auto recorder = canvas->recorder();
699 if (!recorder) {
700 return false;
701 }
702 SkASSERT(src);
703
704 // For Graphite this is a pretty loose heuristic. The Recorder-local cache size (relative
705 // to the large image's size) is used as a proxy for how conservative we should be when
706 // allocating tiles. Since the tiles will actually be owned by the client (via an
707 // ImageProvider) they won't actually add any memory pressure directly to Graphite.
708 size_t cacheSize = recorder->priv().getResourceCacheLimit();
709 size_t maxTextureSize = recorder->priv().caps()->maxTextureSize();
710
711#if defined(GRAPHITE_TEST_UTILS)
712 if (gOverrideMaxTextureSizeGraphite) {
713 maxTextureSize = gOverrideMaxTextureSizeGraphite;
714 }
715 gNumTilesDrawnGraphite.store(0, std::memory_order_relaxed);
716#endif
717
718 [[maybe_unused]] auto [wasTiled, numTiles] =
720 image,
721 *src,
722 dst,
724 sampling,
725 &paint,
726 constraint,
727 cacheSize,
728 maxTextureSize);
729#if defined(GRAPHITE_TEST_UTILS)
730 gNumTilesDrawnGraphite.store(numTiles, std::memory_order_relaxed);
731#endif
732 return wasTiled;
733}
734
735void Device::drawOval(const SkRect& oval, const SkPaint& paint) {
736 if (paint.getPathEffect()) {
737 // Dashing requires that the oval path starts on the right side and travels clockwise. This
738 // is the default for the SkPath::Oval constructor, as used by SkBitmapDevice.
739 this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(SkPath::Oval(oval))),
741 } else {
742 // TODO: This has wasted effort from the SkCanvas level since it instead converts rrects
743 // that happen to be ovals into this, only for us to go right back to rrect.
744 this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(SkRRect::MakeOval(oval))),
746 }
747}
748
749void Device::drawRRect(const SkRRect& rr, const SkPaint& paint) {
750 this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(rr)),
752}
753
754void Device::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
755 // Alternatively, we could move this analysis to SkCanvas. Also, we could consider applying the
756 // path effect, being careful about starting point and direction.
757 if (!paint.getPathEffect() && !path.isInverseFillType()) {
758 if (SkRect oval; path.isOval(&oval)) {
759 this->drawGeometry(this->localToDeviceTransform(),
761 paint,
763 return;
764 }
765 if (SkRRect rrect; path.isRRect(&rrect)) {
766 this->drawGeometry(this->localToDeviceTransform(),
768 paint,
770 return;
771 }
772 if (SkRect rect; paint.getStyle() == SkPaint::kFill_Style && path.isRect(&rect)) {
773 this->drawGeometry(this->localToDeviceTransform(),
775 paint,
777 return;
778 }
779 }
780 this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(path)),
782}
783
784void Device::drawPoints(SkCanvas::PointMode mode, size_t count,
785 const SkPoint* points, const SkPaint& paint) {
787 size_t next = 0;
789 // Treat kPoints mode as stroking zero-length path segments, which produce caps so that
790 // both hairlines and round vs. square geometry are handled entirely on the GPU.
791 // TODO: SkCanvas should probably do the butt to square cap correction.
792 if (paint.getStrokeCap() == SkPaint::kButt_Cap) {
793 stroke.setStrokeParams(SkPaint::kSquare_Cap,
794 paint.getStrokeJoin(),
795 paint.getStrokeMiter());
796 }
797 } else {
798 next = 1;
799 count--;
800 }
801
802 size_t inc = mode == SkCanvas::kLines_PointMode ? 2 : 1;
803 for (size_t i = 0; i < count; i += inc) {
804 this->drawGeometry(this->localToDeviceTransform(),
805 Geometry(Shape(points[i], points[i + next])),
806 paint, stroke);
807 }
808}
809
810void Device::drawEdgeAAQuad(const SkRect& rect,
811 const SkPoint clip[4],
812 SkCanvas::QuadAAFlags aaFlags,
813 const SkColor4f& color,
815 SkPaint solidColorPaint;
816 solidColorPaint.setColor4f(color, /*colorSpace=*/nullptr);
817 solidColorPaint.setBlendMode(mode);
818
819 auto flags = SkEnumBitMask<EdgeAAQuad::Flags>(static_cast<EdgeAAQuad::Flags>(aaFlags));
821 this->drawGeometry(this->localToDeviceTransform(),
822 Geometry(quad),
823 solidColorPaint,
824 DefaultFillStyle(),
825 DrawFlags::kIgnorePathEffect);
826}
827
828void Device::drawEdgeAAImageSet(const SkCanvas::ImageSetEntry set[], int count,
829 const SkPoint dstClips[], const SkMatrix preViewMatrices[],
831 SkCanvas::SrcRectConstraint constraint) {
832 SkASSERT(count > 0);
833
834 SkPaint paintWithShader(paint);
835 int dstClipIndex = 0;
836 for (int i = 0; i < count; ++i) {
837 // If the entry is clipped by 'dstClips', that must be provided
838 SkASSERT(!set[i].fHasClip || dstClips);
839 // Similarly, if it has an extra transform, those must be provided
840 SkASSERT(set[i].fMatrixIndex < 0 || preViewMatrices);
841
842 auto [ imageToDraw, newSampling ] =
843 skgpu::graphite::GetGraphiteBacked(this->recorder(), set[i].fImage.get(), sampling);
844 if (!imageToDraw) {
845 SKGPU_LOG_W("Device::drawImageRect: Creation of Graphite-backed image failed");
846 return;
847 }
848
849 // TODO: Produce an image shading paint key and data directly without having to reconstruct
850 // the equivalent SkPaint for each entry. Reuse the key and data between entries if possible
851 paintWithShader.setShader(paint.refShader());
852 paintWithShader.setAlphaf(paint.getAlphaf() * set[i].fAlpha);
854 imageToDraw.get(), newSampling, set[i].fSrcRect, set[i].fDstRect,
856 &paintWithShader);
857 if (dst.isEmpty()) {
858 return;
859 }
860
861 auto flags =
863 EdgeAAQuad quad = set[i].fHasClip ? EdgeAAQuad(dstClips + dstClipIndex, flags)
864 : EdgeAAQuad(dst, flags);
865
866 // TODO: Calling drawGeometry() for each entry re-evaluates the clip stack every time, which
867 // is consistent with Ganesh's behavior. It also matches the behavior if edge-AA images were
868 // submitted one at a time by SkiaRenderer (a nice client simplification). However, we
869 // should explore the performance trade off with doing one bulk evaluation for the whole set
870 if (set[i].fMatrixIndex < 0) {
871 this->drawGeometry(this->localToDeviceTransform(),
872 Geometry(quad),
873 paintWithShader,
874 DefaultFillStyle(),
875 DrawFlags::kIgnorePathEffect);
876 } else {
877 SkM44 xtraTransform(preViewMatrices[set[i].fMatrixIndex]);
878 this->drawGeometry(this->localToDeviceTransform().concat(xtraTransform),
879 Geometry(quad),
880 paintWithShader,
881 DefaultFillStyle(),
882 DrawFlags::kIgnorePathEffect);
883 }
884
885 dstClipIndex += 4 * set[i].fHasClip;
886 }
887}
888
889void Device::drawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst,
891 SkCanvas::SrcRectConstraint constraint) {
894 dst,
895 /*alpha=*/1.f,
897 this->drawEdgeAAImageSet(&single, 1, nullptr, nullptr, sampling, paint, constraint);
898}
899
900sktext::gpu::AtlasDrawDelegate Device::atlasDelegate() {
901 return [&](const sktext::gpu::AtlasSubRun* subRun,
902 SkPoint drawOrigin,
903 const SkPaint& paint,
904 sk_sp<SkRefCnt> subRunStorage,
905 sktext::gpu::RendererData rendererData) {
906 this->drawAtlasSubRun(subRun, drawOrigin, paint, subRunStorage, rendererData);
907 };
908}
909
910void Device::onDrawGlyphRunList(SkCanvas* canvas,
911 const sktext::GlyphRunList& glyphRunList,
912 const SkPaint& paint) {
914 fRecorder->priv().textBlobCache()->drawGlyphRunList(canvas,
915 this->localToDevice(),
916 glyphRunList,
917 paint,
918 this->strikeDeviceInfo(),
919 this->atlasDelegate());
920}
921
922void Device::drawAtlasSubRun(const sktext::gpu::AtlasSubRun* subRun,
923 SkPoint drawOrigin,
924 const SkPaint& paint,
925 sk_sp<SkRefCnt> subRunStorage,
926 sktext::gpu::RendererData rendererData) {
928
929 const int subRunEnd = subRun->glyphCount();
930 auto regenerateDelegate = [&](sktext::gpu::GlyphVector* glyphs,
931 int begin,
932 int end,
933 skgpu::MaskFormat maskFormat,
934 int padding) {
935 return glyphs->regenerateAtlasForGraphite(begin, end, maskFormat, padding, fRecorder);
936 };
937 for (int subRunCursor = 0; subRunCursor < subRunEnd;) {
938 // For the remainder of the run, add any atlas uploads to the Recorder's TextAtlasManager
939 auto[ok, glyphsRegenerated] = subRun->regenerateAtlas(subRunCursor, subRunEnd,
940 regenerateDelegate);
941 // There was a problem allocating the glyph in the atlas. Bail.
942 if (!ok) {
943 return;
944 }
945 if (glyphsRegenerated) {
946 auto [bounds, localToDevice] = subRun->vertexFiller().boundsAndDeviceMatrix(
947 this->localToDeviceTransform(), drawOrigin);
948 SkPaint subRunPaint = paint;
949 // For color emoji, only the paint alpha affects the final color
950 if (subRun->maskFormat() == skgpu::MaskFormat::kARGB) {
951 subRunPaint.setColor(SK_ColorWHITE);
952 subRunPaint.setAlphaf(paint.getAlphaf());
953 }
954
955 bool useGammaCorrectDistanceTable =
956 this->imageInfo().colorSpace() &&
957 this->imageInfo().colorSpace()->gammaIsLinear();
958 this->drawGeometry(localToDevice,
959 Geometry(SubRunData(subRun,
960 subRunStorage,
961 bounds,
962 this->localToDeviceTransform().inverse(),
963 subRunCursor,
964 glyphsRegenerated,
966 useGammaCorrectDistanceTable,
967 this->surfaceProps().pixelGeometry(),
968 fRecorder,
969 rendererData)),
970 subRunPaint,
971 DefaultFillStyle(),
972 DrawFlags::kIgnorePathEffect);
973 }
974 subRunCursor += glyphsRegenerated;
975
976 if (subRunCursor < subRunEnd) {
977 // Flush if not all the glyphs are handled because the atlas is out of space.
978 // We flush every Device because the glyphs that are being flushed/referenced are not
979 // necessarily specific to this Device. This addresses both multiple SkSurfaces within
980 // a Recorder, and nested layers.
981 TRACE_EVENT_INSTANT0("skia.gpu", "Glyph atlas full", TRACE_EVENT_SCOPE_NAME_THREAD);
982 fRecorder->priv().flushTrackedDevices();
983 }
984 }
985}
986
987void Device::drawGeometry(const Transform& localToDevice,
988 const Geometry& geometry,
989 const SkPaint& paint,
990 const SkStrokeRec& style,
992 sk_sp<SkBlender> primitiveBlender,
993 bool skipColorXform) {
995
996 if (!localToDevice.valid()) {
997 // If the transform is not invertible or not finite then drawing isn't well defined.
998 SKGPU_LOG_W("Skipping draw with non-invertible/non-finite transform.");
999 return;
1000 }
1001
1002 // Heavy weight paint options like path effects, mask filters, and stroke-and-fill style are
1003 // applied on the CPU by generating a new shape and recursing on drawGeometry with updated flags
1004 if (!(flags & DrawFlags::kIgnorePathEffect) && paint.getPathEffect()) {
1005 // Apply the path effect before anything else, which if we are applying here, means that we
1006 // are dealing with a Shape. drawVertices (and a SkVertices geometry) should pass in
1007 // kIgnorePathEffect per SkCanvas spec. Text geometry also should pass in kIgnorePathEffect
1008 // because the path effect is applied per glyph by the SkStrikeSpec already.
1009 SkASSERT(geometry.isShape());
1010
1011 // TODO: If asADash() returns true and the base path matches the dashing fast path, then
1012 // that should be detected now as well. Maybe add dashPath to Device so canvas can handle it
1013 SkStrokeRec newStyle = style;
1014 float maxScaleFactor = localToDevice.maxScaleFactor();
1015 if (localToDevice.type() == Transform::Type::kPerspective) {
1016 auto bounds = geometry.bounds();
1017 float tl = std::get<1>(localToDevice.scaleFactors({bounds.left(), bounds.top()}));
1018 float tr = std::get<1>(localToDevice.scaleFactors({bounds.right(), bounds.top()}));
1019 float br = std::get<1>(localToDevice.scaleFactors({bounds.right(), bounds.bot()}));
1020 float bl = std::get<1>(localToDevice.scaleFactors({bounds.left(), bounds.bot()}));
1021 maxScaleFactor = std::max(std::max(tl, tr), std::max(bl, br));
1022 }
1023 newStyle.setResScale(maxScaleFactor);
1024 SkPath dst;
1025 if (paint.getPathEffect()->filterPath(&dst, geometry.shape().asPath(), &newStyle,
1026 nullptr, localToDevice)) {
1027 dst.setIsVolatile(true);
1028 // Recurse using the path and new style, while disabling downstream path effect handling
1029 this->drawGeometry(localToDevice, Geometry(Shape(dst)), paint, newStyle,
1030 flags | DrawFlags::kIgnorePathEffect, std::move(primitiveBlender),
1031 skipColorXform);
1032 return;
1033 } else {
1034 SKGPU_LOG_W("Path effect failed to apply, drawing original path.");
1035 this->drawGeometry(localToDevice, geometry, paint, style,
1036 flags | DrawFlags::kIgnorePathEffect, std::move(primitiveBlender),
1037 skipColorXform);
1038 return;
1039 }
1040 }
1041
1042 // TODO: The tessellating and atlas path renderers haven't implemented perspective yet, so
1043 // transform to device space so we draw something approximately correct (barring local coord
1044 // issues).
1045 if (geometry.isShape() && localToDevice.type() == Transform::Type::kPerspective &&
1046 !is_simple_shape(geometry.shape(), style.getStyle())) {
1047 SkPath devicePath = geometry.shape().asPath();
1048 devicePath.transform(localToDevice.matrix().asM33());
1049 devicePath.setIsVolatile(true);
1050 this->drawGeometry(Transform::Identity(), Geometry(Shape(devicePath)), paint, style, flags,
1051 std::move(primitiveBlender), skipColorXform);
1052 return;
1053 }
1054
1055 // TODO: Manually snap pixels for rects, rrects, and lines if paint is non-AA (ideally also
1056 // consider snapping stroke width and/or adjusting geometry for hairlines). This pixel snapping
1057 // math should be consistent with how non-AA clip [r]rects are handled.
1058
1059 // If we got here, then path effects should have been handled and the style should be fill or
1060 // stroke/hairline. Stroke-and-fill is not handled by DrawContext, but is emulated here by
1061 // drawing twice--one stroke and one fill--using the same depth value.
1062 SkASSERT(!SkToBool(paint.getPathEffect()) || (flags & DrawFlags::kIgnorePathEffect));
1063
1064 // TODO: Some renderer decisions could depend on the clip (see PathAtlas::addShape for
1065 // one workaround) so we should figure out how to remove this circular dependency.
1066
1067 // We assume that we will receive a renderer, or a PathAtlas. If it's a PathAtlas,
1068 // then we assume that the renderer chosen in PathAtlas::addShape() will have
1069 // single-channel coverage, require AA bounds outsetting, and have a single renderStep.
1070 auto [renderer, pathAtlas] =
1071 this->chooseRenderer(localToDevice, geometry, style, /*requireMSAA=*/false);
1072 if (!renderer && !pathAtlas) {
1073 SKGPU_LOG_W("Skipping draw with no supported renderer or PathAtlas.");
1074 return;
1075 }
1076
1077 // Calculate the clipped bounds of the draw and determine the clip elements that affect the
1078 // draw without updating the clip stack.
1079 const bool outsetBoundsForAA = renderer ? renderer->outsetBoundsForAA() : true;
1080 ClipStack::ElementList clipElements;
1081 const Clip clip =
1082 fClip.visitClipStackForDraw(localToDevice, geometry, style, outsetBoundsForAA,
1083 &clipElements);
1084 if (clip.isClippedOut()) {
1085 // Clipped out, so don't record anything.
1086 return;
1087 }
1088
1089 // Figure out what dst color requirements we have, if any.
1091 const SkBlenderBase* blender = as_BB(paint.getBlender());
1092 const std::optional<SkBlendMode> blendMode = blender ? blender->asBlendMode()
1094 const Coverage rendererCoverage = renderer ? renderer->coverage()
1095 : Coverage::kSingleChannel;
1096 dstReadReq = GetDstReadRequirement(recorder()->priv().caps(), blendMode, rendererCoverage);
1097
1098 // When using a tessellating path renderer a stroke-and-fill is rendered using two draws. When
1099 // drawing from an atlas we issue a single draw as the atlas mask covers both styles.
1100 SkStrokeRec::Style styleType = style.getStyle();
1101 const int numNewRenderSteps =
1102 renderer ? renderer->numRenderSteps() : 1 +
1103 (!pathAtlas && (styleType == SkStrokeRec::kStrokeAndFill_Style)
1104 ? fRecorder->priv().rendererProvider()->tessellatedStrokes()->numRenderSteps()
1105 : 0);
1106
1107 // Decide if we have any reason to flush pending work. We want to flush before updating the clip
1108 // state or making any permanent changes to a path atlas, since otherwise clip operations and/or
1109 // atlas entries for the current draw will be flushed.
1110 const bool needsFlush = this->needsFlushBeforeDraw(numNewRenderSteps, dstReadReq);
1111 if (needsFlush) {
1112 if (pathAtlas != nullptr) {
1113 // We need to flush work for all devices associated with the current Recorder.
1114 // Otherwise we may end up with outstanding draws that depend on past atlas state.
1115 fRecorder->priv().flushTrackedDevices();
1116 } else {
1117 this->flushPendingWorkToRecorder();
1118 }
1119 }
1120
1121 // If an atlas path renderer was chosen we need to insert the shape into the atlas and schedule
1122 // it to be drawn.
1123 std::optional<PathAtlas::MaskAndOrigin> atlasMask; // only used if `pathAtlas != nullptr`
1124 if (pathAtlas != nullptr) {
1125 std::tie(renderer, atlasMask) = pathAtlas->addShape(clip.transformedShapeBounds(),
1126 geometry.shape(),
1127 localToDevice,
1128 style);
1129
1130 // If there was no space in the atlas and we haven't flushed already, then flush pending
1131 // work to clear up space in the atlas. If we had already flushed once (which would have
1132 // cleared the atlas) then the atlas is too small for this shape.
1133 if (!atlasMask && !needsFlush) {
1134 // We need to flush work for all devices associated with the current Recorder.
1135 // Otherwise we may end up with outstanding draws that depend on past atlas state.
1136 fRecorder->priv().flushTrackedDevices();
1137
1138 // Try inserting the shape again.
1139 std::tie(renderer, atlasMask) = pathAtlas->addShape(clip.transformedShapeBounds(),
1140 geometry.shape(),
1141 localToDevice,
1142 style);
1143 }
1144
1145 if (!atlasMask) {
1146 SKGPU_LOG_E("Failed to add shape to atlas!");
1147 // TODO(b/285195175): This can happen if the atlas is not large enough or a compatible
1148 // atlas texture cannot be created. Handle the first case in `chooseRenderer` and make
1149 // sure that the atlas path renderer is not chosen if the path is larger than the atlas
1150 // texture.
1151 return;
1152 }
1153 // Since addShape() was successful we should have a valid Renderer now.
1155 }
1156
1157 // Update the clip stack after issuing a flush (if it was needed). A draw will be recorded after
1158 // this point.
1159 DrawOrder order(fCurrentDepth.next());
1160 CompressedPaintersOrder clipOrder = fClip.updateClipStateForDraw(
1161 clip, clipElements, fColorDepthBoundsManager.get(), order.depth());
1162
1163#if defined(SK_DEBUG)
1164 // Renderers and their component RenderSteps have flexibility in defining their
1165 // DepthStencilSettings. However, the clipping and ordering managed between Device and ClipStack
1166 // requires that only GREATER or GEQUAL depth tests are used for draws recorded through the
1167 // client-facing, painters-order-oriented API. We assert here vs. in Renderer's constructor to
1168 // allow internal-oriented Renderers that are never selected for a "regular" draw call to have
1169 // more flexibility in their settings.
1170 for (const RenderStep* step : renderer->steps()) {
1171 auto dss = step->depthStencilSettings();
1172 SkASSERT((!step->performsShading() || dss.fDepthTestEnabled) &&
1173 (!dss.fDepthTestEnabled ||
1174 dss.fDepthCompareOp == CompareOp::kGreater ||
1175 dss.fDepthCompareOp == CompareOp::kGEqual));
1176 }
1177#endif
1178
1179 // A draw's order always depends on the clips that must be drawn before it
1180 order.dependsOnPaintersOrder(clipOrder);
1181
1182 // A primitive blender should be ignored if there is no primitive color to blend against.
1183 // Additionally, if a renderer emits a primitive color, then a null primitive blender should
1184 // be interpreted as SrcOver blending mode.
1185 if (!renderer->emitsPrimitiveColor()) {
1186 primitiveBlender = nullptr;
1187 } else if (!SkToBool(primitiveBlender)) {
1188 primitiveBlender = SkBlender::Mode(SkBlendMode::kSrcOver);
1189 }
1190
1191 // If a draw is not opaque, it must be drawn after the most recent draw it intersects with in
1192 // order to blend correctly. We always query the most recent draw (even when opaque) because it
1193 // also lets Device easily track whether or not there are any overlapping draws.
1194 PaintParams shading{paint,
1195 std::move(primitiveBlender),
1196 sk_ref_sp(clip.shader()),
1197 dstReadReq,
1198 skipColorXform};
1199 const bool dependsOnDst = rendererCoverage != Coverage::kNone || paint_depends_on_dst(shading);
1200 if (dependsOnDst) {
1201 CompressedPaintersOrder prevDraw =
1202 fColorDepthBoundsManager->getMostRecentDraw(clip.drawBounds());
1203 order.dependsOnPaintersOrder(prevDraw);
1204 }
1205
1206 // Now that the base paint order and draw bounds are finalized, if the Renderer relies on the
1207 // stencil attachment, we compute a secondary sorting field to allow disjoint draws to reorder
1208 // the RenderSteps across draws instead of in sequence for each draw.
1209 if (renderer->depthStencilFlags() & DepthStencilFlags::kStencil) {
1210 DisjointStencilIndex setIndex = fDisjointStencilSet->add(order.paintOrder(),
1211 clip.drawBounds());
1212 order.dependsOnStencil(setIndex);
1213 }
1214
1215 // TODO(b/330864257): This is an extra traversal of all paint effects, that can be avoided when
1216 // the paint key itself is determined inside this function.
1217 shading.notifyImagesInUse(fRecorder, fDC.get());
1218
1219 // If an atlas path renderer was chosen, then record a single CoverageMaskShape draw.
1220 // The shape will be scheduled to be rendered or uploaded into the atlas during the
1221 // next invocation of flushPendingWorkToRecorder().
1222 if (pathAtlas != nullptr) {
1223 // Record the draw as a fill since stroking is handled by the atlas render/upload.
1224 SkASSERT(atlasMask.has_value());
1225 auto [mask, origin] = *atlasMask;
1226 fDC->recordDraw(renderer, Transform::Translate(origin.fX, origin.fY), Geometry(mask),
1227 clip, order, &shading, nullptr);
1228 } else {
1229 if (styleType == SkStrokeRec::kStroke_Style ||
1230 styleType == SkStrokeRec::kHairline_Style ||
1231 styleType == SkStrokeRec::kStrokeAndFill_Style) {
1232 // For stroke-and-fill, 'renderer' is used for the fill and we always use the
1233 // TessellatedStrokes renderer; for stroke and hairline, 'renderer' is used.
1234 StrokeStyle stroke(style.getWidth(), style.getMiter(), style.getJoin(), style.getCap());
1235 fDC->recordDraw(styleType == SkStrokeRec::kStrokeAndFill_Style
1236 ? fRecorder->priv().rendererProvider()->tessellatedStrokes()
1237 : renderer,
1238 localToDevice, geometry, clip, order, &shading, &stroke);
1239 }
1240 if (styleType == SkStrokeRec::kFill_Style ||
1241 styleType == SkStrokeRec::kStrokeAndFill_Style) {
1242 fDC->recordDraw(renderer, localToDevice, geometry, clip, order, &shading, nullptr);
1243 }
1244 }
1245
1246 // TODO: If 'fullyOpaque' is true, it might be useful to store the draw bounds and Z in a
1247 // special occluders list for filtering the DrawList/DrawPass when flushing.
1248 // const bool fullyOpaque = !dependsOnDst &&
1249 // clipOrder == DrawOrder::kNoIntersection &&
1250 // shape.isRect() &&
1251 // localToDevice.type() <= Transform::Type::kRectStaysRect;
1252
1253 // Post-draw book keeping (bounds manager, depth tracking, etc.)
1254 fColorDepthBoundsManager->recordDraw(clip.drawBounds(), order.paintOrder());
1255 fCurrentDepth = order.depth();
1256
1257 // TODO(b/238758897): When we enable layer elision that depends on draws not overlapping, we
1258 // can use the `getMostRecentDraw()` query to determine that, although that will mean querying
1259 // even if the draw does not depend on dst (so should be only be used when the Device is an
1260 // elision candidate).
1261}
1262
1263void Device::drawClipShape(const Transform& localToDevice,
1264 const Shape& shape,
1265 const Clip& clip,
1266 DrawOrder order) {
1267 // A clip draw's state is almost fully defined by the ClipStack. The only thing we need
1268 // to account for is selecting a Renderer and tracking the stencil buffer usage.
1269 Geometry geometry{shape};
1270 auto [renderer, pathAtlas] = this->chooseRenderer(localToDevice,
1271 geometry,
1272 DefaultFillStyle(),
1273 /*requireMSAA=*/true);
1274 if (!renderer) {
1275 SKGPU_LOG_W("Skipping clip with no supported path renderer.");
1276 return;
1277 } else if (renderer->depthStencilFlags() & DepthStencilFlags::kStencil) {
1278 DisjointStencilIndex setIndex = fDisjointStencilSet->add(order.paintOrder(),
1279 clip.drawBounds());
1280 order.dependsOnStencil(setIndex);
1281 }
1282
1283 // This call represents one of the deferred clip shapes that's already pessimistically counted
1284 // in needsFlushBeforeDraw(), so the DrawContext should have room to add it.
1285 SkASSERT(fDC->pendingRenderSteps() + renderer->numRenderSteps() < DrawList::kMaxRenderSteps);
1286
1287 // Anti-aliased clipping requires the renderer to use MSAA to modify the depth per sample, so
1288 // analytic coverage renderers cannot be used.
1289 SkASSERT(renderer->coverage() == Coverage::kNone && renderer->requiresMSAA());
1290 SkASSERT(pathAtlas == nullptr);
1291
1292 // Clips draws are depth-only (null PaintParams), and filled (null StrokeStyle).
1293 // TODO: Remove this CPU-transform once perspective is supported for all path renderers
1294 if (localToDevice.type() == Transform::Type::kPerspective) {
1295 SkPath devicePath = geometry.shape().asPath();
1296 devicePath.transform(localToDevice.matrix().asM33());
1297 fDC->recordDraw(renderer, Transform::Identity(), Geometry(Shape(devicePath)), clip, order,
1298 nullptr, nullptr);
1299 } else {
1300 fDC->recordDraw(renderer, localToDevice, geometry, clip, order, nullptr, nullptr);
1301 }
1302 // This ensures that draws recorded after this clip shape has been popped off the stack will
1303 // be unaffected by the Z value the clip shape wrote to the depth attachment.
1304 if (order.depth() > fCurrentDepth) {
1305 fCurrentDepth = order.depth();
1306 }
1307}
1308
1309// TODO: Currently all Renderers are always defined, but with config options and caps that may not
1310// be the case, in which case chooseRenderer() will have to go through compatible choices.
1311std::pair<const Renderer*, PathAtlas*> Device::chooseRenderer(const Transform& localToDevice,
1312 const Geometry& geometry,
1313 const SkStrokeRec& style,
1314 bool requireMSAA) const {
1315 const RendererProvider* renderers = fRecorder->priv().rendererProvider();
1316 SkASSERT(renderers);
1318
1319 if (geometry.isSubRun()) {
1320 SkASSERT(!requireMSAA);
1321 sktext::gpu::RendererData rendererData = geometry.subRunData().rendererData();
1322 if (!rendererData.isSDF) {
1323 return {renderers->bitmapText(rendererData.isLCD), nullptr};
1324 }
1325 // Even though the SkPaint can request subpixel rendering, we still need to match
1326 // this with the pixel geometry.
1327 bool useLCD = rendererData.isLCD &&
1328 geometry.subRunData().pixelGeometry() != kUnknown_SkPixelGeometry;
1329 return {renderers->sdfText(useLCD), nullptr};
1330 } else if (geometry.isVertices()) {
1331 SkVerticesPriv info(geometry.vertices()->priv());
1332 return {renderers->vertices(info.mode(), info.hasColors(), info.hasTexCoords()), nullptr};
1333 } else if (geometry.isCoverageMaskShape()) {
1334 // drawCoverageMask() passes in CoverageMaskShapes that reference a provided texture.
1335 // The CoverageMask renderer can also be chosen later on if the shape is assigned to
1336 // to be rendered into the PathAtlas, in which case the 2nd return value is non-null.
1337 return {renderers->coverageMask(), nullptr};
1338 } else if (geometry.isEdgeAAQuad()) {
1339 SkASSERT(!requireMSAA && style.isFillStyle());
1340 // handled by specialized system, simplified from rects and round rects
1341 return {renderers->perEdgeAAQuad(), nullptr};
1342 } else if (geometry.isAnalyticBlur()) {
1343 return {renderers->analyticBlur(), nullptr};
1344 } else if (!geometry.isShape()) {
1345 // We must account for new Geometry types with specific Renderers
1346 return {nullptr, nullptr};
1347 }
1348
1349 const Shape& shape = geometry.shape();
1350 // We can't use this renderer if we require MSAA for an effect (i.e. clipping or stroke+fill).
1351 if (!requireMSAA && is_simple_shape(shape, type)) {
1352 return {renderers->analyticRRect(), nullptr};
1353 }
1354
1355 // Path rendering options. For now the strategy is very simple and not optimal:
1356 // I. Use tessellation if MSAA is required for an effect.
1357 // II: otherwise:
1358 // 1. Always use compute AA if supported unless it was excluded by ContextOptions or the
1359 // compute renderer cannot render the shape efficiently yet (based on the result of
1360 // `isSuitableForAtlasing`).
1361 // 2. Fall back to CPU raster AA if hardware MSAA is disabled or it was explicitly requested
1362 // via ContextOptions.
1363 // 3. Otherwise use tessellation.
1364#if defined(GRAPHITE_TEST_UTILS)
1365 PathRendererStrategy strategy = fRecorder->priv().caps()->requestedPathRendererStrategy();
1366#else
1368#endif
1369
1370 PathAtlas* pathAtlas = nullptr;
1371 AtlasProvider* atlasProvider = fRecorder->priv().atlasProvider();
1372
1373 // Prefer compute atlas draws if supported. This currently implicitly filters out clip draws as
1374 // they require MSAA. Eventually we may want to route clip shapes to the atlas as well but not
1375 // if hardware MSAA is required.
1376 std::optional<Rect> drawBounds;
1377 if (atlasProvider->isAvailable(AtlasProvider::PathAtlasFlags::kCompute) &&
1378 use_compute_atlas_when_available(strategy)) {
1379 PathAtlas* atlas = fDC->getComputePathAtlas(fRecorder);
1380 SkASSERT(atlas);
1381
1382 // Don't use the compute renderer if it can't handle the shape efficiently.
1383 //
1384 // Use the conservative clip bounds for a rough estimate of the mask size (this avoids
1385 // having to evaluate the entire clip stack before choosing the renderer as it will have to
1386 // get evaluated again if we fall back to a different renderer).
1387 drawBounds = localToDevice.mapRect(shape.bounds());
1388 if (atlas->isSuitableForAtlasing(*drawBounds, fClip.conservativeBounds())) {
1389 pathAtlas = atlas;
1390 }
1391 }
1392
1393 // Fall back to CPU rendered paths when multisampling is disabled and the compute atlas is not
1394 // available.
1395 // TODO: enable other uses of the software path renderer
1396 if (!pathAtlas && atlasProvider->isAvailable(AtlasProvider::PathAtlasFlags::kRaster) &&
1397 (strategy == PathRendererStrategy::kRasterAA ||
1398 (strategy == PathRendererStrategy::kDefault && !fMSAASupported))) {
1399 // NOTE: RasterPathAtlas doesn't implement `PathAtlas::isSuitableForAtlasing` as it doesn't
1400 // reject paths (unlike ComputePathAtlas).
1401 pathAtlas = atlasProvider->getRasterPathAtlas();
1402 }
1403
1404 if (!requireMSAA && pathAtlas) {
1405 // If we got here it means that we should draw with an atlas renderer if we can and avoid
1406 // resorting to one of the tessellating techniques.
1407 return {nullptr, pathAtlas};
1408 }
1409
1410 // If we got here, it requires tessellated path rendering or an MSAA technique applied to a
1411 // simple shape (so we interpret them as paths to reduce the number of pipelines we need).
1412
1413 // TODO: All shapes that select a tessellating path renderer need to be "pre-chopped" if they
1414 // are large enough to exceed the fixed count tessellation limits. Fills are pre-chopped to the
1415 // viewport bounds, strokes and stroke-and-fills are pre-chopped to the viewport bounds outset
1416 // by the stroke radius (hence taking the whole style and not just its type).
1417
1420 // Unlike in Ganesh, the HW stroke tessellator can work with arbitrary paints since the
1421 // depth test prevents double-blending when there is transparency, thus we can HW stroke
1422 // any path regardless of its paint.
1423 // TODO: We treat inverse-filled strokes as regular strokes. We could handle them by
1424 // stenciling first with the HW stroke tessellator and then covering their bounds, but
1425 // inverse-filled strokes are not well-specified in our public canvas behavior so we may be
1426 // able to remove it.
1427 return {renderers->tessellatedStrokes(), nullptr};
1428 }
1429
1430 // 'type' could be kStrokeAndFill, but in that case chooseRenderer() is meant to return the
1431 // fill renderer since tessellatedStrokes() will always be used for the stroke pass.
1432 if (shape.convex() && !shape.inverted()) {
1433 // TODO: Ganesh doesn't have a curve+middle-out triangles option for convex paths, but it
1434 // would be pretty trivial to spin up.
1435 return {renderers->convexTessellatedWedges(), nullptr};
1436 } else {
1437 if (!drawBounds.has_value()) {
1438 drawBounds = localToDevice.mapRect(shape.bounds());
1439 }
1440 drawBounds->intersect(fClip.conservativeBounds());
1441 const bool preferWedges =
1442 // If the draw bounds don't intersect with the clip stack's conservative bounds,
1443 // we'll be drawing a very small area at most, accounting for coverage, so just
1444 // stick with drawing wedges in that case.
1445 drawBounds->isEmptyNegativeOrNaN() ||
1446
1447 // TODO: Combine this heuristic with what is used in PathStencilCoverOp to choose
1448 // between wedges curves consistently in Graphite and Ganesh.
1449 (shape.isPath() && shape.path().countVerbs() < 50) ||
1450 drawBounds->area() <= (256 * 256);
1451
1452 if (preferWedges) {
1453 return {renderers->stencilTessellatedWedges(shape.fillType()), nullptr};
1454 } else {
1455 return {renderers->stencilTessellatedCurvesAndTris(shape.fillType()), nullptr};
1456 }
1457 }
1458}
1459
1460sk_sp<Task> Device::lastDrawTask() const {
1461 SkASSERT(this->isScratchDevice());
1462 return fLastTask;
1463}
1464
1465void Device::flushPendingWorkToRecorder() {
1466 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
1467
1468 // If this is a scratch device being flushed, it should only be flushing into the expected
1469 // next recording from when the Device was first created.
1470 SkASSERT(fRecorder);
1471 SkASSERT(fScopedRecordingID == 0 || fScopedRecordingID == fRecorder->priv().nextRecordingID());
1472
1473 // TODO(b/330864257): flushPendingWorkToRecorder() can be recursively called if this Device
1474 // recorded a picture shader draw and during a flush (triggered by snap or automatically from
1475 // reaching limits), the picture shader will be rendered to a new device. If that picture drawn
1476 // to the temporary device fills up an atlas it can trigger the global
1477 // recorder->flushTrackedDevices(), which will then encounter this device that is already in
1478 // the midst of flushing. To avoid crashing we only actually flush the first time this is called
1479 // and set a bit to early-out on any recursive calls.
1480 // This is not an ideal solution since the temporary Device's flush-the-world may have reset
1481 // atlas entries that the current Device's flushed draws will reference. But at this stage it's
1482 // not possible to split the already recorded draws into a before-list and an after-list that
1483 // can reference the old and new contents of the atlas. While avoiding the crash, this may cause
1484 // incorrect accesses to a shared atlas. Once paint data is extracted at draw time, picture
1485 // shaders will be resolved outside of flushes and then this will be fixed automatically.
1486 if (fIsFlushing) {
1487 return;
1488 } else {
1489 fIsFlushing = true;
1490 }
1491
1492 this->internalFlush();
1493 sk_sp<Task> drawTask = fDC->snapDrawTask(fRecorder);
1494 if (this->isScratchDevice()) {
1495 // TODO(b/323887221): Once shared atlas resources are less brittle, scratch devices won't
1496 // flush to the recorder at all and will only store the snapped task here.
1497 fLastTask = drawTask;
1498 } else {
1499 // Non-scratch devices do not need to point back to the last snapped task since they are
1500 // always added to the root task list.
1501 // TODO: It is currently possible for scratch devices to be flushed and instantiated before
1502 // their work is finished, meaning they will produce additional tasks to be included in
1503 // a follow-up Recording: https://chat.google.com/room/AAAA2HlH94I/YU0XdFqX2Uw.
1504 // However, in this case they no longer appear scratch because the first Recording
1505 // instantiated the targets. When scratch devices are not actually registered with the
1506 // Recorder and are only included when they are drawn (e.g. restored), we should be able to
1507 // assert that `fLastTask` is null.
1508 fLastTask = nullptr;
1509 }
1510
1511 if (drawTask) {
1512 fRecorder->priv().add(std::move(drawTask));
1513
1514 // TODO(b/297344089): This always regenerates mipmaps on the draw target when it's drawn to.
1515 // This could be wasteful if we draw to a target multiple times before reading from it with
1516 // downscaling.
1517 if (fDC->target()->mipmapped() == Mipmapped::kYes) {
1518 if (!GenerateMipmaps(fRecorder, fDC->refTarget(), fDC->colorInfo())) {
1519 SKGPU_LOG_W("Device::flushPendingWorkToRecorder: Failed to generate mipmaps");
1520 }
1521 }
1522 }
1523
1524 fIsFlushing = false;
1525}
1526
1527void Device::internalFlush() {
1528 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
1530
1531 // Push any pending uploads from the atlas provider that pending draws reference.
1532 fRecorder->priv().atlasProvider()->recordUploads(fDC.get());
1533
1534 // Clip shapes are depth-only draws, but aren't recorded in the DrawContext until a flush in
1535 // order to determine the Z values for each element.
1536 fClip.recordDeferredClipDraws();
1537
1538 // Flush all pending items to the internal task list and reset Device tracking state
1539 fDC->flush(fRecorder);
1540
1541 fColorDepthBoundsManager->reset();
1542 fDisjointStencilSet->reset();
1543 fCurrentDepth = DrawOrder::kClearDepth;
1544
1545 // Any cleanup in the AtlasProvider
1546 fRecorder->priv().atlasProvider()->postFlush();
1547}
1548
1549bool Device::needsFlushBeforeDraw(int numNewRenderSteps, DstReadRequirement dstReadReq) const {
1550 // Must also account for the elements in the clip stack that might need to be recorded.
1551 numNewRenderSteps += fClip.maxDeferredClipDraws() * Renderer::kMaxRenderSteps;
1552 return // Need flush if we don't have room to record into the current list.
1553 (DrawList::kMaxRenderSteps - fDC->pendingRenderSteps()) < numNewRenderSteps ||
1554 // Need flush if this draw needs to copy the dst surface for reading.
1555 dstReadReq == DstReadRequirement::kTextureCopy;
1556}
1557
1558void Device::drawSpecial(SkSpecialImage* special,
1559 const SkMatrix& localToDevice,
1561 const SkPaint& paint,
1562 SkCanvas::SrcRectConstraint constraint) {
1563 SkASSERT(!paint.getMaskFilter() && !paint.getImageFilter());
1564
1565 sk_sp<SkImage> img = special->asImage();
1566 if (!img || !as_IB(img)->isGraphiteBacked()) {
1567 SKGPU_LOG_W("Couldn't get Graphite-backed special image as image");
1568 return;
1569 }
1570
1571 SkPaint paintWithShader(paint);
1573 img.get(),
1574 sampling,
1575 /*src=*/SkRect::Make(special->subset()),
1576 /*dst=*/SkRect::MakeIWH(special->width(), special->height()),
1577 /*strictSrcSubset=*/constraint == SkCanvas::kStrict_SrcRectConstraint,
1578 &paintWithShader);
1579 if (dst.isEmpty()) {
1580 return;
1581 }
1582
1583 this->drawGeometry(Transform(SkM44(localToDevice)),
1584 Geometry(Shape(dst)),
1585 paintWithShader,
1586 DefaultFillStyle(),
1587 DrawFlags::kIgnorePathEffect);
1588}
1589
1590void Device::drawCoverageMask(const SkSpecialImage* mask,
1591 const SkMatrix& localToDevice,
1593 const SkPaint& paint) {
1594 CoverageMaskShape::MaskInfo maskInfo{/*fTextureOrigin=*/{SkTo<uint16_t>(mask->subset().fLeft),
1595 SkTo<uint16_t>(mask->subset().fTop)},
1596 /*fMaskSize=*/{SkTo<uint16_t>(mask->width()),
1597 SkTo<uint16_t>(mask->height())}};
1598
1599 auto maskProxyView = AsView(mask->asImage());
1600 if (!maskProxyView) {
1601 SKGPU_LOG_W("Couldn't get Graphite-backed special image as texture proxy view");
1602 return;
1603 }
1604
1605 // Every other "Image" draw reaches the underlying texture via AddToKey/NotifyInUse, which
1606 // handles notifying the image and either flushing the linked surface or attaching draw tasks
1607 // from a scratch device to the current draw context. In this case, 'mask' is very likely to
1608 // be linked to a scratch device, but we must perform the same notifyInUse manually here because
1609 // the texture is consumed by the RenderStep and not part of the PaintParams.
1610 static_cast<Image_Base*>(mask->asImage().get())->notifyInUse(fRecorder, fDC.get());
1611
1612 // 'mask' logically has 0 coverage outside of its pixels, which is equivalent to kDecal tiling.
1613 // However, since we draw geometry tightly fitting 'mask', we can use the better-supported
1614 // kClamp tiling and behave effectively the same way.
1616
1617 // Ensure this is kept alive; normally textures are kept alive by the PipelineDataGatherer for
1618 // image shaders, or by the PathAtlas. This is a unique circumstance.
1619 // TODO: Find a cleaner way to ensure 'maskProxyView' is transferred to the final Recording.
1620 TextureDataBlock tdb;
1621 // NOTE: CoverageMaskRenderStep controls the final sampling options; this texture data block
1622 // serves only to keep the mask alive so the sampling passed to add() doesn't matter.
1623 tdb.add(maskProxyView.refProxy(), {SkFilterMode::kLinear, kClamp});
1624 fRecorder->priv().textureDataCache()->insert(tdb);
1625
1626 // CoverageMaskShape() wraps a Shape when it's used as a PathAtlas, but in this case the
1627 // original shape has been long lost, so just use a Rect that bounds the image.
1628 CoverageMaskShape maskShape{Shape{Rect::WH((float)mask->width(), (float)mask->height())},
1629 maskProxyView.proxy(),
1630 // Use the active local-to-device transform for this since it
1631 // determines the local coords for evaluating the skpaint, whereas
1632 // the provided 'localToDevice' just places the coverage mask.
1633 this->localToDeviceTransform().inverse(),
1634 maskInfo};
1635
1636 this->drawGeometry(Transform(SkM44(localToDevice)),
1637 Geometry(maskShape),
1638 paint,
1639 DefaultFillStyle(),
1640 DrawFlags::kIgnorePathEffect);
1641}
1642
1643sk_sp<SkSpecialImage> Device::makeSpecial(const SkBitmap&) {
1644 return nullptr;
1645}
1646
1647sk_sp<SkSpecialImage> Device::makeSpecial(const SkImage*) {
1648 return nullptr;
1649}
1650
1651sk_sp<SkSpecialImage> Device::snapSpecial(const SkIRect& subset, bool forceCopy) {
1652 // NOTE: snapSpecial() can be called even after the device has been marked immutable (null
1653 // recorder), but in those cases it should not be a copy and just returns the image view.
1654 sk_sp<Image> deviceImage;
1655 SkIRect finalSubset;
1656 if (forceCopy || !this->readSurfaceView() || this->readSurfaceView().proxy()->isFullyLazy()) {
1657 deviceImage = this->makeImageCopy(
1659 finalSubset = SkIRect::MakeSize(subset.size());
1660 } else {
1661 // TODO(b/323886870): For now snapSpecial() force adds the pending work to the recorder's
1662 // root task list. Once shared atlas management is solved and DrawTasks can be nested in a
1663 // graph then this can go away in favor of auto-flushing through the image's linked device.
1664 if (fRecorder) {
1665 this->flushPendingWorkToRecorder();
1666 }
1667 deviceImage = Image::WrapDevice(sk_ref_sp(this));
1668 finalSubset = subset;
1669 }
1670
1671 if (!deviceImage) {
1672 return nullptr;
1673 }
1674
1675 // For non-copying "snapSpecial", the semantics are returning an image view of the surface data,
1676 // and relying on higher-level draw and restore logic for the contents to make sense.
1678 fRecorder, finalSubset, std::move(deviceImage), this->surfaceProps());
1679}
1680
1681sk_sp<skif::Backend> Device::createImageFilteringBackend(const SkSurfaceProps& surfaceProps,
1682 SkColorType colorType) const {
1683 return skif::MakeGraphiteBackend(fRecorder, surfaceProps, colorType);
1684}
1685
1686TextureProxy* Device::target() { return fDC->target(); }
1687
1688TextureProxyView Device::readSurfaceView() const { return fDC->readSurfaceView(); }
1689
1690bool Device::isScratchDevice() const {
1691 // Scratch device status is inferred from whether or not the Device's target is instantiated.
1692 // By default devices start out un-instantiated unless they are wrapping an existing backend
1693 // texture (definitely not a scratch scenario), or Surface explicitly instantiates the target
1694 // before returning to the client (not a scratch scenario).
1695 //
1696 // Scratch device targets are instantiated during the prepareResources() phase of
1697 // Recorder::snap(). Truly scratch devices that have gone out of scope as intended will have
1698 // already been destroyed at this point. Scratch devices that become longer-lived (linked to
1699 // a client-owned object) automatically transition to non-scratch usage.
1700 return !fDC->target()->isInstantiated() && !fDC->target()->isLazy();
1701}
1702
1703sk_sp<sktext::gpu::Slug> Device::convertGlyphRunListToSlug(const sktext::GlyphRunList& glyphRunList,
1704 const SkPaint& paint) {
1705 return sktext::gpu::SlugImpl::Make(this->localToDevice(),
1706 glyphRunList,
1707 paint,
1708 this->strikeDeviceInfo(),
1710}
1711
1712void Device::drawSlug(SkCanvas* canvas, const sktext::gpu::Slug* slug, const SkPaint& paint) {
1713 auto slugImpl = static_cast<const sktext::gpu::SlugImpl*>(slug);
1714 slugImpl->subRuns()->draw(canvas, slugImpl->origin(), paint, slugImpl, this->atlasDelegate());
1715}
1716
1717bool Device::drawBlurredRRect(const SkRRect& rrect, const SkPaint& paint, float deviceSigma) {
1718 SkStrokeRec style(paint);
1719 if (skgpu::BlurIsEffectivelyIdentity(deviceSigma)) {
1720 this->drawGeometry(this->localToDeviceTransform(),
1722 paint,
1723 style);
1724 return true;
1725 }
1726
1727 std::optional<AnalyticBlurMask> analyticBlur = AnalyticBlurMask::Make(
1728 this->recorder(), this->localToDeviceTransform(), deviceSigma, rrect);
1729 if (!analyticBlur) {
1730 return false;
1731 }
1732
1733 this->drawGeometry(this->localToDeviceTransform(), Geometry(*analyticBlur), paint, style);
1734 return true;
1735}
1736
1737} // namespace skgpu::graphite
static int step(int x, SkScalar min, SkScalar max)
Definition: BlurTest.cpp:215
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
uint16_t glyphs[5]
Definition: FontMgrTest.cpp:46
SkAssertResult(font.textToGlyphs("Hello", 5, SkTextEncoding::kUTF8, glyphs, std::size(glyphs))==count)
int count
Definition: FontMgrTest.cpp:50
static const int points[]
#define SKGPU_LOG_E(fmt,...)
Definition: Log.h:38
#define SKGPU_LOG_W(fmt,...)
Definition: Log.h:40
static float next(float f)
@ kUnknown_SkAlphaType
uninitialized
Definition: SkAlphaType.h:27
#define SkASSERT(cond)
Definition: SkAssert.h:116
SkBackingFit
Definition: SkBackingFit.h:16
SkBlendMode
Definition: SkBlendMode.h:38
@ kSrcOver
r = s + (1-sa)*d
@ kClear
r = 0
SkBlenderBase * as_BB(SkBlender *blend)
Definition: SkBlenderBase.h:69
SkClipOp
Definition: SkClipOp.h:13
SkColorType
Definition: SkColorType.h:19
@ kUnknown_SkColorType
uninitialized
Definition: SkColorType.h:20
constexpr SkColor SK_ColorWHITE
Definition: SkColor.h:122
static SkColorType colorType(AImageDecoder *decoder, const AImageDecoderHeaderInfo *headerInfo)
static bool ok(int result)
SkRect SkModifyPaintAndDstForDrawImageRect(const SkImage *image, const SkSamplingOptions &, SkRect src, SkRect dst, bool strictSrcSubset, SkPaint *paint)
static SkImage_Base * as_IB(SkImage *image)
Definition: SkImage_Base.h:201
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition: SkPath.cpp:3892
sk_sp< T > sk_ref_sp(T *obj)
Definition: SkRefCnt.h:381
@ kUnknown_SkPixelGeometry
SkDEBUGCODE(SK_SPI) SkThreadID SkGetThreadID()
SkTileMode
Definition: SkTileMode.h:13
static constexpr bool SkToBool(const T &x)
Definition: SkTo.h:35
#define TRACE_EVENT_SCOPE_NAME_THREAD
#define TRACE_FUNC
Definition: SkTraceEvent.h:30
Shape
Definition: aaxfermodes.cpp:43
GLenum type
virtual std::optional< SkBlendMode > asBlendMode() const
Definition: SkBlenderBase.h:45
static sk_sp< SkBlender > Mode(SkBlendMode mode)
virtual skgpu::graphite::Recorder * recorder() const
Definition: SkCanvas.cpp:1641
SrcRectConstraint
Definition: SkCanvas.h:1541
@ kStrict_SrcRectConstraint
sample only inside bounds; slower
Definition: SkCanvas.h:1542
@ kFast_SrcRectConstraint
sample outside bounds; faster
Definition: SkCanvas.h:1543
@ kLines_PointMode
draw each pair of points as a line segment
Definition: SkCanvas.h:1242
@ kPoints_PointMode
draw each point separately
Definition: SkCanvas.h:1241
@ kAll_QuadAAFlags
Definition: SkCanvas.h:1665
bool isAlphaUnchanged() const
SkColorSpace * colorSpace() const
Definition: SkImageInfo.cpp:66
SkColorType colorType() const
Definition: SkImageInfo.h:140
void * ReadPixelsContext
Definition: SkImage.h:578
RescaleMode
Definition: SkImage.h:587
RescaleGamma
Definition: SkImage.h:585
SkIRect bounds() const
Definition: SkImage.h:303
void(ReadPixelsContext, std::unique_ptr< const AsyncReadResult >) ReadPixelsCallback
Definition: SkImage.h:583
Definition: SkM44.h:150
static SkColor ComputeLuminanceColor(const SkPaint &)
@ kButt_Cap
no stroke extension
Definition: SkPaint.h:334
@ kSquare_Cap
adds square
Definition: SkPaint.h:336
void setColor(SkColor color)
Definition: SkPaint.cpp:119
@ kStroke_Style
set to stroke geometry
Definition: SkPaint.h:194
@ kFill_Style
set to fill geometry
Definition: SkPaint.h:193
void setColor4f(const SkColor4f &color, SkColorSpace *colorSpace=nullptr)
Definition: SkPaint.h:253
void setShader(sk_sp< SkShader > shader)
void setBlendMode(SkBlendMode mode)
Definition: SkPaint.cpp:151
void setAlphaf(float a)
Definition: SkPaint.cpp:130
Definition: SkPath.h:59
SkPath & setIsVolatile(bool isVolatile)
Definition: SkPath.h:370
static SkPath Oval(const SkRect &, SkPathDirection=SkPathDirection::kCW)
Definition: SkPath.cpp:3590
void transform(const SkMatrix &matrix, SkPath *dst, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
Definition: SkPath.cpp:1711
static bool AllCornersCircular(const SkRRect &rr, SkScalar tolerance=SK_ScalarNearlyZero)
Definition: SkRRect.cpp:353
const SkRect & rect() const
Definition: SkRRect.h:264
static SkRRect MakeOval(const SkRect &oval)
Definition: SkRRect.h:162
bool isRect() const
Definition: SkRRect.h:84
bool getBoundaryPath(SkPath *path) const
bool isRect() const
Definition: SkRegion.h:152
const SkIRect & getBounds() const
Definition: SkRegion.h:165
bool op(const SkIRect &rect, Op op)
Definition: SkRegion.h:384
bool setRect(const SkIRect &rect)
Definition: SkRegion.cpp:192
bool isEmpty() const
Definition: SkRegion.h:146
bool setPath(const SkPath &path, const SkRegion &clip)
virtual bool isOpaque() const
Definition: SkShader.h:44
int width() const
virtual sk_sp< SkImage > asImage() const =0
int height() const
const SkIRect & subset() const
static SkStrikeCache * GlobalStrikeCache()
Style getStyle() const
Definition: SkStrokeRec.cpp:71
@ kStrokeAndFill_Style
Definition: SkStrokeRec.h:36
SkScalar getWidth() const
Definition: SkStrokeRec.h:42
SkPaint::Join getJoin() const
Definition: SkStrokeRec.h:45
SkPaint::Cap getCap() const
Definition: SkStrokeRec.h:44
void setResScale(SkScalar rs)
Definition: SkStrokeRec.h:75
bool isFillStyle() const
Definition: SkStrokeRec.h:51
SkScalar getMiter() const
Definition: SkStrokeRec.h:43
SkSurfaceProps cloneWithPixelGeometry(SkPixelGeometry newPixelGeometry) const
T * get() const
Definition: SkRefCnt.h:303
static std::tuple< bool, size_t > DrawAsTiledImageRect(SkCanvas *, const SkImage *, const SkRect &srcRect, const SkRect &dstRect, SkCanvas::QuadAAFlags, const SkSamplingOptions &, const SkPaint *, SkCanvas::SrcRectConstraint, size_t cacheSize, size_t maxTextureSize)
virtual TextureInfo getDefaultSampledTextureInfo(SkColorType, Mipmapped mipmapped, Protected, Renderable) const =0
DisjointStencilIndex add(CompressedPaintersOrder drawOrder, Rect rect)
Definition: Device.cpp:204
static sk_sp< Device > Make(Recorder *recorder, sk_sp< TextureProxy >, SkISize deviceSize, const SkColorInfo &, const SkSurfaceProps &, LoadOp initialLoadOp, bool registerWithRecorder=true)
Definition: Device.cpp:276
Recorder * recorder() const override
Definition: Device.h:72
TextureProxy * target()
Definition: Device.cpp:1686
static sk_sp< DrawContext > Make(const Caps *caps, sk_sp< TextureProxy > target, SkISize deviceSize, const SkColorInfo &, const SkSurfaceProps &)
Definition: DrawContext.cpp:65
static constexpr DisjointStencilIndex kUnassigned
Definition: DrawOrder.h:115
MonotonicValue next() const
Definition: DrawOrder.h:42
static SkColor4f Color4fPrepForDst(SkColor4f srgb, const SkColorInfo &dstColorInfo)
Definition: PaintParams.cpp:85
Protected isProtected() const
Definition: RecorderPriv.h:52
const Caps * caps() const
Definition: RecorderPriv.h:31
ResourceProvider * resourceProvider()
Definition: RecorderPriv.h:33
void add(sk_sp< TextureProxy > proxy, const SamplerDesc &samplerDesc)
Definition: PipelineData.h:70
static sk_sp< TextureProxy > Make(const Caps *, ResourceProvider *, SkISize dimensions, const TextureInfo &, std::string_view label, skgpu::Budgeted)
Rect inverseMapRect(const Rect &rect) const
Definition: Transform.cpp:248
virtual skgpu::MaskFormat maskFormat() const =0
virtual const VertexFiller & vertexFiller() const =0
virtual std::tuple< bool, int > regenerateAtlas(int begin, int end, RegenerateAtlasDelegate) const =0
virtual int glyphCount() const =0
const gpu::SubRunContainerOwner & subRuns() const
Definition: SlugImpl.h:57
static sk_sp< SlugImpl > Make(const SkMatrix &viewMatrix, const sktext::GlyphRunList &glyphRunList, const SkPaint &paint, SkStrikeDeviceInfo strikeDeviceInfo, sktext::StrikeForGPUCacheInterface *strikeCache)
Definition: SlugImpl.cpp:74
std::tuple< skgpu::graphite::Rect, skgpu::graphite::Transform > boundsAndDeviceMatrix(const skgpu::graphite::Transform &localToDevice, SkPoint drawOrigin) const
const Paint & paint
Definition: color_source.cc:38
DlColor color
static const char * begin(const StringSlice &s)
Definition: editor.cpp:252
@ kRaster
Suitable for thread which raster data.
Definition: embedder.h:266
VkDevice device
Definition: main.cc:53
FlutterSemanticsFlag flags
glong glong end
FlPixelBufferTexturePrivate * priv
@ kDefault
uint32_t * target
SkImage::ReadPixelsContext ReadPixelsContext
Definition: Device.cpp:81
SkImage::ReadPixelsCallback ReadPixelsCallback
Definition: Device.cpp:80
#define ASSERT_SINGLE_OWNER
Definition: Device.cpp:94
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static void drawPath(SkPath &path, SkCanvas *canvas, SkColor color, const SkRect &clip, SkPaint::Cap cap, SkPaint::Join join, SkPaint::Style style, SkPathFillType fill, SkScalar strokeWidth)
Definition: linepaths.cpp:22
double y
double x
constexpr SkColor4f kTransparent
Definition: SkColor.h:434
SK_API sk_sp< SkImage > RasterFromPixmap(const SkPixmap &pixmap, RasterReleaseProc rasterReleaseProc, ReleaseContext releaseContext)
SK_API sk_sp< SkImage > TextureFromImage(GrDirectContext *, const SkImage *, skgpu::Mipmapped=skgpu::Mipmapped::kNo, skgpu::Budgeted=skgpu::Budgeted::kYes)
SK_API sk_sp< SkDocument > Make(SkWStream *dst, const SkSerialProcs *=nullptr, std::function< void(const SkPicture *)> onEndPage=nullptr)
clipRRect(r.rrect, r.opAA.op(), r.opAA.aa())) DRAW(ClipRect
sk_sp< const SkImage > atlas
Definition: SkRecords.h:331
drawSlug(r.slug.get(), r.paint)) DRAW(DrawAtlas
Optional< SkRect > bounds
Definition: SkRecords.h:189
clipRect(r.rect, r.opAA.op(), r.opAA.aa())) template<> void Draw
PODArray< SkPoint > dstClips
Definition: SkRecords.h:364
sk_sp< const SkImage > image
Definition: SkRecords.h:269
ClipOpAndAA opAA SkRegion region
Definition: SkRecords.h:238
clipPath(r.path, r.opAA.op(), r.opAA.aa())) DRAW(ClipRRect
SkRRect rrect
Definition: SkRecords.h:232
SkRect oval
Definition: SkRecords.h:249
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
PODArray< SkMatrix > preViewMatrices
Definition: SkRecords.h:365
SkSamplingOptions sampling
Definition: SkRecords.h:337
sk_sp< SkSpecialImage > MakeGraphite(skgpu::graphite::Recorder *recorder, const SkIRect &subset, sk_sp< SkImage > image, const SkSurfaceProps &props)
SK_API sk_sp< SkSurface > RenderTarget(GrRecordingContext *context, skgpu::Budgeted budgeted, const SkImageInfo &imageInfo, int sampleCount, GrSurfaceOrigin surfaceOrigin, const SkSurfaceProps *surfaceProps, bool shouldCreateWithMips=false, bool isProtected=false)
sk_sp< SkSurface > makeSurface(SkCanvas *canvas, const SkImageInfo &info, const SkSurfaceProps *props)
Definition: ToolUtils.cpp:512
static uint32_t Hash(uint32_t key)
Definition: hashmap_test.cc:65
Clip
Definition: layer.h:53
@ kNone
Definition: layer.h:53
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
Definition: switches.h:228
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not set
Definition: switches.h:76
dst
Definition: cp.py:12
TRect< Scalar > Rect
Definition: rect.h:769
const myers::Point & get< 1 >(const myers::Segment &s)
Definition: Myers.h:81
std::tuple< GrSurfaceProxyView, GrColorType > AsView(GrRecordingContext *rContext, const SkImage *img, skgpu::Mipmapped mipmapped, GrImageTexGenPolicy policy)
bool GenerateMipmaps(Recorder *recorder, sk_sp< TextureProxy > texture, const SkColorInfo &colorInfo)
MonotonicValue< DisjointStencilIndexSequence > DisjointStencilIndex
Definition: DrawOrder.h:75
DstReadRequirement GetDstReadRequirement(const Caps *caps, std::optional< SkBlendMode > blendMode, Coverage coverage)
static constexpr int kMaxBruteForceN
Definition: Device.cpp:322
std::pair< sk_sp< SkImage >, SkSamplingOptions > GetGraphiteBacked(Recorder *recorder, const SkImage *imageIn, SkSamplingOptions sampling)
static constexpr int kMaxGridSize
Definition: Device.cpp:323
DstReadRequirement
Definition: Caps.h:64
MonotonicValue< CompressedPaintersOrderSequence > CompressedPaintersOrder
Definition: DrawOrder.h:58
static constexpr int kGridCellSize
Definition: Device.cpp:321
constexpr bool BlurIsEffectivelyIdentity(float sigma)
Definition: BlurUtils.h:30
SkISize GetApproxSize(SkISize size)
Budgeted
Definition: GpuTypes.h:35
Mipmapped
Definition: GpuTypes.h:53
MaskFormat
Definition: AtlasTypes.h:98
@ kARGB
4-bytes per pixel, color format
sk_sp< Backend > MakeGraphiteBackend(skgpu::graphite::Recorder *recorder, const SkSurfaceProps &surfaceProps, SkColorType colorType)
std::function< void(const sktext::gpu::AtlasSubRun *subRun, SkPoint drawOrigin, const SkPaint &paint, sk_sp< SkRefCnt > subRunStorage, sktext::gpu::RendererData)> AtlasDrawDelegate
skgpu::graphite::Transform Transform
Definition: ref_ptr.h:256
Definition: SkRect.h:32
bool intersect(const SkIRect &r)
Definition: SkRect.h:513
constexpr SkISize size() const
Definition: SkRect.h:172
int32_t fTop
smaller y-axis bounds
Definition: SkRect.h:34
static constexpr SkIRect MakeSize(const SkISize &size)
Definition: SkRect.h:66
int32_t fLeft
smaller x-axis bounds
Definition: SkRect.h:33
static constexpr SkIRect MakePtSize(SkIPoint pt, SkISize size)
Definition: SkRect.h:78
Definition: SkSize.h:16
const SkColorInfo & colorInfo() const
Definition: SkImageInfo.h:404
SkISize dimensions() const
Definition: SkImageInfo.h:421
SkColorType colorType() const
Definition: SkImageInfo.h:373
static SkRect Make(const SkISize &size)
Definition: SkRect.h:669
static SkRect MakeIWH(int w, int h)
Definition: SkRect.h:623
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition: SkRect.h:659
#define TRACE_EVENT0(category_group, name)
Definition: trace_event.h:131
#define TRACE_EVENT_INSTANT0(category_group, name)
Definition: trace_event.h:175
static constexpr SkIRect kDeviceRect