| /* |
| * Copyright 2017 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "GrCCPRCoverageOpsBuilder.h" |
| |
| #include "GrBuffer.h" |
| #include "GrGpuCommandBuffer.h" |
| #include "GrOnFlushResourceProvider.h" |
| #include "GrOpFlushState.h" |
| #include "GrPathUtils.h" |
| #include "SkGeometry.h" |
| #include "SkMakeUnique.h" |
| #include "SkMathPriv.h" |
| #include "SkPath.h" |
| #include "SkPathPriv.h" |
| #include "SkPoint.h" |
| #include "SkNx.h" |
| #include "ops/GrDrawOp.h" |
| #include "../pathops/SkPathOpsCubic.h" |
| #include <numeric> |
| |
| class GrCCPRCoverageOpsBuilder::CoverageOp : public GrDrawOp { |
| public: |
| using PrimitiveTallies = GrCCPRCoverageOpsBuilder::PrimitiveTallies; |
| |
| DEFINE_OP_CLASS_ID |
| |
| CoverageOp(const SkISize& drawBounds, sk_sp<GrBuffer> pointsBuffer, |
| sk_sp<GrBuffer> trianglesBuffer, |
| const PrimitiveTallies baseInstances[kNumScissorModes], |
| const PrimitiveTallies endInstances[kNumScissorModes], SkTArray<ScissorBatch>&&); |
| |
| // GrDrawOp interface. |
| const char* name() const override { return "GrCCPRCoverageOpsBuilder::CoverageOp"; } |
| FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; } |
| RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*) override { |
| return RequiresDstTexture::kNo; |
| } |
| bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; } |
| void onPrepare(GrOpFlushState*) override {} |
| void onExecute(GrOpFlushState*) override; |
| |
| private: |
| void drawMaskPrimitives(GrOpFlushState*, const GrPipeline&, const GrCCPRCoverageProcessor::Mode, |
| GrPrimitiveType, int vertexCount, |
| int PrimitiveTallies::* instanceType) const; |
| |
| const SkISize fDrawBounds; |
| const sk_sp<GrBuffer> fPointsBuffer; |
| const sk_sp<GrBuffer> fTrianglesBuffer; |
| const PrimitiveTallies fBaseInstances[GrCCPRCoverageOpsBuilder::kNumScissorModes]; |
| const PrimitiveTallies fInstanceCounts[GrCCPRCoverageOpsBuilder::kNumScissorModes]; |
| const SkTArray<ScissorBatch> fScissorBatches; |
| |
| mutable SkTArray<GrMesh> fMeshesScratchBuffer; |
| mutable SkTArray<GrPipeline::DynamicState> fDynamicStatesScratchBuffer; |
| |
| typedef GrDrawOp INHERITED; |
| }; |
| |
| /** |
| * This is a view matrix that accumulates two bounding boxes as it maps points: device-space bounds |
| * and "45 degree" device-space bounds (| 1 -1 | * devCoords). |
| * | 1 1 | |
| */ |
| class AccumulatingViewMatrix { |
| public: |
| AccumulatingViewMatrix(const SkMatrix& m, const SkPoint& initialPoint); |
| |
| SkPoint transform(const SkPoint& pt); |
| void getAccumulatedBounds(SkRect* devBounds, SkRect* devBounds45) const; |
| |
| private: |
| Sk4f fX; |
| Sk4f fY; |
| Sk4f fT; |
| |
| Sk4f fTopLeft; |
| Sk4f fBottomRight; |
| }; |
| |
| static int num_pts(uint8_t verb) { |
| switch (verb) { |
| case SkPath::kClose_Verb: |
| case SkPath::kDone_Verb: |
| default: |
| SkFAIL("Path verb does not have an endpoint."); |
| return 0; |
| case SkPath::kMove_Verb: |
| case SkPath::kLine_Verb: |
| return 1; |
| case SkPath::kQuad_Verb: |
| return 2; |
| case SkPath::kConic_Verb: |
| return 2; |
| case SkPath::kCubic_Verb: |
| return 3; |
| } |
| } |
| |
| static SkPoint to_skpoint(double x, double y) { |
| return {static_cast<SkScalar>(x), static_cast<SkScalar>(y)}; |
| } |
| |
| static SkPoint to_skpoint(const SkDPoint& dpoint) { |
| return to_skpoint(dpoint.fX, dpoint.fY); |
| } |
| |
| bool GrCCPRCoverageOpsBuilder::init(GrOnFlushResourceProvider* onFlushRP, |
| const MaxBufferItems& maxBufferItems) { |
| const int maxPoints = maxBufferItems.fMaxFanPoints + maxBufferItems.fMaxControlPoints; |
| fPointsBuffer = onFlushRP->makeBuffer(kTexel_GrBufferType, maxPoints * 2 * sizeof(float)); |
| if (!fPointsBuffer) { |
| return false; |
| } |
| |
| const MaxPrimitives* const maxPrimitives = maxBufferItems.fMaxPrimitives; |
| const int maxInstances = (maxPrimitives[0].sum() + maxPrimitives[1].sum()); |
| fInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType, maxInstances * 4 * sizeof(int)); |
| if (!fInstanceBuffer) { |
| fPointsBuffer.reset(); |
| return false; |
| } |
| |
| fFanPtsIdx = 0; |
| fControlPtsIdx = maxBufferItems.fMaxFanPoints; |
| SkDEBUGCODE(fMaxFanPoints = maxBufferItems.fMaxFanPoints); |
| SkDEBUGCODE(fMaxControlPoints = maxBufferItems.fMaxControlPoints); |
| |
| int baseInstance = 0; |
| for (int i = 0; i < kNumScissorModes; ++i) { |
| fBaseInstances[i].fTriangles = baseInstance; |
| baseInstance += maxPrimitives[i].fMaxTriangles; |
| |
| fBaseInstances[i].fQuadratics = baseInstance; |
| baseInstance += maxPrimitives[i].fMaxQuadratics; |
| |
| fBaseInstances[i].fSerpentines = baseInstance; |
| baseInstance += maxPrimitives[i].fMaxCubics; |
| |
| // Loops grow backwards. |
| fBaseInstances[i].fLoops = baseInstance; |
| |
| fInstanceIndices[i] = fBaseInstances[i]; |
| } |
| |
| fPointsData = static_cast<SkPoint*>(fPointsBuffer->map()); |
| SkASSERT(fPointsData); |
| GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT); |
| GR_STATIC_ASSERT(8 == sizeof(SkPoint)); |
| |
| fInstanceData = static_cast<PrimitiveInstance*>(fInstanceBuffer->map()); |
| SkASSERT(fInstanceData); |
| |
| return true; |
| } |
| |
| using MaxBufferItems = GrCCPRCoverageOpsBuilder::MaxBufferItems; |
| |
| void MaxBufferItems::countPathItems(GrCCPRCoverageOpsBuilder::ScissorMode scissorMode, |
| const SkPath& path) { |
| static constexpr int kMaxQuadraticSegments = 2; |
| static constexpr int kMaxCubicSegments = 3; |
| |
| MaxPrimitives& maxPrimitives = fMaxPrimitives[(int)scissorMode]; |
| int currFanPts = 0; |
| |
| for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { |
| switch (verb) { |
| case SkPath::kMove_Verb: |
| case SkPath::kClose_Verb: |
| fMaxFanPoints += currFanPts; |
| maxPrimitives.fMaxTriangles += SkTMax(0, currFanPts - 2); |
| currFanPts = SkPath::kMove_Verb == verb ? 1 : 0; |
| continue; |
| case SkPath::kLine_Verb: |
| SkASSERT(currFanPts > 0); |
| ++currFanPts; |
| continue; |
| case SkPath::kQuad_Verb: |
| SkASSERT(currFanPts > 0); |
| currFanPts += kMaxQuadraticSegments; |
| fMaxControlPoints += kMaxQuadraticSegments; |
| maxPrimitives.fMaxQuadratics += kMaxQuadraticSegments; |
| continue; |
| case SkPath::kCubic_Verb: |
| GR_STATIC_ASSERT(kMaxCubicSegments >= kMaxQuadraticSegments); |
| SkASSERT(currFanPts > 0); |
| // Over-allocate for the worst case when the cubic is chopped into 3 segments. |
| currFanPts += kMaxCubicSegments; |
| // Each cubic segment has two control points. |
| fMaxControlPoints += kMaxCubicSegments * 2; |
| // Each cubic segment also emits two root t,s values as "control points". |
| fMaxControlPoints += kMaxCubicSegments * 2; |
| maxPrimitives.fMaxCubics += kMaxCubicSegments; |
| // The cubic may also turn out to be a quadratic. While we over-allocate by a fair |
| // amount, this is still a relatively small amount of space compared to the atlas. |
| maxPrimitives.fMaxQuadratics += kMaxQuadraticSegments; |
| continue; |
| case SkPath::kConic_Verb: |
| SkASSERT(currFanPts > 0); |
| SkFAIL("Conics are not supported."); |
| default: |
| SkFAIL("Unexpected path verb."); |
| } |
| } |
| |
| fMaxFanPoints += currFanPts; |
| maxPrimitives.fMaxTriangles += SkTMax(0, currFanPts - 2); |
| |
| ++fMaxPaths; |
| } |
| |
| void GrCCPRCoverageOpsBuilder::parsePath(ScissorMode scissorMode, const SkMatrix& viewMatrix, |
| const SkPath& path, SkRect* devBounds, |
| SkRect* devBounds45) { |
| // Make sure they haven't called finalize yet (or not called init). |
| SkASSERT(fPointsData); |
| SkASSERT(fInstanceData); |
| |
| fCurrScissorMode = scissorMode; |
| fCurrPathIndices = fInstanceIndices[(int)fCurrScissorMode]; |
| fCurrContourStartIdx = fFanPtsIdx; |
| |
| const SkPoint* const pts = SkPathPriv::PointData(path); |
| int ptsIdx = 0; |
| |
| SkASSERT(!path.isEmpty()); |
| SkASSERT(path.countPoints() > 0); |
| AccumulatingViewMatrix m(viewMatrix, pts[0]); |
| |
| for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { |
| switch (verb) { |
| case SkPath::kMove_Verb: |
| this->startContour(m.transform(pts[ptsIdx++])); |
| continue; |
| case SkPath::kClose_Verb: |
| this->closeContour(); |
| continue; |
| case SkPath::kLine_Verb: |
| this->fanTo(m.transform(pts[ptsIdx])); |
| break; |
| case SkPath::kQuad_Verb: |
| SkASSERT(ptsIdx >= 1); // SkPath should have inserted an implicit moveTo if needed. |
| this->quadraticTo(m.transform(pts[ptsIdx]), m.transform(pts[ptsIdx + 1])); |
| break; |
| case SkPath::kCubic_Verb: |
| SkASSERT(ptsIdx >= 1); // SkPath should have inserted an implicit moveTo if needed. |
| this->cubicTo(m.transform(pts[ptsIdx]), m.transform(pts[ptsIdx + 1]), |
| m.transform(pts[ptsIdx + 2])); |
| break; |
| case SkPath::kConic_Verb: |
| SkFAIL("Conics are not supported."); |
| default: |
| SkFAIL("Unexpected path verb."); |
| } |
| |
| ptsIdx += num_pts(verb); |
| } |
| |
| this->closeContour(); |
| |
| m.getAccumulatedBounds(devBounds, devBounds45); |
| SkDEBUGCODE(this->validate();) |
| } |
| |
| void GrCCPRCoverageOpsBuilder::saveParsedPath(const SkIRect& clippedDevIBounds, |
| int16_t atlasOffsetX, int16_t atlasOffsetY) { |
| const PrimitiveTallies& baseIndices = fInstanceIndices[(int)fCurrScissorMode]; |
| const int32_t packedAtlasOffset = (atlasOffsetY << 16) | (atlasOffsetX & 0xffff); |
| for (int i = baseIndices.fTriangles; i < fCurrPathIndices.fTriangles; ++i) { |
| fInstanceData[i].fPackedAtlasOffset = packedAtlasOffset; |
| } |
| for (int i = baseIndices.fQuadratics; i < fCurrPathIndices.fQuadratics; ++i) { |
| fInstanceData[i].fPackedAtlasOffset = packedAtlasOffset; |
| } |
| for (int i = baseIndices.fSerpentines; i < fCurrPathIndices.fSerpentines; ++i) { |
| fInstanceData[i].fPackedAtlasOffset = packedAtlasOffset; |
| } |
| for (int i = baseIndices.fLoops - 1; i >= fCurrPathIndices.fLoops; --i) { |
| fInstanceData[i].fPackedAtlasOffset = packedAtlasOffset; |
| } |
| if (ScissorMode::kScissored == fCurrScissorMode) { |
| fScissorBatches.push_back() = { |
| fCurrPathIndices - fInstanceIndices[(int)fCurrScissorMode], |
| clippedDevIBounds.makeOffset(atlasOffsetX, atlasOffsetY) |
| }; |
| } |
| fInstanceIndices[(int)fCurrScissorMode] = fCurrPathIndices; |
| } |
| |
| void GrCCPRCoverageOpsBuilder::startContour(const SkPoint& anchorPoint) { |
| this->closeContour(); |
| fPointsData[fFanPtsIdx++] = fCurrAnchorPoint = fCurrFanPoint = anchorPoint; |
| SkASSERT(fCurrContourStartIdx == fFanPtsIdx - 1); |
| } |
| |
| void GrCCPRCoverageOpsBuilder::fanTo(const SkPoint& pt) { |
| SkASSERT(fCurrContourStartIdx < fFanPtsIdx); |
| if (pt == fCurrAnchorPoint) { |
| this->startContour(pt); |
| return; |
| } |
| fPointsData[fFanPtsIdx++] = fCurrFanPoint = pt; |
| } |
| |
| void GrCCPRCoverageOpsBuilder::quadraticTo(SkPoint controlPt, SkPoint endPt) { |
| SkASSERT(fCurrPathIndices.fQuadratics+2 <= fBaseInstances[(int)fCurrScissorMode].fSerpentines); |
| |
| SkPoint P[3] = {fCurrFanPoint, controlPt, endPt}; |
| SkPoint chopped[5]; |
| if (GrPathUtils::chopMonotonicQuads(P, chopped)) { |
| this->fanTo(chopped[2]); |
| fPointsData[fControlPtsIdx++] = chopped[1]; |
| fInstanceData[fCurrPathIndices.fQuadratics++].fQuadraticData = { |
| fControlPtsIdx - 1, |
| fFanPtsIdx - 2 |
| }; |
| |
| controlPt = chopped[3]; |
| SkASSERT(endPt == chopped[4]); |
| } |
| |
| this->fanTo(endPt); |
| fPointsData[fControlPtsIdx++] = controlPt; |
| fInstanceData[fCurrPathIndices.fQuadratics++].fQuadraticData = { |
| fControlPtsIdx - 1, |
| fFanPtsIdx - 2 |
| }; |
| } |
| |
| void GrCCPRCoverageOpsBuilder::cubicTo(SkPoint controlPt1, SkPoint controlPt2, SkPoint endPt) { |
| SkPoint P[4] = {fCurrFanPoint, controlPt1, controlPt2, endPt}; |
| double t[2], s[2]; |
| SkCubicType type = SkClassifyCubic(P, t, s); |
| |
| if (SkCubicType::kLineOrPoint == type) { |
| this->fanTo(P[3]); |
| return; |
| } |
| |
| if (SkCubicType::kQuadratic == type) { |
| SkScalar x1 = P[1].y() - P[0].y(), y1 = P[0].x() - P[1].x(), |
| k1 = x1 * P[0].x() + y1 * P[0].y(); |
| SkScalar x2 = P[2].y() - P[3].y(), y2 = P[3].x() - P[2].x(), |
| k2 = x2 * P[3].x() + y2 * P[3].y(); |
| SkScalar rdet = 1 / (x1*y2 - y1*x2); |
| this->quadraticTo({(y2*k1 - y1*k2) * rdet, (x1*k2 - x2*k1) * rdet}, P[3]); |
| return; |
| } |
| |
| SkDCubic C; |
| C.set(P); |
| |
| for (int x = 0; x <= 1; ++x) { |
| if (t[x] * s[x] <= 0) { // This is equivalent to tx/sx <= 0. |
| // This technically also gets taken if tx/sx = infinity, but the code still does |
| // the right thing in that edge case. |
| continue; // Don't increment x0. |
| } |
| if (fabs(t[x]) >= fabs(s[x])) { // tx/sx >= 1. |
| break; |
| } |
| |
| const double chopT = double(t[x]) / double(s[x]); |
| SkASSERT(chopT >= 0 && chopT <= 1); |
| if (chopT <= 0 || chopT >= 1) { // floating-point error. |
| continue; |
| } |
| |
| SkDCubicPair chopped = C.chopAt(chopT); |
| |
| // Ensure the double points are identical if this is a loop (more workarounds for FP error). |
| if (SkCubicType::kLoop == type && 0 == t[0]) { |
| chopped.pts[3] = chopped.pts[0]; |
| } |
| |
| // (This might put ts0/ts1 out of order, but it doesn't matter anymore at this point.) |
| this->emitCubicSegment(type, chopped.first(), |
| to_skpoint(t[1 - x], s[1 - x] * chopT), to_skpoint(1, 1)); |
| t[x] = 0; |
| s[x] = 1; |
| |
| const double r = s[1 - x] * chopT; |
| t[1 - x] -= r; |
| s[1 - x] -= r; |
| |
| C = chopped.second(); |
| } |
| |
| this->emitCubicSegment(type, C, to_skpoint(t[0], s[0]), to_skpoint(t[1], s[1])); |
| } |
| |
| void GrCCPRCoverageOpsBuilder::emitCubicSegment(SkCubicType type, const SkDCubic& C, |
| const SkPoint& ts0, const SkPoint& ts1) { |
| SkASSERT(fCurrPathIndices.fSerpentines < fCurrPathIndices.fLoops); |
| |
| fPointsData[fControlPtsIdx++] = to_skpoint(C[1]); |
| fPointsData[fControlPtsIdx++] = to_skpoint(C[2]); |
| this->fanTo(to_skpoint(C[3])); |
| |
| // Also emit the cubic's root t,s values as "control points". |
| fPointsData[fControlPtsIdx++] = ts0; |
| fPointsData[fControlPtsIdx++] = ts1; |
| |
| // Serpentines grow up from the front, and loops grow down from the back. |
| fInstanceData[SkCubicType::kLoop != type ? |
| fCurrPathIndices.fSerpentines++ : --fCurrPathIndices.fLoops].fCubicData = { |
| fControlPtsIdx - 4, |
| fFanPtsIdx - 2 |
| }; |
| } |
| |
| void GrCCPRCoverageOpsBuilder::closeContour() { |
| int fanSize = fFanPtsIdx - fCurrContourStartIdx; |
| if (fanSize >= 3) { |
| // Technically this can grow to fanSize + log3(fanSize), but we approximate with log2. |
| SkAutoSTMalloc<300, int32_t> indices(fanSize + SkNextLog2(fanSize)); |
| std::iota(indices.get(), indices.get() + fanSize, fCurrContourStartIdx); |
| this->emitHierarchicalFan(indices, fanSize); |
| } |
| |
| // Reset the current contour. |
| fCurrContourStartIdx = fFanPtsIdx; |
| } |
| |
| void GrCCPRCoverageOpsBuilder::emitHierarchicalFan(int32_t indices[], int count) { |
| if (count < 3) { |
| return; |
| } |
| |
| const int32_t oneThirdPt = count / 3; |
| const int32_t twoThirdsPt = (2 * count) / 3; |
| SkASSERT(fCurrPathIndices.fTriangles < fBaseInstances[(int)fCurrScissorMode].fQuadratics); |
| |
| fInstanceData[fCurrPathIndices.fTriangles++].fTriangleData = { |
| indices[0], |
| indices[oneThirdPt], |
| indices[twoThirdsPt] |
| }; |
| |
| this->emitHierarchicalFan(indices, oneThirdPt + 1); |
| this->emitHierarchicalFan(&indices[oneThirdPt], twoThirdsPt - oneThirdPt + 1); |
| |
| int32_t oldIndex = indices[count]; |
| indices[count] = indices[0]; |
| this->emitHierarchicalFan(&indices[twoThirdsPt], count - twoThirdsPt + 1); |
| indices[count] = oldIndex; |
| } |
| |
| std::unique_ptr<GrDrawOp> GrCCPRCoverageOpsBuilder::createIntermediateOp(SkISize drawBounds) { |
| auto op = skstd::make_unique<CoverageOp>(drawBounds, fPointsBuffer, fInstanceBuffer, |
| fBaseInstances, fInstanceIndices, |
| std::move(fScissorBatches)); |
| SkASSERT(fScissorBatches.empty()); |
| |
| fBaseInstances[0] = fInstanceIndices[0]; |
| fBaseInstances[1] = fInstanceIndices[1]; |
| return std::move(op); |
| } |
| |
| std::unique_ptr<GrDrawOp> GrCCPRCoverageOpsBuilder::finalize(SkISize drawBounds) { |
| fPointsBuffer->unmap(); |
| SkDEBUGCODE(fPointsData = nullptr); |
| |
| fInstanceBuffer->unmap(); |
| SkDEBUGCODE(fInstanceData = nullptr); |
| |
| return skstd::make_unique<CoverageOp>(drawBounds, std::move(fPointsBuffer), |
| std::move(fInstanceBuffer), fBaseInstances, |
| fInstanceIndices, std::move(fScissorBatches)); |
| } |
| |
| #ifdef SK_DEBUG |
| |
| void GrCCPRCoverageOpsBuilder::validate() { |
| SkASSERT(fFanPtsIdx <= fMaxFanPoints); |
| SkASSERT(fControlPtsIdx <= fMaxFanPoints + fMaxControlPoints); |
| for (int i = 0; i < kNumScissorModes; ++i) { |
| SkASSERT(fInstanceIndices[i].fTriangles <= fBaseInstances[i].fQuadratics); |
| SkASSERT(fInstanceIndices[i].fQuadratics <= fBaseInstances[i].fSerpentines); |
| SkASSERT(fInstanceIndices[i].fSerpentines <= fInstanceIndices[i].fLoops); |
| } |
| } |
| |
| #endif |
| |
| using CoverageOp = GrCCPRCoverageOpsBuilder::CoverageOp; |
| |
| GrCCPRCoverageOpsBuilder::CoverageOp::CoverageOp(const SkISize& drawBounds, |
| sk_sp<GrBuffer> pointsBuffer, |
| sk_sp<GrBuffer> trianglesBuffer, |
| const PrimitiveTallies baseInstances[kNumScissorModes], |
| const PrimitiveTallies endInstances[kNumScissorModes], |
| SkTArray<ScissorBatch>&& scissorBatches) |
| : INHERITED(ClassID()) |
| , fDrawBounds(drawBounds) |
| , fPointsBuffer(std::move(pointsBuffer)) |
| , fTrianglesBuffer(std::move(trianglesBuffer)) |
| , fBaseInstances{baseInstances[0], baseInstances[1]} |
| , fInstanceCounts{endInstances[0] - baseInstances[0], endInstances[1] - baseInstances[1]} |
| , fScissorBatches(std::move(scissorBatches)) { |
| SkASSERT(fPointsBuffer); |
| SkASSERT(fTrianglesBuffer); |
| this->setBounds(SkRect::MakeIWH(fDrawBounds.width(), fDrawBounds.height()), |
| GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo); |
| } |
| |
| void CoverageOp::onExecute(GrOpFlushState* flushState) { |
| using Mode = GrCCPRCoverageProcessor::Mode; |
| |
| SkDEBUGCODE(GrCCPRCoverageProcessor::Validate(flushState->drawOpArgs().fProxy)); |
| |
| GrPipeline pipeline(flushState->drawOpArgs().fProxy, GrPipeline::ScissorState::kEnabled, |
| SkBlendMode::kPlus); |
| |
| fMeshesScratchBuffer.reserve(1 + fScissorBatches.count()); |
| fDynamicStatesScratchBuffer.reserve(1 + fScissorBatches.count()); |
| |
| // Triangles. |
| auto constexpr kTrianglesGrPrimitiveType = GrCCPRCoverageProcessor::kTrianglesGrPrimitiveType; |
| this->drawMaskPrimitives(flushState, pipeline, Mode::kCombinedTriangleHullsAndEdges, |
| kTrianglesGrPrimitiveType, 3, &PrimitiveTallies::fTriangles); |
| this->drawMaskPrimitives(flushState, pipeline, Mode::kTriangleCorners, |
| kTrianglesGrPrimitiveType, 3, &PrimitiveTallies::fTriangles); |
| |
| // Quadratics. |
| auto constexpr kQuadraticsGrPrimitiveType = GrCCPRCoverageProcessor::kQuadraticsGrPrimitiveType; |
| this->drawMaskPrimitives(flushState, pipeline, Mode::kQuadraticHulls, |
| kQuadraticsGrPrimitiveType, 3, &PrimitiveTallies::fQuadratics); |
| this->drawMaskPrimitives(flushState, pipeline, Mode::kQuadraticCorners, |
| kQuadraticsGrPrimitiveType, 3, &PrimitiveTallies::fQuadratics); |
| |
| // Cubics. |
| auto constexpr kCubicsGrPrimitiveType = GrCCPRCoverageProcessor::kCubicsGrPrimitiveType; |
| this->drawMaskPrimitives(flushState, pipeline, Mode::kSerpentineInsets, |
| kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fSerpentines); |
| this->drawMaskPrimitives(flushState, pipeline, Mode::kLoopInsets, |
| kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fLoops); |
| this->drawMaskPrimitives(flushState, pipeline, Mode::kSerpentineBorders, |
| kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fSerpentines); |
| this->drawMaskPrimitives(flushState, pipeline, Mode::kLoopBorders, |
| kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fLoops); |
| } |
| |
| void CoverageOp::drawMaskPrimitives(GrOpFlushState* flushState, const GrPipeline& pipeline, |
| GrCCPRCoverageProcessor::Mode mode, GrPrimitiveType primType, |
| int vertexCount, int PrimitiveTallies::* instanceType) const { |
| SkASSERT(pipeline.getScissorState().enabled()); |
| |
| fMeshesScratchBuffer.reset(); |
| fDynamicStatesScratchBuffer.reset(); |
| |
| if (const int instanceCount = fInstanceCounts[(int)ScissorMode::kNonScissored].*instanceType) { |
| const int baseInstance = fBaseInstances[(int)ScissorMode::kNonScissored].*instanceType; |
| // Loops grow backwards, which is indicated by a negative instance count. |
| GrMesh& mesh = fMeshesScratchBuffer.emplace_back(primType); |
| mesh.setInstanced(fTrianglesBuffer.get(), abs(instanceCount), |
| baseInstance + SkTMin(instanceCount, 0), vertexCount); |
| fDynamicStatesScratchBuffer.push_back().fScissorRect.setXYWH(0, 0, fDrawBounds.width(), |
| fDrawBounds.height()); |
| } |
| |
| if (fInstanceCounts[(int)ScissorMode::kScissored].*instanceType) { |
| int baseInstance = fBaseInstances[(int)ScissorMode::kScissored].*instanceType; |
| for (const ScissorBatch& batch : fScissorBatches) { |
| SkASSERT(this->bounds().contains(batch.fScissor)); |
| const int instanceCount = batch.fInstanceCounts.*instanceType; |
| if (!instanceCount) { |
| continue; |
| } |
| // Loops grow backwards, which is indicated by a negative instance count. |
| GrMesh& mesh = fMeshesScratchBuffer.emplace_back(primType); |
| mesh.setInstanced(fTrianglesBuffer.get(), abs(instanceCount), |
| baseInstance + SkTMin(instanceCount,0), vertexCount); |
| fDynamicStatesScratchBuffer.push_back().fScissorRect = batch.fScissor; |
| baseInstance += instanceCount; |
| } |
| } |
| |
| SkASSERT(fMeshesScratchBuffer.count() == fDynamicStatesScratchBuffer.count()); |
| |
| if (!fMeshesScratchBuffer.empty()) { |
| GrCCPRCoverageProcessor proc(mode, fPointsBuffer.get()); |
| flushState->commandBuffer()->draw(pipeline, proc, fMeshesScratchBuffer.begin(), |
| fDynamicStatesScratchBuffer.begin(), |
| fMeshesScratchBuffer.count(), this->bounds()); |
| } |
| } |
| |
| using PrimitiveTallies = CoverageOp::PrimitiveTallies; |
| |
| inline PrimitiveTallies PrimitiveTallies::operator+(const PrimitiveTallies& b) const { |
| return {fTriangles + b.fTriangles, |
| fQuadratics + b.fQuadratics, |
| fSerpentines + b.fSerpentines, |
| fLoops + b.fLoops}; |
| } |
| |
| inline PrimitiveTallies PrimitiveTallies::operator-(const PrimitiveTallies& b) const { |
| return {fTriangles - b.fTriangles, |
| fQuadratics - b.fQuadratics, |
| fSerpentines - b.fSerpentines, |
| fLoops - b.fLoops}; |
| } |
| |
| inline int PrimitiveTallies::sum() const { |
| return fTriangles + fQuadratics + fSerpentines + fLoops; |
| } |
| |
| inline AccumulatingViewMatrix::AccumulatingViewMatrix(const SkMatrix& m, |
| const SkPoint& initialPoint) { |
| // m45 transforms into 45 degree space in order to find the octagon's diagonals. We could |
| // use SK_ScalarRoot2Over2 if we wanted an orthonormal transform, but this is irrelevant as |
| // long as the shader uses the correct inverse when coming back to device space. |
| SkMatrix m45; |
| m45.setSinCos(1, 1); |
| m45.preConcat(m); |
| |
| fX = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY()); |
| fY = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY()); |
| fT = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY()); |
| |
| Sk4f transformed = SkNx_fma(fY, Sk4f(initialPoint.y()), fT); |
| transformed = SkNx_fma(fX, Sk4f(initialPoint.x()), transformed); |
| fTopLeft = fBottomRight = transformed; |
| } |
| |
| inline SkPoint AccumulatingViewMatrix::transform(const SkPoint& pt) { |
| Sk4f transformed = SkNx_fma(fY, Sk4f(pt.y()), fT); |
| transformed = SkNx_fma(fX, Sk4f(pt.x()), transformed); |
| |
| fTopLeft = Sk4f::Min(fTopLeft, transformed); |
| fBottomRight = Sk4f::Max(fBottomRight, transformed); |
| |
| // TODO: vst1_lane_f32? (Sk4f::storeLane?) |
| float data[4]; |
| transformed.store(data); |
| return SkPoint::Make(data[0], data[1]); |
| } |
| |
| inline void AccumulatingViewMatrix::getAccumulatedBounds(SkRect* devBounds, |
| SkRect* devBounds45) const { |
| float topLeft[4], bottomRight[4]; |
| fTopLeft.store(topLeft); |
| fBottomRight.store(bottomRight); |
| devBounds->setLTRB(topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]); |
| devBounds45->setLTRB(topLeft[2], topLeft[3], bottomRight[2], bottomRight[3]); |
| } |