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