forked from mirrors/gecko-dev
		
	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:
		
							parent
							
								
									9aa73e790c
								
							
						
					
					
						commit
						0569fbd9be
					
				
					 2 changed files with 41 additions and 21 deletions
				
			
		|  | @ -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
 | ||||
|  |  | |||
|  | @ -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<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
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Jonathan Kew
						Jonathan Kew