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 "experimental/graphite/src/Device.h"
9
10 #include "experimental/graphite/include/Context.h"
11 #include "experimental/graphite/include/Recorder.h"
12 #include "experimental/graphite/include/Recording.h"
13 #include "experimental/graphite/include/SkStuff.h"
14 #include "experimental/graphite/src/Buffer.h"
15 #include "experimental/graphite/src/Caps.h"
16 #include "experimental/graphite/src/ContextPriv.h"
17 #include "experimental/graphite/src/CopyTask.h"
18 #include "experimental/graphite/src/DrawContext.h"
19 #include "experimental/graphite/src/DrawList.h"
20 #include "experimental/graphite/src/Gpu.h"
21 #include "experimental/graphite/src/Log.h"
22 #include "experimental/graphite/src/RecorderPriv.h"
23 #include "experimental/graphite/src/ResourceProvider.h"
24 #include "experimental/graphite/src/Texture.h"
25 #include "experimental/graphite/src/TextureProxy.h"
26 #include "experimental/graphite/src/geom/BoundsManager.h"
27 #include "experimental/graphite/src/geom/IntersectionTree.h"
28 #include "experimental/graphite/src/geom/Shape.h"
29 #include "experimental/graphite/src/geom/Transform_graphite.h"
30
31 #include "include/core/SkPath.h"
32 #include "include/core/SkPathEffect.h"
33 #include "include/core/SkStrokeRec.h"
34
35 #include "src/core/SkConvertPixels.h"
36 #include "src/core/SkMatrixPriv.h"
37 #include "src/core/SkPaintPriv.h"
38 #include "src/core/SkSpecialImage.h"
39
40 #include <unordered_map>
41 #include <vector>
42
43 namespace skgpu {
44
45 namespace {
46
47 static const SkStrokeRec kFillStyle(SkStrokeRec::kFill_InitStyle);
48
paint_depends_on_dst(const PaintParams & paintParams)49 bool paint_depends_on_dst(const PaintParams& paintParams) {
50 std::optional<SkBlendMode> bm = paintParams.asBlendMode();
51 if (!bm.has_value()) {
52 return true;
53 }
54
55 if (bm.value() == SkBlendMode::kSrc || bm.value() == SkBlendMode::kClear) {
56 // src and clear blending never depends on dst
57 return false;
58 } else if (bm.value() == SkBlendMode::kSrcOver) {
59 // src-over does not depend on dst if src is opaque (a = 1)
60 // TODO: This will get more complicated when PaintParams has color filters and blenders
61 return !paintParams.color().isOpaque() ||
62 (paintParams.shader() && !paintParams.shader()->isOpaque());
63 } else {
64 // TODO: Are their other modes that don't depend on dst that can be trivially detected?
65 return true;
66 }
67 }
68
69 } // anonymous namespace
70
71 /**
72 * IntersectionTreeSet controls multiple IntersectionTrees to organize all add rectangles into
73 * disjoint sets. For a given CompressedPaintersOrder and bounds, it returns the smallest
74 * DisjointStencilIndex that guarantees the bounds are disjoint from all other draws that use the
75 * same painters order and stencil index.
76 */
77 class Device::IntersectionTreeSet {
78 public:
79 IntersectionTreeSet() = default;
80
add(CompressedPaintersOrder drawOrder,Rect rect)81 DisjointStencilIndex add(CompressedPaintersOrder drawOrder, Rect rect) {
82 auto& trees = fTrees[drawOrder];
83 DisjointStencilIndex stencil = DrawOrder::kUnassigned.next();
84 for (auto&& tree : trees) {
85 if (tree->add(rect)) {
86 return stencil;
87 }
88 stencil = stencil.next(); // advance to the next tree's index
89 }
90
91 // If here, no existing intersection tree can hold the rect so add a new one
92 IntersectionTree* newTree = this->makeTree();
93 SkAssertResult(newTree->add(rect));
94 trees.push_back(newTree);
95 return stencil;
96 }
97
reset()98 void reset() {
99 fTrees.clear();
100 fTreeStore.reset();
101 }
102
103 private:
104 struct Hash {
operator ()skgpu::Device::IntersectionTreeSet::Hash105 size_t operator()(const CompressedPaintersOrder& o) const noexcept { return o.bits(); }
106 };
107
makeTree()108 IntersectionTree* makeTree() {
109 return fTreeStore.make<IntersectionTree>();
110 }
111
112 // Each compressed painters order defines a barrier around draws so each order's set of draws
113 // are independent, even if they may intersect. Within each order, the list of trees holds the
114 // IntersectionTrees representing each disjoint set.
115 // TODO: This organization of trees is logically convenient but may need to be optimized based
116 // on real world data (e.g. how sparse is the map, how long is each vector of trees,...)
117 std::unordered_map<CompressedPaintersOrder, std::vector<IntersectionTree*>, Hash> fTrees;
118 SkSTArenaAllocWithReset<4 * sizeof(IntersectionTree)> fTreeStore;
119 };
120
Make(Recorder * recorder,const SkImageInfo & ii)121 sk_sp<Device> Device::Make(Recorder* recorder, const SkImageInfo& ii) {
122 if (!recorder) {
123 return nullptr;
124 }
125 auto textureInfo = recorder->priv().caps()->getDefaultSampledTextureInfo(ii.colorType(),
126 /*levelCount=*/1,
127 Protected::kNo,
128 Renderable::kYes);
129 sk_sp<TextureProxy> target(new TextureProxy(ii.dimensions(), textureInfo));
130 return Make(recorder,
131 std::move(target),
132 ii.refColorSpace(),
133 ii.colorType(),
134 ii.alphaType());
135 }
136
Make(Recorder * recorder,sk_sp<TextureProxy> target,sk_sp<SkColorSpace> colorSpace,SkColorType colorType,SkAlphaType alphaType)137 sk_sp<Device> Device::Make(Recorder* recorder,
138 sk_sp<TextureProxy> target,
139 sk_sp<SkColorSpace> colorSpace,
140 SkColorType colorType,
141 SkAlphaType alphaType) {
142 if (!recorder) {
143 return nullptr;
144 }
145
146 sk_sp<DrawContext> dc = DrawContext::Make(std::move(target),
147 std::move(colorSpace),
148 colorType,
149 alphaType);
150 if (!dc) {
151 return nullptr;
152 }
153
154 return sk_sp<Device>(new Device(recorder, std::move(dc)));
155 }
156
Device(Recorder * recorder,sk_sp<DrawContext> dc)157 Device::Device(Recorder* recorder, sk_sp<DrawContext> dc)
158 : SkBaseDevice(dc->imageInfo(), SkSurfaceProps())
159 , fRecorder(recorder)
160 , fDC(std::move(dc))
161 , fColorDepthBoundsManager(std::make_unique<NaiveBoundsManager>())
162 , fDisjointStencilSet(std::make_unique<IntersectionTreeSet>())
163 , fCurrentDepth(DrawOrder::kClearDepth)
164 , fDrawsOverlap(false) {
165 SkASSERT(SkToBool(fDC) && SkToBool(fRecorder));
166 fRecorder->registerDevice(this);
167 }
168
~Device()169 Device::~Device() {
170 if (fRecorder) {
171 this->flushPendingWorkToRecorder();
172 fRecorder->deregisterDevice(this);
173 }
174 }
175
abandonRecorder()176 void Device::abandonRecorder() {
177 fRecorder = nullptr;
178 }
179
onCreateDevice(const CreateInfo & info,const SkPaint *)180 SkBaseDevice* Device::onCreateDevice(const CreateInfo& info, const SkPaint*) {
181 // TODO: Inspect the paint and create info to determine if there's anything that has to be
182 // modified to support inline subpasses.
183 // TODO: onCreateDevice really should return sk_sp<SkBaseDevice>...
184 return Make(fRecorder, info.fInfo).release();
185 }
186
makeSurface(const SkImageInfo & ii,const SkSurfaceProps &)187 sk_sp<SkSurface> Device::makeSurface(const SkImageInfo& ii, const SkSurfaceProps& /* props */) {
188 return MakeGraphite(fRecorder, ii);
189 }
190
onReadPixels(const SkPixmap & pm,int x,int y)191 bool Device::onReadPixels(const SkPixmap& pm, int x, int y) {
192 // We have no access to a context to do a read pixels here.
193 return false;
194 }
195
readPixels(Context * context,Recorder * recorder,const SkPixmap & pm,int x,int y)196 bool Device::readPixels(Context* context,
197 Recorder* recorder,
198 const SkPixmap& pm,
199 int x,
200 int y) {
201 // TODO: Support more formats that we can read back into
202 if (pm.colorType() != kRGBA_8888_SkColorType) {
203 return false;
204 }
205
206 ResourceProvider* resourceProvider = recorder->priv().resourceProvider();
207
208 TextureProxy* srcProxy = fDC->target();
209 if (!srcProxy->instantiate(resourceProvider)) {
210 return false;
211 }
212 sk_sp<Texture> srcTexture = srcProxy->refTexture();
213 SkASSERT(srcTexture);
214
215 size_t rowBytes = pm.rowBytes();
216 size_t size = rowBytes * pm.height();
217 sk_sp<Buffer> dstBuffer = resourceProvider->findOrCreateBuffer(size,
218 BufferType::kXferGpuToCpu,
219 PrioritizeGpuReads::kNo);
220 if (!dstBuffer) {
221 return false;
222 }
223
224 SkIRect srcRect = SkIRect::MakeXYWH(x, y, pm.width(), pm.height());
225 sk_sp<CopyTextureToBufferTask> task =
226 CopyTextureToBufferTask::Make(std::move(srcTexture),
227 srcRect,
228 dstBuffer,
229 /*bufferOffset=*/0,
230 rowBytes);
231 if (!task) {
232 return false;
233 }
234
235 this->flushPendingWorkToRecorder();
236 fRecorder->priv().add(std::move(task));
237
238 // TODO: Can snapping ever fail?
239 context->insertRecording(fRecorder->snap());
240 context->submit(SyncToCpu::kYes);
241
242 void* mappedMemory = dstBuffer->map();
243
244 memcpy(pm.writable_addr(), mappedMemory, size);
245
246 return true;
247 }
248
onWritePixels(const SkPixmap & pm,int x,int y)249 bool Device::onWritePixels(const SkPixmap& pm, int x, int y) {
250 this->flushPendingWorkToRecorder();
251
252 return fDC->writePixels(fRecorder, pm, {x, y});
253 }
254
onDevClipBounds() const255 SkIRect Device::onDevClipBounds() const {
256 auto target = fDC->target();
257 return SkIRect::MakeSize(target->dimensions());
258 }
259
drawPaint(const SkPaint & paint)260 void Device::drawPaint(const SkPaint& paint) {
261 // TODO: check paint params as well
262 if (this->clipIsWideOpen()) {
263 // do fullscreen clear
264 fDC->clear(paint.getColor4f());
265 return;
266 }
267 SkRect deviceBounds = SkRect::Make(this->devClipBounds());
268 // TODO: Should be able to get the inverse from the matrix cache
269 SkM44 devToLocal;
270 if (!this->localToDevice44().invert(&devToLocal)) {
271 // TBD: This matches legacy behavior for drawPaint() that requires local coords, although
272 // v1 handles arbitrary transforms when the paint is solid color because it just fills the
273 // device bounds directly. In the new world it might be nice to have non-invertible
274 // transforms formalized (i.e. no drawing ever, handled at SkCanvas level possibly?)
275 return;
276 }
277 SkRect localCoveringBounds = SkMatrixPriv::MapRect(devToLocal, deviceBounds);
278 this->drawShape(Shape(localCoveringBounds), paint, kFillStyle,
279 DrawFlags::kIgnorePathEffect | DrawFlags::kIgnoreMaskFilter);
280 }
281
drawRect(const SkRect & r,const SkPaint & paint)282 void Device::drawRect(const SkRect& r, const SkPaint& paint) {
283 this->drawShape(Shape(r), paint, SkStrokeRec(paint));
284 }
285
drawOval(const SkRect & oval,const SkPaint & paint)286 void Device::drawOval(const SkRect& oval, const SkPaint& paint) {
287 // TODO: This has wasted effort from the SkCanvas level since it instead converts rrects that
288 // happen to be ovals into this, only for us to go right back to rrect.
289 this->drawShape(Shape(SkRRect::MakeOval(oval)), paint, SkStrokeRec(paint));
290 }
291
drawRRect(const SkRRect & rr,const SkPaint & paint)292 void Device::drawRRect(const SkRRect& rr, const SkPaint& paint) {
293 this->drawShape(Shape(rr), paint, SkStrokeRec(paint));
294 }
295
drawPath(const SkPath & path,const SkPaint & paint,bool pathIsMutable)296 void Device::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
297 // TODO: If we do try to inspect the path, it should happen here and possibly after computing
298 // the path effect. Alternatively, all that should be handled in SkCanvas.
299 this->drawShape(Shape(path), paint, SkStrokeRec(paint));
300 }
301
drawPoints(SkCanvas::PointMode mode,size_t count,const SkPoint * points,const SkPaint & paint)302 void Device::drawPoints(SkCanvas::PointMode mode, size_t count,
303 const SkPoint* points, const SkPaint& paint) {
304 // TODO: I'm [ml] not sure either CPU or GPU backend really has a fast path for this that
305 // isn't captured by drawOval and drawLine, so could easily be moved into SkCanvas.
306 if (mode == SkCanvas::kPoints_PointMode) {
307 float radius = 0.5f * paint.getStrokeWidth();
308 for (size_t i = 0; i < count; ++i) {
309 SkRect pointRect = SkRect::MakeLTRB(points[i].fX - radius, points[i].fY - radius,
310 points[i].fX + radius, points[i].fY + radius);
311 // drawOval/drawRect with a forced fill style
312 if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
313 this->drawShape(Shape(SkRRect::MakeOval(pointRect)), paint, kFillStyle);
314 } else {
315 this->drawShape(Shape(pointRect), paint, kFillStyle);
316 }
317 }
318 } else {
319 // Force the style to be a stroke, using the radius and cap from the paint
320 SkStrokeRec stroke(paint, SkPaint::kStroke_Style);
321 size_t inc = (mode == SkCanvas::kLines_PointMode) ? 2 : 1;
322 for (size_t i = 0; i < count; i += inc) {
323 this->drawShape(Shape(points[i], points[(i + 1) % count]), paint, stroke);
324 }
325 }
326 }
327
drawShape(const Shape & shape,const SkPaint & paint,const SkStrokeRec & style,Mask<DrawFlags> flags)328 void Device::drawShape(const Shape& shape,
329 const SkPaint& paint,
330 const SkStrokeRec& style,
331 Mask<DrawFlags> flags) {
332 // TODO: Device will cache the Transform or otherwise ensure it's computed once per change to
333 // its local-to-device matrix, but that requires updating SkDevice's virtuals. Right now we
334 // re-compute the Transform every draw, as well as any time we recurse on drawShape(), but that
335 // goes away with the caching.
336 Transform localToDevice(this->localToDevice44());
337 if (!localToDevice.valid()) {
338 // If the transform is not invertible or not finite then drawing isn't well defined.
339 SKGPU_LOG_W("Skipping draw with non-invertible/non-finite transform.");
340 return;
341 }
342
343 // Heavy weight paint options like path effects, mask filters, and stroke-and-fill style are
344 // applied on the CPU by generating a new shape and recursing on drawShape() with updated flags
345 if (!(flags & DrawFlags::kIgnorePathEffect) && paint.getPathEffect()) {
346 // Apply the path effect before anything else
347 // TODO: If asADash() returns true and the base path matches the dashing fast path, then
348 // that should be detected now as well. Maybe add dashPath to Device so canvas can handle it
349 SkStrokeRec newStyle = style;
350 newStyle.setResScale(localToDevice.maxScaleFactor());
351 SkPath dst;
352 if (paint.getPathEffect()->filterPath(&dst, shape.asPath(), &newStyle,
353 nullptr, localToDevice)) {
354 // Recurse using the path and new style, while disabling downstream path effect handling
355 this->drawShape(Shape(dst), paint, newStyle, flags | DrawFlags::kIgnorePathEffect);
356 return;
357 } else {
358 SKGPU_LOG_W("Path effect failed to apply, drawing original path.");
359 this->drawShape(shape, paint, style, flags | DrawFlags::kIgnorePathEffect);
360 return;
361 }
362 }
363
364 if (!(flags & DrawFlags::kIgnoreMaskFilter) && paint.getMaskFilter()) {
365 // TODO: Handle mask filters, ignored for the sprint.
366 // TODO: Could this be handled by SkCanvas by drawing a mask, blurring, and then sampling
367 // with a rect draw? What about fast paths for rrect blur masks...
368 this->drawShape(shape, paint, style, flags | DrawFlags::kIgnoreMaskFilter);
369 return;
370 }
371
372 // If we got here, then path effects and mask filters should have been handled and the style
373 // should be fill or stroke/hairline. Stroke-and-fill is not handled by DrawContext, but is
374 // emulated here by drawing twice--one stroke and one fill--using the same depth value.
375 SkASSERT(!SkToBool(paint.getPathEffect()) || (flags & DrawFlags::kIgnorePathEffect));
376 SkASSERT(!SkToBool(paint.getMaskFilter()) || (flags & DrawFlags::kIgnoreMaskFilter));
377
378 // Check if we have room to record into the current list before determining clipping and order
379 const SkStrokeRec::Style styleType = style.getStyle();
380 if (this->needsFlushBeforeDraw(styleType == SkStrokeRec::kStrokeAndFill_Style ? 2 : 1)) {
381 this->flushPendingWorkToRecorder();
382 }
383
384 DrawOrder order(fCurrentDepth.next());
385 auto [clip, clipOrder] = this->applyClipToDraw(localToDevice, shape, style, order.depth());
386 if (clip.drawBounds().isEmptyNegativeOrNaN()) {
387 // Clipped out, so don't record anything
388 return;
389 }
390
391 // A draw's order always depends on the clips that must be drawn before it
392 order.dependsOnPaintersOrder(clipOrder);
393
394 // If a draw is not opaque, it must be drawn after the most recent draw it intersects with in
395 // order to blend correctly. We always query the most recent draw (even when opaque) because it
396 // also lets Device easily track whether or not there are any overlapping draws.
397 PaintParams shading{paint};
398 const bool dependsOnDst = paint_depends_on_dst(shading);
399 CompressedPaintersOrder prevDraw =
400 fColorDepthBoundsManager->getMostRecentDraw(clip.drawBounds());
401 if (dependsOnDst) {
402 order.dependsOnPaintersOrder(prevDraw);
403 }
404 // TODO: if the chosen Renderer for a draw uses coverage AA, then it cannot be considered opaque
405 // regardless of what the PaintParams would do, but we won't know that until after the Renderer
406 // has been selected for the draw.
407
408 if (styleType == SkStrokeRec::kStroke_Style ||
409 styleType == SkStrokeRec::kHairline_Style ||
410 styleType == SkStrokeRec::kStrokeAndFill_Style) {
411 // TODO: If DC supports stroked primitives, Device could choose one of those based on shape
412 StrokeParams stroke(style.getWidth(), style.getMiter(), style.getJoin(), style.getCap());
413 fDC->strokePath(localToDevice, shape, stroke, clip, order, &shading);
414 }
415 if (styleType == SkStrokeRec::kFill_Style ||
416 styleType == SkStrokeRec::kStrokeAndFill_Style) {
417 // TODO: If DC supports filled primitives, Device could choose one of those based on shape
418
419 // TODO: Route all filled shapes to stencil-and-cover for the sprint; convex will draw
420 // correctly but uses an unnecessary stencil step.
421 // if (shape.convex()) {
422 // fDC->fillConvexPath(localToDevice, shape, clip, order, &shading);
423 // } else {
424 DisjointStencilIndex setIndex = fDisjointStencilSet->add(order.paintOrder(),
425 clip.drawBounds());
426 order.dependsOnStencil(setIndex);
427 fDC->stencilAndFillPath(localToDevice, shape, clip, order, &shading);
428 // }
429 }
430
431 // Record the painters order and depth used for this draw
432 const bool fullyOpaque = !dependsOnDst &&
433 shape.isRect() &&
434 localToDevice.type() <= Transform::Type::kRectStaysRect;
435 fColorDepthBoundsManager->recordDraw(shape.bounds(),
436 order.paintOrder(),
437 order.depth(),
438 fullyOpaque);
439
440 fCurrentDepth = order.depth();
441 fDrawsOverlap |= (prevDraw != DrawOrder::kNoIntersection);
442 }
443
applyClipToDraw(const Transform & localToDevice,const Shape & shape,const SkStrokeRec & style,PaintersDepth z)444 std::pair<Clip, CompressedPaintersOrder> Device::applyClipToDraw(const Transform& localToDevice,
445 const Shape& shape,
446 const SkStrokeRec& style,
447 PaintersDepth z) {
448 SkIRect scissor = this->devClipBounds();
449
450 Rect drawBounds = shape.bounds();
451 if (!style.isHairlineStyle()) {
452 float localStyleOutset = style.getInflationRadius();
453 drawBounds.outset(localStyleOutset);
454 }
455 drawBounds = localToDevice.mapRect(drawBounds);
456
457 // Hairlines get an extra pixel *after* transforming to device space
458 if (style.isHairlineStyle()) {
459 drawBounds.outset(0.5f);
460 }
461
462 drawBounds.intersect(SkRect::Make(scissor));
463 if (drawBounds.isEmptyNegativeOrNaN()) {
464 // Trivially clipped out, so return now
465 return {{drawBounds, scissor}, DrawOrder::kNoIntersection};
466 }
467
468 // TODO: iterate the clip stack and accumulate draw bounds into clip usage
469 return {{drawBounds, scissor}, DrawOrder::kNoIntersection};
470 }
471
flushPendingWorkToRecorder()472 void Device::flushPendingWorkToRecorder() {
473 SkASSERT(fRecorder);
474
475 // TODO: we may need to further split this function up since device->device drawList and
476 // DrawPass stealing will need to share some of the same logic w/o becoming a Task.
477
478 auto uploadTask = fDC->snapUploadTask(fRecorder);
479 if (uploadTask) {
480 fRecorder->priv().add(std::move(uploadTask));
481 }
482
483 // TODO: iterate the clip stack and issue a depth-only draw for every clip element that has
484 // a non-empty usage bounds, using that bounds as the scissor.
485 auto drawTask = fDC->snapRenderPassTask(fRecorder, fColorDepthBoundsManager.get());
486 if (drawTask) {
487 fRecorder->priv().add(std::move(drawTask));
488 }
489
490 // Reset accumulated state tracking since everything that it referred to has been moved into
491 // an immutable DrawPass.
492 fColorDepthBoundsManager->reset();
493 fDisjointStencilSet->reset();
494 fCurrentDepth = DrawOrder::kClearDepth;
495 // NOTE: fDrawsOverlap is not reset here because that is a persistent property of everything
496 // drawn into the Device, and not just the currently accumulating pass.
497 }
498
needsFlushBeforeDraw(int numNewDraws) const499 bool Device::needsFlushBeforeDraw(int numNewDraws) const {
500 // TODO: iterate the clip stack and count the number of clip elements (both w/ and w/o usage
501 // since we want to know the max # of clip shapes that flushing might add as draws).
502 // numNewDraws += clip element count...
503 return (DrawList::kMaxDraws - fDC->pendingDrawCount()) < numNewDraws;
504 }
505
makeSpecial(const SkBitmap &)506 sk_sp<SkSpecialImage> Device::makeSpecial(const SkBitmap&) {
507 return nullptr;
508 }
509
makeSpecial(const SkImage *)510 sk_sp<SkSpecialImage> Device::makeSpecial(const SkImage*) {
511 return nullptr;
512 }
513
snapSpecial(const SkIRect & subset,bool forceCopy)514 sk_sp<SkSpecialImage> Device::snapSpecial(const SkIRect& subset, bool forceCopy) {
515 this->flushPendingWorkToRecorder();
516 return nullptr;
517 }
518
519 } // namespace skgpu
520