Skip to content

Commit

Permalink
Restore Path cheap copying (refcount/COW PathStream)
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=258758
rdar://111595182

Reviewed by Myles C. Maxfield.

265569@main undid the "copy on write" behavior of class; assigning a Path would copy
the underlying PathStream.

To fix this, store the Vector<PathSegment> in an internal, thread-safe ref-counted SegmentsData
struct, and have PathStream hold a DataRef to this struct.

All functions that mutate the path segments call `access()` on the DataRef to trigger a copy.

* Source/WebCore/platform/graphics/PathStream.cpp:
(WebCore::PathStream::create):
(WebCore::PathStream::PathStream):
(WebCore::PathStream::clone const):
(WebCore::PathStream::operator== const):
(WebCore::PathStream::lastIfMoveTo const):
(WebCore::PathStream::moveTo):
(WebCore::PathStream::addLineTo):
(WebCore::PathStream::addQuadCurveTo):
(WebCore::PathStream::addBezierCurveTo):
(WebCore::PathStream::addArcTo):
(WebCore::PathStream::addArc):
(WebCore::PathStream::addRect):
(WebCore::PathStream::addEllipse):
(WebCore::PathStream::addEllipseInRect):
(WebCore::PathStream::addRoundedRect):
(WebCore::PathStream::closeSubpath):
(WebCore::PathStream::segments const):
(WebCore::PathStream::applySegments const):
(WebCore::PathStream::applyElements const):
(WebCore::PathStream::singleSegment const):
(WebCore::PathStream::isClosed const):
(WebCore::PathStream::currentPoint const):
(WebCore::PathStream::fastBoundingRect const):
(WebCore::PathStream::boundingRect const):
* Source/WebCore/platform/graphics/PathStream.h:

Canonical link: https://commits.webkit.org/265810@main
  • Loading branch information
smfr committed Jul 6, 2023
1 parent 1e8400a commit bb3cac8
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 35 deletions.
77 changes: 47 additions & 30 deletions Source/WebCore/platform/graphics/PathStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ UniqueRef<PathStream> PathStream::create()
return makeUniqueRef<PathStream>();
}

UniqueRef<PathStream> PathStream::create(const PathStream& pathStream)
{
return makeUniqueRef<PathStream>(pathStream);
}

UniqueRef<PathStream> PathStream::create(Vector<PathSegment>&& segments)
{
return makeUniqueRef<PathStream>(WTFMove(segments));
Expand All @@ -59,71 +64,82 @@ UniqueRef<PathStream> PathStream::create(const Vector<FloatPoint>& points)
return stream;
}

PathStream::PathStream()
: m_segmentsData(SegmentsData::create())
{
}

PathStream::PathStream(Vector<PathSegment>&& segments)
: m_segments(WTFMove(segments))
: m_segmentsData(SegmentsData::create(WTFMove(segments)))
{
}

PathStream::PathStream(const Vector<PathSegment>& segments)
: m_segments(segments)
: m_segmentsData(SegmentsData::create(segments))
{
}

PathStream::PathStream(const PathStream& pathStream)
: m_segmentsData(pathStream.m_segmentsData)
{
}

UniqueRef<PathImpl> PathStream::clone() const
{
return create(m_segments);
return create(*this);
}

bool PathStream::operator==(const PathImpl& other) const
{
if (!is<PathStream>(other))
return false;
return m_segments == downcast<PathStream>(other).m_segments;

return m_segmentsData == downcast<PathStream>(other).m_segmentsData;
}

const PathMoveTo* PathStream::lastIfMoveTo() const
{
if (m_segments.isEmpty())
if (isEmpty())
return nullptr;

return std::get_if<PathMoveTo>(&m_segments.last().data());
return std::get_if<PathMoveTo>(&m_segmentsData->segments.last().data());
}

void PathStream::moveTo(const FloatPoint& point)
{
m_segments.append(PathMoveTo { point });
segments().append(PathMoveTo { point });
}

void PathStream::addLineTo(const FloatPoint& point)
{
if (const auto* moveTo = lastIfMoveTo())
m_segments.last() = { PathDataLine { moveTo->point, point } };
segments().last() = { PathDataLine { moveTo->point, point } };
else
m_segments.append(PathLineTo { point });
segments().append(PathLineTo { point });
}

void PathStream::addQuadCurveTo(const FloatPoint& controlPoint, const FloatPoint& endPoint)
{
if (const auto* moveTo = lastIfMoveTo())
m_segments.last() = { PathDataQuadCurve { moveTo->point, controlPoint, endPoint } };
segments().last() = { PathDataQuadCurve { moveTo->point, controlPoint, endPoint } };
else
m_segments.append(PathQuadCurveTo { controlPoint, endPoint });
segments().append(PathQuadCurveTo { controlPoint, endPoint });
}

void PathStream::addBezierCurveTo(const FloatPoint& controlPoint1, const FloatPoint& controlPoint2, const FloatPoint& endPoint)
{
if (const auto* moveTo = lastIfMoveTo())
m_segments.last() = { PathDataBezierCurve { moveTo->point, controlPoint1, controlPoint2, endPoint } };
segments().last() = { PathDataBezierCurve { moveTo->point, controlPoint1, controlPoint2, endPoint } };
else
m_segments.append(PathBezierCurveTo { controlPoint1, controlPoint2, endPoint });
segments().append(PathBezierCurveTo { controlPoint1, controlPoint2, endPoint });
}

void PathStream::addArcTo(const FloatPoint& point1, const FloatPoint& point2, float radius)
{
if (const auto* moveTo = lastIfMoveTo())
m_segments.last() = { PathDataArc { moveTo->point, point1, point2, radius } };
segments().last() = { PathDataArc { moveTo->point, point1, point2, radius } };
else
m_segments.append(PathArcTo { point1, point2, radius });
segments().append(PathArcTo { point1, point2, radius });
}

void PathStream::addArc(const FloatPoint& point, float radius, float startAngle, float endAngle, RotationDirection direction)
Expand All @@ -134,56 +150,57 @@ void PathStream::addArc(const FloatPoint& point, float radius, float startAngle,
if (!std::isfinite(radius) || !std::isfinite(startAngle) || !std::isfinite(endAngle))
return;

m_segments.append(PathArc { point, radius, startAngle, endAngle, direction });
segments().append(PathArc { point, radius, startAngle, endAngle, direction });
}

void PathStream::addRect(const FloatRect& rect)
{
m_segments.append(PathRect { rect });
segments().append(PathRect { rect });
}

void PathStream::addEllipse(const FloatPoint& point, float radiusX, float radiusY, float rotation, float startAngle, float endAngle, RotationDirection direction)
{
m_segments.append(PathEllipse { point, radiusX, radiusY, rotation, startAngle, endAngle, direction });
segments().append(PathEllipse { point, radiusX, radiusY, rotation, startAngle, endAngle, direction });
}

void PathStream::addEllipseInRect(const FloatRect& rect)
{
m_segments.append(PathEllipseInRect { rect });
segments().append(PathEllipseInRect { rect });
}

void PathStream::addRoundedRect(const FloatRoundedRect& roundedRect, PathRoundedRect::Strategy strategy)
{
m_segments.append(PathRoundedRect { roundedRect, strategy });
segments().append(PathRoundedRect { roundedRect, strategy });
}

void PathStream::closeSubpath()
{
m_segments.append(std::monostate());
segments().append(std::monostate());
}

const Vector<PathSegment>& PathStream::segments() const
{
return m_segments;
return m_segmentsData->segments;
}

void PathStream::applySegments(const PathSegmentApplier& applier) const
{
for (auto& segment : m_segments)
for (auto& segment : m_segmentsData->segments)
applier(segment);
}

void PathStream::applyElements(const PathElementApplier& applier) const
{
for (auto& segment : m_segments)
for (auto& segment : m_segmentsData->segments)
segment.applyElements(applier);
}

std::optional<PathSegment> PathStream::singleSegment() const
{
if (m_segments.size() != 1)
if (m_segmentsData->segments.size() != 1)
return std::nullopt;
return m_segments.first();

return m_segmentsData->segments.first();
}

template<class DataType>
Expand Down Expand Up @@ -223,15 +240,15 @@ bool PathStream::isClosed() const
if (isEmpty())
return false;

return m_segments.last().isCloseSubPath();
return m_segmentsData->segments.last().isCloseSubPath();
}

FloatPoint PathStream::currentPoint() const
{
FloatPoint lastMoveToPoint;
FloatPoint currentPoint;

for (auto& segment : m_segments)
for (auto& segment : m_segmentsData->segments)
currentPoint = segment.calculateEndPoint(currentPoint, lastMoveToPoint);

return currentPoint;
Expand All @@ -243,7 +260,7 @@ FloatRect PathStream::fastBoundingRect() const
FloatPoint currentPoint;
FloatRect boundingRect = FloatRect::smallestRect();

for (auto& segment : m_segments) {
for (auto& segment : m_segmentsData->segments) {
segment.extendFastBoundingRect(currentPoint, lastMoveToPoint, boundingRect);
currentPoint = segment.calculateEndPoint(currentPoint, lastMoveToPoint);
}
Expand All @@ -260,7 +277,7 @@ FloatRect PathStream::boundingRect() const
FloatPoint currentPoint;
FloatRect boundingRect = FloatRect::smallestRect();

for (auto& segment : m_segments) {
for (auto& segment : m_segmentsData->segments) {
segment.extendBoundingRect(currentPoint, lastMoveToPoint, boundingRect);
currentPoint = segment.calculateEndPoint(currentPoint, lastMoveToPoint);
}
Expand Down
52 changes: 47 additions & 5 deletions Source/WebCore/platform/graphics/PathStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,20 @@

#include "PathImpl.h"
#include "PathSegment.h"
#include <wtf/DataRef.h>
#include <wtf/ThreadSafeRefCounted.h>
#include <wtf/Vector.h>

namespace WebCore {

class PathStream final : public PathImpl {
public:
static UniqueRef<PathStream> create();
static UniqueRef<PathStream> create(Vector<PathSegment>&&);
static UniqueRef<PathStream> create(const Vector<PathSegment>&);
static UniqueRef<PathStream> create(const Vector<FloatPoint>&);
static UniqueRef<PathStream> create(Vector<PathSegment>&&);

PathStream() = default;
PathStream();
PathStream(const PathStream&);
PathStream(Vector<PathSegment>&&);
PathStream(const Vector<PathSegment>&);

Expand Down Expand Up @@ -70,6 +72,44 @@ class PathStream final : public PathImpl {
FloatRect boundingRect() const final;

private:
struct SegmentsData : public ThreadSafeRefCounted<SegmentsData> {
WTF_MAKE_STRUCT_FAST_ALLOCATED;

static Ref<SegmentsData> create()
{
return adoptRef(*new SegmentsData);
}

static Ref<SegmentsData> create(Vector<PathSegment>&& segments)
{
auto result = adoptRef(*new SegmentsData);
result->segments = WTFMove(segments);
return result;
}

static Ref<SegmentsData> create(const Vector<PathSegment>& segments)
{
auto result = adoptRef(*new SegmentsData);
result->segments = segments;
return result;
}

Ref<SegmentsData> copy() const
{
return create(segments);
}

bool operator==(const SegmentsData& other) const
{
return segments == other.segments;
}

Vector<PathSegment> segments;
};

static UniqueRef<PathStream> create(const PathStream&);
static UniqueRef<PathStream> create(const Vector<PathSegment>&);

const PathMoveTo* lastIfMoveTo() const;

bool isPathStream() const final { return true; }
Expand All @@ -83,12 +123,14 @@ class PathStream final : public PathImpl {
std::optional<PathDataQuadCurve> singleQuadCurve() const final;
std::optional<PathDataBezierCurve> singleBezierCurve() const final;

bool isEmpty() const final { return m_segments.isEmpty(); }
bool isEmpty() const final { return m_segmentsData->segments.isEmpty(); }

bool isClosed() const final;
FloatPoint currentPoint() const final;

Vector<PathSegment> m_segments;
Vector<PathSegment>& segments() { return m_segmentsData.access().segments; }

DataRef<SegmentsData> m_segmentsData;
};

} // namespace WebCore
Expand Down

0 comments on commit bb3cac8

Please sign in to comment.