1/*
2 * Copyright 2015 Google Inc.
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/ops/GrTriangulatingPathRenderer.h"
9
10#include "include/private/SkIDChangeListener.h"
11#include "src/core/SkGeometry.h"
12#include "src/gpu/GrAuditTrail.h"
13#include "src/gpu/GrCaps.h"
14#include "src/gpu/GrClip.h"
15#include "src/gpu/GrDefaultGeoProcFactory.h"
16#include "src/gpu/GrDrawOpTest.h"
17#include "src/gpu/GrEagerVertexAllocator.h"
18#include "src/gpu/GrOpFlushState.h"
19#include "src/gpu/GrProgramInfo.h"
20#include "src/gpu/GrResourceCache.h"
21#include "src/gpu/GrResourceProvider.h"
22#include "src/gpu/GrSimpleMesh.h"
23#include "src/gpu/GrStyle.h"
24#include "src/gpu/GrTriangulator.h"
25#include "src/gpu/geometry/GrPathUtils.h"
26#include "src/gpu/geometry/GrShape.h"
27#include "src/gpu/ops/GrMeshDrawOp.h"
28#include "src/gpu/ops/GrSimpleMeshDrawOpHelperWithStencil.h"
29
30#include <cstdio>
31
32#ifndef GR_AA_TESSELLATOR_MAX_VERB_COUNT
33#define GR_AA_TESSELLATOR_MAX_VERB_COUNT 10
34#endif
35
36/*
37 * This path renderer linearizes and decomposes the path into triangles using GrTriangulator,
38 * uploads the triangles to a vertex buffer, and renders them with a single draw call. It can do
39 * screenspace antialiasing with a one-pixel coverage ramp.
40 */
41namespace {
42
43struct TessInfo {
44 SkScalar fTolerance;
45 int fCount;
46};
47
48// When the SkPathRef genID changes, invalidate a corresponding GrResource described by key.
49class UniqueKeyInvalidator : public SkIDChangeListener {
50public:
51 UniqueKeyInvalidator(const GrUniqueKey& key, uint32_t contextUniqueID)
52 : fMsg(key, contextUniqueID) {}
53
54private:
55 GrUniqueKeyInvalidatedMessage fMsg;
56
57 void changed() override { SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(fMsg); }
58};
59
60bool cache_match(GrGpuBuffer* vertexBuffer, SkScalar tol, int* actualCount) {
61 if (!vertexBuffer) {
62 return false;
63 }
64 const SkData* data = vertexBuffer->getUniqueKey().getCustomData();
65 SkASSERT(data);
66 const TessInfo* info = static_cast<const TessInfo*>(data->data());
67 if (info->fTolerance == 0 || info->fTolerance < 3.0f * tol) {
68 *actualCount = info->fCount;
69 return true;
70 }
71 return false;
72}
73
74class StaticVertexAllocator : public GrEagerVertexAllocator {
75public:
76 StaticVertexAllocator(GrResourceProvider* resourceProvider, bool canMapVB)
77 : fResourceProvider(resourceProvider)
78 , fCanMapVB(canMapVB)
79 , fVertices(nullptr) {
80 }
81#ifdef SK_DEBUG
82 ~StaticVertexAllocator() override {
83 SkASSERT(!fLockStride);
84 }
85#endif
86 void* lock(size_t stride, int eagerCount) override {
87 SkASSERT(!fLockStride);
88 SkASSERT(stride);
89 size_t size = eagerCount * stride;
90 fVertexBuffer = fResourceProvider->createBuffer(size, GrGpuBufferType::kVertex,
91 kStatic_GrAccessPattern);
92 if (!fVertexBuffer.get()) {
93 return nullptr;
94 }
95 if (fCanMapVB) {
96 fVertices = fVertexBuffer->map();
97 } else {
98 fVertices = sk_malloc_throw(eagerCount * stride);
99 }
100 fLockStride = stride;
101 return fVertices;
102 }
103 void unlock(int actualCount) override {
104 SkASSERT(fLockStride);
105 if (fCanMapVB) {
106 fVertexBuffer->unmap();
107 } else {
108 fVertexBuffer->updateData(fVertices, actualCount * fLockStride);
109 sk_free(fVertices);
110 }
111 fVertices = nullptr;
112 fLockStride = 0;
113 }
114 sk_sp<GrGpuBuffer> detachVertexBuffer() { return std::move(fVertexBuffer); }
115
116private:
117 sk_sp<GrGpuBuffer> fVertexBuffer;
118 GrResourceProvider* fResourceProvider;
119 bool fCanMapVB;
120 void* fVertices;
121 size_t fLockStride = 0;
122};
123
124} // namespace
125
126GrTriangulatingPathRenderer::GrTriangulatingPathRenderer()
127 : fMaxVerbCount(GR_AA_TESSELLATOR_MAX_VERB_COUNT) {
128}
129
130GrPathRenderer::CanDrawPath
131GrTriangulatingPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
132 // This path renderer can draw fill styles, and can do screenspace antialiasing via a
133 // one-pixel coverage ramp. It can do convex and concave paths, but we'll leave the convex
134 // ones to simpler algorithms. We pass on paths that have styles, though they may come back
135 // around after applying the styling information to the geometry to create a filled path.
136 if (!args.fShape->style().isSimpleFill() || args.fShape->knownToBeConvex()) {
137 return CanDrawPath::kNo;
138 }
139 switch (args.fAAType) {
140 case GrAAType::kNone:
141 case GrAAType::kMSAA:
142 // Prefer MSAA, if any antialiasing. In the non-analytic-AA case, We skip paths that
143 // don't have a key since the real advantage of this path renderer comes from caching
144 // the tessellated geometry.
145 if (!args.fShape->hasUnstyledKey()) {
146 return CanDrawPath::kNo;
147 }
148 break;
149 case GrAAType::kCoverage:
150 // Use analytic AA if we don't have MSAA. In this case, we do not cache, so we accept
151 // paths without keys.
152 SkPath path;
153 args.fShape->asPath(&path);
154 if (path.countVerbs() > fMaxVerbCount) {
155 return CanDrawPath::kNo;
156 }
157 break;
158 }
159 return CanDrawPath::kYes;
160}
161
162namespace {
163
164class TriangulatingPathOp final : public GrMeshDrawOp {
165private:
166 using Helper = GrSimpleMeshDrawOpHelperWithStencil;
167
168public:
169 DEFINE_OP_CLASS_ID
170
171 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
172 GrPaint&& paint,
173 const GrShape& shape,
174 const SkMatrix& viewMatrix,
175 SkIRect devClipBounds,
176 GrAAType aaType,
177 const GrUserStencilSettings* stencilSettings) {
178 return Helper::FactoryHelper<TriangulatingPathOp>(context, std::move(paint), shape,
179 viewMatrix, devClipBounds, aaType,
180 stencilSettings);
181 }
182
183 const char* name() const override { return "TriangulatingPathOp"; }
184
185 void visitProxies(const VisitProxyFunc& func) const override {
186 if (fProgramInfo) {
187 fProgramInfo->visitFPProxies(func);
188 } else {
189 fHelper.visitProxies(func);
190 }
191 }
192
193#ifdef SK_DEBUG
194 SkString dumpInfo() const override {
195 SkString string;
196 string.appendf("Color 0x%08x, aa: %d\n", fColor.toBytes_RGBA(), fAntiAlias);
197 string += fHelper.dumpInfo();
198 string += INHERITED::dumpInfo();
199 return string;
200 }
201#endif
202
203 TriangulatingPathOp(Helper::MakeArgs helperArgs,
204 const SkPMColor4f& color,
205 const GrShape& shape,
206 const SkMatrix& viewMatrix,
207 const SkIRect& devClipBounds,
208 GrAAType aaType,
209 const GrUserStencilSettings* stencilSettings)
210 : INHERITED(ClassID())
211 , fHelper(helperArgs, aaType, stencilSettings)
212 , fColor(color)
213 , fShape(shape)
214 , fViewMatrix(viewMatrix)
215 , fDevClipBounds(devClipBounds)
216 , fAntiAlias(GrAAType::kCoverage == aaType) {
217 SkRect devBounds;
218 viewMatrix.mapRect(&devBounds, shape.bounds());
219 if (shape.inverseFilled()) {
220 // Because the clip bounds are used to add a contour for inverse fills, they must also
221 // include the path bounds.
222 devBounds.join(SkRect::Make(fDevClipBounds));
223 }
224 this->setBounds(devBounds, HasAABloat::kNo, IsHairline::kNo);
225 }
226
227 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
228
229 GrProcessorSet::Analysis finalize(
230 const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
231 GrClampType clampType) override {
232 GrProcessorAnalysisCoverage coverage = fAntiAlias
233 ? GrProcessorAnalysisCoverage::kSingleChannel
234 : GrProcessorAnalysisCoverage::kNone;
235 // This Op uses uniform (not vertex) color, so doesn't need to track wide color.
236 return fHelper.finalizeProcessors(
237 caps, clip, hasMixedSampledCoverage, clampType, coverage, &fColor, nullptr);
238 }
239
240private:
241 SkPath getPath() const {
242 SkASSERT(!fShape.style().applies());
243 SkPath path;
244 fShape.asPath(&path);
245 return path;
246 }
247
248 void draw(Target* target) {
249 SkASSERT(!fAntiAlias);
250 GrResourceProvider* rp = target->resourceProvider();
251 bool inverseFill = fShape.inverseFilled();
252 // construct a cache key from the path's genID and the view matrix
253 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
254 GrUniqueKey key;
255 static constexpr int kClipBoundsCnt = sizeof(fDevClipBounds) / sizeof(uint32_t);
256 int shapeKeyDataCnt = fShape.unstyledKeySize();
257 SkASSERT(shapeKeyDataCnt >= 0);
258 GrUniqueKey::Builder builder(&key, kDomain, shapeKeyDataCnt + kClipBoundsCnt, "Path");
259 fShape.writeUnstyledKey(&builder[0]);
260 // For inverse fills, the tessellation is dependent on clip bounds.
261 if (inverseFill) {
262 memcpy(&builder[shapeKeyDataCnt], &fDevClipBounds, sizeof(fDevClipBounds));
263 } else {
264 memset(&builder[shapeKeyDataCnt], 0, sizeof(fDevClipBounds));
265 }
266 builder.finish();
267 sk_sp<GrGpuBuffer> cachedVertexBuffer(rp->findByUniqueKey<GrGpuBuffer>(key));
268 int actualCount;
269 SkScalar tol = GrPathUtils::kDefaultTolerance;
270 tol = GrPathUtils::scaleToleranceToSrc(tol, fViewMatrix, fShape.bounds());
271 if (cache_match(cachedVertexBuffer.get(), tol, &actualCount)) {
272 this->createMesh(target, std::move(cachedVertexBuffer), 0, actualCount);
273 return;
274 }
275
276 SkRect clipBounds = SkRect::Make(fDevClipBounds);
277
278 SkMatrix vmi;
279 if (!fViewMatrix.invert(&vmi)) {
280 return;
281 }
282 vmi.mapRect(&clipBounds);
283 bool isLinear;
284 bool canMapVB = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags();
285 StaticVertexAllocator allocator(rp, canMapVB);
286 int count = GrTriangulator::PathToTriangles(getPath(), tol, clipBounds, &allocator,
287 GrTriangulator::Mode::kNormal, &isLinear);
288 if (count == 0) {
289 return;
290 }
291 sk_sp<GrGpuBuffer> vb = allocator.detachVertexBuffer();
292 TessInfo info;
293 info.fTolerance = isLinear ? 0 : tol;
294 info.fCount = count;
295 fShape.addGenIDChangeListener(
296 sk_make_sp<UniqueKeyInvalidator>(key, target->contextUniqueID()));
297 key.setCustomData(SkData::MakeWithCopy(&info, sizeof(info)));
298 rp->assignUniqueKeyToResource(key, vb.get());
299
300 this->createMesh(target, std::move(vb), 0, count);
301 }
302
303 void drawAA(Target* target) {
304 SkASSERT(fAntiAlias);
305 SkPath path = getPath();
306 if (path.isEmpty()) {
307 return;
308 }
309 SkRect clipBounds = SkRect::Make(fDevClipBounds);
310 path.transform(fViewMatrix);
311 SkScalar tol = GrPathUtils::kDefaultTolerance;
312 sk_sp<const GrBuffer> vertexBuffer;
313 int firstVertex;
314 bool isLinear;
315 GrEagerDynamicVertexAllocator allocator(target, &vertexBuffer, &firstVertex);
316 int count = GrTriangulator::PathToTriangles(path, tol, clipBounds, &allocator,
317 GrTriangulator::Mode::kEdgeAntialias, &isLinear);
318 if (count == 0) {
319 return;
320 }
321 this->createMesh(target, std::move(vertexBuffer), firstVertex, count);
322 }
323
324 GrProgramInfo* programInfo() override { return fProgramInfo; }
325
326 void onCreateProgramInfo(const GrCaps* caps,
327 SkArenaAlloc* arena,
328 const GrSurfaceProxyView* writeView,
329 GrAppliedClip&& appliedClip,
330 const GrXferProcessor::DstProxyView& dstProxyView) override {
331 GrGeometryProcessor* gp;
332 {
333 using namespace GrDefaultGeoProcFactory;
334
335 Color color(fColor);
336 LocalCoords::Type localCoordsType = fHelper.usesLocalCoords()
337 ? LocalCoords::kUsePosition_Type
338 : LocalCoords::kUnused_Type;
339 Coverage::Type coverageType;
340 if (fAntiAlias) {
341 if (fHelper.compatibleWithCoverageAsAlpha()) {
342 coverageType = Coverage::kAttributeTweakAlpha_Type;
343 } else {
344 coverageType = Coverage::kAttribute_Type;
345 }
346 } else {
347 coverageType = Coverage::kSolid_Type;
348 }
349 if (fAntiAlias) {
350 gp = GrDefaultGeoProcFactory::MakeForDeviceSpace(arena, color, coverageType,
351 localCoordsType, fViewMatrix);
352 } else {
353 gp = GrDefaultGeoProcFactory::Make(arena, color, coverageType, localCoordsType,
354 fViewMatrix);
355 }
356 }
357 if (!gp) {
358 return;
359 }
360
361#ifdef SK_DEBUG
362 auto mode = (fAntiAlias) ? GrTriangulator::Mode::kEdgeAntialias
363 : GrTriangulator::Mode::kNormal;
364 SkASSERT(GrTriangulator::GetVertexStride(mode) == gp->vertexStride());
365#endif
366
367 GrPrimitiveType primitiveType = TRIANGULATOR_WIREFRAME ? GrPrimitiveType::kLines
368 : GrPrimitiveType::kTriangles;
369
370 fProgramInfo = fHelper.createProgramInfoWithStencil(caps, arena, writeView,
371 std::move(appliedClip), dstProxyView,
372 gp, primitiveType);
373 }
374
375 void onPrepareDraws(Target* target) override {
376 if (fAntiAlias) {
377 this->drawAA(target);
378 } else {
379 this->draw(target);
380 }
381 }
382
383 void createMesh(Target* target, sk_sp<const GrBuffer> vb, int firstVertex, int count) {
384 fMesh = target->allocMesh();
385 fMesh->set(std::move(vb), count, firstVertex);
386 }
387
388 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
389 if (!fProgramInfo) {
390 this->createProgramInfo(flushState);
391 }
392
393 if (!fProgramInfo || !fMesh) {
394 return;
395 }
396
397 flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
398 flushState->bindTextures(fProgramInfo->primProc(), nullptr, fProgramInfo->pipeline());
399 flushState->drawMesh(*fMesh);
400 }
401
402 Helper fHelper;
403 SkPMColor4f fColor;
404 GrShape fShape;
405 SkMatrix fViewMatrix;
406 SkIRect fDevClipBounds;
407 bool fAntiAlias;
408
409 GrSimpleMesh* fMesh = nullptr;
410 GrProgramInfo* fProgramInfo = nullptr;
411
412 typedef GrMeshDrawOp INHERITED;
413};
414
415} // anonymous namespace
416
417bool GrTriangulatingPathRenderer::onDrawPath(const DrawPathArgs& args) {
418 GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
419 "GrTriangulatingPathRenderer::onDrawPath");
420 SkIRect clipBoundsI;
421 args.fClip->getConservativeBounds(args.fRenderTargetContext->width(),
422 args.fRenderTargetContext->height(),
423 &clipBoundsI);
424 std::unique_ptr<GrDrawOp> op = TriangulatingPathOp::Make(
425 args.fContext, std::move(args.fPaint), *args.fShape, *args.fViewMatrix, clipBoundsI,
426 args.fAAType, args.fUserStencilSettings);
427 args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op));
428 return true;
429}
430
431///////////////////////////////////////////////////////////////////////////////////////////////////
432
433#if GR_TEST_UTILS
434
435GR_DRAW_OP_TEST_DEFINE(TriangulatingPathOp) {
436 SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
437 SkPath path = GrTest::TestPath(random);
438 SkIRect devClipBounds = SkIRect::MakeLTRB(
439 random->nextU(), random->nextU(), random->nextU(), random->nextU());
440 devClipBounds.sort();
441 static constexpr GrAAType kAATypes[] = {GrAAType::kNone, GrAAType::kMSAA, GrAAType::kCoverage};
442 GrAAType aaType;
443 do {
444 aaType = kAATypes[random->nextULessThan(SK_ARRAY_COUNT(kAATypes))];
445 } while(GrAAType::kMSAA == aaType && numSamples <= 1);
446 GrStyle style;
447 do {
448 GrTest::TestStyle(random, &style);
449 } while (!style.isSimpleFill());
450 GrShape shape(path, style);
451 return TriangulatingPathOp::Make(context, std::move(paint), shape, viewMatrix, devClipBounds,
452 aaType, GrGetRandomStencil(random, context));
453}
454
455#endif
456