Lottie: optimize rendering performance

implemented an aggressive culling strategy to eliminate
unnecessary renderings if the rendering visuals were hidden
by other overlaid opaque fills or strokes

this pretty improves the performance for those scenarios
when rendering sequences are fragmented by fills/strokes.

Performance has been improved ~7% with those cases
This commit is contained in:
Hermet Park 2025-02-26 22:44:44 +09:00 committed by Hermet Park
parent 89752ff661
commit d386a5654a
4 changed files with 76 additions and 47 deletions

View file

@ -233,73 +233,100 @@ static void _updateStroke(LottieStroke* stroke, float frameNo, RenderContext* ct
}
bool LottieBuilder::fragmented(LottieGroup* parent, LottieObject** child, Inlist<RenderContext>& contexts, RenderContext* ctx)
bool LottieBuilder::fragmented(LottieGroup* parent, LottieObject** child, Inlist<RenderContext>& contexts, RenderContext* ctx, RenderFragment fragment)
{
if (!ctx->reqFragment) return false;
if (ctx->fragmenting) return true;
if (ctx->fragment) return true;
contexts.back(new RenderContext(*ctx, static_cast<Shape*>(PAINT(ctx->propagator)->duplicate(parent->pooling()))));
auto fragment = contexts.tail;
fragment->begin = child - 1;
ctx->fragmenting = true;
if (!ctx->reqFragment || ctx->fragment == RenderFragment::ByNone) return false;
contexts.back(new RenderContext(*ctx, (Shape*)(PAINT(ctx->propagator)->duplicate(parent->pooling()))));
contexts.tail->begin = child - 1;
ctx->fragment = fragment;
return false;
}
void LottieBuilder::updateSolidStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
bool LottieBuilder::updateSolidStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
{
if (fragmented(parent, child, contexts, ctx)) return;
auto stroke = static_cast<LottieSolidStroke*>(*child);
auto opacity = stroke->opacity(frameNo, tween, exps);
//interrupted by fully opaque, stop the current rendering
if (ctx->fragment == RenderFragment::ByStroke && opacity == 255) return true;
if (fragmented(parent, child, contexts, ctx, RenderFragment::ByStroke)) return false;
if (opacity == 0) return false;
ctx->merging = nullptr;
auto color = stroke->color(frameNo, tween, exps);
ctx->propagator->strokeFill(color.rgb[0], color.rgb[1], color.rgb[2], stroke->opacity(frameNo, tween, exps));
ctx->propagator->strokeFill(color.rgb[0], color.rgb[1], color.rgb[2], opacity);
_updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx, tween, exps);
return false;
}
void LottieBuilder::updateGradientStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
bool LottieBuilder::updateGradientStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
{
if (fragmented(parent, child, contexts, ctx)) return;
auto stroke = static_cast<LottieGradientStroke*>(*child);
auto opacity = stroke->opacity(frameNo, tween, exps);
//interrupted by fully opaque, stop the current rendering
if (ctx->fragment == RenderFragment::ByStroke && stroke->opaque && opacity == 255) return true;
if (fragmented(parent, child, contexts, ctx, RenderFragment::ByStroke)) return false;
ctx->merging = nullptr;
ctx->propagator->strokeFill(stroke->fill(frameNo, tween, exps));
if (auto val = stroke->fill(frameNo, opacity, tween, exps)) ctx->propagator->strokeFill(val);
_updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx, tween, exps);
return false;
}
void LottieBuilder::updateSolidFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
bool LottieBuilder::updateSolidFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
{
if (fragmented(parent, child, contexts, ctx)) return;
auto fill = static_cast<LottieSolidFill*>(*child);
auto opacity = fill->opacity(frameNo, tween, exps);
//interrupted by fully opaque, stop the current rendering
if (ctx->fragment == RenderFragment::ByFill && opacity == 255) return true;
if (fragmented(parent, child, contexts, ctx, RenderFragment::ByFill)) return false;
if (opacity == 0) return false;
ctx->merging = nullptr;
auto color = fill->color(frameNo, tween, exps);
ctx->propagator->fill(color.rgb[0], color.rgb[1], color.rgb[2], fill->opacity(frameNo, tween, exps));
ctx->propagator->fill(color.rgb[0], color.rgb[1], color.rgb[2], opacity);
ctx->propagator->fill(fill->rule);
if (ctx->propagator->strokeWidth() > 0) ctx->propagator->order(true);
return false;
}
void LottieBuilder::updateGradientFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
bool LottieBuilder::updateGradientFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx)
{
if (fragmented(parent, child, contexts, ctx)) return;
auto fill = static_cast<LottieGradientFill*>(*child);
auto opacity = fill->opacity(frameNo, tween, exps);
//interrupted by fully opaque, stop the current rendering
if (ctx->fragment == RenderFragment::ByFill && fill->opaque && opacity == 255) return true;
if (fragmented(parent, child, contexts, ctx, RenderFragment::ByFill)) return false;
ctx->merging = nullptr;
//TODO: reuse the fill instance?
ctx->propagator->fill(fill->fill(frameNo, tween, exps));
if (auto val = fill->fill(frameNo, opacity, tween, exps)) ctx->propagator->fill(val);
ctx->propagator->fill(fill->rule);
if (ctx->propagator->strokeWidth() > 0) ctx->propagator->order(true);
return false;
}
@ -769,6 +796,7 @@ void LottieBuilder::updateChildren(LottieGroup* parent, float frameNo, Inlist<Re
while (!contexts.empty()) {
auto ctx = contexts.front();
ctx->reqFragment = parent->reqFragment;
auto stop = false;
for (auto child = ctx->begin; child >= parent->children.data; --child) {
//Here switch-case statements are more performant than virtual methods.
switch ((*child)->type) {
@ -781,19 +809,19 @@ void LottieBuilder::updateChildren(LottieGroup* parent, float frameNo, Inlist<Re
break;
}
case LottieObject::SolidFill: {
updateSolidFill(parent, child, frameNo, contexts, ctx);
stop = updateSolidFill(parent, child, frameNo, contexts, ctx);
break;
}
case LottieObject::SolidStroke: {
updateSolidStroke(parent, child, frameNo, contexts, ctx);
stop = updateSolidStroke(parent, child, frameNo, contexts, ctx);
break;
}
case LottieObject::GradientFill: {
updateGradientFill(parent, child, frameNo, contexts, ctx);
stop = updateGradientFill(parent, child, frameNo, contexts, ctx);
break;
}
case LottieObject::GradientStroke: {
updateGradientStroke(parent, child, frameNo, contexts, ctx);
stop = updateGradientStroke(parent, child, frameNo, contexts, ctx);
break;
}
case LottieObject::Rect: {
@ -832,7 +860,7 @@ void LottieBuilder::updateChildren(LottieGroup* parent, float frameNo, Inlist<Re
}
//stop processing for those invisible contents
if (ctx->propagator->opacity() == skip) break;
if (stop || ctx->propagator->opacity() == skip) break;
}
delete(ctx);
}

View file

@ -46,6 +46,8 @@ struct RenderRepeater
bool inorder;
};
enum RenderFragment : uint8_t {ByNone = 0, ByFill, ByStroke};
struct RenderContext
{
INLIST_ITEM(RenderContext);
@ -58,7 +60,7 @@ struct RenderContext
LottieRoundnessModifier* roundness = nullptr;
LottieOffsetModifier* offset = nullptr;
LottieModifier* modifier = nullptr;
bool fragmenting = false; //render context has been fragmented by filling
RenderFragment fragment = ByNone; //render context has been fragmented
bool reqFragment = false; //requirement to fragment the render context
RenderContext(Shape* propagator)
@ -76,12 +78,12 @@ struct RenderContext
delete(offset);
}
RenderContext(const RenderContext& rhs, Shape* propagator, bool mergeable = false)
RenderContext(const RenderContext& rhs, Shape* propagator, bool mergeable = false) : propagator(propagator)
{
if (mergeable) merging = rhs.merging;
propagator->ref();
this->propagator = propagator;
repeaters = rhs.repeaters;
fragment = rhs.fragment;
if (rhs.roundness) {
roundness = new LottieRoundnessModifier(rhs.roundness->buffer, rhs.roundness->r);
update(roundness);
@ -138,7 +140,7 @@ struct LottieBuilder
private:
void appendRect(Shape* shape, Point& pos, Point& size, float r, bool clockwise, RenderContext* ctx);
bool fragmented(LottieGroup* parent, LottieObject** child, Inlist<RenderContext>& contexts, RenderContext* ctx);
bool fragmented(LottieGroup* parent, LottieObject** child, Inlist<RenderContext>& contexts, RenderContext* ctx, RenderFragment fragment);
void updateStrokeEffect(LottieLayer* layer, LottieFxStroke* effect, float frameNo);
void updateEffect(LottieLayer* layer, float frameNo);
@ -154,10 +156,10 @@ private:
void updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderContext>& contexts, uint8_t skip);
void updateGroup(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& pcontexts, RenderContext* ctx, uint8_t skip);
void updateTransform(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateSolidFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateSolidStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateGradientFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateGradientStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
bool updateSolidFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
bool updateSolidStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
bool updateGradientFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
bool updateGradientStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateRect(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateEllipse(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updatePath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);

View file

@ -311,6 +311,7 @@ uint32_t LottieGradient::populate(ColorStop& color, size_t count)
}
aidx += 2;
}
if (cs.a < 255) opaque = false;
output.push(cs);
}
@ -321,6 +322,7 @@ uint32_t LottieGradient::populate(ColorStop& color, size_t count)
cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f);
cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f);
cs.a = (output.count > 0) ? output.last().a : 255;
if (cs.a < 255) opaque = false;
output.push(cs);
cidx += 4;
}
@ -329,6 +331,7 @@ uint32_t LottieGradient::populate(ColorStop& color, size_t count)
while (aidx < color.input->count) {
cs.offset = (*color.input)[aidx];
cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f);
if (cs.a < 255) opaque = false;
if (output.count > 0) {
cs.r = output.last().r;
cs.g = output.last().g;
@ -348,12 +351,11 @@ uint32_t LottieGradient::populate(ColorStop& color, size_t count)
}
Fill* LottieGradient::fill(float frameNo, Tween& tween, LottieExpressions* exps)
Fill* LottieGradient::fill(float frameNo, uint8_t opacity, Tween& tween, LottieExpressions* exps)
{
auto opacity = this->opacity(frameNo, tween, exps);
if (opacity == 0) return nullptr;
Fill* fill = nullptr;
Fill* fill;
auto s = start(frameNo, tween, exps);
auto e = end(frameNo, tween, exps);
@ -361,11 +363,9 @@ Fill* LottieGradient::fill(float frameNo, Tween& tween, LottieExpressions* exps)
if (id == 1) {
fill = LinearGradient::gen();
static_cast<LinearGradient*>(fill)->linear(s.x, s.y, e.x, e.y);
}
//Radial Gradient
if (id == 2) {
} else {
fill = RadialGradient::gen();
auto w = fabsf(e.x - s.x);
auto h = fabsf(e.y - s.y);
auto r = (w > h) ? (w + 0.375f * h) : (h + 0.375f * w);
@ -384,8 +384,6 @@ Fill* LottieGradient::fill(float frameNo, Tween& tween, LottieExpressions* exps)
}
}
if (!fill) return nullptr;
colorStops(frameNo, fill, tween, exps);
//multiply the current opacity with the fill

View file

@ -701,7 +701,7 @@ struct LottieGradient : LottieObject
}
uint32_t populate(ColorStop& color, size_t count);
Fill* fill(float frameNo, Tween& tween, LottieExpressions* exps);
Fill* fill(float frameNo, uint8_t opacity, Tween& tween, LottieExpressions* exps);
LottieScalar start = Point{0.0f, 0.0f};
LottieScalar end = Point{0.0f, 0.0f};
@ -709,7 +709,8 @@ struct LottieGradient : LottieObject
LottieFloat angle = 0.0f;
LottieOpacity opacity = 255;
LottieColorStop colorStops;
uint8_t id = 0; //1: linear, 2: radial
uint8_t id = 0; //1: linear, 2: radial
bool opaque = true; //fully opaque or not
};