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
This commit is contained in:
Jonathan Kew 2023-02-06 15:01:20 +00:00
Родитель 9aa73e790c
Коммит 0569fbd9be
2 изменённых файлов: 41 добавлений и 21 удалений

Просмотреть файл

@ -138,46 +138,52 @@ Float FlattenedPath::ComputeLength() {
} }
Point FlattenedPath::ComputePointAtLength(Float aLength, Point* aTangent) { Point FlattenedPath::ComputePointAtLength(Float aLength, Point* aTangent) {
// We track the last point that -wasn't- in the same place as the current if (aLength < mCursor.mLength) {
// point so if we pass the edge of the path with a bunch of zero length // If cursor is beyond the target length, reset to the beginning.
// paths we still get the correct tangent vector. mCursor.Reset();
Point lastPointSinceMove; } else {
Point currentPoint; // Adjust aLength to account for the position where we'll start searching.
for (uint32_t i = 0; i < mPathOps.size(); i++) { aLength -= mCursor.mLength;
if (mPathOps[i].mType == FlatPathOp::OP_MOVETO) { }
if (Distance(currentPoint, mPathOps[i].mPoint)) {
lastPointSinceMove = currentPoint; 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 { } else {
Float segmentLength = Distance(currentPoint, mPathOps[i].mPoint); Float segmentLength = Distance(mCursor.mCurrentPoint, op.mPoint);
if (segmentLength) { if (segmentLength) {
lastPointSinceMove = currentPoint; mCursor.mLastPointSinceMove = mCursor.mCurrentPoint;
if (segmentLength > aLength) { if (segmentLength > aLength) {
Point currentVector = mPathOps[i].mPoint - currentPoint; Point currentVector = op.mPoint - mCursor.mCurrentPoint;
Point tangent = currentVector / segmentLength; Point tangent = currentVector / segmentLength;
if (aTangent) { if (aTangent) {
*aTangent = tangent; *aTangent = tangent;
} }
return currentPoint + tangent * aLength; return mCursor.mCurrentPoint + tangent * aLength;
} }
} }
aLength -= segmentLength; aLength -= segmentLength;
currentPoint = mPathOps[i].mPoint; mCursor.mLength += segmentLength;
mCursor.mCurrentPoint = op.mPoint;
} }
mCursor.mIndex++;
} }
if (aTangent) { if (aTangent) {
Point currentVector = currentPoint - lastPointSinceMove; Point currentVector = mCursor.mCurrentPoint - mCursor.mLastPointSinceMove;
if (auto h = hypotf(currentVector.x, currentVector.y)) { if (auto h = hypotf(currentVector.x, currentVector.y)) {
*aTangent = currentVector / h; *aTangent = currentVector / h;
} else { } else {
*aTangent = Point(); *aTangent = Point();
} }
} }
return currentPoint; return mCursor.mCurrentPoint;
} }
// This function explicitly permits aControlPoints to refer to the same object // This function explicitly permits aControlPoints to refer to the same object

Просмотреть файл

@ -24,8 +24,6 @@ class FlattenedPath : public PathSink {
public: public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FlattenedPath, override) MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FlattenedPath, override)
FlattenedPath() : mCachedLength(0), mCalculatedLength(false) {}
virtual void MoveTo(const Point& aPoint) override; virtual void MoveTo(const Point& aPoint) override;
virtual void LineTo(const Point& aPoint) override; virtual void LineTo(const Point& aPoint) override;
virtual void BezierTo(const Point& aCP1, const Point& aCP2, virtual void BezierTo(const Point& aCP1, const Point& aCP2,
@ -43,10 +41,26 @@ class FlattenedPath : public PathSink {
Point ComputePointAtLength(Float aLength, Point* aTangent); Point ComputePointAtLength(Float aLength, Point* aTangent);
private: private:
Float mCachedLength; Float mCachedLength = 0.0f;
bool mCalculatedLength; bool mCalculatedLength = false;
std::vector<FlatPathOp> mPathOps; std::vector<FlatPathOp> 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 } // namespace gfx