From 0569fbd9be9b2d0a8e2bc030189fb61c282668e5 Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Mon, 6 Feb 2023 15:01:20 +0000 Subject: [PATCH] Bug 1801463 - Use a cursor in FlattenedPath to accelerate successive calls to ComputePointAtLength for SVG text-on-a-path layout. r=gfx-reviewers,nical This enables my local build to achieve 60fps on the js1k demo linked from the bug, whereas without the patch I get barely 10fps. Note: it's still possible for ComputePointAtLength would perform poorly if the caller is iterating backwards or doing random access to a long path. A potential mitigation for that would be to add an mLength field to FlatPathOp, storing the length-so-far of the path, so that ComputePointAtLength could do a binary search instead of linear accumulation. But this would add significant memory overhead, and may not be worth doing; the low-overhead cursor here appears to be enough to make text-on-a-path perform much better. Differential Revision: https://phabricator.services.mozilla.com/D168937 --- gfx/2d/Path.cpp | 40 +++++++++++++++++++++++----------------- gfx/2d/PathAnalysis.h | 22 ++++++++++++++++++---- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/gfx/2d/Path.cpp b/gfx/2d/Path.cpp index 69e2e976257f..69b5680c24fd 100644 --- a/gfx/2d/Path.cpp +++ b/gfx/2d/Path.cpp @@ -138,46 +138,52 @@ Float FlattenedPath::ComputeLength() { } Point FlattenedPath::ComputePointAtLength(Float aLength, Point* aTangent) { - // We track the last point that -wasn't- in the same place as the current - // point so if we pass the edge of the path with a bunch of zero length - // paths we still get the correct tangent vector. - Point lastPointSinceMove; - Point currentPoint; - for (uint32_t i = 0; i < mPathOps.size(); i++) { - if (mPathOps[i].mType == FlatPathOp::OP_MOVETO) { - if (Distance(currentPoint, mPathOps[i].mPoint)) { - lastPointSinceMove = currentPoint; + if (aLength < mCursor.mLength) { + // If cursor is beyond the target length, reset to the beginning. + mCursor.Reset(); + } else { + // Adjust aLength to account for the position where we'll start searching. + aLength -= mCursor.mLength; + } + + while (mCursor.mIndex < mPathOps.size()) { + const auto& op = mPathOps[mCursor.mIndex]; + if (op.mType == FlatPathOp::OP_MOVETO) { + if (Distance(mCursor.mCurrentPoint, op.mPoint) > 0.0f) { + mCursor.mLastPointSinceMove = mCursor.mCurrentPoint; } - currentPoint = mPathOps[i].mPoint; + mCursor.mCurrentPoint = op.mPoint; } else { - Float segmentLength = Distance(currentPoint, mPathOps[i].mPoint); + Float segmentLength = Distance(mCursor.mCurrentPoint, op.mPoint); if (segmentLength) { - lastPointSinceMove = currentPoint; + mCursor.mLastPointSinceMove = mCursor.mCurrentPoint; if (segmentLength > aLength) { - Point currentVector = mPathOps[i].mPoint - currentPoint; + Point currentVector = op.mPoint - mCursor.mCurrentPoint; Point tangent = currentVector / segmentLength; if (aTangent) { *aTangent = tangent; } - return currentPoint + tangent * aLength; + return mCursor.mCurrentPoint + tangent * aLength; } } aLength -= segmentLength; - currentPoint = mPathOps[i].mPoint; + mCursor.mLength += segmentLength; + mCursor.mCurrentPoint = op.mPoint; } + mCursor.mIndex++; } if (aTangent) { - Point currentVector = currentPoint - lastPointSinceMove; + Point currentVector = mCursor.mCurrentPoint - mCursor.mLastPointSinceMove; if (auto h = hypotf(currentVector.x, currentVector.y)) { *aTangent = currentVector / h; } else { *aTangent = Point(); } } - return currentPoint; + return mCursor.mCurrentPoint; } // This function explicitly permits aControlPoints to refer to the same object diff --git a/gfx/2d/PathAnalysis.h b/gfx/2d/PathAnalysis.h index f162d962131f..5821b8839eea 100644 --- a/gfx/2d/PathAnalysis.h +++ b/gfx/2d/PathAnalysis.h @@ -24,8 +24,6 @@ class FlattenedPath : public PathSink { public: MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FlattenedPath, override) - FlattenedPath() : mCachedLength(0), mCalculatedLength(false) {} - virtual void MoveTo(const Point& aPoint) override; virtual void LineTo(const Point& aPoint) override; virtual void BezierTo(const Point& aCP1, const Point& aCP2, @@ -43,10 +41,26 @@ class FlattenedPath : public PathSink { Point ComputePointAtLength(Float aLength, Point* aTangent); private: - Float mCachedLength; - bool mCalculatedLength; + Float mCachedLength = 0.0f; + bool mCalculatedLength = false; std::vector mPathOps; + + // Used to accelerate ComputePointAtLength for the common case of iterating + // forward along the path. + struct { + uint32_t mIndex = 0; + Float mLength = 0.0f; + Point mCurrentPoint; + Point mLastPointSinceMove; + + void Reset() { + mIndex = 0; + mLength = 0.0f; + mCurrentPoint = Point(); + mLastPointSinceMove = Point(); + } + } mCursor; }; } // namespace gfx