• 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/SkStuff.h"
12 #include "experimental/graphite/src/Buffer.h"
13 #include "experimental/graphite/src/Caps.h"
14 #include "experimental/graphite/src/ContextPriv.h"
15 #include "experimental/graphite/src/CopyTask.h"
16 #include "experimental/graphite/src/DrawContext.h"
17 #include "experimental/graphite/src/DrawList.h"
18 #include "experimental/graphite/src/Gpu.h"
19 #include "experimental/graphite/src/Recorder.h"
20 #include "experimental/graphite/src/Recording.h"
21 #include "experimental/graphite/src/ResourceProvider.h"
22 #include "experimental/graphite/src/Texture.h"
23 #include "experimental/graphite/src/TextureProxy.h"
24 #include "experimental/graphite/src/geom/BoundsManager.h"
25 #include "experimental/graphite/src/geom/Shape.h"
26 #include "experimental/graphite/src/geom/Transform_graphite.h"
27 
28 #include "include/core/SkPath.h"
29 #include "include/core/SkPathEffect.h"
30 #include "include/core/SkStrokeRec.h"
31 
32 #include "src/core/SkConvertPixels.h"
33 #include "src/core/SkMatrixPriv.h"
34 #include "src/core/SkPaintPriv.h"
35 #include "src/core/SkSpecialImage.h"
36 
37 namespace skgpu {
38 
39 namespace {
40 
41 static const SkStrokeRec kFillStyle(SkStrokeRec::kFill_InitStyle);
42 
is_opaque(const PaintParams & paint)43 bool is_opaque(const PaintParams& paint) {
44     // TODO: implement this
45     return false;
46 }
47 
48 } // anonymous namespace
49 
Make(sk_sp<Recorder> recorder,const SkImageInfo & ii)50 sk_sp<Device> Device::Make(sk_sp<Recorder> recorder, const SkImageInfo& ii) {
51     const Gpu* gpu = recorder->context()->priv().gpu();
52     auto textureInfo = gpu->caps()->getDefaultSampledTextureInfo(ii.colorType(), /*levelCount=*/1,
53                                                                  Protected::kNo, Renderable::kYes);
54     auto target = sk_sp<TextureProxy>(new TextureProxy(ii.dimensions(), textureInfo));
55     sk_sp<DrawContext> dc = DrawContext::Make(target,
56                                               ii.refColorSpace(),
57                                               ii.colorType(),
58                                               ii.alphaType());
59     if (!dc) {
60         return nullptr;
61     }
62 
63     return sk_sp<Device>(new Device(std::move(recorder), std::move(dc)));
64 }
65 
Device(sk_sp<Recorder> recorder,sk_sp<DrawContext> dc)66 Device::Device(sk_sp<Recorder> recorder, sk_sp<DrawContext> dc)
67         : SkBaseDevice(dc->imageInfo(), SkSurfaceProps())
68         , fRecorder(std::move(recorder))
69         , fDC(std::move(dc))
70         , fColorDepthBoundsManager(std::make_unique<NaiveBoundsManager>())
71         , fCurrentDepth(DrawOrder::kClearDepth)
72         , fMaxStencilIndex(DrawOrder::kUnassigned)
73         , fDrawsOverlap(false) {
74     SkASSERT(SkToBool(fDC) && SkToBool(fRecorder));
75 }
76 
77 Device::~Device() = default;
78 
onCreateDevice(const CreateInfo & info,const SkPaint *)79 SkBaseDevice* Device::onCreateDevice(const CreateInfo& info, const SkPaint*) {
80     // TODO: Inspect the paint and create info to determine if there's anything that has to be
81     // modified to support inline subpasses.
82     // TODO: onCreateDevice really should return sk_sp<SkBaseDevice>...
83     return Make(fRecorder, info.fInfo).release();
84 }
85 
makeSurface(const SkImageInfo & ii,const SkSurfaceProps &)86 sk_sp<SkSurface> Device::makeSurface(const SkImageInfo& ii, const SkSurfaceProps& /* props */) {
87     return MakeGraphite(fRecorder, ii);
88 }
89 
onReadPixels(const SkPixmap & pm,int x,int y)90 bool Device::onReadPixels(const SkPixmap& pm, int x, int y) {
91     // TODO: Support more formats that we can read back into
92     if (pm.colorType() != kRGBA_8888_SkColorType) {
93         return false;
94     }
95 
96     auto context = fRecorder->context();
97     auto resourceProvider = context->priv().resourceProvider();
98 
99     TextureProxy* srcProxy = fDC->target();
100     if(!srcProxy->instantiate(resourceProvider)) {
101         return false;
102     }
103     sk_sp<Texture> srcTexture = srcProxy->refTexture();
104     SkASSERT(srcTexture);
105 
106     size_t rowBytes = pm.rowBytes();
107     size_t size = rowBytes * pm.height();
108     sk_sp<Buffer> dstBuffer = resourceProvider->findOrCreateBuffer(size,
109                                                                    BufferType::kXferGpuToCpu,
110                                                                    PrioritizeGpuReads::kNo);
111     if (!dstBuffer) {
112         return false;
113     }
114 
115     SkIRect srcRect = SkIRect::MakeXYWH(x, y, pm.width(), pm.height());
116     sk_sp<CopyTextureToBufferTask> task =
117             CopyTextureToBufferTask::Make(std::move(srcTexture),
118                                           srcRect,
119                                           dstBuffer,
120                                           /*bufferOffset=*/0,
121                                           rowBytes);
122     if (!task) {
123         return false;
124     }
125 
126     this->flushPendingWorkToRecorder();
127     fRecorder->add(std::move(task));
128 
129     // TODO: Can snapping ever fail?
130     context->insertRecording(fRecorder->snap());
131     context->submit(SyncToCpu::kYes);
132 
133     void* mappedMemory = dstBuffer->map();
134 
135     memcpy(pm.writable_addr(), mappedMemory, size);
136 
137     return true;
138 }
139 
onDevClipBounds() const140 SkIRect Device::onDevClipBounds() const {
141     auto target = fDC->target();
142     return SkIRect::MakeSize(target->dimensions());
143 }
144 
drawPaint(const SkPaint & paint)145 void Device::drawPaint(const SkPaint& paint) {
146     // TODO: check paint params as well
147      if (this->clipIsWideOpen()) {
148         // do fullscreen clear
149         fDC->clear(paint.getColor4f());
150         return;
151     }
152     SkRect deviceBounds = SkRect::Make(this->devClipBounds());
153     // TODO: Should be able to get the inverse from the matrix cache
154     SkM44 devToLocal;
155     if (!this->localToDevice44().invert(&devToLocal)) {
156         // TBD: This matches legacy behavior for drawPaint() that requires local coords, although
157         // v1 handles arbitrary transforms when the paint is solid color because it just fills the
158         // device bounds directly. In the new world it might be nice to have non-invertible
159         // transforms formalized (i.e. no drawing ever, handled at SkCanvas level possibly?)
160         return;
161     }
162     SkRect localCoveringBounds = SkMatrixPriv::MapRect(devToLocal, deviceBounds);
163     this->drawShape(Shape(localCoveringBounds), paint, kFillStyle,
164                     DrawFlags::kIgnorePathEffect | DrawFlags::kIgnoreMaskFilter);
165 }
166 
drawRect(const SkRect & r,const SkPaint & paint)167 void Device::drawRect(const SkRect& r, const SkPaint& paint) {
168     this->drawShape(Shape(r), paint, SkStrokeRec(paint));
169 }
170 
drawOval(const SkRect & oval,const SkPaint & paint)171 void Device::drawOval(const SkRect& oval, const SkPaint& paint) {
172     // TODO: This has wasted effort from the SkCanvas level since it instead converts rrects that
173     // happen to be ovals into this, only for us to go right back to rrect.
174     this->drawShape(Shape(SkRRect::MakeOval(oval)), paint, SkStrokeRec(paint));
175 }
176 
drawRRect(const SkRRect & rr,const SkPaint & paint)177 void Device::drawRRect(const SkRRect& rr, const SkPaint& paint) {
178     this->drawShape(Shape(rr), paint, SkStrokeRec(paint));
179 }
180 
drawPath(const SkPath & path,const SkPaint & paint,bool pathIsMutable)181 void Device::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
182     // TODO: If we do try to inspect the path, it should happen here and possibly after computing
183     // the path effect. Alternatively, all that should be handled in SkCanvas.
184     this->drawShape(Shape(path), paint, SkStrokeRec(paint));
185 }
186 
drawPoints(SkCanvas::PointMode mode,size_t count,const SkPoint * points,const SkPaint & paint)187 void Device::drawPoints(SkCanvas::PointMode mode, size_t count,
188                         const SkPoint* points, const SkPaint& paint) {
189     // TODO: I'm [ml] not sure either CPU or GPU backend really has a fast path for this that
190     // isn't captured by drawOval and drawLine, so could easily be moved into SkCanvas.
191     if (mode == SkCanvas::kPoints_PointMode) {
192         float radius = 0.5f * paint.getStrokeWidth();
193         for (size_t i = 0; i < count; ++i) {
194             SkRect pointRect = SkRect::MakeLTRB(points[i].fX - radius, points[i].fY - radius,
195                                                 points[i].fX + radius, points[i].fY + radius);
196             // drawOval/drawRect with a forced fill style
197             if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
198                 this->drawShape(Shape(SkRRect::MakeOval(pointRect)), paint, kFillStyle);
199             } else {
200                 this->drawShape(Shape(pointRect), paint, kFillStyle);
201             }
202         }
203     } else {
204         // Force the style to be a stroke, using the radius and cap from the paint
205         SkStrokeRec stroke(paint, SkPaint::kStroke_Style);
206         size_t inc = (mode == SkCanvas::kLines_PointMode) ? 2 : 1;
207         for (size_t i = 0; i < count; i += inc) {
208             this->drawShape(Shape(points[i], points[(i + 1) % count]), paint, stroke);
209         }
210     }
211 }
212 
drawShape(const Shape & shape,const SkPaint & paint,const SkStrokeRec & style,Mask<DrawFlags> flags)213 void Device::drawShape(const Shape& shape,
214                        const SkPaint& paint,
215                        const SkStrokeRec& style,
216                        Mask<DrawFlags> flags) {
217     // TODO: Device will cache the Transform or otherwise ensure it's computed once per change to
218     // its local-to-device matrix, but that requires updating SkDevice's virtuals. Right now we
219     // re-compute the Transform every draw, as well as any time we recurse on drawShape(), but that
220     // goes away with the caching.
221     Transform localToDevice(this->localToDevice44());
222     if (!localToDevice.valid()) {
223         // If the transform is not invertible or not finite then drawing isn't well defined.
224         // TBD: This warning should go through the general purpose graphite logging system
225         SkDebugf("[graphite] WARNING - Skipping draw with non-invertible/non-finite transform.\n");
226         return;
227     }
228 
229     // Heavy weight paint options like path effects, mask filters, and stroke-and-fill style are
230     // applied on the CPU by generating a new shape and recursing on drawShape() with updated flags
231     if (!(flags & DrawFlags::kIgnorePathEffect) && paint.getPathEffect()) {
232         // Apply the path effect before anything else
233         // TODO: If asADash() returns true and the base path matches the dashing fast path, then
234         // that should be detected now as well. Maybe add dashPath to Device so canvas can handle it
235         SkStrokeRec newStyle = style;
236         newStyle.setResScale(localToDevice.maxScaleFactor());
237         SkPath dst;
238         if (paint.getPathEffect()->filterPath(&dst, shape.asPath(), &newStyle,
239                                               nullptr, localToDevice)) {
240             // Recurse using the path and new style, while disabling downstream path effect handling
241             this->drawShape(Shape(dst), paint, newStyle, flags | DrawFlags::kIgnorePathEffect);
242             return;
243         } else {
244             // TBD: This warning should go through the general purpose graphite logging system
245             SkDebugf("[graphite] WARNING - Path effect failed to apply, drawing original path.\n");
246             this->drawShape(shape, paint, style, flags | DrawFlags::kIgnorePathEffect);
247             return;
248         }
249     }
250 
251     if (!(flags & DrawFlags::kIgnoreMaskFilter) && paint.getMaskFilter()) {
252         // TODO: Handle mask filters, ignored for the sprint.
253         // TODO: Could this be handled by SkCanvas by drawing a mask, blurring, and then sampling
254         // with a rect draw? What about fast paths for rrect blur masks...
255         this->drawShape(shape, paint, style, flags | DrawFlags::kIgnoreMaskFilter);
256         return;
257     }
258 
259     // If we got here, then path effects and mask filters should have been handled and the style
260     // should be fill or stroke/hairline. Stroke-and-fill is not handled by DrawContext, but is
261     // emulated here by drawing twice--one stroke and one fill--using the same depth value.
262     SkASSERT(!SkToBool(paint.getPathEffect()) || (flags & DrawFlags::kIgnorePathEffect));
263     SkASSERT(!SkToBool(paint.getMaskFilter()) || (flags & DrawFlags::kIgnoreMaskFilter));
264 
265     // Check if we have room to record into the current list before determining clipping and order
266     const SkStrokeRec::Style styleType = style.getStyle();
267     if (this->needsFlushBeforeDraw(styleType == SkStrokeRec::kStrokeAndFill_Style ? 2 : 1)) {
268         this->flushPendingWorkToRecorder();
269     }
270 
271     DrawOrder order(fCurrentDepth.next());
272     auto [clip, clipOrder] = this->applyClipToDraw(localToDevice, shape, style, order.depth());
273     if (clip.drawBounds().isEmptyNegativeOrNaN()) {
274         // Clipped out, so don't record anything
275         return;
276     }
277 
278     // A draw's order always depends on the clips that must be drawn before it
279     order.dependsOnPaintersOrder(clipOrder);
280 
281     auto blendMode = paint.asBlendMode();
282     PaintParams shading{paint.getColor4f(),
283                         blendMode.has_value() ? *blendMode : SkBlendMode::kSrcOver,
284                         paint.refShader()};
285 
286     // If a draw is not opaque, it must be drawn after the most recent draw it intersects with in
287     // order to blend correctly. We always query the most recent draw (even when opaque) because it
288     // also lets Device easily track whether or not there are any overlapping draws.
289     const bool opaque = is_opaque(shading);
290     CompressedPaintersOrder prevDraw =
291             fColorDepthBoundsManager->getMostRecentDraw(clip.drawBounds());
292     if (!opaque) {
293         order.dependsOnPaintersOrder(prevDraw);
294     }
295 
296     if (styleType == SkStrokeRec::kStroke_Style ||
297         styleType == SkStrokeRec::kHairline_Style ||
298         styleType == SkStrokeRec::kStrokeAndFill_Style) {
299         // TODO: If DC supports stroked primitives, Device could choose one of those based on shape
300         StrokeParams stroke(style.getWidth(), style.getMiter(), style.getJoin(), style.getCap());
301         fDC->strokePath(localToDevice, shape, stroke, clip, order, &shading);
302     }
303     if (styleType == SkStrokeRec::kFill_Style ||
304         styleType == SkStrokeRec::kStrokeAndFill_Style) {
305         // TODO: If DC supports filled primitives, Device could choose one of those based on shape
306 
307         // TODO: Route all filled shapes to stencil-and-cover for the sprint; convex will draw
308         // correctly but uses an unnecessary stencil step.
309         // if (shape.convex()) {
310         //     fDC->fillConvexPath(localToDevice, shape, clip, order, &shading);
311         // } else {
312             order.dependsOnStencil(fMaxStencilIndex.next());
313             fDC->stencilAndFillPath(localToDevice, shape, clip, order, &shading);
314         // }
315     }
316 
317     // Record the painters order and depth used for this draw
318     const bool fullyOpaque = opaque && shape.isRect() &&
319                              localToDevice.type() <= Transform::Type::kRectStaysRect;
320     fColorDepthBoundsManager->recordDraw(shape.bounds(),
321                                          order.paintOrder(),
322                                          order.depth(),
323                                          fullyOpaque);
324 
325     fCurrentDepth = order.depth();
326     if (order.stencilIndex() != DrawOrder::kUnassigned) {
327         fMaxStencilIndex = std::max(fMaxStencilIndex, order.stencilIndex());
328     }
329     fDrawsOverlap |= (prevDraw != DrawOrder::kNoIntersection);
330 }
331 
applyClipToDraw(const Transform & localToDevice,const Shape & shape,const SkStrokeRec & style,PaintersDepth z)332 std::pair<Clip, CompressedPaintersOrder> Device::applyClipToDraw(const Transform& localToDevice,
333                                                                  const Shape& shape,
334                                                                  const SkStrokeRec& style,
335                                                                  PaintersDepth z) {
336     SkIRect scissor = this->devClipBounds();
337 
338     Rect drawBounds = shape.bounds();
339     if (!style.isHairlineStyle()) {
340         float localStyleOutset = style.getInflationRadius();
341         drawBounds.outset(localStyleOutset);
342     }
343     drawBounds = localToDevice.mapRect(drawBounds);
344 
345     // Hairlines get an extra pixel *after* transforming to device space
346     if (style.isHairlineStyle()) {
347         drawBounds.outset(0.5f);
348     }
349 
350     drawBounds.intersect(SkRect::Make(scissor));
351     if (drawBounds.isEmptyNegativeOrNaN()) {
352         // Trivially clipped out, so return now
353         return {{drawBounds, scissor}, DrawOrder::kNoIntersection};
354     }
355 
356     // TODO: iterate the clip stack and accumulate draw bounds into clip usage
357     return {{drawBounds, scissor}, DrawOrder::kNoIntersection};
358 }
359 
flushPendingWorkToRecorder()360 void Device::flushPendingWorkToRecorder() {
361     // TODO: we may need to further split this function up since device->device drawList and
362     // DrawPass stealing will need to share some of the same logic w/o becoming a Task.
363 
364     // TODO: iterate the clip stack and issue a depth-only draw for every clip element that has
365     // a non-empty usage bounds, using that bounds as the scissor.
366     auto drawTask = fDC->snapRenderPassTask(fRecorder.get(), fColorDepthBoundsManager.get());
367     if (drawTask) {
368         fRecorder->add(std::move(drawTask));
369     }
370 }
371 
needsFlushBeforeDraw(int numNewDraws) const372 bool Device::needsFlushBeforeDraw(int numNewDraws) const {
373     // TODO: iterate the clip stack and count the number of clip elements (both w/ and w/o usage
374     // since we want to know the max # of clip shapes that flushing might add as draws).
375     // numNewDraws += clip element count...
376     return (DrawList::kMaxDraws - fDC->pendingDrawCount()) < numNewDraws;
377 }
378 
makeSpecial(const SkBitmap &)379 sk_sp<SkSpecialImage> Device::makeSpecial(const SkBitmap&) {
380     return nullptr;
381 }
382 
makeSpecial(const SkImage *)383 sk_sp<SkSpecialImage> Device::makeSpecial(const SkImage*) {
384     return nullptr;
385 }
386 
snapSpecial(const SkIRect & subset,bool forceCopy)387 sk_sp<SkSpecialImage> Device::snapSpecial(const SkIRect& subset, bool forceCopy) {
388     this->flushPendingWorkToRecorder();
389     return nullptr;
390 }
391 
392 } // namespace skgpu
393