Anim: Restrict range of fcurve_to_keylist

# Issue
Having a lot of keys in your scene can dramatically slow down the Dope Sheet.
That is because everytime the Dope Sheet is redrawn,
the Keylists have to be recomputed.
In the case of the summary channel, that means going through
all keyframes of all the `FCurves` and adding them to the keylist.
That eats up 95% of the time it takes to draw a frame.

# This PR
It's not a perfect solution, rather it solves the performance issue
for the case when you are not displaying all keys.
Instead of going through all the keys, only add the
keys visible in the view to the keylist.
This speeds up the Dope Sheet significantly
depending on the zoom level.

This also improves the responsiveness when selecting and transforming keyframes.

# Performance changes

The function measured is `ED_channel_list_flush`
which is responsible for building the keylists and drawing them.
The test setup contains 62 bones with all
10 channels keyed (location, rot quaternion, scale) on 6000 frames.
So 3.720.000 keys. The heavier the dataset the bigger the performance impact.

The data was recorded with only the Dope Sheet open, and 3 channels visible
* Summary
* Object
* Action

The more channels are visible, the greater the performance gain. This can be seen in the video.

| visible range | before | after |
| - | - | - |
| 200f | 250ms | 10ms |
| 400f | 250ms | 18ms |
| 3000f | 250ms | 130ms |
| 6000f | 250ms | 250ms |

Pull Request: https://projects.blender.org/blender/blender/pulls/114854
This commit is contained in:
Christoph Lendenfeld 2023-11-23 16:37:32 +01:00 committed by Christoph Lendenfeld
parent 9d8283d113
commit f06fd85d97
10 changed files with 100 additions and 50 deletions

View File

@ -588,11 +588,11 @@ static bool find_prev_next_keyframes(bContext *C, int *r_nextfra, int *r_prevfra
}
/* populate tree with keyframe nodes */
scene_to_keylist(&ads, scene, keylist, 0);
scene_to_keylist(&ads, scene, keylist, 0, {-FLT_MAX, FLT_MAX});
gpencil_to_keylist(&ads, scene->gpd, keylist, false);
if (ob) {
ob_to_keylist(&ads, ob, keylist, 0);
ob_to_keylist(&ads, ob, keylist, 0, {-FLT_MAX, FLT_MAX});
gpencil_to_keylist(&ads, static_cast<bGPdata *>(ob->data), keylist, false);
}

View File

@ -310,7 +310,7 @@ static void motionpath_calculate_update_range(MPathTarget *mpt,
* we ignore all others (which can potentially make an update range unnecessary wide). */
for (FCurve *fcu = static_cast<FCurve *>(fcurve_list->first); fcu != nullptr; fcu = fcu->next) {
AnimKeylist *keylist = ED_keylist_create();
fcurve_to_keylist(adt, fcu, keylist, 0);
fcurve_to_keylist(adt, fcu, keylist, 0, {-FLT_MAX, FLT_MAX});
ED_keylist_prepare_for_direct_access(keylist);
int fcu_sfra = motionpath_get_prev_prev_keyframe(mpt, keylist, current_frame);
@ -360,7 +360,7 @@ void animviz_motionpath_compute_range(Object *ob, Scene *scene)
AnimKeylist *keylist = ED_keylist_create();
LISTBASE_FOREACH (FCurve *, fcu, &ob->adt->action->curves) {
fcurve_to_keylist(ob->adt, fcu, keylist, 0);
fcurve_to_keylist(ob->adt, fcu, keylist, 0, {-FLT_MAX, FLT_MAX});
}
Range2f frame_range;
@ -460,12 +460,12 @@ void animviz_calc_motionpaths(Depsgraph *depsgraph,
if (agrp) {
fcurve_list = &agrp->channels;
action_group_to_keylist(adt, agrp, mpt->keylist, 0);
action_group_to_keylist(adt, agrp, mpt->keylist, 0, {-FLT_MAX, FLT_MAX});
}
}
else {
fcurve_list = &adt->action->curves;
action_to_keylist(adt, adt->action, mpt->keylist, 0);
action_to_keylist(adt, adt->action, mpt->keylist, 0, {-FLT_MAX, FLT_MAX});
}
}
ED_keylist_prepare_for_direct_access(mpt->keylist);

View File

@ -416,31 +416,31 @@ struct ChannelListElement {
MaskLayer *masklay;
};
static void build_channel_keylist(ChannelListElement *elem)
static void build_channel_keylist(ChannelListElement *elem, blender::float2 range)
{
switch (elem->type) {
case ChannelType::SUMMARY: {
summary_to_keylist(elem->ac, elem->keylist, elem->saction_flag);
summary_to_keylist(elem->ac, elem->keylist, elem->saction_flag, range);
break;
}
case ChannelType::SCENE: {
scene_to_keylist(elem->ads, elem->sce, elem->keylist, elem->saction_flag);
scene_to_keylist(elem->ads, elem->sce, elem->keylist, elem->saction_flag, range);
break;
}
case ChannelType::OBJECT: {
ob_to_keylist(elem->ads, elem->ob, elem->keylist, elem->saction_flag);
ob_to_keylist(elem->ads, elem->ob, elem->keylist, elem->saction_flag, range);
break;
}
case ChannelType::FCURVE: {
fcurve_to_keylist(elem->adt, elem->fcu, elem->keylist, elem->saction_flag);
fcurve_to_keylist(elem->adt, elem->fcu, elem->keylist, elem->saction_flag, range);
break;
}
case ChannelType::ACTION: {
action_to_keylist(elem->adt, elem->act, elem->keylist, elem->saction_flag);
action_to_keylist(elem->adt, elem->act, elem->keylist, elem->saction_flag, range);
break;
}
case ChannelType::ACTION_GROUP: {
action_group_to_keylist(elem->adt, elem->agrp, elem->keylist, elem->saction_flag);
action_group_to_keylist(elem->adt, elem->agrp, elem->keylist, elem->saction_flag, range);
break;
}
case ChannelType::GREASE_PENCIL_CELS: {
@ -506,10 +506,10 @@ ChannelDrawList *ED_channel_draw_list_create()
return static_cast<ChannelDrawList *>(MEM_callocN(sizeof(ChannelDrawList), __func__));
}
static void channel_list_build_keylists(ChannelDrawList *channel_list)
static void channel_list_build_keylists(ChannelDrawList *channel_list, blender::float2 range)
{
LISTBASE_FOREACH (ChannelListElement *, elem, &channel_list->channels) {
build_channel_keylist(elem);
build_channel_keylist(elem, range);
prepare_channel_for_drawing(elem);
}
}
@ -594,7 +594,7 @@ static void channel_list_draw(ChannelDrawList *channel_list, View2D *v2d)
void ED_channel_list_flush(ChannelDrawList *channel_list, View2D *v2d)
{
channel_list_build_keylists(channel_list);
channel_list_build_keylists(channel_list, {v2d->cur.xmin, v2d->cur.xmax});
channel_list_draw(channel_list, v2d);
}

View File

@ -928,7 +928,10 @@ int actkeyblock_get_valid_hold(const ActKeyColumn *ac)
/* *************************** Keyframe List Conversions *************************** */
void summary_to_keylist(bAnimContext *ac, AnimKeylist *keylist, const int saction_flag)
void summary_to_keylist(bAnimContext *ac,
AnimKeylist *keylist,
const int saction_flag,
blender::float2 range)
{
if (!ac) {
return;
@ -950,7 +953,8 @@ void summary_to_keylist(bAnimContext *ac, AnimKeylist *keylist, const int sactio
* there isn't really any benefit at all from including them. - Aligorith */
switch (ale->datatype) {
case ALE_FCURVE:
fcurve_to_keylist(ale->adt, static_cast<FCurve *>(ale->data), keylist, saction_flag);
fcurve_to_keylist(
ale->adt, static_cast<FCurve *>(ale->data), keylist, saction_flag, range);
break;
case ALE_MASKLAY:
mask_to_keylist(ac->ads, static_cast<MaskLayer *>(ale->data), keylist);
@ -970,7 +974,11 @@ void summary_to_keylist(bAnimContext *ac, AnimKeylist *keylist, const int sactio
ANIM_animdata_freelist(&anim_data);
}
void scene_to_keylist(bDopeSheet *ads, Scene *sce, AnimKeylist *keylist, const int saction_flag)
void scene_to_keylist(bDopeSheet *ads,
Scene *sce,
AnimKeylist *keylist,
const int saction_flag,
blender::float2 range)
{
bAnimContext ac = {nullptr};
ListBase anim_data = {nullptr, nullptr};
@ -999,13 +1007,17 @@ void scene_to_keylist(bDopeSheet *ads, Scene *sce, AnimKeylist *keylist, const i
/* Loop through each F-Curve, grabbing the keyframes. */
LISTBASE_FOREACH (const bAnimListElem *, ale, &anim_data) {
fcurve_to_keylist(ale->adt, static_cast<FCurve *>(ale->data), keylist, saction_flag);
fcurve_to_keylist(ale->adt, static_cast<FCurve *>(ale->data), keylist, saction_flag, range);
}
ANIM_animdata_freelist(&anim_data);
}
void ob_to_keylist(bDopeSheet *ads, Object *ob, AnimKeylist *keylist, const int saction_flag)
void ob_to_keylist(bDopeSheet *ads,
Object *ob,
AnimKeylist *keylist,
const int saction_flag,
blender::float2 range)
{
bAnimContext ac = {nullptr};
ListBase anim_data = {nullptr, nullptr};
@ -1036,7 +1048,7 @@ void ob_to_keylist(bDopeSheet *ads, Object *ob, AnimKeylist *keylist, const int
/* Loop through each F-Curve, grabbing the keyframes. */
LISTBASE_FOREACH (const bAnimListElem *, ale, &anim_data) {
fcurve_to_keylist(ale->adt, static_cast<FCurve *>(ale->data), keylist, saction_flag);
fcurve_to_keylist(ale->adt, static_cast<FCurve *>(ale->data), keylist, saction_flag, range);
}
ANIM_animdata_freelist(&anim_data);
@ -1071,13 +1083,18 @@ void cachefile_to_keylist(bDopeSheet *ads,
/* Loop through each F-Curve, grabbing the keyframes. */
LISTBASE_FOREACH (const bAnimListElem *, ale, &anim_data) {
fcurve_to_keylist(ale->adt, static_cast<FCurve *>(ale->data), keylist, saction_flag);
fcurve_to_keylist(
ale->adt, static_cast<FCurve *>(ale->data), keylist, saction_flag, {-FLT_MAX, FLT_MAX});
}
ANIM_animdata_freelist(&anim_data);
}
void fcurve_to_keylist(AnimData *adt, FCurve *fcu, AnimKeylist *keylist, const int saction_flag)
void fcurve_to_keylist(AnimData *adt,
FCurve *fcu,
AnimKeylist *keylist,
const int saction_flag,
blender::float2 range)
{
if (!fcu || fcu->totvert == 0 || !fcu->bezt) {
return;
@ -1093,8 +1110,22 @@ void fcurve_to_keylist(AnimData *adt, FCurve *fcu, AnimKeylist *keylist, const i
BezTripleChain chain = {nullptr};
int start_index = 0;
/* Used in an exclusive way. */
int end_index = fcu->totvert;
bool replace;
start_index = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, range[0], fcu->totvert, &replace);
if (start_index > 0) {
start_index--;
}
end_index = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, range[1], fcu->totvert, &replace);
if (end_index < fcu->totvert) {
end_index++;
}
/* Loop through beztriples, making ActKeysColumns. */
for (int v = 0; v < fcu->totvert; v++) {
for (int v = start_index; v < end_index; v++) {
chain.cur = &fcu->bezt[v];
/* Neighbor columns, accounting for being cyclic. */
@ -1110,7 +1141,7 @@ void fcurve_to_keylist(AnimData *adt, FCurve *fcu, AnimKeylist *keylist, const i
add_bezt_to_keycolumns_list(keylist, &chain);
}
update_keyblocks(keylist, fcu->bezt, fcu->totvert);
update_keyblocks(keylist, &fcu->bezt[start_index], end_index - start_index);
if (adt) {
ANIM_nla_mapping_apply_fcurve(adt, fcu, true, false);
@ -1120,7 +1151,8 @@ void fcurve_to_keylist(AnimData *adt, FCurve *fcu, AnimKeylist *keylist, const i
void action_group_to_keylist(AnimData *adt,
bActionGroup *agrp,
AnimKeylist *keylist,
const int saction_flag)
const int saction_flag,
blender::float2 range)
{
if (!agrp) {
return;
@ -1130,18 +1162,22 @@ void action_group_to_keylist(AnimData *adt,
if (fcu->grp != agrp) {
break;
}
fcurve_to_keylist(adt, fcu, keylist, saction_flag);
fcurve_to_keylist(adt, fcu, keylist, saction_flag, range);
}
}
void action_to_keylist(AnimData *adt, bAction *act, AnimKeylist *keylist, const int saction_flag)
void action_to_keylist(AnimData *adt,
bAction *act,
AnimKeylist *keylist,
const int saction_flag,
blender::float2 range)
{
if (!act) {
return;
}
LISTBASE_FOREACH (FCurve *, fcu, &act->curves) {
fcurve_to_keylist(adt, fcu, keylist, saction_flag);
fcurve_to_keylist(adt, fcu, keylist, saction_flag, range);
}
}

View File

@ -43,7 +43,7 @@ static AnimKeylist *create_test_keylist()
build_fcurve(*fcurve);
AnimKeylist *keylist = ED_keylist_create();
fcurve_to_keylist(nullptr, fcurve, keylist, 0);
fcurve_to_keylist(nullptr, fcurve, keylist, 0, {-FLT_MAX, FLT_MAX});
BKE_fcurve_free(fcurve);
ED_keylist_prepare_for_direct_access(keylist);

View File

@ -1019,7 +1019,7 @@ static int pose_slide_invoke_common(bContext *C, wmOperator *op, const wmEvent *
/* Do this for each F-Curve. */
LISTBASE_FOREACH (LinkData *, ld, &pfl->fcurves) {
FCurve *fcu = (FCurve *)ld->data;
fcurve_to_keylist(pfl->ob->adt, fcu, pso->keylist, 0);
fcurve_to_keylist(pfl->ob->adt, fcu, pso->keylist, 0, {-FLT_MAX, FLT_MAX});
}
}
@ -1804,7 +1804,7 @@ static void get_keyed_frames_in_range(ListBase *pflinks,
LISTBASE_FOREACH (tPChanFCurveLink *, pfl, pflinks) {
LISTBASE_FOREACH (LinkData *, ld, &pfl->fcurves) {
FCurve *fcu = (FCurve *)ld->data;
fcurve_to_keylist(nullptr, fcu, keylist, 0);
fcurve_to_keylist(nullptr, fcu, keylist, 0, {start_frame, end_frame});
}
}
LISTBASE_FOREACH (ActKeyColumn *, column, ED_keylist_listbase(keylist)) {
@ -1827,7 +1827,7 @@ static void get_selected_frames(ListBase *pflinks, ListBase /*FrameLink*/ *targe
LISTBASE_FOREACH (tPChanFCurveLink *, pfl, pflinks) {
LISTBASE_FOREACH (LinkData *, ld, &pfl->fcurves) {
FCurve *fcu = (FCurve *)ld->data;
fcurve_to_keylist(nullptr, fcu, keylist, 0);
fcurve_to_keylist(nullptr, fcu, keylist, 0, {-FLT_MAX, FLT_MAX});
}
}
LISTBASE_FOREACH (ActKeyColumn *, column, ED_keylist_listbase(keylist)) {

View File

@ -8,6 +8,7 @@
#pragma once
#include "BLI_math_vector_types.hh"
#include "BLI_range.h"
struct AnimData;
@ -145,26 +146,37 @@ int64_t ED_keylist_array_len(const AnimKeylist *keylist);
/* Key-data Generation --------------- */
/* F-Curve */
void fcurve_to_keylist(AnimData *adt, FCurve *fcu, AnimKeylist *keylist, int saction_flag);
/** Add the keyframes of the F-Curve to the keylist.
* \param adt can be a nullptr.
* \param range only adds keys in the given range to the keylist.
*/
void fcurve_to_keylist(
AnimData *adt, FCurve *fcu, AnimKeylist *keylist, int saction_flag, blender::float2 range);
/* Action Group */
void action_group_to_keylist(AnimData *adt,
bActionGroup *agrp,
AnimKeylist *keylist,
int saction_flag);
int saction_flag,
blender::float2 range);
/* Action */
void action_to_keylist(AnimData *adt, bAction *act, AnimKeylist *keylist, int saction_flag);
void action_to_keylist(
AnimData *adt, bAction *act, AnimKeylist *keylist, int saction_flag, blender::float2 range);
/* Object */
void ob_to_keylist(bDopeSheet *ads, Object *ob, AnimKeylist *keylist, int saction_flag);
void ob_to_keylist(
bDopeSheet *ads, Object *ob, AnimKeylist *keylist, int saction_flag, blender::float2 range);
/* Cache File */
void cachefile_to_keylist(bDopeSheet *ads,
CacheFile *cache_file,
AnimKeylist *keylist,
int saction_flag);
/* Scene */
void scene_to_keylist(bDopeSheet *ads, Scene *sce, AnimKeylist *keylist, int saction_flag);
void scene_to_keylist(
bDopeSheet *ads, Scene *sce, AnimKeylist *keylist, int saction_flag, blender::float2 range);
/* DopeSheet Summary */
void summary_to_keylist(bAnimContext *ac, AnimKeylist *keylist, int saction_flag);
void summary_to_keylist(bAnimContext *ac,
AnimKeylist *keylist,
int saction_flag,
blender::float2 range);
/* Grease Pencil datablock summary (Legacy) */
void gpencil_to_keylist(bDopeSheet *ads, bGPdata *gpd, AnimKeylist *keylist, bool active);

View File

@ -3194,10 +3194,10 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op)
}
/* populate tree with keyframe nodes */
scene_to_keylist(&ads, scene, keylist, 0);
scene_to_keylist(&ads, scene, keylist, 0, {-FLT_MAX, FLT_MAX});
if (ob) {
ob_to_keylist(&ads, ob, keylist, 0);
ob_to_keylist(&ads, ob, keylist, 0, {-FLT_MAX, FLT_MAX});
if (ob->type == OB_GPENCIL_LEGACY) {
const bool active = !(scene->flag & SCE_KEYS_NO_SELONLY);

View File

@ -98,38 +98,40 @@ static void actkeys_list_element_to_keylist(bAnimContext *ac,
ads = static_cast<bDopeSheet *>(ac->data);
}
blender::float2 range = {ac->region->v2d.cur.xmin, ac->region->v2d.cur.xmax};
if (ale->key_data) {
switch (ale->datatype) {
case ALE_SCE: {
Scene *scene = (Scene *)ale->key_data;
scene_to_keylist(ads, scene, keylist, 0);
scene_to_keylist(ads, scene, keylist, 0, range);
break;
}
case ALE_OB: {
Object *ob = (Object *)ale->key_data;
ob_to_keylist(ads, ob, keylist, 0);
ob_to_keylist(ads, ob, keylist, 0, range);
break;
}
case ALE_ACT: {
bAction *act = (bAction *)ale->key_data;
action_to_keylist(adt, act, keylist, 0);
action_to_keylist(adt, act, keylist, 0, range);
break;
}
case ALE_FCURVE: {
FCurve *fcu = (FCurve *)ale->key_data;
fcurve_to_keylist(adt, fcu, keylist, 0);
fcurve_to_keylist(adt, fcu, keylist, 0, range);
break;
}
}
}
else if (ale->type == ANIMTYPE_SUMMARY) {
/* dopesheet summary covers everything */
summary_to_keylist(ac, keylist, 0);
summary_to_keylist(ac, keylist, 0, range);
}
else if (ale->type == ANIMTYPE_GROUP) {
/* TODO: why don't we just give groups key_data too? */
bActionGroup *agrp = (bActionGroup *)ale->data;
action_group_to_keylist(adt, agrp, keylist, 0);
action_group_to_keylist(adt, agrp, keylist, 0, range);
}
else if (ale->type == ANIMTYPE_GREASE_PENCIL_LAYER) {
/* TODO: why don't we just give grease pencil layers key_data too? */

View File

@ -85,7 +85,7 @@ static void nla_action_draw_keyframes(
/* get a list of the keyframes with NLA-scaling applied */
AnimKeylist *keylist = ED_keylist_create();
action_to_keylist(adt, act, keylist, 0);
action_to_keylist(adt, act, keylist, 0, {-FLT_MAX, FLT_MAX});
if (ED_keylist_is_empty(keylist)) {
ED_keylist_free(keylist);