| /* |
| * 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 "GrCCPRCoverageOp.h" |
| |
| #include "GrGpuCommandBuffer.h" |
| #include "GrOnFlushResourceProvider.h" |
| #include "GrOpFlushState.h" |
| #include "SkMathPriv.h" |
| #include "SkPath.h" |
| #include "SkPathPriv.h" |
| #include "SkPoint.h" |
| #include "SkNx.h" |
| #include "ccpr/GrCCPRGeometry.h" |
| |
| using TriangleInstance = GrCCPRCoverageProcessor::TriangleInstance; |
| using CurveInstance = GrCCPRCoverageProcessor::CurveInstance; |
| |
| /** |
| * 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; |
| }; |
| |
| 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]); |
| } |
| |
| void GrCCPRCoverageOpsBuilder::parsePath(const SkMatrix& viewMatrix, const SkPath& path, |
| SkRect* devBounds, SkRect* devBounds45) { |
| SkASSERT(!fParsingPath); |
| SkDEBUGCODE(fParsingPath = true); |
| |
| fCurrPathPointsIdx = fGeometry.points().count(); |
| fCurrPathVerbsIdx = fGeometry.verbs().count(); |
| fCurrPathTallies = PrimitiveTallies(); |
| |
| fGeometry.beginPath(); |
| |
| if (path.isEmpty()) { |
| devBounds->setEmpty(); |
| devBounds45->setEmpty(); |
| return; |
| } |
| |
| const SkPoint* const pts = SkPathPriv::PointData(path); |
| int ptsIdx = 0; |
| bool insideContour = false; |
| |
| AccumulatingViewMatrix m(viewMatrix, pts[0]); |
| |
| for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { |
| switch (verb) { |
| case SkPath::kMove_Verb: |
| this->endContourIfNeeded(insideContour); |
| fGeometry.beginContour(m.transform(pts[ptsIdx++])); |
| insideContour = true; |
| continue; |
| case SkPath::kClose_Verb: |
| this->endContourIfNeeded(insideContour); |
| insideContour = false; |
| continue; |
| case SkPath::kLine_Verb: |
| fGeometry.lineTo(m.transform(pts[ptsIdx++])); |
| continue; |
| case SkPath::kQuad_Verb: |
| SkASSERT(ptsIdx >= 1); // SkPath should have inserted an implicit moveTo if needed. |
| fGeometry.quadraticTo(m.transform(pts[ptsIdx]), m.transform(pts[ptsIdx + 1])); |
| ptsIdx += 2; |
| continue; |
| case SkPath::kCubic_Verb: |
| SkASSERT(ptsIdx >= 1); // SkPath should have inserted an implicit moveTo if needed. |
| fGeometry.cubicTo(m.transform(pts[ptsIdx]), m.transform(pts[ptsIdx + 1]), |
| m.transform(pts[ptsIdx + 2])); |
| ptsIdx += 3; |
| continue; |
| case SkPath::kConic_Verb: |
| SK_ABORT("Conics are not supported."); |
| default: |
| SK_ABORT("Unexpected path verb."); |
| } |
| } |
| |
| this->endContourIfNeeded(insideContour); |
| m.getAccumulatedBounds(devBounds, devBounds45); |
| } |
| |
| void GrCCPRCoverageOpsBuilder::endContourIfNeeded(bool insideContour) { |
| if (insideContour) { |
| fCurrPathTallies += fGeometry.endContour(); |
| } |
| } |
| |
| void GrCCPRCoverageOpsBuilder::saveParsedPath(ScissorMode scissorMode, |
| const SkIRect& clippedDevIBounds, |
| int16_t atlasOffsetX, int16_t atlasOffsetY) { |
| SkASSERT(fParsingPath); |
| |
| fPathsInfo.push_back() = { |
| scissorMode, |
| (int32_t) (((uint32_t) (int32_t) atlasOffsetY << 16) | (atlasOffsetX & 0xffff)), |
| std::move(fTerminatingOp) |
| }; |
| |
| fTallies[(int)scissorMode] += fCurrPathTallies; |
| |
| if (ScissorMode::kScissored == scissorMode) { |
| fScissorBatches.push_back() = { |
| fCurrPathTallies, |
| clippedDevIBounds.makeOffset(atlasOffsetX, atlasOffsetY) |
| }; |
| } |
| |
| SkDEBUGCODE(fParsingPath = false); |
| } |
| |
| void GrCCPRCoverageOpsBuilder::discardParsedPath() { |
| SkASSERT(fParsingPath); |
| |
| // The code will still work whether or not the below assertion is true. It is just unlikely that |
| // the caller would want this, and probably indicative of of a mistake. (Why emit an |
| // intermediate Op (to switch to a new atlas?), just to then throw the path away?) |
| SkASSERT(!fTerminatingOp); |
| |
| fGeometry.resize_back(fCurrPathPointsIdx, fCurrPathVerbsIdx); |
| SkDEBUGCODE(fParsingPath = false); |
| } |
| |
| void GrCCPRCoverageOpsBuilder::emitOp(SkISize drawBounds) { |
| SkASSERT(!fTerminatingOp); |
| fTerminatingOp.reset(new GrCCPRCoverageOp(std::move(fScissorBatches), drawBounds)); |
| SkASSERT(fScissorBatches.empty()); |
| } |
| |
| // Emits a contour's triangle fan. |
| // |
| // Classic Redbook fanning would be the triangles: [0 1 2], [0 2 3], ..., [0 n-2 n-1]. |
| // |
| // This function emits the triangle: [0 n/3 n*2/3], and then recurses on all three sides. The |
| // advantage to this approach is that for a convex-ish contour, it generates larger triangles. |
| // Classic fanning tends to generate long, skinny triangles, which are expensive to draw since they |
| // have a longer perimeter to rasterize and antialias. |
| // |
| // The indices array indexes the fan's points (think: glDrawElements), and must have at least log3 |
| // elements past the end for this method to use as scratch space. |
| // |
| // Returns the next triangle instance after the final one emitted. |
| static TriangleInstance* emit_recursive_fan(SkTArray<int32_t, true>& indices, int firstIndex, |
| int indexCount, int32_t packedAtlasOffset, |
| TriangleInstance out[]) { |
| if (indexCount < 3) { |
| return out; |
| } |
| |
| const int32_t oneThirdCount = indexCount / 3; |
| const int32_t twoThirdsCount = (2 * indexCount) / 3; |
| *out++ = { |
| indices[firstIndex], |
| indices[firstIndex + oneThirdCount], |
| indices[firstIndex + twoThirdsCount], |
| packedAtlasOffset |
| }; |
| |
| out = emit_recursive_fan(indices, firstIndex, oneThirdCount + 1, packedAtlasOffset, out); |
| out = emit_recursive_fan(indices, firstIndex + oneThirdCount, |
| twoThirdsCount - oneThirdCount + 1, packedAtlasOffset, out); |
| |
| int endIndex = firstIndex + indexCount; |
| int32_t oldValue = indices[endIndex]; |
| indices[endIndex] = indices[firstIndex]; |
| out = emit_recursive_fan(indices, firstIndex + twoThirdsCount, indexCount - twoThirdsCount + 1, |
| packedAtlasOffset, out); |
| indices[endIndex] = oldValue; |
| |
| return out; |
| } |
| |
| bool GrCCPRCoverageOpsBuilder::finalize(GrOnFlushResourceProvider* onFlushRP, |
| SkTArray<std::unique_ptr<GrCCPRCoverageOp>>* ops) { |
| SkASSERT(!fParsingPath); |
| |
| const SkTArray<SkPoint, true>& points = fGeometry.points(); |
| sk_sp<GrBuffer> pointsBuffer = onFlushRP->makeBuffer(kTexel_GrBufferType, |
| points.count() * 2 * sizeof(float), |
| points.begin()); |
| if (!pointsBuffer) { |
| return false; |
| } |
| |
| // Configure the instance buffer layout. |
| PrimitiveTallies baseInstances[kNumScissorModes]; |
| // int4 indices. |
| baseInstances[0].fTriangles = 0; |
| baseInstances[1].fTriangles = baseInstances[0].fTriangles + fTallies[0].fTriangles; |
| // int2 indices (curves index the buffer as int2 rather than int4). |
| baseInstances[0].fQuadratics = (baseInstances[1].fTriangles + fTallies[1].fTriangles) * 2; |
| baseInstances[1].fQuadratics = baseInstances[0].fQuadratics + fTallies[0].fQuadratics; |
| baseInstances[0].fSerpentines = baseInstances[1].fQuadratics + fTallies[1].fQuadratics; |
| baseInstances[1].fSerpentines = baseInstances[0].fSerpentines + fTallies[0].fSerpentines; |
| baseInstances[0].fLoops = baseInstances[1].fSerpentines + fTallies[1].fSerpentines; |
| baseInstances[1].fLoops = baseInstances[0].fLoops + fTallies[0].fLoops; |
| int instanceBufferSize = (baseInstances[1].fLoops + fTallies[1].fLoops) * sizeof(CurveInstance); |
| |
| sk_sp<GrBuffer> instanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType, |
| instanceBufferSize); |
| if (!instanceBuffer) { |
| return false; |
| } |
| |
| TriangleInstance* triangleInstanceData = static_cast<TriangleInstance*>(instanceBuffer->map()); |
| CurveInstance* curveInstanceData = reinterpret_cast<CurveInstance*>(triangleInstanceData); |
| SkASSERT(curveInstanceData); |
| |
| PathInfo* currPathInfo = fPathsInfo.begin(); |
| int32_t packedAtlasOffset; |
| int ptsIdx = -1; |
| PrimitiveTallies instanceIndices[2] = {baseInstances[0], baseInstances[1]}; |
| PrimitiveTallies* currIndices; |
| SkSTArray<256, int32_t, true> currFan; |
| |
| #ifdef SK_DEBUG |
| int numScissoredPaths = 0; |
| int numScissorBatches = 0; |
| PrimitiveTallies initialBaseInstances[] = {baseInstances[0], baseInstances[1]}; |
| #endif |
| |
| // Expand the ccpr verbs into GPU instance buffers. |
| for (GrCCPRGeometry::Verb verb : fGeometry.verbs()) { |
| switch (verb) { |
| case GrCCPRGeometry::Verb::kBeginPath: |
| SkASSERT(currFan.empty()); |
| currIndices = &instanceIndices[(int)currPathInfo->fScissorMode]; |
| packedAtlasOffset = currPathInfo->fPackedAtlasOffset; |
| #ifdef SK_DEBUG |
| if (ScissorMode::kScissored == currPathInfo->fScissorMode) { |
| ++numScissoredPaths; |
| } |
| #endif |
| if (auto op = std::move(currPathInfo->fTerminatingOp)) { |
| op->setBuffers(pointsBuffer, instanceBuffer, baseInstances, instanceIndices); |
| baseInstances[0] = instanceIndices[0]; |
| baseInstances[1] = instanceIndices[1]; |
| SkDEBUGCODE(numScissorBatches += op->fScissorBatches.count()); |
| ops->push_back(std::move(op)); |
| } |
| ++currPathInfo; |
| continue; |
| |
| case GrCCPRGeometry::Verb::kBeginContour: |
| SkASSERT(currFan.empty()); |
| currFan.push_back(++ptsIdx); |
| continue; |
| |
| case GrCCPRGeometry::Verb::kLineTo: |
| SkASSERT(!currFan.empty()); |
| currFan.push_back(++ptsIdx); |
| continue; |
| |
| case GrCCPRGeometry::Verb::kMonotonicQuadraticTo: |
| SkASSERT(!currFan.empty()); |
| curveInstanceData[currIndices->fQuadratics++] = {ptsIdx, packedAtlasOffset}; |
| currFan.push_back(ptsIdx += 2); |
| continue; |
| |
| case GrCCPRGeometry::Verb::kMonotonicSerpentineTo: |
| SkASSERT(!currFan.empty()); |
| curveInstanceData[currIndices->fSerpentines++] = {ptsIdx, packedAtlasOffset}; |
| currFan.push_back(ptsIdx += 3); |
| continue; |
| |
| case GrCCPRGeometry::Verb::kMonotonicLoopTo: |
| SkASSERT(!currFan.empty()); |
| curveInstanceData[currIndices->fLoops++] = {ptsIdx, packedAtlasOffset}; |
| currFan.push_back(ptsIdx += 3); |
| continue; |
| |
| case GrCCPRGeometry::Verb::kEndClosedContour: // endPt == startPt. |
| SkASSERT(!currFan.empty()); |
| currFan.pop_back(); |
| // fallthru. |
| case GrCCPRGeometry::Verb::kEndOpenContour: // endPt != startPt. |
| if (currFan.count() >= 3) { |
| int fanSize = currFan.count(); |
| // Reserve space for emit_recursive_fan. Technically this can grow to |
| // fanSize + log3(fanSize), but we approximate with log2. |
| currFan.push_back_n(SkNextLog2(fanSize)); |
| SkDEBUGCODE(TriangleInstance* end =) |
| emit_recursive_fan(currFan, 0, fanSize, packedAtlasOffset, |
| triangleInstanceData + currIndices->fTriangles); |
| currIndices->fTriangles += fanSize - 2; |
| SkASSERT(triangleInstanceData + currIndices->fTriangles == end); |
| } |
| currFan.reset(); |
| continue; |
| } |
| } |
| |
| instanceBuffer->unmap(); |
| |
| if (auto op = std::move(fTerminatingOp)) { |
| op->setBuffers(std::move(pointsBuffer), std::move(instanceBuffer), baseInstances, |
| instanceIndices); |
| SkDEBUGCODE(numScissorBatches += op->fScissorBatches.count()); |
| ops->push_back(std::move(op)); |
| } |
| |
| SkASSERT(currPathInfo == fPathsInfo.end()); |
| SkASSERT(ptsIdx == points.count() - 1); |
| SkASSERT(numScissoredPaths == numScissorBatches); |
| SkASSERT(instanceIndices[0].fTriangles == initialBaseInstances[1].fTriangles); |
| SkASSERT(instanceIndices[1].fTriangles * 2 == initialBaseInstances[0].fQuadratics); |
| SkASSERT(instanceIndices[0].fQuadratics == initialBaseInstances[1].fQuadratics); |
| SkASSERT(instanceIndices[1].fQuadratics == initialBaseInstances[0].fSerpentines); |
| SkASSERT(instanceIndices[0].fSerpentines == initialBaseInstances[1].fSerpentines); |
| SkASSERT(instanceIndices[1].fSerpentines == initialBaseInstances[0].fLoops); |
| SkASSERT(instanceIndices[0].fLoops == initialBaseInstances[1].fLoops); |
| SkASSERT(instanceIndices[1].fLoops * (int) sizeof(CurveInstance) == instanceBufferSize); |
| return true; |
| } |
| |
| void GrCCPRCoverageOp::setBuffers(sk_sp<GrBuffer> pointsBuffer, sk_sp<GrBuffer> instanceBuffer, |
| const PrimitiveTallies baseInstances[kNumScissorModes], |
| const PrimitiveTallies endInstances[kNumScissorModes]) { |
| fPointsBuffer = std::move(pointsBuffer); |
| fInstanceBuffer = std::move(instanceBuffer); |
| fBaseInstances[0] = baseInstances[0]; |
| fBaseInstances[1] = baseInstances[1]; |
| fInstanceCounts[0] = endInstances[0] - baseInstances[0]; |
| fInstanceCounts[1] = endInstances[1] - baseInstances[1]; |
| } |
| |
| void GrCCPRCoverageOp::onExecute(GrOpFlushState* flushState) { |
| using Mode = GrCCPRCoverageProcessor::Mode; |
| |
| SkDEBUGCODE(GrCCPRCoverageProcessor::Validate(flushState->drawOpArgs().fProxy)); |
| SkASSERT(fPointsBuffer); |
| SkASSERT(fInstanceBuffer); |
| |
| 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::kTriangleHulls, |
| kTrianglesGrPrimitiveType, 3, &PrimitiveTallies::fTriangles); |
| this->drawMaskPrimitives(flushState, pipeline, Mode::kTriangleEdges, |
| 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::kSerpentineHulls, |
| kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fSerpentines); |
| this->drawMaskPrimitives(flushState, pipeline, Mode::kLoopHulls, |
| kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fLoops); |
| this->drawMaskPrimitives(flushState, pipeline, Mode::kSerpentineCorners, |
| kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fSerpentines); |
| this->drawMaskPrimitives(flushState, pipeline, Mode::kLoopCorners, |
| kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fLoops); |
| } |
| |
| void GrCCPRCoverageOp::drawMaskPrimitives(GrOpFlushState* flushState, const GrPipeline& pipeline, |
| GrCCPRCoverageProcessor::Mode mode, |
| GrPrimitiveType primType, int vertexCount, |
| int PrimitiveTallies::* instanceType) const { |
| using ScissorMode = GrCCPRCoverageOpsBuilder::ScissorMode; |
| SkASSERT(pipeline.getScissorState().enabled()); |
| |
| fMeshesScratchBuffer.reset(); |
| fDynamicStatesScratchBuffer.reset(); |
| |
| if (const int instanceCount = fInstanceCounts[(int)ScissorMode::kNonScissored].*instanceType) { |
| SkASSERT(instanceCount > 0); |
| const int baseInstance = fBaseInstances[(int)ScissorMode::kNonScissored].*instanceType; |
| GrMesh& mesh = fMeshesScratchBuffer.emplace_back(primType); |
| mesh.setInstanced(fInstanceBuffer.get(), instanceCount, baseInstance, 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; |
| } |
| SkASSERT(instanceCount > 0); |
| GrMesh& mesh = fMeshesScratchBuffer.emplace_back(primType); |
| mesh.setInstanced(fInstanceBuffer.get(), instanceCount, baseInstance, vertexCount); |
| fDynamicStatesScratchBuffer.push_back().fScissorRect = batch.fScissor; |
| baseInstance += instanceCount; |
| } |
| } |
| |
| SkASSERT(fMeshesScratchBuffer.count() == fDynamicStatesScratchBuffer.count()); |
| |
| if (!fMeshesScratchBuffer.empty()) { |
| GrCCPRCoverageProcessor proc(mode, fPointsBuffer.get()); |
| SkASSERT(flushState->rtCommandBuffer()); |
| flushState->rtCommandBuffer()->draw(pipeline, proc, fMeshesScratchBuffer.begin(), |
| fDynamicStatesScratchBuffer.begin(), |
| fMeshesScratchBuffer.count(), this->bounds()); |
| } |
| } |