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 "src/gpu/AtlasTypes.h"
13 #include "src/gpu/graphite/Buffer.h"
14 #include "src/gpu/graphite/Caps.h"
15 #include "src/gpu/graphite/CommandBuffer.h"
16 #include "src/gpu/graphite/ContextPriv.h"
17 #include "src/gpu/graphite/CopyTask.h"
18 #include "src/gpu/graphite/DrawContext.h"
19 #include "src/gpu/graphite/DrawList.h"
20 #include "src/gpu/graphite/DrawParams.h"
21 #include "src/gpu/graphite/ImageUtils.h"
22 #include "src/gpu/graphite/Image_Graphite.h"
23 #include "src/gpu/graphite/Log.h"
24 #include "src/gpu/graphite/RecorderPriv.h"
25 #include "src/gpu/graphite/Renderer.h"
26 #include "src/gpu/graphite/RendererProvider.h"
27 #include "src/gpu/graphite/SharedContext.h"
28 #include "src/gpu/graphite/TextureProxy.h"
29 #include "src/gpu/graphite/TextureUtils.h"
30 #include "src/gpu/graphite/geom/BoundsManager.h"
31 #include "src/gpu/graphite/geom/Geometry.h"
32 #include "src/gpu/graphite/geom/IntersectionTree.h"
33 #include "src/gpu/graphite/geom/Shape.h"
34 #include "src/gpu/graphite/geom/Transform_graphite.h"
35 #include "src/gpu/graphite/text/AtlasManager.h"
36
37 #include "include/core/SkColorSpace.h"
38 #include "include/core/SkPath.h"
39 #include "include/core/SkPathEffect.h"
40 #include "include/core/SkStrokeRec.h"
41
42 #include "src/core/SkBlenderBase.h"
43 #include "src/core/SkColorSpacePriv.h"
44 #include "src/core/SkConvertPixels.h"
45 #include "src/core/SkImageInfoPriv.h"
46 #include "src/core/SkMatrixPriv.h"
47 #include "src/core/SkPaintPriv.h"
48 #include "src/core/SkRRectPriv.h"
49 #include "src/core/SkSpecialImage.h"
50 #include "src/core/SkTraceEvent.h"
51 #include "src/core/SkVerticesPriv.h"
52 #include "src/shaders/SkImageShader.h"
53 #include "src/text/gpu/SubRunContainer.h"
54 #include "src/text/gpu/TextBlobRedrawCoordinator.h"
55
56 #include <unordered_map>
57 #include <vector>
58
59 using RescaleGamma = SkImage::RescaleGamma;
60 using RescaleMode = SkImage::RescaleMode;
61 using ReadPixelsCallback = SkImage::ReadPixelsCallback;
62 using ReadPixelsContext = SkImage::ReadPixelsContext;
63
64 namespace skgpu::graphite {
65
66 namespace {
67
68 static const SkStrokeRec kFillStyle(SkStrokeRec::kFill_InitStyle);
69
paint_depends_on_dst(SkColor4f color,const SkShader * shader,const SkColorFilter * colorFilter,const SkBlender * blender)70 bool paint_depends_on_dst(SkColor4f color,
71 const SkShader* shader,
72 const SkColorFilter* colorFilter,
73 const SkBlender* blender) {
74 std::optional<SkBlendMode> bm = blender ? as_BB(blender)->asBlendMode() : SkBlendMode::kSrcOver;
75 if (!bm.has_value()) {
76 return true;
77 }
78 if (bm.value() == SkBlendMode::kSrc || bm.value() == SkBlendMode::kClear) {
79 // src and clear blending never depends on dst
80 return false;
81 }
82 if (bm.value() == SkBlendMode::kSrcOver) {
83 // src-over does not depend on dst if src is opaque (a = 1)
84 return !color.isOpaque() ||
85 (shader && !shader->isOpaque()) ||
86 (colorFilter && !colorFilter->isAlphaUnchanged());
87 }
88 // TODO: Are their other modes that don't depend on dst that can be trivially detected?
89 return true;
90 }
91
paint_depends_on_dst(const PaintParams & paintParams)92 bool paint_depends_on_dst(const PaintParams& paintParams) {
93 return paint_depends_on_dst(paintParams.color(), paintParams.shader(),
94 paintParams.colorFilter(), paintParams.finalBlender());
95 }
96
paint_depends_on_dst(const SkPaint & paint)97 bool paint_depends_on_dst(const SkPaint& paint) {
98 // CAUTION: getMaskFilter is intentionally ignored here.
99 SkASSERT(!paint.getImageFilter()); // no paints in SkDevice should have an image filter
100 return paint_depends_on_dst(paint.getColor4f(), paint.getShader(),
101 paint.getColorFilter(), paint.getBlender());
102 }
103
104 /** 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)105 std::optional<SkColor4f> extract_paint_color(const SkPaint& paint,
106 const SkColorInfo& dstColorInfo) {
107 SkASSERT(!paint_depends_on_dst(paint));
108 if (paint.getShader()) {
109 return std::nullopt;
110 }
111
112 SkColor4f dstPaintColor = PaintParams::Color4fPrepForDst(paint.getColor4f(), dstColorInfo);
113
114 if (SkColorFilter* filter = paint.getColorFilter()) {
115 SkColorSpace* dstCS = dstColorInfo.colorSpace();
116 return filter->filterColor4f(dstPaintColor, dstCS, dstCS);
117 }
118 return dstPaintColor;
119 }
120
rect_to_pixelbounds(const Rect & r)121 SkIRect rect_to_pixelbounds(const Rect& r) {
122 return r.makeRoundOut().asSkIRect();
123 }
124
125 // TODO: this doesn't support the SrcRectConstraint option.
create_img_shader_paint(sk_sp<SkImage> image,const SkRect & subset,const SkSamplingOptions & sampling,const SkMatrix * localMatrix,SkPaint * paint)126 bool create_img_shader_paint(sk_sp<SkImage> image,
127 const SkRect& subset,
128 const SkSamplingOptions& sampling,
129 const SkMatrix* localMatrix,
130 SkPaint* paint) {
131 bool imageIsAlphaOnly = SkColorTypeIsAlphaOnly(image->colorType());
132
133 sk_sp<SkShader> imgShader = SkImageShader::MakeSubset(std::move(image), subset,
134 SkTileMode::kClamp, SkTileMode::kClamp,
135 sampling, localMatrix);
136 if (!imgShader) {
137 SKGPU_LOG_W("Couldn't create subset image shader");
138 return false;
139 }
140 if (imageIsAlphaOnly && paint->getShader()) {
141 // Compose the image shader with the paint's shader. Alpha images+shaders should output the
142 // texture's alpha multiplied by the shader's color. DstIn (d*sa) will achieve this with
143 // the source image and dst shader (MakeBlend takes dst first, src second).
144 imgShader = SkShaders::Blend(SkBlendMode::kDstIn, paint->refShader(), std::move(imgShader));
145 }
146
147 paint->setStyle(SkPaint::kFill_Style);
148 paint->setShader(std::move(imgShader));
149 paint->setPathEffect(nullptr); // neither drawSpecial nor drawImageRect support path effects
150 return true;
151 }
152
is_simple_shape(const Shape & shape,SkStrokeRec::Style type)153 bool is_simple_shape(const Shape& shape, SkStrokeRec::Style type) {
154 // We send regular filled and hairline [round] rectangles and quadrilaterals, and stroked
155 // [r]rects with circular corners to a single Renderer that does not trigger MSAA.
156 return !shape.inverted() && type != SkStrokeRec::kStrokeAndFill_Style &&
157 (shape.isRect() /* || shape.isQuadrilateral()*/ ||
158 (shape.isRRect() && (type != SkStrokeRec::kStroke_Style ||
159 SkRRectPriv::AllCornersCircular(shape.rrect()))));
160 }
161
162 } // anonymous namespace
163
164 /**
165 * IntersectionTreeSet controls multiple IntersectionTrees to organize all add rectangles into
166 * disjoint sets. For a given CompressedPaintersOrder and bounds, it returns the smallest
167 * DisjointStencilIndex that guarantees the bounds are disjoint from all other draws that use the
168 * same painters order and stencil index.
169 */
170 class Device::IntersectionTreeSet {
171 public:
172 IntersectionTreeSet() = default;
173
add(CompressedPaintersOrder drawOrder,Rect rect)174 DisjointStencilIndex add(CompressedPaintersOrder drawOrder, Rect rect) {
175 auto& trees = fTrees[drawOrder];
176 DisjointStencilIndex stencil = DrawOrder::kUnassigned.next();
177 for (auto&& tree : trees) {
178 if (tree->add(rect)) {
179 return stencil;
180 }
181 stencil = stencil.next(); // advance to the next tree's index
182 }
183
184 // If here, no existing intersection tree can hold the rect so add a new one
185 IntersectionTree* newTree = this->makeTree();
186 SkAssertResult(newTree->add(rect));
187 trees.push_back(newTree);
188 return stencil;
189 }
190
reset()191 void reset() {
192 fTrees.clear();
193 fTreeStore.reset();
194 }
195
196 private:
197 struct Hash {
operator ()skgpu::graphite::Device::IntersectionTreeSet::Hash198 size_t operator()(const CompressedPaintersOrder& o) const noexcept { return o.bits(); }
199 };
200
makeTree()201 IntersectionTree* makeTree() {
202 return fTreeStore.make<IntersectionTree>();
203 }
204
205 // Each compressed painters order defines a barrier around draws so each order's set of draws
206 // are independent, even if they may intersect. Within each order, the list of trees holds the
207 // IntersectionTrees representing each disjoint set.
208 // TODO: This organization of trees is logically convenient but may need to be optimized based
209 // on real world data (e.g. how sparse is the map, how long is each vector of trees,...)
210 std::unordered_map<CompressedPaintersOrder, std::vector<IntersectionTree*>, Hash> fTrees;
211 SkSTArenaAllocWithReset<4 * sizeof(IntersectionTree)> fTreeStore;
212 };
213
Make(Recorder * recorder,const SkImageInfo & ii,skgpu::Budgeted budgeted,Mipmapped mipmapped,const SkSurfaceProps & props,bool addInitialClear)214 sk_sp<Device> Device::Make(Recorder* recorder,
215 const SkImageInfo& ii,
216 skgpu::Budgeted budgeted,
217 Mipmapped mipmapped,
218 const SkSurfaceProps& props,
219 bool addInitialClear) {
220 if (!recorder) {
221 return nullptr;
222 }
223
224 sk_sp<TextureProxy> target = TextureProxy::Make(recorder->priv().caps(),
225 ii.dimensions(),
226 ii.colorType(),
227 mipmapped,
228 Protected::kNo,
229 Renderable::kYes,
230 budgeted);
231 if (!target) {
232 return nullptr;
233 }
234
235 return Make(recorder, std::move(target), ii.colorInfo(), props, addInitialClear);
236 }
237
Make(Recorder * recorder,sk_sp<TextureProxy> target,const SkColorInfo & colorInfo,const SkSurfaceProps & props,bool addInitialClear)238 sk_sp<Device> Device::Make(Recorder* recorder,
239 sk_sp<TextureProxy> target,
240 const SkColorInfo& colorInfo,
241 const SkSurfaceProps& props,
242 bool addInitialClear) {
243 return Make(recorder, target, target->dimensions(), colorInfo, props, addInitialClear);
244 }
245
Make(Recorder * recorder,sk_sp<TextureProxy> target,SkISize deviceSize,const SkColorInfo & colorInfo,const SkSurfaceProps & props,bool addInitialClear)246 sk_sp<Device> Device::Make(Recorder* recorder,
247 sk_sp<TextureProxy> target,
248 SkISize deviceSize,
249 const SkColorInfo& colorInfo,
250 const SkSurfaceProps& props,
251 bool addInitialClear) {
252 if (!recorder) {
253 return nullptr;
254 }
255 if (colorInfo.alphaType() != kPremul_SkAlphaType) {
256 return nullptr;
257 }
258
259 sk_sp<DrawContext> dc = DrawContext::Make(std::move(target), deviceSize, colorInfo, props);
260 if (!dc) {
261 return nullptr;
262 }
263
264 return sk_sp<Device>(new Device(recorder, std::move(dc), addInitialClear));
265 }
266
267 // These default tuning numbers for the HybridBoundsManager were chosen from looking at performance
268 // and accuracy curves produced by the BoundsManagerBench for random draw bounding boxes. This
269 // config will use brute force for the first 64 draw calls to the Device and then switch to a grid
270 // that is dynamically sized to produce cells that are 16x16, which seemed to be in the sweet spot
271 // for maintaining good performance without becoming too inaccurate.
272 // TODO: These could be exposed as context options or surface options, and we may want to have
273 // different strategies in place for a base device vs. a layer's device.
274 static constexpr int kGridCellSize = 16;
275 static constexpr int kMaxBruteForceN = 64;
276
Device(Recorder * recorder,sk_sp<DrawContext> dc,bool addInitialClear)277 Device::Device(Recorder* recorder, sk_sp<DrawContext> dc, bool addInitialClear)
278 : SkBaseDevice(dc->imageInfo(), dc->surfaceProps())
279 , fRecorder(recorder)
280 , fDC(std::move(dc))
281 , fClip(this)
282 , fColorDepthBoundsManager(
283 std::make_unique<HybridBoundsManager>(fDC->imageInfo().dimensions(),
284 kGridCellSize,
285 kMaxBruteForceN))
286 , fDisjointStencilSet(std::make_unique<IntersectionTreeSet>())
287 , fCachedLocalToDevice(SkM44())
288 , fCurrentDepth(DrawOrder::kClearDepth)
289 , fSDFTControl(recorder->priv().caps()->getSDFTControl(false))
290 , fDrawsOverlap(false) {
291 SkASSERT(SkToBool(fDC) && SkToBool(fRecorder));
292 fRecorder->registerDevice(this);
293
294 if (addInitialClear) {
295 fDC->clear(SkColors::kTransparent);
296 }
297 }
298
~Device()299 Device::~Device() {
300 if (fRecorder) {
301 this->flushPendingWorkToRecorder();
302 fRecorder->deregisterDevice(this);
303 }
304 }
305
abandonRecorder()306 void Device::abandonRecorder() {
307 fRecorder = nullptr;
308 }
309
localToDeviceTransform()310 const Transform& Device::localToDeviceTransform() {
311 if (this->checkLocalToDeviceDirty()) {
312 fCachedLocalToDevice = Transform{this->localToDevice44()};
313 }
314 return fCachedLocalToDevice;
315 }
316
strikeDeviceInfo() const317 SkStrikeDeviceInfo Device::strikeDeviceInfo() const {
318 return {this->surfaceProps(), this->scalerContextFlags(), &fSDFTControl};
319 }
320
onCreateDevice(const CreateInfo & info,const SkPaint *)321 SkBaseDevice* Device::onCreateDevice(const CreateInfo& info, const SkPaint*) {
322 // TODO: Inspect the paint and create info to determine if there's anything that has to be
323 // modified to support inline subpasses.
324 // TODO: onCreateDevice really should return sk_sp<SkBaseDevice>...
325 SkSurfaceProps props(this->surfaceProps().flags(), info.fPixelGeometry);
326
327 // Skia's convention is to only clear a device if it is non-opaque.
328 bool addInitialClear = !info.fInfo.isOpaque();
329
330 return Make(fRecorder,
331 info.fInfo,
332 skgpu::Budgeted::kYes,
333 Mipmapped::kNo,
334 props,
335 addInitialClear)
336 .release();
337 }
338
makeSurface(const SkImageInfo & ii,const SkSurfaceProps & props)339 sk_sp<SkSurface> Device::makeSurface(const SkImageInfo& ii, const SkSurfaceProps& props) {
340 return SkSurface::MakeGraphite(fRecorder, ii, Mipmapped::kNo, &props);
341 }
342
createCopy(const SkIRect * subset,Mipmapped mipmapped)343 TextureProxyView Device::createCopy(const SkIRect* subset, Mipmapped mipmapped) {
344 this->flushPendingWorkToRecorder();
345
346 TextureProxyView srcView = this->readSurfaceView();
347 if (!srcView) {
348 return {};
349 }
350
351 SkIRect srcRect = subset ? *subset : SkIRect::MakeSize(this->imageInfo().dimensions());
352 return TextureProxyView::Copy(this->recorder(),
353 this->imageInfo().colorInfo(),
354 srcView,
355 srcRect,
356 mipmapped);
357 }
358
Copy(Recorder * recorder,const SkColorInfo & srcColorInfo,const TextureProxyView & srcView,SkIRect srcRect,Mipmapped mipmapped)359 TextureProxyView TextureProxyView::Copy(Recorder* recorder,
360 const SkColorInfo& srcColorInfo,
361 const TextureProxyView& srcView,
362 SkIRect srcRect,
363 Mipmapped mipmapped) {
364 SkASSERT(SkIRect::MakeSize(srcView.proxy()->dimensions()).contains(srcRect));
365
366 sk_sp<TextureProxy> dest = TextureProxy::Make(recorder->priv().caps(),
367 srcRect.size(),
368 srcColorInfo.colorType(),
369 mipmapped,
370 srcView.proxy()->textureInfo().isProtected(),
371 Renderable::kNo,
372 skgpu::Budgeted::kNo);
373 if (!dest) {
374 return {};
375 }
376
377 sk_sp<CopyTextureToTextureTask> copyTask = CopyTextureToTextureTask::Make(srcView.refProxy(),
378 srcRect,
379 dest,
380 {0, 0});
381 if (!copyTask) {
382 return {};
383 }
384
385 recorder->priv().add(std::move(copyTask));
386
387 return { std::move(dest), srcView.swizzle() };
388 }
389
onReadPixels(const SkPixmap & pm,int srcX,int srcY)390 bool Device::onReadPixels(const SkPixmap& pm, int srcX, int srcY) {
391 #if GRAPHITE_TEST_UTILS
392 if (Context* context = fRecorder->priv().context()) {
393 this->flushPendingWorkToRecorder();
394 // Add all previous commands generated to the command buffer.
395 // If the client snaps later they'll only get post-read commands in their Recording,
396 // but since they're doing a readPixels in the middle that shouldn't be unexpected.
397 std::unique_ptr<Recording> recording = fRecorder->snap();
398 if (!recording) {
399 return false;
400 }
401 InsertRecordingInfo info;
402 info.fRecording = recording.get();
403 if (!context->insertRecording(info)) {
404 return false;
405 }
406 return context->priv().readPixels(pm, fDC->target(), this->imageInfo(), srcX, srcY);
407 }
408 #endif
409 // We have no access to a context to do a read pixels here.
410 return false;
411 }
412
asyncRescaleAndReadPixels(const SkImageInfo & info,SkIRect srcRect,RescaleGamma rescaleGamma,RescaleMode rescaleMode,ReadPixelsCallback callback,ReadPixelsContext context)413 void Device::asyncRescaleAndReadPixels(const SkImageInfo& info,
414 SkIRect srcRect,
415 RescaleGamma rescaleGamma,
416 RescaleMode rescaleMode,
417 ReadPixelsCallback callback,
418 ReadPixelsContext context) {
419 // Not supported for Graphite
420 callback(context, nullptr);
421 }
422
asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,sk_sp<SkColorSpace> dstColorSpace,SkIRect srcRect,SkISize dstSize,RescaleGamma rescaleGamma,RescaleMode rescaleMode,ReadPixelsCallback callback,ReadPixelsContext context)423 void Device::asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
424 sk_sp<SkColorSpace> dstColorSpace,
425 SkIRect srcRect,
426 SkISize dstSize,
427 RescaleGamma rescaleGamma,
428 RescaleMode rescaleMode,
429 ReadPixelsCallback callback,
430 ReadPixelsContext context) {
431 // TODO: implement for Graphite
432 callback(context, nullptr);
433 }
434
onWritePixels(const SkPixmap & src,int x,int y)435 bool Device::onWritePixels(const SkPixmap& src, int x, int y) {
436 // TODO: we may need to share this in a more central place to handle uploads
437 // to backend textures
438
439 const TextureProxy* target = fDC->target();
440
441 // TODO: add mipmap support for createBackendTexture
442
443 if (src.colorType() == kUnknown_SkColorType) {
444 return false;
445 }
446
447 // If one alpha type is unknown and the other isn't, it's too underspecified.
448 if ((src.alphaType() == kUnknown_SkAlphaType) !=
449 (this->imageInfo().alphaType() == kUnknown_SkAlphaType)) {
450 return false;
451 }
452
453 // TODO: check for readOnly or framebufferOnly target and return false if so
454
455 // TODO: canvas2DFastPath?
456 // TODO: check that surface supports writePixels
457 // TODO: handle writePixels as draw if needed (e.g., canvas2DFastPath || !supportsWritePixels)
458
459 // TODO: check for flips and either handle here or pass info to UploadTask
460
461 // Determine rect to copy
462 SkIRect dstRect = SkIRect::MakePtSize({x, y}, src.dimensions());
463 if (!target->isFullyLazy() && !dstRect.intersect(SkIRect::MakeSize(target->dimensions()))) {
464 return false;
465 }
466
467 // Set up copy location
468 const void* addr = src.addr(dstRect.fLeft - x, dstRect.fTop - y);
469 std::vector<MipLevel> levels;
470 levels.push_back({addr, src.rowBytes()});
471
472 this->flushPendingWorkToRecorder();
473
474 return fDC->recordUpload(fRecorder, sk_ref_sp(target), src.info().colorInfo(),
475 this->imageInfo().colorInfo(), levels, dstRect, nullptr);
476 }
477
478
479 ///////////////////////////////////////////////////////////////////////////////
480
onClipIsAA() const481 bool Device::onClipIsAA() const {
482 // All clips are AA'ed unless it's wide-open, empty, or a device-rect with integer coordinates
483 ClipStack::ClipState type = fClip.clipState();
484 if (type == ClipStack::ClipState::kWideOpen || type == ClipStack::ClipState::kEmpty) {
485 return false;
486 } else if (type == ClipStack::ClipState::kDeviceRect) {
487 const ClipStack::Element rect = *fClip.begin();
488 SkASSERT(rect.fShape.isRect() && rect.fLocalToDevice.type() == Transform::Type::kIdentity);
489 return rect.fShape.rect() != rect.fShape.rect().makeRoundOut();
490 } else {
491 return true;
492 }
493 }
494
onGetClipType() const495 SkBaseDevice::ClipType Device::onGetClipType() const {
496 ClipStack::ClipState state = fClip.clipState();
497 if (state == ClipStack::ClipState::kEmpty) {
498 return ClipType::kEmpty;
499 } else if (state == ClipStack::ClipState::kDeviceRect ||
500 state == ClipStack::ClipState::kWideOpen) {
501 return ClipType::kRect;
502 } else {
503 return ClipType::kComplex;
504 }
505 }
506
onDevClipBounds() const507 SkIRect Device::onDevClipBounds() const {
508 return rect_to_pixelbounds(fClip.conservativeBounds());
509 }
510
511 // TODO: This is easy enough to support, but do we still need this API in Skia at all?
onAsRgnClip(SkRegion * region) const512 void Device::onAsRgnClip(SkRegion* region) const {
513 SkIRect bounds = this->devClipBounds();
514 // Assume wide open and then perform intersect/difference operations reducing the region
515 region->setRect(bounds);
516 const SkRegion deviceBounds(bounds);
517 for (const ClipStack::Element& e : fClip) {
518 SkRegion tmp;
519 if (e.fShape.isRect() && e.fLocalToDevice.type() == Transform::Type::kIdentity) {
520 tmp.setRect(rect_to_pixelbounds(e.fShape.rect()));
521 } else {
522 SkPath tmpPath = e.fShape.asPath();
523 tmpPath.transform(e.fLocalToDevice);
524 tmp.setPath(tmpPath, deviceBounds);
525 }
526
527 region->op(tmp, (SkRegion::Op) e.fOp);
528 }
529 }
530
onClipRect(const SkRect & rect,SkClipOp op,bool aa)531 void Device::onClipRect(const SkRect& rect, SkClipOp op, bool aa) {
532 SkASSERT(op == SkClipOp::kIntersect || op == SkClipOp::kDifference);
533 // TODO: Snap rect edges to pixel bounds if non-AA and axis-aligned?
534 fClip.clipShape(this->localToDeviceTransform(), Shape{rect}, op);
535 }
536
onClipRRect(const SkRRect & rrect,SkClipOp op,bool aa)537 void Device::onClipRRect(const SkRRect& rrect, SkClipOp op, bool aa) {
538 SkASSERT(op == SkClipOp::kIntersect || op == SkClipOp::kDifference);
539 // TODO: Snap rrect edges to pixel bounds if non-AA and axis-aligned? Is that worth doing to
540 // seam with non-AA rects even if the curves themselves are AA'ed?
541 fClip.clipShape(this->localToDeviceTransform(), Shape{rrect}, op);
542 }
543
onClipPath(const SkPath & path,SkClipOp op,bool aa)544 void Device::onClipPath(const SkPath& path, SkClipOp op, bool aa) {
545 SkASSERT(op == SkClipOp::kIntersect || op == SkClipOp::kDifference);
546 // TODO: Ensure all path inspection is handled here or in SkCanvas, and that non-AA rects as
547 // paths are routed appropriately.
548 // TODO: Must also detect paths that are lines so the clip stack can be set to empty
549 fClip.clipShape(this->localToDeviceTransform(), Shape{path}, op);
550 }
551
onClipShader(sk_sp<SkShader> shader)552 void Device::onClipShader(sk_sp<SkShader> shader) {
553 fClip.clipShader(std::move(shader));
554 }
555
556 // TODO: Is clipRegion() on the deprecation chopping block. If not it should be...
onClipRegion(const SkRegion & globalRgn,SkClipOp op)557 void Device::onClipRegion(const SkRegion& globalRgn, SkClipOp op) {
558 SkASSERT(op == SkClipOp::kIntersect || op == SkClipOp::kDifference);
559
560 Transform globalToDevice{this->globalToDevice()};
561
562 if (globalRgn.isEmpty()) {
563 fClip.clipShape(globalToDevice, Shape{}, op);
564 } else if (globalRgn.isRect()) {
565 // TODO: Region clips are non-AA so this should match non-AA onClipRect(), but we use a
566 // different transform so can't just call that instead.
567 fClip.clipShape(globalToDevice, Shape{SkRect::Make(globalRgn.getBounds())}, op);
568 } else {
569 // TODO: Can we just iterate the region and do non-AA rects for each chunk?
570 SkPath path;
571 globalRgn.getBoundaryPath(&path);
572 fClip.clipShape(globalToDevice, Shape{path}, op);
573 }
574 }
575
onReplaceClip(const SkIRect & rect)576 void Device::onReplaceClip(const SkIRect& rect) {
577 // ReplaceClip() is currently not intended to be supported in Graphite since it's only used
578 // for emulating legacy clip ops in Android Framework, and apps/devices that require that
579 // should not use Graphite. However, if it needs to be supported, we could probably implement
580 // it by:
581 // 1. Flush all pending clip element depth draws.
582 // 2. Draw a fullscreen rect to the depth attachment using a Z value greater than what's
583 // been used so far.
584 // 3. Make sure all future "unclipped" draws use this Z value instead of 0 so they aren't
585 // sorted before the depth reset.
586 // 4. Make sure all prior elements are inactive so they can't affect subsequent draws.
587 //
588 // For now, just ignore it.
589 }
590
591 ///////////////////////////////////////////////////////////////////////////////
592
drawPaint(const SkPaint & paint)593 void Device::drawPaint(const SkPaint& paint) {
594 // We never want to do a fullscreen clear on a fully-lazy render target, because the device size
595 // may be smaller than the final surface we draw to, in which case we don't want to fill the
596 // entire final surface.
597 if (this->clipIsWideOpen() && !fDC->target()->isFullyLazy()) {
598 if (!paint_depends_on_dst(paint)) {
599 if (std::optional<SkColor4f> color = extract_paint_color(paint, fDC->colorInfo())) {
600 // do fullscreen clear
601 fDC->clear(*color);
602 return;
603 }
604 // TODO(michaelludwig): this paint doesn't depend on the destination, so we can reset
605 // the DrawContext to use a discard load op. The drawPaint will cover anything else
606 // entirely. We still need shader evaluation to get per-pixel colors (since the paint
607 // couldn't be reduced to a solid color).
608 }
609 }
610
611 const Transform& localToDevice = this->localToDeviceTransform();
612 if (!localToDevice.valid()) {
613 // TBD: This matches legacy behavior for drawPaint() that requires local coords, although
614 // v1 handles arbitrary transforms when the paint is solid color because it just fills the
615 // device bounds directly. In the new world it might be nice to have non-invertible
616 // transforms formalized (i.e. no drawing ever, handled at SkCanvas level possibly?)
617 return;
618 }
619 Rect localCoveringBounds = localToDevice.inverseMapRect(fClip.conservativeBounds());
620 this->drawGeometry(localToDevice, Geometry(Shape(localCoveringBounds)), paint, kFillStyle,
621 DrawFlags::kIgnorePathEffect | DrawFlags::kIgnoreMaskFilter);
622 }
623
drawRect(const SkRect & r,const SkPaint & paint)624 void Device::drawRect(const SkRect& r, const SkPaint& paint) {
625 this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(r)),
626 paint, SkStrokeRec(paint));
627 }
628
drawVertices(const SkVertices * vertices,sk_sp<SkBlender> blender,const SkPaint & paint,bool skipColorXform)629 void Device::drawVertices(const SkVertices* vertices, sk_sp<SkBlender> blender,
630 const SkPaint& paint, bool skipColorXform) {
631 // TODO - Add GPU handling of skipColorXform once Graphite has its color system more fleshed out.
632 this->drawGeometry(this->localToDeviceTransform(),
633 Geometry(sk_ref_sp(vertices)),
634 paint,
635 kFillStyle,
636 DrawFlags::kIgnorePathEffect | DrawFlags::kIgnoreMaskFilter,
637 std::move(blender),
638 skipColorXform);
639 }
640
drawOval(const SkRect & oval,const SkPaint & paint)641 void Device::drawOval(const SkRect& oval, const SkPaint& paint) {
642 // TODO: This has wasted effort from the SkCanvas level since it instead converts rrects that
643 // happen to be ovals into this, only for us to go right back to rrect.
644 this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(SkRRect::MakeOval(oval))),
645 paint, SkStrokeRec(paint));
646 }
647
drawRRect(const SkRRect & rr,const SkPaint & paint)648 void Device::drawRRect(const SkRRect& rr, const SkPaint& paint) {
649 this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(rr)),
650 paint, SkStrokeRec(paint));
651 }
652
drawPath(const SkPath & path,const SkPaint & paint,bool pathIsMutable)653 void Device::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
654 // TODO: If we do try to inspect the path, it should happen here and possibly after computing
655 // the path effect. Alternatively, all that should be handled in SkCanvas.
656 this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(path)),
657 paint, SkStrokeRec(paint));
658 }
659
drawPoints(SkCanvas::PointMode mode,size_t count,const SkPoint * points,const SkPaint & paint)660 void Device::drawPoints(SkCanvas::PointMode mode, size_t count,
661 const SkPoint* points, const SkPaint& paint) {
662 // TODO: I'm [ml] not sure either CPU or GPU backend really has a fast path for this that
663 // isn't captured by drawOval and drawLine, so could easily be moved into SkCanvas.
664 if (mode == SkCanvas::kPoints_PointMode) {
665 float radius = 0.5f * paint.getStrokeWidth();
666 for (size_t i = 0; i < count; ++i) {
667 SkRect pointRect = SkRect::MakeLTRB(points[i].fX - radius, points[i].fY - radius,
668 points[i].fX + radius, points[i].fY + radius);
669 // drawOval/drawRect with a forced fill style
670 if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
671 this->drawGeometry(this->localToDeviceTransform(),
672 Geometry(Shape(SkRRect::MakeOval(pointRect))),
673 paint, kFillStyle);
674 } else {
675 this->drawGeometry(this->localToDeviceTransform(), Geometry(Shape(pointRect)),
676 paint, kFillStyle);
677 }
678 }
679 } else {
680 // Force the style to be a stroke, using the radius and cap from the paint
681 SkStrokeRec stroke(paint, SkPaint::kStroke_Style);
682 size_t inc = (mode == SkCanvas::kLines_PointMode) ? 2 : 1;
683 for (size_t i = 0; i < count-1; i += inc) {
684 this->drawGeometry(this->localToDeviceTransform(),
685 Geometry(Shape(points[i], points[i + 1])),
686 paint, stroke);
687 }
688 }
689 }
690
drawEdgeAAQuad(const SkRect & rect,const SkPoint clip[4],SkCanvas::QuadAAFlags aaFlags,const SkColor4f & color,SkBlendMode mode)691 void Device::drawEdgeAAQuad(const SkRect& rect,
692 const SkPoint clip[4],
693 SkCanvas::QuadAAFlags aaFlags,
694 const SkColor4f& color,
695 SkBlendMode mode) {
696 SkPaint solidColorPaint;
697 solidColorPaint.setColor4f(color, /*colorSpace=*/nullptr);
698 solidColorPaint.setBlendMode(mode);
699
700 auto flags = SkEnumBitMask<EdgeAAQuad::Flags>(static_cast<EdgeAAQuad::Flags>(aaFlags));
701 EdgeAAQuad quad = clip ? EdgeAAQuad(clip, flags) : EdgeAAQuad(rect, flags);
702 this->drawGeometry(this->localToDeviceTransform(), Geometry(quad), solidColorPaint, kFillStyle,
703 DrawFlags::kIgnoreMaskFilter | DrawFlags::kIgnorePathEffect);
704 }
705
drawEdgeAAImageSet(const SkCanvas::ImageSetEntry[],int count,const SkPoint dstClips[],const SkMatrix preViewMatrices[],const SkSamplingOptions &,const SkPaint &,SkCanvas::SrcRectConstraint)706 void Device::drawEdgeAAImageSet(const SkCanvas::ImageSetEntry[], int count,
707 const SkPoint dstClips[], const SkMatrix preViewMatrices[],
708 const SkSamplingOptions&, const SkPaint&,
709 SkCanvas::SrcRectConstraint) {
710 // TODO: Implement this by merging the logic of drawImageRect and drawEdgeAAQuad.
711 }
712
drawImageRect(const SkImage * image,const SkRect * src,const SkRect & dst,const SkSamplingOptions & sampling,const SkPaint & paint,SkCanvas::SrcRectConstraint constraint)713 void Device::drawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst,
714 const SkSamplingOptions& sampling, const SkPaint& paint,
715 SkCanvas::SrcRectConstraint constraint) {
716 SkASSERT(dst.isFinite());
717 SkASSERT(dst.isSorted());
718
719 // TODO: All of this logic should be handled in SkCanvas, since it's the same for every backend
720 SkRect tmpSrc, tmpDst = dst;
721 SkRect imgBounds = SkRect::Make(image->bounds());
722
723 if (src) {
724 tmpSrc = *src;
725 } else {
726 tmpSrc = SkRect::Make(image->bounds());
727 }
728 SkMatrix matrix = SkMatrix::RectToRect(tmpSrc, dst);
729
730 // clip the tmpSrc to the bounds of the image, and recompute the dest rect if
731 // needed (i.e., if the src was clipped). No check needed if src==null.
732 if (src) {
733 if (!imgBounds.contains(tmpSrc)) {
734 if (!tmpSrc.intersect(imgBounds)) {
735 return; // nothing to draw
736 }
737 // recompute dst, based on the smaller tmpSrc
738 matrix.mapRect(&tmpDst, tmpSrc);
739 if (!tmpDst.isFinite()) {
740 return;
741 }
742 }
743 }
744
745 auto [ imageToDraw, newSampling ] = skgpu::graphite::GetGraphiteBacked(this->recorder(),
746 image, sampling);
747 if (!imageToDraw) {
748 SKGPU_LOG_W("Device::drawImageRect: Creation of Graphite-backed image failed");
749 return;
750 }
751
752 SkPaint paintWithShader(paint);
753 if (!create_img_shader_paint(std::move(imageToDraw), tmpSrc, newSampling,
754 &matrix, &paintWithShader)) {
755 return;
756 }
757
758 this->drawRect(tmpDst, paintWithShader);
759 }
760
onDrawGlyphRunList(SkCanvas * canvas,const sktext::GlyphRunList & glyphRunList,const SkPaint & initialPaint,const SkPaint & drawingPaint)761 void Device::onDrawGlyphRunList(SkCanvas* canvas,
762 const sktext::GlyphRunList& glyphRunList,
763 const SkPaint& initialPaint,
764 const SkPaint& drawingPaint) {
765 fRecorder->priv().textBlobCache()->drawGlyphRunList(canvas,
766 this->localToDevice(),
767 glyphRunList,
768 drawingPaint,
769 this->strikeDeviceInfo(),
770 this);
771 }
772
drawAtlasSubRun(const sktext::gpu::AtlasSubRun * subRun,SkPoint drawOrigin,const SkPaint & paint,sk_sp<SkRefCnt> subRunStorage)773 void Device::drawAtlasSubRun(const sktext::gpu::AtlasSubRun* subRun,
774 SkPoint drawOrigin,
775 const SkPaint& paint,
776 sk_sp<SkRefCnt> subRunStorage) {
777 const int subRunEnd = subRun->glyphCount();
778 for (int subRunCursor = 0; subRunCursor < subRunEnd;) {
779 // For the remainder of the run, add any atlas uploads to the Recorder's AtlasManager
780 auto[ok, glyphsRegenerated] = subRun->regenerateAtlas(subRunCursor, subRunEnd, fRecorder);
781 // There was a problem allocating the glyph in the atlas. Bail.
782 if (!ok) {
783 return;
784 }
785 if (glyphsRegenerated) {
786 auto [bounds, localToDevice] = subRun->boundsAndDeviceMatrix(
787 this->localToDeviceTransform(), drawOrigin);
788 SkPaint subRunPaint = paint;
789 // For color emoji, only the paint alpha affects the final color
790 if (subRun->maskFormat() == skgpu::MaskFormat::kARGB) {
791 subRunPaint.setColor(SK_ColorWHITE);
792 subRunPaint.setAlphaf(paint.getAlphaf());
793 }
794 this->drawGeometry(localToDevice,
795 Geometry(SubRunData(subRun, subRunStorage, bounds, subRunCursor,
796 glyphsRegenerated, fRecorder)),
797 subRunPaint,
798 kFillStyle,
799 DrawFlags::kIgnorePathEffect | DrawFlags::kIgnoreMaskFilter);
800 }
801 subRunCursor += glyphsRegenerated;
802
803 if (subRunCursor < subRunEnd) {
804 // Flush if not all the glyphs are handled because the atlas is out of space.
805 // We flush every Device because the glyphs that are being flushed/referenced are not
806 // necessarily specific to this Device. This addresses both multiple SkSurfaces within
807 // a Recorder, and nested layers.
808 ATRACE_ANDROID_FRAMEWORK_ALWAYS("Atlas full");
809 fRecorder->priv().flushTrackedDevices();
810 }
811 }
812 }
813
drawGeometry(const Transform & localToDevice,const Geometry & geometry,const SkPaint & paint,const SkStrokeRec & style,SkEnumBitMask<DrawFlags> flags,sk_sp<SkBlender> primitiveBlender,bool skipColorXform)814 void Device::drawGeometry(const Transform& localToDevice,
815 const Geometry& geometry,
816 const SkPaint& paint,
817 const SkStrokeRec& style,
818 SkEnumBitMask<DrawFlags> flags,
819 sk_sp<SkBlender> primitiveBlender,
820 bool skipColorXform) {
821 if (!localToDevice.valid()) {
822 // If the transform is not invertible or not finite then drawing isn't well defined.
823 SKGPU_LOG_W("Skipping draw with non-invertible/non-finite transform.");
824 return;
825 }
826
827 // Heavy weight paint options like path effects, mask filters, and stroke-and-fill style are
828 // applied on the CPU by generating a new shape and recursing on drawShape() with updated flags
829 if (!(flags & DrawFlags::kIgnorePathEffect) && paint.getPathEffect()) {
830 // Apply the path effect before anything else, which if we are applying here, means that we
831 // are dealing with a Shape. drawVertices (and a SkVertices geometry) should pass in
832 // kIgnorePathEffect per SkCanvas spec. Text geometry also should pass in kIgnorePathEffect
833 // because the path effect is applied per glyph by the SkStrikeSpec already.
834 SkASSERT(geometry.isShape());
835
836 // TODO: If asADash() returns true and the base path matches the dashing fast path, then
837 // that should be detected now as well. Maybe add dashPath to Device so canvas can handle it
838 SkStrokeRec newStyle = style;
839 newStyle.setResScale(localToDevice.maxScaleFactor());
840 SkPath dst;
841 if (paint.getPathEffect()->filterPath(&dst, geometry.shape().asPath(), &newStyle,
842 nullptr, localToDevice)) {
843 // Recurse using the path and new style, while disabling downstream path effect handling
844 this->drawGeometry(localToDevice, Geometry(Shape(dst)), paint, newStyle,
845 flags | DrawFlags::kIgnorePathEffect, std::move(primitiveBlender),
846 skipColorXform);
847 return;
848 } else {
849 SKGPU_LOG_W("Path effect failed to apply, drawing original path.");
850 this->drawGeometry(localToDevice, geometry, paint, style,
851 flags | DrawFlags::kIgnorePathEffect, std::move(primitiveBlender),
852 skipColorXform);
853 return;
854 }
855 }
856
857 if (!(flags & DrawFlags::kIgnoreMaskFilter) && paint.getMaskFilter()) {
858 // TODO: Handle mask filters, ignored for the sprint.
859 // TODO: Could this be handled by SkCanvas by drawing a mask, blurring, and then sampling
860 // with a rect draw? What about fast paths for rrect blur masks...
861 this->drawGeometry(localToDevice, geometry, paint, style,
862 flags | DrawFlags::kIgnoreMaskFilter, std::move(primitiveBlender),
863 skipColorXform);
864 return;
865 }
866
867 // TODO: The tessellating path renderers haven't implemented perspective yet, so transform to
868 // device space so we draw something approximately correct (barring local coord issues).
869 if (geometry.isShape() && localToDevice.type() == Transform::Type::kProjection &&
870 !is_simple_shape(geometry.shape(), style.getStyle())) {
871 SkPath devicePath = geometry.shape().asPath();
872 devicePath.transform(localToDevice.matrix().asM33());
873 this->drawGeometry(Transform::Identity(), Geometry(Shape(devicePath)), paint, style, flags,
874 std::move(primitiveBlender), skipColorXform);
875 return;
876 }
877
878 // TODO: Manually snap pixels for rects, rrects, and lines if paint is non-AA (ideally also
879 // consider snapping stroke width and/or adjusting geometry for hairlines). This pixel snapping
880 // math should be consistent with how non-AA clip [r]rects are handled.
881
882 // If we got here, then path effects and mask filters should have been handled and the style
883 // should be fill or stroke/hairline. Stroke-and-fill is not handled by DrawContext, but is
884 // emulated here by drawing twice--one stroke and one fill--using the same depth value.
885 SkASSERT(!SkToBool(paint.getPathEffect()) || (flags & DrawFlags::kIgnorePathEffect));
886 SkASSERT(!SkToBool(paint.getMaskFilter()) || (flags & DrawFlags::kIgnoreMaskFilter));
887
888 // Check if we have room to record into the current list before determining clipping and order
889 SkStrokeRec::Style styleType = style.getStyle();
890 if (this->needsFlushBeforeDraw(styleType == SkStrokeRec::kStrokeAndFill_Style ? 2 : 1)) {
891 this->flushPendingWorkToRecorder();
892 }
893
894 DrawOrder order(fCurrentDepth.next());
895 auto [clip, clipOrder] = fClip.applyClipToDraw(
896 fColorDepthBoundsManager.get(), localToDevice, geometry, style, order.depth());
897 if (clip.drawBounds().isEmptyNegativeOrNaN()) {
898 // Clipped out, so don't record anything
899 return;
900 }
901 // Some Renderer decisions are based on estimated fill rate, which requires the clipped bounds.
902 // Since the fallbacks shouldn't change the bounds of the draw, it's okay to have evaluated the
903 // clip stack before calling ChooseRenderer.
904 const Renderer* renderer = this->chooseRenderer(geometry, clip, style, /*requireMSAA=*/false);
905 if (!renderer) {
906 SKGPU_LOG_W("Skipping draw with no supported renderer.");
907 return;
908 }
909
910 #if defined(SK_DEBUG)
911 // Renderers and their component RenderSteps have flexibility in defining their
912 // DepthStencilSettings. However, the clipping and ordering managed between Device and ClipStack
913 // requires that only GREATER or GEQUAL depth tests are used for draws recorded through the
914 // client-facing, painters-order-oriented API. We assert here vs. in Renderer's constructor to
915 // allow internal-oriented Renderers that are never selected for a "regular" draw call to have
916 // more flexibility in their settings.
917 for (const RenderStep* step : renderer->steps()) {
918 auto dss = step->depthStencilSettings();
919 SkASSERT((!step->performsShading() || dss.fDepthTestEnabled) &&
920 (!dss.fDepthTestEnabled ||
921 dss.fDepthCompareOp == CompareOp::kGreater ||
922 dss.fDepthCompareOp == CompareOp::kGEqual));
923 }
924 #endif
925
926 // A draw's order always depends on the clips that must be drawn before it
927 order.dependsOnPaintersOrder(clipOrder);
928
929 // A primitive blender should be ignored if there is no primitive color to blend against.
930 // Additionally, if a renderer emits a primitive color, then a null primitive blender should
931 // be interpreted as SrcOver blending mode.
932 if (!renderer->emitsPrimitiveColor()) {
933 primitiveBlender = nullptr;
934 } else if (!SkToBool(primitiveBlender)) {
935 primitiveBlender = SkBlender::Mode(SkBlendMode::kSrcOver);
936 }
937
938 // If a draw is not opaque, it must be drawn after the most recent draw it intersects with in
939 // order to blend correctly. We always query the most recent draw (even when opaque) because it
940 // also lets Device easily track whether or not there are any overlapping draws.
941 PaintParams shading{paint, std::move(primitiveBlender), skipColorXform};
942 const bool dependsOnDst = renderer->emitsCoverage() || paint_depends_on_dst(shading);
943 CompressedPaintersOrder prevDraw =
944 fColorDepthBoundsManager->getMostRecentDraw(clip.drawBounds());
945 if (dependsOnDst) {
946 order.dependsOnPaintersOrder(prevDraw);
947 }
948
949 // Now that the base paint order and draw bounds are finalized, if the Renderer relies on the
950 // stencil attachment, we compute a secondary sorting field to allow disjoint draws to reorder
951 // the RenderSteps across draws instead of in sequence for each draw.
952 if (renderer->depthStencilFlags() & DepthStencilFlags::kStencil) {
953 DisjointStencilIndex setIndex = fDisjointStencilSet->add(order.paintOrder(),
954 clip.drawBounds());
955 order.dependsOnStencil(setIndex);
956 }
957
958 if (styleType == SkStrokeRec::kStroke_Style ||
959 styleType == SkStrokeRec::kHairline_Style ||
960 styleType == SkStrokeRec::kStrokeAndFill_Style) {
961 // For stroke-and-fill, 'renderer' is used for the fill and we always use the
962 // TessellatedStrokes renderer; for stroke and hairline, 'renderer' is used.
963 StrokeStyle stroke(style.getWidth(), style.getMiter(), style.getJoin(), style.getCap());
964 fDC->recordDraw(styleType == SkStrokeRec::kStrokeAndFill_Style
965 ? fRecorder->priv().rendererProvider()->tessellatedStrokes()
966 : renderer,
967 localToDevice, geometry, clip, order, &shading, &stroke);
968 }
969 if (styleType == SkStrokeRec::kFill_Style ||
970 styleType == SkStrokeRec::kStrokeAndFill_Style) {
971 fDC->recordDraw(renderer, localToDevice, geometry, clip, order, &shading, nullptr);
972 }
973
974 // TODO: If 'fullyOpaque' is true, it might be useful to store the draw bounds and Z in a
975 // special occluders list for filtering the DrawList/DrawPass when flushing.
976 // const bool fullyOpaque = !dependsOnDst &&
977 // clipOrder == DrawOrder::kNoIntersection &&
978 // shape.isRect() &&
979 // localToDevice.type() <= Transform::Type::kRectStaysRect;
980
981 // Post-draw book keeping (bounds manager, depth tracking, etc.)
982 fColorDepthBoundsManager->recordDraw(clip.drawBounds(), order.paintOrder());
983 fCurrentDepth = order.depth();
984 fDrawsOverlap |= (prevDraw != DrawOrder::kNoIntersection);
985 }
986
drawClipShape(const Transform & localToDevice,const Shape & shape,const Clip & clip,DrawOrder order)987 void Device::drawClipShape(const Transform& localToDevice,
988 const Shape& shape,
989 const Clip& clip,
990 DrawOrder order) {
991 // This call represents one of the deferred clip shapes that's already pessimistically counted
992 // in needsFlushBeforeDraw(), so the DrawContext should have room to add it.
993 SkASSERT(fDC->pendingDrawCount() + 1 < DrawList::kMaxDraws);
994
995 // A clip draw's state is almost fully defined by the ClipStack. The only thing we need
996 // to account for is selecting a Renderer and tracking the stencil buffer usage.
997 Geometry geometry{shape};
998 const Renderer* renderer = this->chooseRenderer(geometry, clip, kFillStyle,
999 /*requireMSAA=*/true);
1000 if (!renderer) {
1001 SKGPU_LOG_W("Skipping clip with no supported path renderer.");
1002 return;
1003 } else if (renderer->depthStencilFlags() & DepthStencilFlags::kStencil) {
1004 DisjointStencilIndex setIndex = fDisjointStencilSet->add(order.paintOrder(),
1005 clip.drawBounds());
1006 order.dependsOnStencil(setIndex);
1007 }
1008 // Anti-aliased clipping requires the renderer to use MSAA to modify the depth per sample, so
1009 // analytic coverage renderers cannot be used.
1010 SkASSERT(!renderer->emitsCoverage() && renderer->requiresMSAA());
1011
1012 // Clips draws are depth-only (null PaintParams), and filled (null StrokeStyle).
1013 // TODO: Remove this CPU-transform once perspective is supported for all path renderers
1014 if (localToDevice.type() == Transform::Type::kProjection) {
1015 SkPath devicePath = geometry.shape().asPath();
1016 devicePath.transform(localToDevice.matrix().asM33());
1017 fDC->recordDraw(renderer, Transform::Identity(), Geometry(Shape(devicePath)), clip, order,
1018 nullptr, nullptr);
1019 } else {
1020 fDC->recordDraw(renderer, localToDevice, geometry, clip, order, nullptr, nullptr);
1021 }
1022 // This ensures that draws recorded after this clip shape has been popped off the stack will
1023 // be unaffected by the Z value the clip shape wrote to the depth attachment.
1024 if (order.depth() > fCurrentDepth) {
1025 fCurrentDepth = order.depth();
1026 }
1027 }
1028
1029 // TODO: Currently all Renderers are always defined, but with config options and caps that may not
1030 // be the case, in which case chooseRenderer() will have to go through compatible choices.
chooseRenderer(const Geometry & geometry,const Clip & clip,const SkStrokeRec & style,bool requireMSAA) const1031 const Renderer* Device::chooseRenderer(const Geometry& geometry,
1032 const Clip& clip,
1033 const SkStrokeRec& style,
1034 bool requireMSAA) const {
1035 const RendererProvider* renderers = fRecorder->priv().rendererProvider();
1036 SkStrokeRec::Style type = style.getStyle();
1037
1038 if (geometry.isSubRun()) {
1039 SkASSERT(!requireMSAA);
1040 return geometry.subRunData().subRun()->renderer(renderers);
1041 } else if (geometry.isVertices()) {
1042 SkVerticesPriv info(geometry.vertices()->priv());
1043 return renderers->vertices(info.mode(), info.hasColors(), info.hasTexCoords());
1044 } else if (geometry.isEdgeAAQuad()) {
1045 SkASSERT(!requireMSAA && style.isFillStyle());
1046 return renderers->analyticRRect(); // handled by the same system as rects and round rects
1047 } else if (!geometry.isShape()) {
1048 // We must account for new Geometry types with specific Renderers
1049 return nullptr;
1050 }
1051
1052 const Shape& shape = geometry.shape();
1053 // We can't use this renderer if we require MSAA for an effect (i.e. clipping or stroke+fill).
1054 if (!requireMSAA && is_simple_shape(shape, type)) {
1055 return renderers->analyticRRect();
1056 }
1057
1058 // If we got here, it requires tessellated path rendering or an MSAA technique applied to a
1059 // simple shape (so we interpret them as paths to reduce the number of pipelines we need).
1060
1061 // TODO: All shapes that select a tessellating path renderer need to be "pre-chopped" if they
1062 // are large enough to exceed the fixed count tessellation limits. Fills are pre-chopped to the
1063 // viewport bounds, strokes and stroke-and-fills are pre-chopped to the viewport bounds outset
1064 // by the stroke radius (hence taking the whole style and not just its type).
1065
1066 if (type == SkStrokeRec::kStroke_Style ||
1067 type == SkStrokeRec::kHairline_Style) {
1068 // Unlike in Ganesh, the HW stroke tessellator can work with arbitrary paints since the
1069 // depth test prevents double-blending when there is transparency, thus we can HW stroke
1070 // any path regardless of its paint.
1071 // TODO: We treat inverse-filled strokes as regular strokes. We could handle them by
1072 // stenciling first with the HW stroke tessellator and then covering their bounds, but
1073 // inverse-filled strokes are not well-specified in our public canvas behavior so we may be
1074 // able to remove it.
1075 return renderers->tessellatedStrokes();
1076 }
1077
1078 // 'type' could be kStrokeAndFill, but in that case chooseRenderer() is meant to return the
1079 // fill renderer since tessellatedStrokes() will always be used for the stroke pass.
1080 if (shape.convex() && !shape.inverted()) {
1081 // TODO: Ganesh doesn't have a curve+middle-out triangles option for convex paths, but it
1082 // would be pretty trivial to spin up.
1083 return renderers->convexTessellatedWedges();
1084 } else {
1085 // TODO: Combine this heuristic with what is used in PathStencilCoverOp to choose between
1086 // wedges curves consistently in Graphite and Ganesh.
1087 const bool preferWedges = (shape.isPath() && shape.path().countVerbs() < 50) ||
1088 clip.drawBounds().area() <= (256 * 256);
1089
1090 if (preferWedges) {
1091 return renderers->stencilTessellatedWedges(shape.fillType());
1092 } else {
1093 return renderers->stencilTessellatedCurvesAndTris(shape.fillType());
1094 }
1095 }
1096 }
1097
flushPendingWorkToRecorder()1098 void Device::flushPendingWorkToRecorder() {
1099 SkASSERT(fRecorder);
1100
1101 // TODO: we may need to further split this function up since device->device drawList and
1102 // DrawPass stealing will need to share some of the same logic w/o becoming a Task.
1103
1104 // push any pending uploads from the atlasmanager
1105 auto atlasManager = fRecorder->priv().atlasManager();
1106 if (!fDC->recordTextUploads(atlasManager)) {
1107 SKGPU_LOG_E("AtlasManager uploads have failed -- may see invalid results.");
1108 }
1109
1110 auto uploadTask = fDC->snapUploadTask(fRecorder);
1111 if (uploadTask) {
1112 fRecorder->priv().add(std::move(uploadTask));
1113 }
1114
1115 #ifdef SK_ENABLE_PIET_GPU
1116 auto pietTask = fDC->snapPietRenderTask(fRecorder);
1117 if (pietTask) {
1118 fRecorder->priv().add(std::move(pietTask));
1119 }
1120 #endif
1121
1122 fClip.recordDeferredClipDraws();
1123 auto drawTask = fDC->snapRenderPassTask(fRecorder);
1124 if (drawTask) {
1125 fRecorder->priv().add(std::move(drawTask));
1126 }
1127
1128 // Reset accumulated state tracking since everything that it referred to has been moved into
1129 // an immutable DrawPass.
1130 fColorDepthBoundsManager->reset();
1131 fDisjointStencilSet->reset();
1132 fCurrentDepth = DrawOrder::kClearDepth;
1133 // NOTE: fDrawsOverlap is not reset here because that is a persistent property of everything
1134 // drawn into the Device, and not just the currently accumulating pass.
1135 }
1136
needsFlushBeforeDraw(int numNewDraws) const1137 bool Device::needsFlushBeforeDraw(int numNewDraws) const {
1138 // Must also account for the elements in the clip stack that might need to be recorded.
1139 numNewDraws += fClip.maxDeferredClipDraws();
1140 return (DrawList::kMaxDraws - fDC->pendingDrawCount()) < numNewDraws;
1141 }
1142
drawDevice(SkBaseDevice * device,const SkSamplingOptions & sampling,const SkPaint & paint)1143 void Device::drawDevice(SkBaseDevice* device,
1144 const SkSamplingOptions& sampling,
1145 const SkPaint& paint) {
1146 this->SkBaseDevice::drawDevice(device, sampling, paint);
1147 }
1148
drawSpecial(SkSpecialImage * special,const SkMatrix & localToDevice,const SkSamplingOptions & sampling,const SkPaint & paint)1149 void Device::drawSpecial(SkSpecialImage* special,
1150 const SkMatrix& localToDevice,
1151 const SkSamplingOptions& sampling,
1152 const SkPaint& paint) {
1153 SkASSERT(!paint.getMaskFilter() && !paint.getImageFilter());
1154
1155 sk_sp<SkImage> img = special->asImage();
1156 if (!img) {
1157 SKGPU_LOG_W("Couldn't get Graphite-backed special image as image");
1158 return;
1159 }
1160
1161 // TODO: remove this check once Graphite has image filter support.
1162 if (!img->isTextureBacked()) {
1163 return;
1164 }
1165
1166 SkRect src = SkRect::Make(special->subset());
1167 SkRect dst = SkRect::MakeWH(special->width(), special->height());
1168 SkMatrix srcToDst = SkMatrix::RectToRect(src, dst);
1169 SkASSERT(srcToDst.isTranslate());
1170
1171 SkPaint paintWithShader(paint);
1172 if (!create_img_shader_paint(std::move(img), src, sampling, &srcToDst, &paintWithShader)) {
1173 return;
1174 }
1175
1176 this->drawGeometry(Transform(SkM44(localToDevice)),
1177 Geometry(Shape(dst)),
1178 paintWithShader,
1179 kFillStyle,
1180 DrawFlags::kIgnorePathEffect | DrawFlags::kIgnoreMaskFilter);
1181 }
1182
makeSpecial(const SkBitmap &)1183 sk_sp<SkSpecialImage> Device::makeSpecial(const SkBitmap&) {
1184 return nullptr;
1185 }
1186
makeSpecial(const SkImage *)1187 sk_sp<SkSpecialImage> Device::makeSpecial(const SkImage*) {
1188 return nullptr;
1189 }
1190
snapSpecial(const SkIRect & subset,bool forceCopy)1191 sk_sp<SkSpecialImage> Device::snapSpecial(const SkIRect& subset, bool forceCopy) {
1192 this->flushPendingWorkToRecorder();
1193
1194 SkIRect finalSubset = subset;
1195 TextureProxyView view = fDC->readSurfaceView(fRecorder->priv().caps());
1196 if (forceCopy || !view) {
1197 // TODO: fill this in. 'forceCopy' is only true for backdrop saveLayers. A non-readable
1198 // surface view could happen any time though.
1199 return nullptr;
1200 }
1201
1202 return SkSpecialImage::MakeGraphite(fRecorder,
1203 finalSubset,
1204 kNeedNewImageUniqueID_SpecialImage,
1205 std::move(view),
1206 this->imageInfo().colorInfo(),
1207 this->surfaceProps());
1208 }
1209
target()1210 TextureProxy* Device::target() { return fDC->target(); }
1211
readSurfaceView() const1212 TextureProxyView Device::readSurfaceView() const {
1213 if (!fRecorder) {
1214 return {};
1215 }
1216 return fDC->readSurfaceView(fRecorder->priv().caps());
1217 }
1218
1219 } // namespace skgpu::graphite
1220