lottie: ++scene rendering optimization

Apply LottieRenderPooler to path/rect/ellipse/polystar.

This enhances the animation performance: ~10%
This commit is contained in:
Hermet Park 2024-07-13 17:33:11 +09:00
parent d0210a1a72
commit cf253dd873
10 changed files with 138 additions and 83 deletions

View file

@ -99,7 +99,7 @@ struct RenderContext
static void _updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderContext>& contexts, LottieExpressions* exps);
static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo, LottieExpressions* exps);
static bool _buildComposition(LottieComposition* comp, LottieLayer* parent);
static bool _draw(LottieGroup* parent, RenderContext* ctx);
static bool _draw(LottieGroup* parent, LottieShape* shape, RenderContext* ctx);
static void _rotationXYZ(Matrix* m, float degreeX, float degreeY, float degreeZ)
@ -275,7 +275,7 @@ static void _updateGroup(LottieGroup* parent, LottieObject** child, float frameN
group->reqFragment |= ctx->reqFragment;
//generate a merging shape to consolidate partial shapes into a single entity
if (group->mergeable()) _draw(parent, ctx);
if (group->mergeable()) _draw(parent, nullptr, ctx);
Inlist<RenderContext> contexts;
contexts.back(new RenderContext(*ctx, group->mergeable()));
@ -374,20 +374,24 @@ static void _updateGradientFill(TVG_UNUSED LottieGroup* parent, LottieObject** c
}
static bool _draw(LottieGroup* parent, RenderContext* ctx)
static bool _draw(LottieGroup* parent, LottieShape* shape, RenderContext* ctx)
{
if (ctx->merging) return false;
auto shape = cast<Shape>(ctx->propagator->duplicate());
ctx->merging = shape.get();
parent->scene->push(std::move(shape));
if (shape) {
ctx->merging = shape->pooling();
PP(ctx->propagator)->duplicate(ctx->merging);
} else {
ctx->merging = static_cast<Shape*>(ctx->propagator->duplicate());
}
parent->scene->push(cast(ctx->merging));
return true;
}
//OPTIMIZE: path?
static void _repeat(LottieGroup* parent, unique_ptr<Shape> path, RenderContext* ctx)
static void _repeat(LottieGroup* parent, Shape* path, RenderContext* ctx)
{
Array<Shape*> propagators;
propagators.push(ctx->propagator);
@ -401,7 +405,7 @@ static void _repeat(LottieGroup* parent, unique_ptr<Shape> path, RenderContext*
for (auto propagator = propagators.begin(); propagator < propagators.end(); ++propagator) {
auto shape = static_cast<Shape*>((*propagator)->duplicate());
P(shape)->rs.path = P(path.get())->rs.path;
P(shape)->rs.path = P(path)->rs.path;
auto opacity = repeater->interpOpacity ? mathLerp<uint8_t>(repeater->startOpacity, repeater->endOpacity, static_cast<float>(i + 1) / repeater->cnt) : repeater->startOpacity;
shape->opacity(opacity);
@ -542,11 +546,12 @@ static void _updateRect(LottieGroup* parent, LottieObject** child, float frameNo
}
if (!ctx->repeaters.empty()) {
auto path = Shape::gen();
_appendRect(path.get(), position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness, ctx->transform, rect->clockwise);
_repeat(parent, std::move(path), ctx);
auto shape = rect->pooling();
shape->reset();
_appendRect(shape, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness, ctx->transform, rect->clockwise);
_repeat(parent, shape, ctx);
} else {
_draw(parent, ctx);
_draw(parent, rect, ctx);
_appendRect(ctx->merging, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness, ctx->transform, rect->clockwise);
}
}
@ -598,11 +603,12 @@ static void _updateEllipse(LottieGroup* parent, LottieObject** child, float fram
auto size = ellipse->size(frameNo, exps);
if (!ctx->repeaters.empty()) {
auto path = Shape::gen();
_appendCircle(path.get(), position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->transform, ellipse->clockwise);
_repeat(parent, std::move(path), ctx);
auto shape = ellipse->pooling();
shape->reset();
_appendCircle(shape, position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->transform, ellipse->clockwise);
_repeat(parent, shape, ctx);
} else {
_draw(parent, ctx);
_draw(parent, ellipse, ctx);
_appendCircle(ctx->merging, position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->transform, ellipse->clockwise);
}
}
@ -613,11 +619,12 @@ static void _updatePath(LottieGroup* parent, LottieObject** child, float frameNo
auto path = static_cast<LottiePath*>(*child);
if (!ctx->repeaters.empty()) {
auto p = Shape::gen();
path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts, ctx->transform, ctx->roundness, exps);
_repeat(parent, std::move(p), ctx);
auto shape = path->pooling();
shape->reset();
path->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, ctx->transform, ctx->roundness, exps);
_repeat(parent, shape, ctx);
} else {
_draw(parent, ctx);
_draw(parent, path, ctx);
if (path->pathset(frameNo, P(ctx->merging)->rs.path.cmds, P(ctx->merging)->rs.path.pts, ctx->transform, ctx->roundness, exps)) {
P(ctx->merging)->update(RenderUpdateFlag::Path);
}
@ -708,8 +715,14 @@ static void _updateStar(LottieGroup* parent, LottiePolyStar* star, Matrix* trans
auto direction = star->clockwise ? 1.0f : -1.0f;
auto hasRoundness = false;
bool roundedCorner = (roundness > ROUNDNESS_EPSILON) && (mathZero(innerRoundness) || mathZero(outerRoundness));
//TODO: we can use PathCommand / PathCoord directly.
auto shape = roundedCorner ? Shape::gen().release() : merging;
Shape* shape;
if (roundedCorner) {
shape = star->pooling();
shape->reset();
} else {
shape = merging;
}
float x, y;
@ -798,10 +811,7 @@ static void _updateStar(LottieGroup* parent, LottiePolyStar* star, Matrix* trans
}
shape->close();
if (roundedCorner) {
_applyRoundedCorner(shape, merging, outerRoundness, roundness, hasRoundness);
delete(shape);
}
if (roundedCorner) _applyRoundedCorner(shape, merging, outerRoundness, roundness, hasRoundness);
}
@ -876,7 +886,7 @@ static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* tr
static void _updatePolystar(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx, LottieExpressions* exps)
{
auto star= static_cast<LottiePolyStar*>(*child);
auto star = static_cast<LottiePolyStar*>(*child);
//Optimize: Can we skip the individual coords transform?
Matrix matrix;
@ -890,12 +900,13 @@ static void _updatePolystar(LottieGroup* parent, LottieObject** child, float fra
auto identity = mathIdentity((const Matrix*)&matrix);
if (!ctx->repeaters.empty()) {
auto p = Shape::gen();
if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, ctx->roundness, frameNo, p.get(), exps);
else _updatePolygon(parent, star, identity ? nullptr : &matrix, frameNo, p.get(), exps);
_repeat(parent, std::move(p), ctx);
auto shape = star->pooling();
shape->reset();
if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, ctx->roundness, frameNo, shape, exps);
else _updatePolygon(parent, star, identity ? nullptr : &matrix, frameNo, shape, exps);
_repeat(parent, shape, ctx);
} else {
_draw(parent, ctx);
_draw(parent, star, ctx);
if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, ctx->roundness, frameNo, ctx->merging, exps);
else _updatePolygon(parent, star, identity ? nullptr : &matrix, frameNo, ctx->merging, exps);
P(ctx->merging)->update(RenderUpdateFlag::Path);

View file

@ -37,6 +37,16 @@
/* External Class Implementation */
/************************************************************************/
void LottieShape::prepare(LottieObject::Type type)
{
LottieObject::type = type;
auto shape = tvg::Shape::gen().release();
PP(shape)->ref();
pooler.push(shape);
}
void LottieSlot::reset()
{
if (!overriden) return;

View file

@ -266,15 +266,18 @@ struct LottieTrimpath : LottieObject
};
struct LottieShape : LottieObject
struct LottieShape : LottieObject, LottieRenderPooler<tvg::Shape>
{
virtual ~LottieShape() {}
bool clockwise = true; //clockwise or counter-clockwise
virtual ~LottieShape() {}
bool mergeable() override
{
return true;
}
void prepare(LottieObject::Type type);
};
@ -292,7 +295,7 @@ struct LottiePath : LottieShape
{
void prepare()
{
LottieObject::type = LottieObject::Path;
LottieShape::prepare(LottieObject::Path);
}
LottiePathSet pathset;
@ -303,7 +306,7 @@ struct LottieRect : LottieShape
{
void prepare()
{
LottieObject::type = LottieObject::Rect;
LottieShape::prepare(LottieObject::Rect);
}
LottiePosition position = Point{0.0f, 0.0f};
@ -318,7 +321,7 @@ struct LottiePolyStar : LottieShape
void prepare()
{
LottieObject::type = LottieObject::Polystar;
LottieShape::prepare(LottieObject::Polystar);
}
LottiePosition position = Point{0.0f, 0.0f};
@ -336,7 +339,7 @@ struct LottieEllipse : LottieShape
{
void prepare()
{
LottieObject::type = LottieObject::Ellipse;
LottieShape::prepare(LottieObject::Ellipse);
}
LottiePosition position = Point{0.0f, 0.0f};
@ -552,7 +555,7 @@ struct LottieGroup : LottieObject
return nullptr;
}
Scene* scene = nullptr; //tvg render data
Scene* scene = nullptr;
Array<LottieObject*> children;
bool reqFragment = false; //requirment to fragment the render context

View file

@ -158,16 +158,22 @@ Iterator* Paint::Impl::iterator()
}
Paint* Paint::Impl::duplicate()
Paint* Paint::Impl::duplicate(Paint* ret)
{
Paint* ret;
PAINT_METHOD(ret, duplicate());
if (ret) ret->composite(nullptr, CompositeMethod::None);
PAINT_METHOD(ret, duplicate(ret));
//duplicate Transform
if (rTransform) {
ret->pImpl->rTransform = new RenderTransform();
if (!ret->pImpl->rTransform) {
ret->pImpl->rTransform = new RenderTransform();
}
*ret->pImpl->rTransform = *rTransform;
ret->pImpl->renderFlag |= RenderUpdateFlag::Transform;
} else {
delete(ret->pImpl->rTransform);
ret->pImpl->rTransform = nullptr;
}
ret->pImpl->opacity = opacity;

View file

@ -138,7 +138,7 @@ namespace tvg
bool bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking);
RenderData update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false);
bool render(RenderMethod* renderer);
Paint* duplicate();
Paint* duplicate(Paint* ret = nullptr);
};
}

View file

@ -189,19 +189,21 @@ struct Picture::Impl
}
}
Paint* duplicate()
Paint* duplicate(Paint* ret)
{
if (ret) TVGERR("RENDERER", "TODO: duplicate()");
load();
auto ret = Picture::gen().release();
auto dup = ret->pImpl;
auto picture = Picture::gen().release();
auto dup = picture->pImpl;
if (paint) dup->paint = paint->duplicate();
if (loader) {
dup->loader = loader;
++dup->loader->sharing;
PP(ret)->renderFlag |= RenderUpdateFlag::Image;
PP(picture)->renderFlag |= RenderUpdateFlag::Image;
}
dup->surface = surface;
@ -215,7 +217,7 @@ struct Picture::Impl
memcpy(dup->rm.triangles, rm.triangles, sizeof(Polygon) * rm.triangleCnt);
}
return ret;
return picture;
}
Iterator* iterator()

View file

@ -147,6 +147,31 @@ struct RenderStroke
bool simultaneous = true;
} trim;
void operator=(const RenderStroke& rhs)
{
width = rhs.width;
memcpy(color, rhs.color, sizeof(color));
delete(fill);
if (rhs.fill) fill = rhs.fill->duplicate();
else fill = nullptr;
free(dashPattern);
if (rhs.dashCnt > 0) {
dashPattern = static_cast<float*>(malloc(sizeof(float) * rhs.dashCnt));
memcpy(dashPattern, rhs.dashPattern, sizeof(float) * rhs.dashCnt);
} else {
dashPattern = nullptr;
}
dashCnt = rhs.dashCnt;
miterlimit = rhs.miterlimit;
cap = rhs.cap;
join = rhs.join;
strokeFirst = rhs.strokeFirst;
trim = rhs.trim;
}
~RenderStroke()
{
free(dashPattern);

View file

@ -198,10 +198,12 @@ struct Scene::Impl
return true;
}
Paint* duplicate()
Paint* duplicate(Paint* ret)
{
auto ret = Scene::gen().release();
auto dup = ret->pImpl;
if (ret) TVGERR("RENDERER", "TODO: duplicate()");
auto scene = Scene::gen().release();
auto dup = scene->pImpl;
for (auto paint : paints) {
auto cdup = paint->duplicate();
@ -209,7 +211,7 @@ struct Scene::Impl
dup->paints.push_back(cdup);
}
return ret;
return scene;
}
void clear(bool free)

View file

@ -34,6 +34,7 @@ struct Shape::Impl
RenderData rd = nullptr; //engine data
Shape* shape;
uint8_t flag = RenderUpdateFlag::None;
uint8_t opacity; //for composition
bool needComp = false; //composite or not
@ -359,47 +360,40 @@ struct Shape::Impl
this->flag |= flag;
}
Paint* duplicate()
Paint* duplicate(Paint* ret)
{
auto ret = Shape::gen().release();
auto dup = ret->pImpl;
auto shape = static_cast<Shape*>(ret);
if (shape) shape->reset();
else shape = Shape::gen().release();
auto dup = shape->pImpl;
delete(dup->rs.fill);
//Default Properties
dup->flag = RenderUpdateFlag::All;
dup->rs.rule = rs.rule;
//Color
memcpy(dup->rs.color, rs.color, sizeof(rs.color));
dup->flag = RenderUpdateFlag::Color;
//Path
if (rs.path.cmds.count > 0 && rs.path.pts.count > 0) {
dup->rs.path.cmds = rs.path.cmds;
dup->rs.path.pts = rs.path.pts;
dup->flag |= RenderUpdateFlag::Path;
}
dup->rs.path.cmds.push(rs.path.cmds);
dup->rs.path.pts.push(rs.path.pts);
//Stroke
if (rs.stroke) {
dup->rs.stroke = new RenderStroke();
if (!dup->rs.stroke) dup->rs.stroke = new RenderStroke;
*dup->rs.stroke = *rs.stroke;
memcpy(dup->rs.stroke->color, rs.stroke->color, sizeof(rs.stroke->color));
if (rs.stroke->dashCnt > 0) {
dup->rs.stroke->dashPattern = static_cast<float*>(malloc(sizeof(float) * rs.stroke->dashCnt));
memcpy(dup->rs.stroke->dashPattern, rs.stroke->dashPattern, sizeof(float) * rs.stroke->dashCnt);
}
if (rs.stroke->fill) {
dup->rs.stroke->fill = rs.stroke->fill->duplicate();
dup->flag |= RenderUpdateFlag::GradientStroke;
}
dup->flag |= RenderUpdateFlag::Stroke;
} else {
delete(dup->rs.stroke);
dup->rs.stroke = nullptr;
}
//Fill
if (rs.fill) {
dup->rs.fill = rs.fill->duplicate();
dup->flag |= RenderUpdateFlag::Gradient;
}
if (rs.fill) dup->rs.fill = rs.fill->duplicate();
else dup->rs.fill = nullptr;
return ret;
return shape;
}
Iterator* iterator()

View file

@ -152,12 +152,14 @@ struct Text::Impl
return true;
}
Paint* duplicate()
Paint* duplicate(Paint* ret)
{
if (ret) TVGERR("RENDERER", "TODO: duplicate()");
load();
auto ret = Text::gen().release();
auto dup = ret->pImpl;
auto text = Text::gen().release();
auto dup = text->pImpl;
if (paint) dup->paint = static_cast<Shape*>(paint->duplicate());
if (loader) {
@ -169,7 +171,7 @@ struct Text::Impl
dup->italic = italic;
dup->fontSize = fontSize;
return ret;
return text;
}
Iterator* iterator()