• 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 "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