Add RenderPath::addRawPath

Addresses this issue: https://2dimensions.slack.com/archives/C07VD44ASE5/p1739456802968219

Rive's RenderPath steals the memory for the RawPath the runtime builds, which is usually fine apart from cases where we expect to use the RawPath after drawing with it. For example, with inner feather we want to be able to compute the bounds of the RawPath after it has been drawn. If during animation a user turns on inner feathering on a path which has not changed recently, which need to be able to compute its bounds, we can't if the RawPath is now gone.

This also can happen with trim paths and dash paths which are re-trimmed/dashed after having being rendered a few frames. Right now we force rebuild the whole path to re-trim it because we lose the RawPath.

This PR allows the ShapePaintPath to hold on to its RawPath by adding RenderPath::addRawPath. This is optimized memory usage but not having the runtime need to re-alloc the RawPath whenever it animates, but it means we're further from making RenderPaths immutable.

For the future: It'd be good to chat about a long term solution that can accommodate all goals of allocating less memory (prior to this change we were re-allocating RawPath memory every frame when animating), not rebuilding paths as often, and still allow for immutability.

Diffs=
9058a3fdad Add RenderPath::addRawPath (#9038)

Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
This commit is contained in:
luigi-rosso
2025-02-14 21:23:43 +00:00
parent e5ede9c6a3
commit 7f971fba3c
16 changed files with 82 additions and 17 deletions

View File

@@ -1 +1 @@
7a6019fb974ed415d8531980b0b1516ff9f095c8
9058a3fdad2444ca9eab3ef06bc49376eadba9de

View File

@@ -93,7 +93,11 @@ public:
CGRenderPath(Span<const Vec2D> pts, Span<const PathVerb> vbs, FillRule rule)
{
m_fillMode = convert(rule);
addRawPathCommands(pts, vbs);
}
void addRawPathCommands(Span<const Vec2D> pts, Span<const PathVerb> vbs)
{
auto p = pts.data();
for (auto v : vbs)
{
@@ -135,6 +139,11 @@ public:
assert(p == pts.end());
}
void addRawPath(const RawPath& path) override
{
addRawPathCommands(path.points(), path.verbs());
}
CGPathRef path() const { return m_path.get(); }
CGPathDrawingMode drawingMode(bool isStroke) const
{

View File

@@ -16,7 +16,7 @@
#include "rive/shapes/paint/stroke_cap.hpp"
#include "rive/shapes/paint/stroke_join.hpp"
#include "utils/lite_rtti.hpp"
#include "rive/math/raw_path.hpp"
#include <stdio.h>
#include <cstdint>
@@ -192,6 +192,8 @@ public:
{
// No-op on non rive renderer.
}
virtual void addRawPath(const RawPath& path) = 0;
};
class Renderer

View File

@@ -27,7 +27,6 @@ protected:
std::vector<ShapePaint*> m_ShapePaints;
void addPaint(ShapePaint* paint);
// TODO: void draw(Renderer* renderer, PathComposer& composer);
public:
static ShapePaintContainer* from(Component* component);

View File

@@ -51,7 +51,10 @@ public:
m_rawPath.addRect(aabb, dir);
}
const bool hasRenderPath() const { return m_renderPath != nullptr; }
const bool hasRenderPath() const
{
return m_renderPath != nullptr && !m_isRenderPathDirty;
}
#ifdef TESTING
size_t numContours()
@@ -68,6 +71,7 @@ public:
}
#endif
private:
bool m_isRenderPathDirty = true;
rcp<RenderPath> m_renderPath;
RawPath m_rawPath;
bool m_isLocal;

View File

@@ -82,9 +82,12 @@ void RiveRenderPath::addRenderPath(RenderPath* path, const Mat2D& matrix)
{
assert(m_rawPathMutationLockCount == 0);
RiveRenderPath* riveRenderPath = static_cast<RiveRenderPath*>(path);
bool isIdentity = matrix == Mat2D();
RawPath::Iter transformedPathIter =
m_rawPath.addPath(riveRenderPath->m_rawPath, &matrix);
if (matrix != Mat2D())
m_rawPath.addPath(riveRenderPath->m_rawPath,
isIdentity ? nullptr : &matrix);
if (!isIdentity)
{
// Prune any segments that became empty after the transform.
m_rawPath.pruneEmptySegments(transformedPathIter);
@@ -106,6 +109,11 @@ void RiveRenderPath::addRenderPathBackwards(RenderPath* path,
m_dirt = kAllDirt;
}
void RiveRenderPath::addRawPath(const RawPath& path)
{
m_rawPath.addPath(path, nullptr);
}
const AABB& RiveRenderPath::getBounds() const
{
if (m_dirt & kPathBoundsDirt)

View File

@@ -32,7 +32,7 @@ public:
void addRenderPath(RenderPath* path, const Mat2D& matrix) override;
void addRenderPathBackwards(RenderPath* path,
const Mat2D& transform) override;
void addRawPath(const RawPath& path) override;
const RawPath& getRawPath() const { return m_rawPath; }
FillRule getFillRule() const { return m_fillRule; }

View File

@@ -39,6 +39,7 @@ public:
void rewind() override;
void addRenderPath(RenderPath* path, const Mat2D& transform) override;
void addRawPath(const RawPath& path) override;
void fillRule(FillRule value) override;
void moveTo(float x, float y) override;
void lineTo(float x, float y) override;
@@ -132,6 +133,23 @@ void SkiaRenderPath::addRenderPath(RenderPath* path, const Mat2D& transform)
m_Path.addPath(skPath->m_Path, ToSkia::convert(transform));
}
void SkiaRenderPath::addRawPath(const RawPath& path)
{
const bool isVolatile = false;
const SkScalar* conicWeights = nullptr;
const int conicWeightCount = 0;
auto skPath =
SkPath::Make(reinterpret_cast<const SkPoint*>(path.points().data()),
path.points().size(),
(uint8_t*)path.verbs().data(),
path.verbs().size(),
conicWeights,
conicWeightCount,
m_Path.getFillType(),
isVolatile);
m_Path.addPath(skPath);
}
void SkiaRenderPath::moveTo(float x, float y) { m_Path.moveTo(x, y); }
void SkiaRenderPath::lineTo(float x, float y) { m_Path.lineTo(x, y); }
void SkiaRenderPath::cubicTo(float ox,

View File

@@ -13,14 +13,14 @@ ShapePaintPath::ShapePaintPath(bool isLocal, FillRule fillRule) :
void ShapePaintPath::rewind()
{
m_rawPath.rewind();
m_renderPath = nullptr;
m_isRenderPathDirty = true;
}
void ShapePaintPath::addPath(const RawPath& rawPath, const Mat2D* transform)
{
auto iter = m_rawPath.addPath(rawPath, transform);
m_rawPath.pruneEmptySegments(iter);
m_renderPath = nullptr;
m_isRenderPathDirty = true;
}
void ShapePaintPath::addPathClockwise(const RawPath& rawPath,
@@ -46,25 +46,30 @@ void ShapePaintPath::addPathBackwards(const RawPath& rawPath,
{
auto iter = m_rawPath.addPathBackwards(rawPath, transform);
m_rawPath.pruneEmptySegments(iter);
m_renderPath = nullptr;
m_isRenderPathDirty = true;
}
RenderPath* ShapePaintPath::renderPath(const Component* component)
{
assert(component != nullptr);
if (!m_renderPath)
{
auto factory = component->artboard()->factory();
m_renderPath = factory->makeRenderPath(m_rawPath, m_fillRule);
}
return m_renderPath.get();
return renderPath(component->artboard()->factory());
}
RenderPath* ShapePaintPath::renderPath(Factory* factory)
{
if (!m_renderPath)
{
m_renderPath = factory->makeRenderPath(m_rawPath, m_fillRule);
m_renderPath = factory->makeEmptyRenderPath();
m_renderPath->addRawPath(m_rawPath);
m_renderPath->fillRule(m_fillRule);
m_isRenderPathDirty = false;
}
else if (m_isRenderPathDirty)
{
m_renderPath->rewind();
m_renderPath->addRawPath(m_rawPath);
m_isRenderPathDirty = false;
}
return m_renderPath.get();
}

View File

@@ -58,6 +58,7 @@ public:
override;
void close() override;
void addRenderPath(RenderPath* path, const Mat2D& transform) override;
void addRawPath(const RawPath& path) override;
const SegmentedContour& segmentedContour() const;
bool triangulate();

View File

@@ -23,6 +23,7 @@ public:
void fillRule(FillRule value) override {}
void addPath(CommandPath* path, const Mat2D& transform) override {}
void addRenderPath(RenderPath* path, const Mat2D& transform) override {}
void addRawPath(const RawPath& path) override {}
void moveTo(float x, float y) override {}
void lineTo(float x, float y) override {}

View File

@@ -46,6 +46,11 @@ void TessRenderPath::addRenderPath(RenderPath* path, const Mat2D& transform)
m_subPaths.emplace_back(SubPath(path, transform));
}
void TessRenderPath::addRawPath(const RawPath& path)
{
m_rawPath.addPath(path, nullptr);
}
const SegmentedContour& TessRenderPath::segmentedContour() const
{
return m_segmentedContour;

View File

@@ -153,6 +153,11 @@ protected:
m_begin = shape->m_begin;
}
void addRawPath(const RawPath& path) override
{
m_path->addRawPath(path);
}
protected:
Path m_path;
Vec2D m_pen = {0, 0};

View File

@@ -41,6 +41,10 @@ public:
void addRenderPath(rive::RenderPath* path,
const rive::Mat2D& transform) override
{}
void addRawPath(const rive::RawPath& path) override
{
rawPath.addPath(path, nullptr);
}
void moveTo(float x, float y) override {}
void lineTo(float x, float y) override {}

View File

@@ -45,6 +45,8 @@ class TestRenderPath : public rive::RenderPath
{
public:
std::vector<TestPathCommand> commands;
void addRawPath(const rive::RawPath& path) override { path.addTo(this); }
void rewind() override
{
commands.emplace_back(TestPathCommand{TestPathCommandType::Reset,

View File

@@ -38,6 +38,8 @@ public:
override
{}
void close() override {}
void addRawPath(const RawPath& path) override {}
};
} // namespace