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