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 "include/core/SkString.h" |
9 | #include "src/core/SkGeometry.h" |
10 | #include "src/core/SkPathPriv.h" |
11 | #include "src/core/SkTraceEvent.h" |
12 | #include "src/gpu/GrAuditTrail.h" |
13 | #include "src/gpu/GrCaps.h" |
14 | #include "src/gpu/GrDefaultGeoProcFactory.h" |
15 | #include "src/gpu/GrDrawOpTest.h" |
16 | #include "src/gpu/GrGeometryProcessor.h" |
17 | #include "src/gpu/GrOpFlushState.h" |
18 | #include "src/gpu/GrProcessor.h" |
19 | #include "src/gpu/GrProgramInfo.h" |
20 | #include "src/gpu/GrRenderTargetContext.h" |
21 | #include "src/gpu/GrStyle.h" |
22 | #include "src/gpu/GrVertexWriter.h" |
23 | #include "src/gpu/geometry/GrPathUtils.h" |
24 | #include "src/gpu/geometry/GrStyledShape.h" |
25 | #include "src/gpu/glsl/GrGLSLGeometryProcessor.h" |
26 | #include "src/gpu/ops/GrAAConvexTessellator.h" |
27 | #include "src/gpu/ops/GrAALinearizingConvexPathRenderer.h" |
28 | #include "src/gpu/ops/GrMeshDrawOp.h" |
29 | #include "src/gpu/ops/GrSimpleMeshDrawOpHelperWithStencil.h" |
30 | |
31 | static const int DEFAULT_BUFFER_SIZE = 100; |
32 | |
33 | // The thicker the stroke, the harder it is to produce high-quality results using tessellation. For |
34 | // the time being, we simply drop back to software rendering above this stroke width. |
35 | static const SkScalar kMaxStrokeWidth = 20.0; |
36 | |
37 | GrAALinearizingConvexPathRenderer::GrAALinearizingConvexPathRenderer() = default; |
38 | |
39 | /////////////////////////////////////////////////////////////////////////////// |
40 | |
41 | GrPathRenderer::CanDrawPath |
42 | GrAALinearizingConvexPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { |
43 | if (GrAAType::kCoverage != args.fAAType) { |
44 | return CanDrawPath::kNo; |
45 | } |
46 | if (!args.fShape->knownToBeConvex()) { |
47 | return CanDrawPath::kNo; |
48 | } |
49 | if (args.fShape->style().pathEffect()) { |
50 | return CanDrawPath::kNo; |
51 | } |
52 | if (args.fShape->inverseFilled()) { |
53 | return CanDrawPath::kNo; |
54 | } |
55 | if (args.fShape->bounds().width() <= 0 && args.fShape->bounds().height() <= 0) { |
56 | // Stroked zero length lines should draw, but this PR doesn't handle that case |
57 | return CanDrawPath::kNo; |
58 | } |
59 | const SkStrokeRec& stroke = args.fShape->style().strokeRec(); |
60 | |
61 | if (stroke.getStyle() == SkStrokeRec::kStroke_Style || |
62 | stroke.getStyle() == SkStrokeRec::kStrokeAndFill_Style) { |
63 | if (!args.fViewMatrix->isSimilarity()) { |
64 | return CanDrawPath::kNo; |
65 | } |
66 | SkScalar strokeWidth = args.fViewMatrix->getMaxScale() * stroke.getWidth(); |
67 | if (strokeWidth < 1.0f && stroke.getStyle() == SkStrokeRec::kStroke_Style) { |
68 | return CanDrawPath::kNo; |
69 | } |
70 | if (strokeWidth > kMaxStrokeWidth || |
71 | !args.fShape->knownToBeClosed() || |
72 | stroke.getJoin() == SkPaint::Join::kRound_Join) { |
73 | return CanDrawPath::kNo; |
74 | } |
75 | return CanDrawPath::kYes; |
76 | } |
77 | if (stroke.getStyle() != SkStrokeRec::kFill_Style) { |
78 | return CanDrawPath::kNo; |
79 | } |
80 | // This can almost handle perspective. It would need to use 3 component explicit local coords |
81 | // when there are FPs that require them. This is difficult to test because AAConvexPathRenderer |
82 | // takes almost all filled paths that could get here. So just avoid perspective fills. |
83 | if (args.fViewMatrix->hasPerspective()) { |
84 | return CanDrawPath::kNo; |
85 | } |
86 | return CanDrawPath::kYes; |
87 | } |
88 | |
89 | // extract the result vertices and indices from the GrAAConvexTessellator |
90 | static void (const GrAAConvexTessellator& tess, |
91 | const SkMatrix* localCoordsMatrix, |
92 | void* vertData, |
93 | const GrVertexColor& color, |
94 | uint16_t firstIndex, |
95 | uint16_t* idxs) { |
96 | GrVertexWriter verts{vertData}; |
97 | for (int i = 0; i < tess.numPts(); ++i) { |
98 | SkPoint lc; |
99 | if (localCoordsMatrix) { |
100 | localCoordsMatrix->mapPoints(&lc, &tess.point(i), 1); |
101 | } |
102 | verts.write(tess.point(i), color, GrVertexWriter::If(localCoordsMatrix, lc), |
103 | tess.coverage(i)); |
104 | } |
105 | |
106 | for (int i = 0; i < tess.numIndices(); ++i) { |
107 | idxs[i] = tess.index(i) + firstIndex; |
108 | } |
109 | } |
110 | |
111 | static GrGeometryProcessor* create_lines_only_gp(SkArenaAlloc* arena, |
112 | bool tweakAlphaForCoverage, |
113 | bool usesLocalCoords, |
114 | bool wideColor) { |
115 | using namespace GrDefaultGeoProcFactory; |
116 | |
117 | Coverage::Type coverageType = |
118 | tweakAlphaForCoverage ? Coverage::kAttributeTweakAlpha_Type : Coverage::kAttribute_Type; |
119 | LocalCoords::Type localCoordsType = |
120 | usesLocalCoords ? LocalCoords::kHasExplicit_Type : LocalCoords::kUnused_Type; |
121 | Color::Type colorType = |
122 | wideColor ? Color::kPremulWideColorAttribute_Type : Color::kPremulGrColorAttribute_Type; |
123 | |
124 | return Make(arena, colorType, coverageType, localCoordsType, SkMatrix::I()); |
125 | } |
126 | |
127 | namespace { |
128 | |
129 | class AAFlatteningConvexPathOp final : public GrMeshDrawOp { |
130 | private: |
131 | using Helper = GrSimpleMeshDrawOpHelperWithStencil; |
132 | |
133 | public: |
134 | DEFINE_OP_CLASS_ID |
135 | static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context, |
136 | GrPaint&& paint, |
137 | const SkMatrix& viewMatrix, |
138 | const SkPath& path, |
139 | SkScalar strokeWidth, |
140 | SkStrokeRec::Style style, |
141 | SkPaint::Join join, |
142 | SkScalar miterLimit, |
143 | const GrUserStencilSettings* stencilSettings) { |
144 | return Helper::FactoryHelper<AAFlatteningConvexPathOp>(context, std::move(paint), |
145 | viewMatrix, path, |
146 | strokeWidth, style, join, miterLimit, |
147 | stencilSettings); |
148 | } |
149 | |
150 | AAFlatteningConvexPathOp(const Helper::MakeArgs& helperArgs, |
151 | const SkPMColor4f& color, |
152 | const SkMatrix& viewMatrix, |
153 | const SkPath& path, |
154 | SkScalar strokeWidth, |
155 | SkStrokeRec::Style style, |
156 | SkPaint::Join join, |
157 | SkScalar miterLimit, |
158 | const GrUserStencilSettings* stencilSettings) |
159 | : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage, stencilSettings) { |
160 | fPaths.emplace_back( |
161 | PathData{viewMatrix, path, color, strokeWidth, miterLimit, style, join}); |
162 | |
163 | // compute bounds |
164 | SkRect bounds = path.getBounds(); |
165 | SkScalar w = strokeWidth; |
166 | if (w > 0) { |
167 | w /= 2; |
168 | SkScalar maxScale = viewMatrix.getMaxScale(); |
169 | // We should not have a perspective matrix, thus we should have a valid scale. |
170 | SkASSERT(maxScale != -1); |
171 | if (SkPaint::kMiter_Join == join && w * maxScale > 1.f) { |
172 | w *= miterLimit; |
173 | } |
174 | bounds.outset(w, w); |
175 | } |
176 | this->setTransformedBounds(bounds, viewMatrix, HasAABloat::kYes, IsHairline::kNo); |
177 | } |
178 | |
179 | const char* name() const override { return "AAFlatteningConvexPathOp" ; } |
180 | |
181 | void visitProxies(const VisitProxyFunc& func) const override { |
182 | if (fProgramInfo) { |
183 | fProgramInfo->visitFPProxies(func); |
184 | } else { |
185 | fHelper.visitProxies(func); |
186 | } |
187 | } |
188 | |
189 | FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } |
190 | |
191 | GrProcessorSet::Analysis finalize( |
192 | const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, |
193 | GrClampType clampType) override { |
194 | return fHelper.finalizeProcessors( |
195 | caps, clip, hasMixedSampledCoverage, clampType, |
196 | GrProcessorAnalysisCoverage::kSingleChannel, &fPaths.back().fColor, &fWideColor); |
197 | } |
198 | |
199 | private: |
200 | GrProgramInfo* programInfo() override { return fProgramInfo; } |
201 | |
202 | void onCreateProgramInfo(const GrCaps* caps, |
203 | SkArenaAlloc* arena, |
204 | const GrSurfaceProxyView* writeView, |
205 | GrAppliedClip&& appliedClip, |
206 | const GrXferProcessor::DstProxyView& dstProxyView) override { |
207 | GrGeometryProcessor* gp = create_lines_only_gp(arena, |
208 | fHelper.compatibleWithCoverageAsAlpha(), |
209 | fHelper.usesLocalCoords(), |
210 | fWideColor); |
211 | if (!gp) { |
212 | SkDebugf("Couldn't create a GrGeometryProcessor\n" ); |
213 | return; |
214 | } |
215 | |
216 | fProgramInfo = fHelper.createProgramInfoWithStencil(caps, arena, writeView, |
217 | std::move(appliedClip), dstProxyView, |
218 | gp, GrPrimitiveType::kTriangles); |
219 | } |
220 | |
221 | void recordDraw(Target* target, |
222 | int vertexCount, size_t vertexStride, void* vertices, |
223 | int indexCount, uint16_t* indices) { |
224 | if (vertexCount == 0 || indexCount == 0) { |
225 | return; |
226 | } |
227 | sk_sp<const GrBuffer> vertexBuffer; |
228 | int firstVertex; |
229 | void* verts = target->makeVertexSpace(vertexStride, vertexCount, &vertexBuffer, |
230 | &firstVertex); |
231 | if (!verts) { |
232 | SkDebugf("Could not allocate vertices\n" ); |
233 | return; |
234 | } |
235 | memcpy(verts, vertices, vertexCount * vertexStride); |
236 | |
237 | sk_sp<const GrBuffer> indexBuffer; |
238 | int firstIndex; |
239 | uint16_t* idxs = target->makeIndexSpace(indexCount, &indexBuffer, &firstIndex); |
240 | if (!idxs) { |
241 | SkDebugf("Could not allocate indices\n" ); |
242 | return; |
243 | } |
244 | memcpy(idxs, indices, indexCount * sizeof(uint16_t)); |
245 | GrSimpleMesh* mesh = target->allocMesh(); |
246 | mesh->setIndexed(std::move(indexBuffer), indexCount, firstIndex, 0, vertexCount - 1, |
247 | GrPrimitiveRestart::kNo, std::move(vertexBuffer), firstVertex); |
248 | fMeshes.push_back(mesh); |
249 | } |
250 | |
251 | void onPrepareDraws(Target* target) override { |
252 | if (!fProgramInfo) { |
253 | this->createProgramInfo(target); |
254 | if (!fProgramInfo) { |
255 | return; |
256 | } |
257 | } |
258 | |
259 | size_t vertexStride = fProgramInfo->primProc().vertexStride(); |
260 | int instanceCount = fPaths.count(); |
261 | |
262 | int64_t vertexCount = 0; |
263 | int64_t indexCount = 0; |
264 | int64_t maxVertices = DEFAULT_BUFFER_SIZE; |
265 | int64_t maxIndices = DEFAULT_BUFFER_SIZE; |
266 | uint8_t* vertices = (uint8_t*) sk_malloc_throw(maxVertices * vertexStride); |
267 | uint16_t* indices = (uint16_t*) sk_malloc_throw(maxIndices * sizeof(uint16_t)); |
268 | for (int i = 0; i < instanceCount; i++) { |
269 | const PathData& args = fPaths[i]; |
270 | GrAAConvexTessellator tess(args.fStyle, args.fStrokeWidth, |
271 | args.fJoin, args.fMiterLimit); |
272 | |
273 | if (!tess.tessellate(args.fViewMatrix, args.fPath)) { |
274 | continue; |
275 | } |
276 | |
277 | int currentVertices = tess.numPts(); |
278 | if (vertexCount + currentVertices > static_cast<int>(UINT16_MAX)) { |
279 | // if we added the current instance, we would overflow the indices we can store in a |
280 | // uint16_t. Draw what we've got so far and reset. |
281 | this->recordDraw(target, vertexCount, vertexStride, vertices, indexCount, indices); |
282 | vertexCount = 0; |
283 | indexCount = 0; |
284 | } |
285 | if (vertexCount + currentVertices > maxVertices) { |
286 | maxVertices = std::max(vertexCount + currentVertices, maxVertices * 2); |
287 | if (maxVertices * vertexStride > SK_MaxS32) { |
288 | sk_free(vertices); |
289 | sk_free(indices); |
290 | return; |
291 | } |
292 | vertices = (uint8_t*) sk_realloc_throw(vertices, maxVertices * vertexStride); |
293 | } |
294 | int currentIndices = tess.numIndices(); |
295 | if (indexCount + currentIndices > maxIndices) { |
296 | maxIndices = std::max(indexCount + currentIndices, maxIndices * 2); |
297 | if (maxIndices * sizeof(uint16_t) > SK_MaxS32) { |
298 | sk_free(vertices); |
299 | sk_free(indices); |
300 | return; |
301 | } |
302 | indices = (uint16_t*) sk_realloc_throw(indices, maxIndices * sizeof(uint16_t)); |
303 | } |
304 | |
305 | const SkMatrix* localCoordsMatrix = nullptr; |
306 | SkMatrix ivm; |
307 | if (fHelper.usesLocalCoords()) { |
308 | if (!args.fViewMatrix.invert(&ivm)) { |
309 | ivm = SkMatrix::I(); |
310 | } |
311 | localCoordsMatrix = &ivm; |
312 | } |
313 | |
314 | extract_verts(tess, localCoordsMatrix, vertices + vertexStride * vertexCount, |
315 | GrVertexColor(args.fColor, fWideColor), vertexCount, |
316 | indices + indexCount); |
317 | vertexCount += currentVertices; |
318 | indexCount += currentIndices; |
319 | } |
320 | if (vertexCount <= SK_MaxS32 && indexCount <= SK_MaxS32) { |
321 | this->recordDraw(target, vertexCount, vertexStride, vertices, indexCount, indices); |
322 | } |
323 | sk_free(vertices); |
324 | sk_free(indices); |
325 | } |
326 | |
327 | void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { |
328 | if (!fProgramInfo || fMeshes.isEmpty()) { |
329 | return; |
330 | } |
331 | |
332 | flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); |
333 | flushState->bindTextures(fProgramInfo->primProc(), nullptr, fProgramInfo->pipeline()); |
334 | for (int i = 0; i < fMeshes.count(); ++i) { |
335 | flushState->drawMesh(*fMeshes[i]); |
336 | } |
337 | } |
338 | |
339 | CombineResult onCombineIfPossible(GrOp* t, GrRecordingContext::Arenas*, |
340 | const GrCaps& caps) override { |
341 | AAFlatteningConvexPathOp* that = t->cast<AAFlatteningConvexPathOp>(); |
342 | if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { |
343 | return CombineResult::kCannotCombine; |
344 | } |
345 | |
346 | fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin()); |
347 | fWideColor |= that->fWideColor; |
348 | return CombineResult::kMerged; |
349 | } |
350 | |
351 | #if GR_TEST_UTILS |
352 | SkString onDumpInfo() const override { |
353 | SkString string; |
354 | for (const auto& path : fPaths) { |
355 | string.appendf( |
356 | "Color: 0x%08x, StrokeWidth: %.2f, Style: %d, Join: %d, " |
357 | "MiterLimit: %.2f\n" , |
358 | path.fColor.toBytes_RGBA(), path.fStrokeWidth, path.fStyle, path.fJoin, |
359 | path.fMiterLimit); |
360 | } |
361 | string += fHelper.dumpInfo(); |
362 | return string; |
363 | } |
364 | #endif |
365 | |
366 | struct PathData { |
367 | SkMatrix fViewMatrix; |
368 | SkPath fPath; |
369 | SkPMColor4f fColor; |
370 | SkScalar fStrokeWidth; |
371 | SkScalar fMiterLimit; |
372 | SkStrokeRec::Style fStyle; |
373 | SkPaint::Join fJoin; |
374 | }; |
375 | |
376 | SkSTArray<1, PathData, true> fPaths; |
377 | Helper fHelper; |
378 | bool fWideColor; |
379 | |
380 | SkTDArray<GrSimpleMesh*> fMeshes; |
381 | GrProgramInfo* fProgramInfo = nullptr; |
382 | |
383 | typedef GrMeshDrawOp INHERITED; |
384 | }; |
385 | |
386 | } // anonymous namespace |
387 | |
388 | bool GrAALinearizingConvexPathRenderer::onDrawPath(const DrawPathArgs& args) { |
389 | GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), |
390 | "GrAALinearizingConvexPathRenderer::onDrawPath" ); |
391 | SkASSERT(args.fRenderTargetContext->numSamples() <= 1); |
392 | SkASSERT(!args.fShape->isEmpty()); |
393 | SkASSERT(!args.fShape->style().pathEffect()); |
394 | |
395 | SkPath path; |
396 | args.fShape->asPath(&path); |
397 | bool fill = args.fShape->style().isSimpleFill(); |
398 | const SkStrokeRec& stroke = args.fShape->style().strokeRec(); |
399 | SkScalar strokeWidth = fill ? -1.0f : stroke.getWidth(); |
400 | SkPaint::Join join = fill ? SkPaint::Join::kMiter_Join : stroke.getJoin(); |
401 | SkScalar miterLimit = stroke.getMiter(); |
402 | |
403 | std::unique_ptr<GrDrawOp> op = AAFlatteningConvexPathOp::Make( |
404 | args.fContext, std::move(args.fPaint), *args.fViewMatrix, path, strokeWidth, |
405 | stroke.getStyle(), join, miterLimit, args.fUserStencilSettings); |
406 | args.fRenderTargetContext->addDrawOp(args.fClip, std::move(op)); |
407 | return true; |
408 | } |
409 | |
410 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
411 | |
412 | #if GR_TEST_UTILS |
413 | |
414 | GR_DRAW_OP_TEST_DEFINE(AAFlatteningConvexPathOp) { |
415 | SkMatrix viewMatrix = GrTest::TestMatrixPreservesRightAngles(random); |
416 | const SkPath& path = GrTest::TestPathConvex(random); |
417 | |
418 | SkStrokeRec::Style styles[3] = { SkStrokeRec::kFill_Style, |
419 | SkStrokeRec::kStroke_Style, |
420 | SkStrokeRec::kStrokeAndFill_Style }; |
421 | |
422 | SkStrokeRec::Style style = styles[random->nextU() % 3]; |
423 | |
424 | SkScalar strokeWidth = -1.f; |
425 | SkPaint::Join join = SkPaint::kMiter_Join; |
426 | SkScalar miterLimit = 0.5f; |
427 | |
428 | if (SkStrokeRec::kFill_Style != style) { |
429 | strokeWidth = random->nextRangeF(1.0f, 10.0f); |
430 | if (random->nextBool()) { |
431 | join = SkPaint::kMiter_Join; |
432 | } else { |
433 | join = SkPaint::kBevel_Join; |
434 | } |
435 | miterLimit = random->nextRangeF(0.5f, 2.0f); |
436 | } |
437 | const GrUserStencilSettings* stencilSettings = GrGetRandomStencil(random, context); |
438 | return AAFlatteningConvexPathOp::Make(context, std::move(paint), viewMatrix, path, strokeWidth, |
439 | style, join, miterLimit, stencilSettings); |
440 | } |
441 | |
442 | #endif |
443 | |