• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
8 #include "src/gpu/graphite/Device.h"
9 
10 #include "include/gpu/graphite/Recorder.h"
11 #include "include/gpu/graphite/Recording.h"
12 #include "include/gpu/graphite/Surface.h"
13 #include "include/private/gpu/graphite/ContextOptionsPriv.h"
14 #include "src/gpu/AtlasTypes.h"
15 #include "src/gpu/BlurUtils.h"
16 #include "src/gpu/SkBackingFit.h"
17 #include "src/gpu/graphite/AtlasProvider.h"
18 #include "src/gpu/graphite/Buffer.h"
19 #include "src/gpu/graphite/Caps.h"
20 #include "src/gpu/graphite/CommandBuffer.h"
21 #include "src/gpu/graphite/ContextPriv.h"
22 #include "src/gpu/graphite/ContextUtils.h"
23 #include "src/gpu/graphite/DrawContext.h"
24 #include "src/gpu/graphite/DrawList.h"
25 #include "src/gpu/graphite/DrawParams.h"
26 #include "src/gpu/graphite/Image_Graphite.h"
27 #include "src/gpu/graphite/Log.h"
28 #include "src/gpu/graphite/PathAtlas.h"
29 #include "src/gpu/graphite/RasterPathAtlas.h"
30 #include "src/gpu/graphite/RecorderPriv.h"
31 #include "src/gpu/graphite/Renderer.h"
32 #include "src/gpu/graphite/RendererProvider.h"
33 #include "src/gpu/graphite/ResourceTypes.h"
34 #include "src/gpu/graphite/SharedContext.h"
35 #include "src/gpu/graphite/SpecialImage_Graphite.h"
36 #include "src/gpu/graphite/Surface_Graphite.h"
37 #include "src/gpu/graphite/TextureProxy.h"
38 #include "src/gpu/graphite/TextureUtils.h"
39 #include "src/gpu/graphite/geom/BoundsManager.h"
40 #include "src/gpu/graphite/geom/Geometry.h"
41 #include "src/gpu/graphite/geom/IntersectionTree.h"
42 #include "src/gpu/graphite/geom/Shape.h"
43 #include "src/gpu/graphite/geom/Transform_graphite.h"
44 #include "src/gpu/graphite/text/TextAtlasManager.h"
45 
46 #include "include/core/SkColorSpace.h"
47 #include "include/core/SkPath.h"
48 #include "include/core/SkPathEffect.h"
49 #include "include/core/SkStrokeRec.h"
50 
51 #include "src/core/SkBlenderBase.h"
52 #include "src/core/SkBlurMaskFilterImpl.h"
53 #include "src/core/SkColorSpacePriv.h"
54 #include "src/core/SkConvertPixels.h"
55 #include "src/core/SkImageFilterTypes.h"
56 #include "src/core/SkImageInfoPriv.h"
57 #include "src/core/SkImagePriv.h"
58 #include "src/core/SkMatrixPriv.h"
59 #include "src/core/SkPaintPriv.h"
60 #include "src/core/SkRRectPriv.h"
61 #include "src/core/SkSpecialImage.h"
62 #include "src/core/SkStrikeCache.h"
63 #include "src/core/SkTraceEvent.h"
64 #include "src/core/SkVerticesPriv.h"
65 #include "src/gpu/TiledTextureUtils.h"
66 #include "src/text/GlyphRun.h"
67 #include "src/text/gpu/GlyphVector.h"
68 #include "src/text/gpu/SlugImpl.h"
69 #include "src/text/gpu/SubRunContainer.h"
70 #include "src/text/gpu/TextBlobRedrawCoordinator.h"
71 #include "src/text/gpu/VertexFiller.h"
72 
73 #include <functional>
74 #include <tuple>
75 #include <unordered_map>
76 #include <vector>
77 
78 using RescaleGamma       = SkImage::RescaleGamma;
79 using RescaleMode        = SkImage::RescaleMode;
80 using ReadPixelsCallback = SkImage::ReadPixelsCallback;
81 using ReadPixelsContext  = SkImage::ReadPixelsContext;
82 
83 #if defined(GRAPHITE_TEST_UTILS)
84 int 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.
89 std::atomic<int> gNumTilesDrawnGraphite{0};
90 #endif
91 
92 namespace skgpu::graphite {
93 
94 #define ASSERT_SINGLE_OWNER SkASSERT(fRecorder); SKGPU_ASSERT_SINGLE_OWNER(fRecorder->singleOwner())
95 
96 namespace {
97 
DefaultFillStyle()98 const SkStrokeRec& DefaultFillStyle() {
99     static const SkStrokeRec kFillStyle(SkStrokeRec::kFill_InitStyle);
100     return kFillStyle;
101 }
102 
blender_depends_on_dst(const SkBlender * blender,bool srcIsTransparent)103 bool 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 
paint_depends_on_dst(SkColor4f color,const SkShader * shader,const SkColorFilter * colorFilter,const SkBlender * finalBlender,const SkBlender * primitiveBlender)120 bool 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 
paint_depends_on_dst(const PaintParams & paintParams)135 bool 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 
paint_depends_on_dst(const SkPaint & paint)143 bool 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. */
extract_paint_color(const SkPaint & paint,const SkColorInfo & dstColorInfo)154 std::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 
rect_to_pixelbounds(const Rect & r)170 SkIRect rect_to_pixelbounds(const Rect& r) {
171     return r.makeRoundOut().asSkIRect();
172 }
173 
is_simple_shape(const Shape & shape,SkStrokeRec::Style type)174 bool 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 
use_compute_atlas_when_available(PathRendererStrategy strategy)185 bool use_compute_atlas_when_available(PathRendererStrategy strategy) {
186     return strategy == PathRendererStrategy::kComputeAnalyticAA ||
187            strategy == PathRendererStrategy::kComputeMSAA16 ||
188            strategy == PathRendererStrategy::kComputeMSAA8 ||
189            strategy == PathRendererStrategy::kDefault;
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  */
200 class Device::IntersectionTreeSet {
201 public:
202     IntersectionTreeSet() = default;
203 
add(CompressedPaintersOrder drawOrder,Rect rect)204     DisjointStencilIndex add(CompressedPaintersOrder drawOrder, Rect rect) {
205         auto& trees = fTrees[drawOrder];
206         DisjointStencilIndex stencil = DrawOrder::kUnassigned.next();
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 
reset()221     void reset() {
222         fTrees.clear();
223         fTreeStore.reset();
224     }
225 
226 private:
227     struct Hash {
operator ()skgpu::graphite::Device::IntersectionTreeSet::Hash228         size_t operator()(const CompressedPaintersOrder& o) const noexcept { return o.bits(); }
229     };
230 
makeTree()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 
Make(Recorder * recorder,const SkImageInfo & ii,skgpu::Budgeted budgeted,Mipmapped mipmapped,SkBackingFit backingFit,const SkSurfaceProps & props,LoadOp initialLoadOp,std::string_view label,bool registerWithRecorder)244 sk_sp<Device> Device::Make(Recorder* recorder,
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,
263                                                           recorder->priv().isProtected(),
264                                                           Renderable::kYes);
265 
266     return Make(recorder,
267                 TextureProxy::Make(caps, recorder->priv().resourceProvider(),
268                                    backingDimensions, textureInfo, std::move(label), budgeted),
269                 ii.dimensions(),
270                 ii.colorInfo(),
271                 props,
272                 initialLoadOp,
273                 registerWithRecorder);
274 }
275 
Make(Recorder * recorder,sk_sp<TextureProxy> target,SkISize deviceSize,const SkColorInfo & colorInfo,const SkSurfaceProps & props,LoadOp initialLoadOp,bool registerWithRecorder)276 sk_sp<Device> Device::Make(Recorder* recorder,
277                            sk_sp<TextureProxy> target,
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 
287     sk_sp<DrawContext> dc = DrawContext::Make(recorder->priv().caps(),
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.
321 static constexpr int kGridCellSize = 16;
322 static constexpr int kMaxBruteForceN = 64;
323 static constexpr int kMaxGridSize = 32;
324 
Device(Recorder * recorder,sk_sp<DrawContext> dc)325 Device::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(),
344                                                                        Discardable::kYes);
345             fMSAASupported = msaaTexInfo.isValid();
346         }
347     }
348 }
349 
~Device()350 Device::~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 
setImmutable()366 void 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 
localToDeviceTransform()381 const Transform& Device::localToDeviceTransform() {
382     if (this->checkLocalToDeviceDirty()) {
383         fCachedLocalToDevice = Transform{this->localToDevice44()};
384     }
385     return fCachedLocalToDevice;
386 }
387 
strikeDeviceInfo() const388 SkStrikeDeviceInfo Device::strikeDeviceInfo() const {
389     return {this->surfaceProps(), this->scalerContextFlags(), &fSDFTControl};
390 }
391 
createDevice(const CreateInfo & info,const SkPaint *)392 sk_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,
410                 skgpu::Budgeted::kYes,
411                 Mipmapped::kNo,
412                 SkBackingFit::kApprox,
413                 props,
414                 initialLoadOp,
415                 label);
416 }
417 
makeSurface(const SkImageInfo & ii,const SkSurfaceProps & props)418 sk_sp<SkSurface> Device::makeSurface(const SkImageInfo& ii, const SkSurfaceProps& props) {
419     return SkSurfaces::RenderTarget(fRecorder, ii, Mipmapped::kNo, &props);
420 }
421 
makeImageCopy(const SkIRect & subset,Budgeted budgeted,Mipmapped mipmapped,SkBackingFit backingFit)422 sk_sp<Image> Device::makeImageCopy(const SkIRect& subset,
423                                    Budgeted budgeted,
424                                    Mipmapped mipmapped,
425                                    SkBackingFit backingFit) {
426     ASSERT_SINGLE_OWNER
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 
onReadPixels(const SkPixmap & pm,int srcX,int srcY)449 bool 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.
453     ASSERT_SINGLE_OWNER
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 
onWritePixels(const SkPixmap & src,int x,int y)474 bool Device::onWritePixels(const SkPixmap& src, int x, int y) {
475     ASSERT_SINGLE_OWNER
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);
497         image = SkImages::TextureFromImage(fRecorder, image.get());
498         if (!image) {
499             return false;
500         }
501 
502         SkPaint paint;
503         paint.setBlendMode(SkBlendMode::kSrc);
504         this->drawImageRect(image.get(),
505                             /*src=*/nullptr,
506                             SkRect::MakeXYWH(x, y, src.width(), src.height()),
507                             SkFilterMode::kNearest,
508                             paint,
509                             SkCanvas::kFast_SrcRectConstraint);
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 
isClipAntiAliased() const539 bool 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;
544     } else if (type == ClipStack::ClipState::kDeviceRect) {
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 
devClipBounds() const553 SkIRect 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?
android_utils_clipAsRgn(SkRegion * region) const558 void 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
561     region->setRect(bounds);
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 
clipRect(const SkRect & rect,SkClipOp op,bool aa)577 void Device::clipRect(const SkRect& rect, SkClipOp op, bool aa) {
578     SkASSERT(op == SkClipOp::kIntersect || op == SkClipOp::kDifference);
579     // TODO: Snap rect edges to pixel bounds if non-AA and axis-aligned?
580     fClip.clipShape(this->localToDeviceTransform(), Shape{rect}, op);
581 }
582 
clipRRect(const SkRRect & rrect,SkClipOp op,bool aa)583 void Device::clipRRect(const SkRRect& rrect, SkClipOp op, bool aa) {
584     SkASSERT(op == SkClipOp::kIntersect || op == SkClipOp::kDifference);
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 
clipPath(const SkPath & path,SkClipOp op,bool aa)590 void Device::clipPath(const SkPath& path, SkClipOp op, bool aa) {
591     SkASSERT(op == SkClipOp::kIntersect || op == SkClipOp::kDifference);
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 
onClipShader(sk_sp<SkShader> shader)598 void 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...
clipRegion(const SkRegion & globalRgn,SkClipOp op)603 void Device::clipRegion(const SkRegion& globalRgn, SkClipOp op) {
604     SkASSERT(op == SkClipOp::kIntersect || op == SkClipOp::kDifference);
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 
replaceClip(const SkIRect & rect)622 void 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 
drawPaint(const SkPaint & paint)639 void Device::drawPaint(const SkPaint& paint) {
640     ASSERT_SINGLE_OWNER
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 
drawRect(const SkRect & r,const SkPaint & paint)674 void Device::drawRect(const SkRect& r, const SkPaint& paint) {
675     this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(r)),
676                        paint, SkStrokeRec(paint));
677 }
678 
drawVertices(const SkVertices * vertices,sk_sp<SkBlender> blender,const SkPaint & paint,bool skipColorXform)679 void 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 
drawAsTiledImageRect(SkCanvas * canvas,const SkImage * image,const SkRect * src,const SkRect & dst,const SkSamplingOptions & sampling,const SkPaint & paint,SkCanvas::SrcRectConstraint constraint)691 bool Device::drawAsTiledImageRect(SkCanvas* canvas,
692                                   const SkImage* image,
693                                   const SkRect* src,
694                                   const SkRect& dst,
695                                   const SkSamplingOptions& sampling,
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] =
719             skgpu::TiledTextureUtils::DrawAsTiledImageRect(canvas,
720                                                            image,
721                                                            *src,
722                                                            dst,
723                                                            SkCanvas::kAll_QuadAAFlags,
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 
drawOval(const SkRect & oval,const SkPaint & paint)735 void 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))),
740                            paint, SkStrokeRec(paint));
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))),
745                            paint, SkStrokeRec(paint));
746     }
747 }
748 
drawRRect(const SkRRect & rr,const SkPaint & paint)749 void Device::drawRRect(const SkRRect& rr, const SkPaint& paint) {
750     this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(rr)),
751                        paint, SkStrokeRec(paint));
752 }
753 
drawPath(const SkPath & path,const SkPaint & paint,bool pathIsMutable)754 void 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(),
760                                Geometry(Shape(SkRRect::MakeOval(oval))),
761                                paint,
762                                SkStrokeRec(paint));
763             return;
764         }
765         if (SkRRect rrect; path.isRRect(&rrect)) {
766             this->drawGeometry(this->localToDeviceTransform(),
767                                Geometry(Shape(rrect)),
768                                paint,
769                                SkStrokeRec(paint));
770             return;
771         }
772         if (SkRect rect; paint.getStyle() == SkPaint::kFill_Style && path.isRect(&rect)) {
773             this->drawGeometry(this->localToDeviceTransform(),
774                                Geometry(Shape(rect)),
775                                paint,
776                                SkStrokeRec(paint));
777             return;
778         }
779     }
780     this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(path)),
781                        paint, SkStrokeRec(paint));
782 }
783 
drawPoints(SkCanvas::PointMode mode,size_t count,const SkPoint * points,const SkPaint & paint)784 void Device::drawPoints(SkCanvas::PointMode mode, size_t count,
785                         const SkPoint* points, const SkPaint& paint) {
786     SkStrokeRec stroke(paint, SkPaint::kStroke_Style);
787     size_t next = 0;
788     if (mode == SkCanvas::kPoints_PointMode) {
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 
drawEdgeAAQuad(const SkRect & rect,const SkPoint clip[4],SkCanvas::QuadAAFlags aaFlags,const SkColor4f & color,SkBlendMode mode)810 void Device::drawEdgeAAQuad(const SkRect& rect,
811                             const SkPoint clip[4],
812                             SkCanvas::QuadAAFlags aaFlags,
813                             const SkColor4f& color,
814                             SkBlendMode mode) {
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));
820     EdgeAAQuad quad = clip ? EdgeAAQuad(clip, flags) : EdgeAAQuad(rect, flags);
821     this->drawGeometry(this->localToDeviceTransform(),
822                        Geometry(quad),
823                        solidColorPaint,
824                        DefaultFillStyle(),
825                        DrawFlags::kIgnorePathEffect);
826 }
827 
drawEdgeAAImageSet(const SkCanvas::ImageSetEntry set[],int count,const SkPoint dstClips[],const SkMatrix preViewMatrices[],const SkSamplingOptions & sampling,const SkPaint & paint,SkCanvas::SrcRectConstraint constraint)828 void Device::drawEdgeAAImageSet(const SkCanvas::ImageSetEntry set[], int count,
829                                 const SkPoint dstClips[], const SkMatrix preViewMatrices[],
830                                 const SkSamplingOptions& sampling, const SkPaint& paint,
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);
853         SkRect dst = SkModifyPaintAndDstForDrawImageRect(
854                     imageToDraw.get(), newSampling, set[i].fSrcRect, set[i].fDstRect,
855                     constraint == SkCanvas::kStrict_SrcRectConstraint,
856                     &paintWithShader);
857         if (dst.isEmpty()) {
858             return;
859         }
860 
861         auto flags =
862                 SkEnumBitMask<EdgeAAQuad::Flags>(static_cast<EdgeAAQuad::Flags>(set[i].fAAFlags));
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 
drawImageRect(const SkImage * image,const SkRect * src,const SkRect & dst,const SkSamplingOptions & sampling,const SkPaint & paint,SkCanvas::SrcRectConstraint constraint)889 void Device::drawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst,
890                            const SkSamplingOptions& sampling, const SkPaint& paint,
891                            SkCanvas::SrcRectConstraint constraint) {
892     SkCanvas::ImageSetEntry single{sk_ref_sp(image),
893                                    src ? *src : SkRect::Make(image->bounds()),
894                                    dst,
895                                    /*alpha=*/1.f,
896                                    SkCanvas::kAll_QuadAAFlags};
897     this->drawEdgeAAImageSet(&single, 1, nullptr, nullptr, sampling, paint, constraint);
898 }
899 
atlasDelegate()900 sktext::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 
onDrawGlyphRunList(SkCanvas * canvas,const sktext::GlyphRunList & glyphRunList,const SkPaint & paint)910 void Device::onDrawGlyphRunList(SkCanvas* canvas,
911                                 const sktext::GlyphRunList& glyphRunList,
912                                 const SkPaint& paint) {
913     ASSERT_SINGLE_OWNER
914     fRecorder->priv().textBlobCache()->drawGlyphRunList(canvas,
915                                                         this->localToDevice(),
916                                                         glyphRunList,
917                                                         paint,
918                                                         this->strikeDeviceInfo(),
919                                                         this->atlasDelegate());
920 }
921 
drawAtlasSubRun(const sktext::gpu::AtlasSubRun * subRun,SkPoint drawOrigin,const SkPaint & paint,sk_sp<SkRefCnt> subRunStorage,sktext::gpu::RendererData rendererData)922 void Device::drawAtlasSubRun(const sktext::gpu::AtlasSubRun* subRun,
923                              SkPoint drawOrigin,
924                              const SkPaint& paint,
925                              sk_sp<SkRefCnt> subRunStorage,
926                              sktext::gpu::RendererData rendererData) {
927     ASSERT_SINGLE_OWNER
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,
965                                                    SkPaintPriv::ComputeLuminanceColor(subRunPaint),
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 
drawGeometry(const Transform & localToDevice,const Geometry & geometry,const SkPaint & paint,const SkStrokeRec & style,SkEnumBitMask<DrawFlags> flags,sk_sp<SkBlender> primitiveBlender,bool skipColorXform)987 void Device::drawGeometry(const Transform& localToDevice,
988                           const Geometry& geometry,
989                           const SkPaint& paint,
990                           const SkStrokeRec& style,
991                           SkEnumBitMask<DrawFlags> flags,
992                           sk_sp<SkBlender> primitiveBlender,
993                           bool skipColorXform) {
994     ASSERT_SINGLE_OWNER
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.
1090     DstReadRequirement dstReadReq = DstReadRequirement::kNone;
1091     const SkBlenderBase* blender = as_BB(paint.getBlender());
1092     const std::optional<SkBlendMode> blendMode = blender ? blender->asBlendMode()
1093                                                          : SkBlendMode::kSrcOver;
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.
1154         SkASSERT(renderer);
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 
drawClipShape(const Transform & localToDevice,const Shape & shape,const Clip & clip,DrawOrder order)1263 void 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.
chooseRenderer(const Transform & localToDevice,const Geometry & geometry,const SkStrokeRec & style,bool requireMSAA) const1311 std::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);
1317     SkStrokeRec::Style type = style.getStyle();
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
1367     PathRendererStrategy strategy = PathRendererStrategy::kDefault;
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 
1418     if (type == SkStrokeRec::kStroke_Style ||
1419         type == SkStrokeRec::kHairline_Style) {
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 
lastDrawTask() const1460 sk_sp<Task> Device::lastDrawTask() const {
1461     SkASSERT(this->isScratchDevice());
1462     return fLastTask;
1463 }
1464 
flushPendingWorkToRecorder()1465 void 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 
internalFlush()1527 void Device::internalFlush() {
1528     TRACE_EVENT0("skia.gpu", TRACE_FUNC);
1529     ASSERT_SINGLE_OWNER
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 
needsFlushBeforeDraw(int numNewRenderSteps,DstReadRequirement dstReadReq) const1549 bool 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 
drawSpecial(SkSpecialImage * special,const SkMatrix & localToDevice,const SkSamplingOptions & sampling,const SkPaint & paint,SkCanvas::SrcRectConstraint constraint)1558 void Device::drawSpecial(SkSpecialImage* special,
1559                          const SkMatrix& localToDevice,
1560                          const SkSamplingOptions& sampling,
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);
1572     SkRect dst = SkModifyPaintAndDstForDrawImageRect(
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 
drawCoverageMask(const SkSpecialImage * mask,const SkMatrix & localToDevice,const SkSamplingOptions & sampling,const SkPaint & paint)1590 void Device::drawCoverageMask(const SkSpecialImage* mask,
1591                               const SkMatrix& localToDevice,
1592                               const SkSamplingOptions& sampling,
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.
1615     const SkTileMode kClamp[2] = {SkTileMode::kClamp, SkTileMode::kClamp};
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(fRecorder->priv().caps(), SkFilterMode::kLinear, kClamp, maskProxyView.refProxy());
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 
makeSpecial(const SkBitmap &)1643 sk_sp<SkSpecialImage> Device::makeSpecial(const SkBitmap&) {
1644     return nullptr;
1645 }
1646 
makeSpecial(const SkImage *)1647 sk_sp<SkSpecialImage> Device::makeSpecial(const SkImage*) {
1648     return nullptr;
1649 }
1650 
snapSpecial(const SkIRect & subset,bool forceCopy)1651 sk_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(
1658                 subset, Budgeted::kYes, Mipmapped::kNo, SkBackingFit::kApprox);
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.
1677     return SkSpecialImages::MakeGraphite(
1678             fRecorder, finalSubset, std::move(deviceImage), this->surfaceProps());
1679 }
1680 
createImageFilteringBackend(const SkSurfaceProps & surfaceProps,SkColorType colorType) const1681 sk_sp<skif::Backend> Device::createImageFilteringBackend(const SkSurfaceProps& surfaceProps,
1682                                                          SkColorType colorType) const {
1683     return skif::MakeGraphiteBackend(fRecorder, surfaceProps, colorType);
1684 }
1685 
target()1686 TextureProxy* Device::target() { return fDC->target(); }
1687 
readSurfaceView() const1688 TextureProxyView Device::readSurfaceView() const { return fDC->readSurfaceView(); }
1689 
isScratchDevice() const1690 bool 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 
convertGlyphRunListToSlug(const sktext::GlyphRunList & glyphRunList,const SkPaint & paint)1703 sk_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(),
1709                                        SkStrikeCache::GlobalStrikeCache());
1710 }
1711 
drawSlug(SkCanvas * canvas,const sktext::gpu::Slug * slug,const SkPaint & paint)1712 void 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 
drawBlurredRRect(const SkRRect & rrect,const SkPaint & paint,float deviceSigma)1717 bool Device::drawBlurredRRect(const SkRRect& rrect, const SkPaint& paint, float deviceSigma) {
1718     SkStrokeRec style(paint);
1719     if (skgpu::BlurIsEffectivelyIdentity(deviceSigma)) {
1720         this->drawGeometry(this->localToDeviceTransform(),
1721                            Geometry(rrect.isRect() ? Shape(rrect.rect()) : Shape(rrect)),
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
1738