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/GrShape.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 | #ifdef SK_DEBUG |
190 | SkString dumpInfo() const override { |
191 | SkString string; |
192 | for (const auto& path : fPaths) { |
193 | string.appendf( |
194 | "Color: 0x%08x, StrokeWidth: %.2f, Style: %d, Join: %d, " |
195 | "MiterLimit: %.2f\n" , |
196 | path.fColor.toBytes_RGBA(), path.fStrokeWidth, path.fStyle, path.fJoin, |
197 | path.fMiterLimit); |
198 | } |
199 | string += fHelper.dumpInfo(); |
200 | string += INHERITED::dumpInfo(); |
201 | return string; |
202 | } |
203 | #endif |
204 | |
205 | FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } |
206 | |
207 | GrProcessorSet::Analysis finalize( |
208 | const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, |
209 | GrClampType clampType) override { |
210 | return fHelper.finalizeProcessors( |
211 | caps, clip, hasMixedSampledCoverage, clampType, |
212 | GrProcessorAnalysisCoverage::kSingleChannel, &fPaths.back().fColor, &fWideColor); |
213 | } |
214 | |
215 | private: |
216 | GrProgramInfo* programInfo() override { return fProgramInfo; } |
217 | |
218 | void onCreateProgramInfo(const GrCaps* caps, |
219 | SkArenaAlloc* arena, |
220 | const GrSurfaceProxyView* writeView, |
221 | GrAppliedClip&& appliedClip, |
222 | const GrXferProcessor::DstProxyView& dstProxyView) override { |
223 | GrGeometryProcessor* gp = create_lines_only_gp(arena, |
224 | fHelper.compatibleWithCoverageAsAlpha(), |
225 | fHelper.usesLocalCoords(), |
226 | fWideColor); |
227 | if (!gp) { |
228 | SkDebugf("Couldn't create a GrGeometryProcessor\n" ); |
229 | return; |
230 | } |
231 | |
232 | fProgramInfo = fHelper.createProgramInfoWithStencil(caps, arena, writeView, |
233 | std::move(appliedClip), dstProxyView, |
234 | gp, GrPrimitiveType::kTriangles); |
235 | } |
236 | |
237 | void recordDraw(Target* target, |
238 | int vertexCount, size_t vertexStride, void* vertices, |
239 | int indexCount, uint16_t* indices) { |
240 | if (vertexCount == 0 || indexCount == 0) { |
241 | return; |
242 | } |
243 | sk_sp<const GrBuffer> vertexBuffer; |
244 | int firstVertex; |
245 | void* verts = target->makeVertexSpace(vertexStride, vertexCount, &vertexBuffer, |
246 | &firstVertex); |
247 | if (!verts) { |
248 | SkDebugf("Could not allocate vertices\n" ); |
249 | return; |
250 | } |
251 | memcpy(verts, vertices, vertexCount * vertexStride); |
252 | |
253 | sk_sp<const GrBuffer> indexBuffer; |
254 | int firstIndex; |
255 | uint16_t* idxs = target->makeIndexSpace(indexCount, &indexBuffer, &firstIndex); |
256 | if (!idxs) { |
257 | SkDebugf("Could not allocate indices\n" ); |
258 | return; |
259 | } |
260 | memcpy(idxs, indices, indexCount * sizeof(uint16_t)); |
261 | GrSimpleMesh* mesh = target->allocMesh(); |
262 | mesh->setIndexed(std::move(indexBuffer), indexCount, firstIndex, 0, vertexCount - 1, |
263 | GrPrimitiveRestart::kNo, std::move(vertexBuffer), firstVertex); |
264 | fMeshes.push_back(mesh); |
265 | } |
266 | |
267 | void onPrepareDraws(Target* target) override { |
268 | if (!fProgramInfo) { |
269 | this->createProgramInfo(target); |
270 | if (!fProgramInfo) { |
271 | return; |
272 | } |
273 | } |
274 | |
275 | size_t vertexStride = fProgramInfo->primProc().vertexStride(); |
276 | int instanceCount = fPaths.count(); |
277 | |
278 | int64_t vertexCount = 0; |
279 | int64_t indexCount = 0; |
280 | int64_t maxVertices = DEFAULT_BUFFER_SIZE; |
281 | int64_t maxIndices = DEFAULT_BUFFER_SIZE; |
282 | uint8_t* vertices = (uint8_t*) sk_malloc_throw(maxVertices * vertexStride); |
283 | uint16_t* indices = (uint16_t*) sk_malloc_throw(maxIndices * sizeof(uint16_t)); |
284 | for (int i = 0; i < instanceCount; i++) { |
285 | const PathData& args = fPaths[i]; |
286 | GrAAConvexTessellator tess(args.fStyle, args.fStrokeWidth, |
287 | args.fJoin, args.fMiterLimit); |
288 | |
289 | if (!tess.tessellate(args.fViewMatrix, args.fPath)) { |
290 | continue; |
291 | } |
292 | |
293 | int currentVertices = tess.numPts(); |
294 | if (vertexCount + currentVertices > static_cast<int>(UINT16_MAX)) { |
295 | // if we added the current instance, we would overflow the indices we can store in a |
296 | // uint16_t. Draw what we've got so far and reset. |
297 | this->recordDraw(target, vertexCount, vertexStride, vertices, indexCount, indices); |
298 | vertexCount = 0; |
299 | indexCount = 0; |
300 | } |
301 | if (vertexCount + currentVertices > maxVertices) { |
302 | maxVertices = std::max(vertexCount + currentVertices, maxVertices * 2); |
303 | if (maxVertices * vertexStride > SK_MaxS32) { |
304 | sk_free(vertices); |
305 | sk_free(indices); |
306 | return; |
307 | } |
308 | vertices = (uint8_t*) sk_realloc_throw(vertices, maxVertices * vertexStride); |
309 | } |
310 | int currentIndices = tess.numIndices(); |
311 | if (indexCount + currentIndices > maxIndices) { |
312 | maxIndices = std::max(indexCount + currentIndices, maxIndices * 2); |
313 | if (maxIndices * sizeof(uint16_t) > SK_MaxS32) { |
314 | sk_free(vertices); |
315 | sk_free(indices); |
316 | return; |
317 | } |
318 | indices = (uint16_t*) sk_realloc_throw(indices, maxIndices * sizeof(uint16_t)); |
319 | } |
320 | |
321 | const SkMatrix* localCoordsMatrix = nullptr; |
322 | SkMatrix ivm; |
323 | if (fHelper.usesLocalCoords()) { |
324 | if (!args.fViewMatrix.invert(&ivm)) { |
325 | ivm = SkMatrix::I(); |
326 | } |
327 | localCoordsMatrix = &ivm; |
328 | } |
329 | |
330 | extract_verts(tess, localCoordsMatrix, vertices + vertexStride * vertexCount, |
331 | GrVertexColor(args.fColor, fWideColor), vertexCount, |
332 | indices + indexCount); |
333 | vertexCount += currentVertices; |
334 | indexCount += currentIndices; |
335 | } |
336 | if (vertexCount <= SK_MaxS32 && indexCount <= SK_MaxS32) { |
337 | this->recordDraw(target, vertexCount, vertexStride, vertices, indexCount, indices); |
338 | } |
339 | sk_free(vertices); |
340 | sk_free(indices); |
341 | } |
342 | |
343 | void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { |
344 | if (!fProgramInfo || fMeshes.isEmpty()) { |
345 | return; |
346 | } |
347 | |
348 | flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); |
349 | flushState->bindTextures(fProgramInfo->primProc(), nullptr, fProgramInfo->pipeline()); |
350 | for (int i = 0; i < fMeshes.count(); ++i) { |
351 | flushState->drawMesh(*fMeshes[i]); |
352 | } |
353 | } |
354 | |
355 | CombineResult onCombineIfPossible(GrOp* t, GrRecordingContext::Arenas*, |
356 | const GrCaps& caps) override { |
357 | AAFlatteningConvexPathOp* that = t->cast<AAFlatteningConvexPathOp>(); |
358 | if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { |
359 | return CombineResult::kCannotCombine; |
360 | } |
361 | |
362 | fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin()); |
363 | fWideColor |= that->fWideColor; |
364 | return CombineResult::kMerged; |
365 | } |
366 | |
367 | |
368 | struct PathData { |
369 | SkMatrix fViewMatrix; |
370 | SkPath fPath; |
371 | SkPMColor4f fColor; |
372 | SkScalar fStrokeWidth; |
373 | SkScalar fMiterLimit; |
374 | SkStrokeRec::Style fStyle; |
375 | SkPaint::Join fJoin; |
376 | }; |
377 | |
378 | SkSTArray<1, PathData, true> fPaths; |
379 | Helper fHelper; |
380 | bool fWideColor; |
381 | |
382 | SkTDArray<GrSimpleMesh*> fMeshes; |
383 | GrProgramInfo* fProgramInfo = nullptr; |
384 | |
385 | typedef GrMeshDrawOp INHERITED; |
386 | }; |
387 | |
388 | } // anonymous namespace |
389 | |
390 | bool GrAALinearizingConvexPathRenderer::onDrawPath(const DrawPathArgs& args) { |
391 | GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), |
392 | "GrAALinearizingConvexPathRenderer::onDrawPath" ); |
393 | SkASSERT(args.fRenderTargetContext->numSamples() <= 1); |
394 | SkASSERT(!args.fShape->isEmpty()); |
395 | SkASSERT(!args.fShape->style().pathEffect()); |
396 | |
397 | SkPath path; |
398 | args.fShape->asPath(&path); |
399 | bool fill = args.fShape->style().isSimpleFill(); |
400 | const SkStrokeRec& stroke = args.fShape->style().strokeRec(); |
401 | SkScalar strokeWidth = fill ? -1.0f : stroke.getWidth(); |
402 | SkPaint::Join join = fill ? SkPaint::Join::kMiter_Join : stroke.getJoin(); |
403 | SkScalar miterLimit = stroke.getMiter(); |
404 | |
405 | std::unique_ptr<GrDrawOp> op = AAFlatteningConvexPathOp::Make( |
406 | args.fContext, std::move(args.fPaint), *args.fViewMatrix, path, strokeWidth, |
407 | stroke.getStyle(), join, miterLimit, args.fUserStencilSettings); |
408 | args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); |
409 | return true; |
410 | } |
411 | |
412 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
413 | |
414 | #if GR_TEST_UTILS |
415 | |
416 | GR_DRAW_OP_TEST_DEFINE(AAFlatteningConvexPathOp) { |
417 | SkMatrix viewMatrix = GrTest::TestMatrixPreservesRightAngles(random); |
418 | SkPath path = GrTest::TestPathConvex(random); |
419 | |
420 | SkStrokeRec::Style styles[3] = { SkStrokeRec::kFill_Style, |
421 | SkStrokeRec::kStroke_Style, |
422 | SkStrokeRec::kStrokeAndFill_Style }; |
423 | |
424 | SkStrokeRec::Style style = styles[random->nextU() % 3]; |
425 | |
426 | SkScalar strokeWidth = -1.f; |
427 | SkPaint::Join join = SkPaint::kMiter_Join; |
428 | SkScalar miterLimit = 0.5f; |
429 | |
430 | if (SkStrokeRec::kFill_Style != style) { |
431 | strokeWidth = random->nextRangeF(1.0f, 10.0f); |
432 | if (random->nextBool()) { |
433 | join = SkPaint::kMiter_Join; |
434 | } else { |
435 | join = SkPaint::kBevel_Join; |
436 | } |
437 | miterLimit = random->nextRangeF(0.5f, 2.0f); |
438 | } |
439 | const GrUserStencilSettings* stencilSettings = GrGetRandomStencil(random, context); |
440 | return AAFlatteningConvexPathOp::Make(context, std::move(paint), viewMatrix, path, strokeWidth, |
441 | style, join, miterLimit, stencilSettings); |
442 | } |
443 | |
444 | #endif |
445 | |