lottie: optimize the rendering performance.

this optimization nicely merges shapes
when a child group has a only (transformed) pathes.

this also fixes the following compatibility issue nicely.

issue: https://github.com/thorvg/thorvg/issues/2061
This commit is contained in:
Hermet Park 2024-03-28 21:22:53 +09:00 committed by Hermet Park
parent 41ae563a84
commit a1463cdc3e
2 changed files with 84 additions and 38 deletions

View file

@ -59,10 +59,11 @@ struct RenderContext
Shape* merging = nullptr; //merging shapes if possible (if shapes have same properties)
LottieObject** begin = nullptr; //iteration entry point
RenderRepeater* repeater = nullptr;
Matrix* transform = nullptr;
float roundness = 0.0f;
bool fragmenting = false; //render context has been fragmented by filling
bool reqFragment = false; //requirment to fragment the render context
bool allowMerging = true; //individual trimpath doesn't allow merging shapes
bool ownPropagator = true; //this rendering context shares the propergator
RenderContext()
{
@ -71,13 +72,21 @@ struct RenderContext
~RenderContext()
{
delete(propagator);
if (ownPropagator) delete(propagator);
delete(repeater);
free(transform);
}
RenderContext(const RenderContext& rhs)
RenderContext(const RenderContext& rhs, bool mergeable = false)
{
propagator = static_cast<Shape*>(rhs.propagator->duplicate());
if (mergeable) {
this->ownPropagator = false;
propagator = rhs.propagator;
merging = rhs.merging;
} else {
propagator = static_cast<Shape*>(rhs.propagator->duplicate());
}
if (rhs.repeater) {
repeater = new RenderRepeater();
*repeater = *rhs.repeater;
@ -90,6 +99,7 @@ struct RenderContext
static void _updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderContext>& contexts);
static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo);
static bool _buildComposition(LottieComposition* comp, LottieGroup* parent);
static Shape* _draw(LottieGroup* parent, RenderContext* ctx);
static void _rotateX(Matrix* m, float degree)
{
@ -181,15 +191,22 @@ static void _updateTransform(LottieLayer* layer, float frameNo)
}
static void _updateTransform(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
static void _updateTransform(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
{
auto transform = static_cast<LottieTransform*>(*child);
if (!transform) return;
uint8_t opacity;
if (parent->mergeable) {
if (!ctx->transform) ctx->transform = (Matrix*)malloc(sizeof(Matrix));
_updateTransform(transform, frameNo, false, *ctx->transform, opacity);
return;
}
ctx->merging = nullptr;
Matrix matrix;
uint8_t opacity;
if (!_updateTransform(transform, frameNo, false, matrix, opacity)) return;
auto pmatrix = PP(ctx->propagator)->transform();
@ -214,8 +231,11 @@ static void _updateGroup(LottieGroup* parent, LottieObject** child, float frameN
group->scene = parent->scene;
group->reqFragment |= ctx->reqFragment;
//generate a merging shape to consolidate partial shapes into a single entity
if (group->mergeable) _draw(parent, ctx);
Inlist<RenderContext> contexts;
contexts.back(new RenderContext(*ctx));
contexts.back(new RenderContext(*ctx, group->mergeable));
_updateChildren(group, frameNo, contexts);
@ -315,7 +335,7 @@ static Shape* _updateGradientFill(TVG_UNUSED LottieGroup* parent, LottieObject**
static Shape* _draw(LottieGroup* parent, RenderContext* ctx)
{
if (ctx->allowMerging && ctx->merging) return ctx->merging;
if (ctx->merging) return ctx->merging;
auto shape = cast<Shape>(ctx->propagator->duplicate());
ctx->merging = shape.get();
@ -372,7 +392,7 @@ static void _repeat(LottieGroup* parent, unique_ptr<Shape> path, RenderContext*
}
static void _appendRect(Shape* shape, float x, float y, float w, float h, float r)
static void _appendRect(Shape* shape, float x, float y, float w, float h, float r, Matrix* transform)
{
//sharp rect
if (mathZero(r)) {
@ -380,7 +400,11 @@ static void _appendRect(Shape* shape, float x, float y, float w, float h, float
PathCommand::MoveTo, PathCommand::LineTo, PathCommand::LineTo,
PathCommand::LineTo, PathCommand::Close
};
Point points[] = {{x + w, y}, {x + w, y + h}, {x, y + h}, {x, y}};
if (transform) {
for (int i = 0; i < 4; i++) mathTransform(transform, &points[i]);
}
shape->appendPath(commands, 5, points, 4);
//round rect
} else {
@ -398,7 +422,8 @@ static void _appendRect(Shape* shape, float x, float y, float w, float h, float
auto hrx = rx * PATH_KAPPA;
auto hry = ry * PATH_KAPPA;
Point points[] = {
constexpr int ptsCnt = 17;
Point points[ptsCnt] = {
{x + w, y + ry}, //moveTo
{x + w, y + h - ry}, //lineTo
{x + w, y + h - ry + hry}, {x + w - rx + hrx, y + h}, {x + w - rx, y + h}, //cubicTo
@ -410,7 +435,10 @@ static void _appendRect(Shape* shape, float x, float y, float w, float h, float
{x + w - rx + hrx, y}, {x + w, y + ry - hry}, {x + w, y + ry} //cubicTo
};
shape->appendPath(commands, 10, points, 17);
if (transform) {
for (int i = 0; i < ptsCnt; i++) mathTransform(transform, &points[i]);
}
shape->appendPath(commands, 10, points, ptsCnt);
}
}
@ -430,29 +458,29 @@ static void _updateRect(LottieGroup* parent, LottieObject** child, float frameNo
if (ctx->repeater) {
auto path = Shape::gen();
_appendRect(path.get(), position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness);
_appendRect(path.get(), position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness, ctx->transform);
_repeat(parent, std::move(path), ctx);
} else {
auto merging = _draw(parent, ctx);
_appendRect(merging, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness);
_appendRect(merging, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness, ctx->transform);
if (rect->direction == 2) merging->fill(FillRule::EvenOdd);
}
}
static void _appendCircle(Shape* shape, float cx, float cy, float rx, float ry)
static void _appendCircle(Shape* shape, float cx, float cy, float rx, float ry, Matrix* transform)
{
auto rxKappa = rx * PATH_KAPPA;
auto ryKappa = ry * PATH_KAPPA;
constexpr int commandsSize = 6;
PathCommand commands[commandsSize] = {
constexpr int cmdsCnt = 6;
PathCommand commands[cmdsCnt] = {
PathCommand::MoveTo, PathCommand::CubicTo, PathCommand::CubicTo,
PathCommand::CubicTo, PathCommand::CubicTo, PathCommand::Close
};
constexpr int pointsSize = 13;
Point points[pointsSize] = {
constexpr int ptsCnt = 13;
Point points[ptsCnt] = {
{cx, cy - ry}, //moveTo
{cx + rxKappa, cy - ry}, {cx + rx, cy - ryKappa}, {cx + rx, cy}, //cubicTo
{cx + rx, cy + ryKappa}, {cx + rxKappa, cy + ry}, {cx, cy + ry}, //cubicTo
@ -460,9 +488,14 @@ static void _appendCircle(Shape* shape, float cx, float cy, float rx, float ry)
{cx - rx, cy - ryKappa}, {cx - rxKappa, cy - ry}, {cx, cy - ry} //cubicTo
};
shape->appendPath(commands, commandsSize, points, pointsSize);
if (transform) {
for (int i = 0; i < ptsCnt; ++i) mathTransform(transform, &points[i]);
}
shape->appendPath(commands, cmdsCnt, points, ptsCnt);
}
static void _updateEllipse(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
{
auto ellipse = static_cast<LottieEllipse*>(*child);
@ -472,11 +505,11 @@ static void _updateEllipse(LottieGroup* parent, LottieObject** child, float fram
if (ctx->repeater) {
auto path = Shape::gen();
_appendCircle(path.get(), position.x, position.y, size.x * 0.5f, size.y * 0.5f);
_appendCircle(path.get(), position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->transform);
_repeat(parent, std::move(path), ctx);
} else {
auto merging = _draw(parent, ctx);
_appendCircle(merging, position.x, position.y, size.x * 0.5f, size.y * 0.5f);
_appendCircle(merging, position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->transform);
if (ellipse->direction == 2) merging->fill(FillRule::EvenOdd);
}
}
@ -488,11 +521,11 @@ static void _updatePath(LottieGroup* parent, LottieObject** child, float frameNo
if (ctx->repeater) {
auto p = Shape::gen();
path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts);
path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts, ctx->transform);
_repeat(parent, std::move(p), ctx);
} else {
auto merging = _draw(parent, ctx);
if (path->pathset(frameNo, P(merging)->rs.path.cmds, P(merging)->rs.path.pts)) {
if (path->pathset(frameNo, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, ctx->transform)) {
P(merging)->update(RenderUpdateFlag::Path);
}
if (ctx->roundness > 1.0f && P(merging)->rs.stroke) {
@ -766,6 +799,8 @@ static void _updatePolystar(LottieGroup* parent, LottieObject** child, float fra
mathTranslate(&matrix, position.x, position.y);
mathRotate(&matrix, star->rotation(frameNo));
if (ctx->transform) mathMultiply(ctx->transform, &matrix);
auto identity = mathIdentity((const Matrix*)&matrix);
if (ctx->repeater) {
@ -868,7 +903,7 @@ static void _updateTrimpath(TVG_UNUSED LottieGroup* parent, LottieObject** child
P(ctx->propagator)->strokeTrim(begin, end);
if (trimpath->type == LottieTrimpath::Individual) ctx->allowMerging = false;
//TODO: individual or simultaenous mode
}

View file

@ -93,13 +93,22 @@ static inline RGB24 operator*(const RGB24& lhs, float rhs)
}
static void copy(PathSet& pathset, Array<Point>& outPts)
static void copy(PathSet& pathset, Array<Point>& outPts, Matrix* transform)
{
Array<Point> inPts;
inPts.data = pathset.pts;
inPts.count = pathset.ptsCnt;
outPts.push(inPts);
inPts.data = nullptr;
if (transform) {
for (int i = 0; i < pathset.ptsCnt; ++i) {
Point pt = pathset.pts[i];
mathMultiply(&pt, transform);
outPts.push(pt);
}
} else {
inPts.data = pathset.pts;
inPts.count = pathset.ptsCnt;
outPts.push(inPts);
inPts.data = nullptr;
}
}
@ -315,23 +324,23 @@ struct LottiePathSet : LottieProperty
return (*frames)[frames->count];
}
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts)
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform = nullptr)
{
if (!frames) {
copy(value, cmds);
copy(value, pts);
copy(value, pts, transform);
return true;
}
if (frames->count == 1 || frameNo <= frames->first().no) {
copy(frames->first().value, cmds);
copy(frames->first().value, pts);
copy(frames->first().value, pts, transform);
return true;
}
if (frameNo >= frames->last().no) {
copy(frames->last().value, cmds);
copy(frames->last().value, pts);
copy(frames->last().value, pts, transform);
return true;
}
@ -339,7 +348,7 @@ struct LottiePathSet : LottieProperty
if (frame->no == frameNo) {
copy(frame->value, cmds);
copy(frame->value, pts);
copy(frame->value, pts, transform);
return true;
}
@ -350,8 +359,8 @@ struct LottiePathSet : LottieProperty
if (frame->interpolator) t = frame->interpolator->progress(t);
if (frame->hold) {
if (t < 1.0f) copy(frame->value, pts);
else copy((frame + 1)->value, pts);
if (t < 1.0f) copy(frame->value, pts, transform);
else copy((frame + 1)->value, pts, transform);
return true;
}
@ -359,7 +368,9 @@ struct LottiePathSet : LottieProperty
auto e = (frame + 1)->value.pts;
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) {
pts.push(mathLerp(*s, *e, t));
auto pt = mathLerp(*s, *e, t);
if (transform) mathMultiply(&pt,transform);
pts.push(pt);
}
return true;
}