| 1 | /* |
| 2 | * Copyright 2020 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 "src/gpu/tessellate/GrStrokeTessellateOp.h" |
| 9 | |
| 10 | #include "src/core/SkPathPriv.h" |
| 11 | #include "src/gpu/tessellate/GrStrokePatchBuilder.h" |
| 12 | #include "src/gpu/tessellate/GrStrokeTessellateShader.h" |
| 13 | |
| 14 | static SkPMColor4f get_paint_constant_blended_color(const GrPaint& paint) { |
| 15 | SkPMColor4f constantColor; |
| 16 | // Patches can overlap, so until a stencil technique is implemented, the provided paints must be |
| 17 | // constant blended colors. |
| 18 | SkAssertResult(paint.isConstantBlendedColor(&constantColor)); |
| 19 | return constantColor; |
| 20 | } |
| 21 | |
| 22 | GrStrokeTessellateOp::GrStrokeTessellateOp(GrAAType aaType, const SkMatrix& viewMatrix, |
| 23 | const SkPath& path, const SkStrokeRec& stroke, |
| 24 | GrPaint&& paint) |
| 25 | : GrDrawOp(ClassID()) |
| 26 | , fPathStrokes(path, stroke) |
| 27 | , fTotalCombinedVerbCnt(path.countVerbs()) |
| 28 | , fAAType(aaType) |
| 29 | , fColor(get_paint_constant_blended_color(paint)) |
| 30 | , fProcessors(std::move(paint)) { |
| 31 | SkASSERT(fAAType != GrAAType::kCoverage); // No mixed samples support yet. |
| 32 | if (stroke.getJoin() == SkPaint::kMiter_Join) { |
| 33 | float miter = stroke.getMiter(); |
| 34 | if (miter <= 0) { |
| 35 | fPathStrokes.head().fStroke.setStrokeParams(stroke.getCap(), SkPaint::kBevel_Join, 0); |
| 36 | } else { |
| 37 | fMiterLimitOrZero = miter; |
| 38 | } |
| 39 | } |
| 40 | if (!(viewMatrix.getType() & ~SkMatrix::kScale_Mask) && |
| 41 | viewMatrix.getScaleX() == viewMatrix.getScaleY()) { |
| 42 | fMatrixScale = viewMatrix.getScaleX(); |
| 43 | fSkewMatrix = SkMatrix::I(); |
| 44 | } else { |
| 45 | SkASSERT(!viewMatrix.hasPerspective()); // getMaxScale() doesn't work with perspective. |
| 46 | fMatrixScale = viewMatrix.getMaxScale(); |
| 47 | float invScale = SkScalarInvert(fMatrixScale); |
| 48 | fSkewMatrix = viewMatrix; |
| 49 | fSkewMatrix.preScale(invScale, invScale); |
| 50 | } |
| 51 | SkASSERT(fMatrixScale >= 0); |
| 52 | SkRect devBounds = fPathStrokes.head().fPath.getBounds(); |
| 53 | float inflationRadius = fPathStrokes.head().fStroke.getInflationRadius(); |
| 54 | devBounds.outset(inflationRadius, inflationRadius); |
| 55 | viewMatrix.mapRect(&devBounds, devBounds); |
| 56 | this->setBounds(devBounds, HasAABloat(GrAAType::kCoverage == fAAType), IsHairline::kNo); |
| 57 | } |
| 58 | |
| 59 | GrDrawOp::FixedFunctionFlags GrStrokeTessellateOp::fixedFunctionFlags() const { |
| 60 | auto flags = FixedFunctionFlags::kNone; |
| 61 | if (GrAAType::kNone != fAAType) { |
| 62 | flags |= FixedFunctionFlags::kUsesHWAA; |
| 63 | } |
| 64 | return flags; |
| 65 | } |
| 66 | |
| 67 | GrProcessorSet::Analysis GrStrokeTessellateOp::finalize(const GrCaps& caps, |
| 68 | const GrAppliedClip* clip, |
| 69 | bool hasMixedSampledCoverage, |
| 70 | GrClampType clampType) { |
| 71 | return fProcessors.finalize(fColor, GrProcessorAnalysisCoverage::kNone, clip, |
| 72 | &GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps, |
| 73 | clampType, &fColor); |
| 74 | } |
| 75 | |
| 76 | GrOp::CombineResult GrStrokeTessellateOp::onCombineIfPossible(GrOp* grOp, |
| 77 | GrRecordingContext::Arenas* arenas, |
| 78 | const GrCaps&) { |
| 79 | auto* op = grOp->cast<GrStrokeTessellateOp>(); |
| 80 | if (fColor != op->fColor || |
| 81 | // TODO: When stroking is finished, we may want to consider whether a unique matrix scale |
| 82 | // can be stored with each PathStroke instead. This might improve batching. |
| 83 | fMatrixScale != op->fMatrixScale || |
| 84 | fSkewMatrix != op->fSkewMatrix || |
| 85 | fAAType != op->fAAType || |
| 86 | ((fMiterLimitOrZero * op->fMiterLimitOrZero != 0) && // Are both non-zero? |
| 87 | fMiterLimitOrZero != op->fMiterLimitOrZero) || |
| 88 | fProcessors != op->fProcessors) { |
| 89 | return CombineResult::kCannotCombine; |
| 90 | } |
| 91 | |
| 92 | fPathStrokes.concat(std::move(op->fPathStrokes), arenas->recordTimeAllocator()); |
| 93 | if (op->fMiterLimitOrZero != 0) { |
| 94 | SkASSERT(fMiterLimitOrZero == 0 || fMiterLimitOrZero == op->fMiterLimitOrZero); |
| 95 | fMiterLimitOrZero = op->fMiterLimitOrZero; |
| 96 | } |
| 97 | fTotalCombinedVerbCnt += op->fTotalCombinedVerbCnt; |
| 98 | |
| 99 | return CombineResult::kMerged; |
| 100 | } |
| 101 | |
| 102 | void GrStrokeTessellateOp::onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView* writeView, |
| 103 | GrAppliedClip*, const GrXferProcessor::DstProxyView&) { |
| 104 | } |
| 105 | |
| 106 | void GrStrokeTessellateOp::onPrepare(GrOpFlushState* flushState) { |
| 107 | GrStrokePatchBuilder builder(flushState, &fVertexChunks, fMatrixScale, fTotalCombinedVerbCnt); |
| 108 | for (auto& [path, stroke] : fPathStrokes) { |
| 109 | builder.addPath(path, stroke); |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | void GrStrokeTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { |
| 114 | GrPipeline::InitArgs initArgs; |
| 115 | if (GrAAType::kNone != fAAType) { |
| 116 | initArgs.fInputFlags |= GrPipeline::InputFlags::kHWAntialias; |
| 117 | SkASSERT(flushState->proxy()->numSamples() > 1); // No mixed samples yet. |
| 118 | SkASSERT(fAAType != GrAAType::kCoverage); // No mixed samples yet. |
| 119 | } |
| 120 | initArgs.fCaps = &flushState->caps(); |
| 121 | initArgs.fDstProxyView = flushState->drawOpArgs().dstProxyView(); |
| 122 | initArgs.fWriteSwizzle = flushState->drawOpArgs().writeSwizzle(); |
| 123 | GrPipeline pipeline(initArgs, std::move(fProcessors), flushState->detachAppliedClip()); |
| 124 | |
| 125 | GrStrokeTessellateShader strokeShader(fSkewMatrix, fColor, fMiterLimitOrZero); |
| 126 | GrPathShader::ProgramInfo programInfo(flushState->writeView(), &pipeline, &strokeShader); |
| 127 | |
| 128 | SkASSERT(chainBounds == this->bounds()); |
| 129 | flushState->bindPipelineAndScissorClip(programInfo, this->bounds()); |
| 130 | flushState->bindTextures(strokeShader, nullptr, pipeline); |
| 131 | |
| 132 | for (const auto& chunk : fVertexChunks) { |
| 133 | if (chunk.fVertexBuffer) { |
| 134 | flushState->bindBuffers(nullptr, nullptr, std::move(chunk.fVertexBuffer)); |
| 135 | flushState->draw(chunk.fVertexCount, chunk.fBaseVertex); |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | |