Anim: Graph Editor - use Map to update TransInfo pointers instead of searching
This is a performance improvement when moving a bunch of keys on the same `FCurve` in heavy scenes. When moving keys in such a way that the `BezTriple` array of the FCurve has to be sorted, the pointers of `TransInfo` also have to be updated. This used to happen by doing a nested loop over all `BeztMap` and all `TransData2D`. There was a bit of optimization with the `blender::Vector<bool> adjusted` which stored if a `TransData2D` has been fixed yet. But in general the complexity was still `BeztMap.size() * TransData.size()`. There are two optimizations that can be done here. * Skip any BeztMap if `old_index == new_index`. If the Key is not going to move any pointers to it will still be valid. * Use a `Map<float *, int>` built beforehand to quickly get the `TransData2D` that needs updating instead of searching. The `int` in this case is the index to the `TransData2D` array. Doing this reduces the complexity to `BeztMap.size() + TransData.size()`. Measurements of `beztmap_to_data` | - | Before | After | | - | - | - | | Moving 1 key of 1 FCurve | ~24000 ns | ~5800ns | | Moving ~1000 keys of 1 FCurve | 17ms | 0.02ms | Measurements of `remake_graph_transdata` | - | Before | After | | - | - | - | | Moving 1 key of 279 FCurves | 290ms | 22ms | | Moving ~300 keys of 279 FCurves | 82 **SECONDS** | 80ms | Test file used https://download.blender.org/ftp/sybren/animation-rigging/heavy_mocap_test.blend The deeper technical explanation. `TransInfo` has an array of `TransData`. `TransData` has pointers to the float arrays of a `BezTriple`. The `BezTriple` array is sorted by swapping data, meaning the `TransData` will now point to the wrong data in the array. This has to be updated and we can do that by using the `BeztMap`. This is all under the assumption that `BeztMap` is sorted in the exact same way as `BezTriple` otherwise this method will fail. But by doing it the same way, we can know at which index the `BezTriple` is before and after sorting. Now we just need to find the corresponding `TransData`. That can be done by comparing pointers. The `BeztMap` stores the `BezTriple` it represents and from it we can get the pointers to its `vec` 0, 1 and 2. (key and handles) Pull Request: https://projects.blender.org/blender/blender/pulls/120816
This commit is contained in:
committed by
Christoph Lendenfeld
parent
3d3dfb6518
commit
c6c7d3d8c4
@@ -783,79 +783,77 @@ static void sort_time_beztmaps(const blender::MutableSpan<BeztMap> bezms)
|
||||
}
|
||||
}
|
||||
|
||||
/* This function firstly adjusts the pointers that the transdata has to each BezTriple. */
|
||||
static void update_transdata_bezt_pointers(TransInfo *t,
|
||||
FCurve *fcu,
|
||||
static inline void update_trans_data(TransData *td,
|
||||
const FCurve *fcu,
|
||||
const int new_index,
|
||||
const bool swap_handles)
|
||||
{
|
||||
if (td->flag & TD_BEZTRIPLE && td->hdata) {
|
||||
if (swap_handles) {
|
||||
td->hdata->h1 = &fcu->bezt[new_index].h2;
|
||||
td->hdata->h2 = &fcu->bezt[new_index].h1;
|
||||
}
|
||||
else {
|
||||
td->hdata->h1 = &fcu->bezt[new_index].h1;
|
||||
td->hdata->h2 = &fcu->bezt[new_index].h2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Adjust the pointers that the transdata has to each BezTriple. */
|
||||
static void update_transdata_bezt_pointers(TransDataContainer *tc,
|
||||
const blender::Map<float *, int> &trans_data_map,
|
||||
const FCurve *fcu,
|
||||
const blender::Span<BeztMap> bezms)
|
||||
{
|
||||
TransData2D *td2d;
|
||||
TransData *td;
|
||||
/* At this point, beztmaps are already sorted, so their current index is assumed to be what the
|
||||
* BezTriple index will be after sorting. */
|
||||
for (const int new_index : bezms.index_range()) {
|
||||
const BeztMap &bezm = bezms[new_index];
|
||||
if (new_index == bezm.oldIndex && !bezm.swap_handles) {
|
||||
/* If the index is the same, any pointers to BezTriple will still point to the correct data.
|
||||
* Handles might need to be swapped though. */
|
||||
continue;
|
||||
}
|
||||
|
||||
TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);
|
||||
TransData2D *td2d;
|
||||
TransData *td;
|
||||
|
||||
/* Used to mark whether an TransData's pointers have been fixed already, so that we don't
|
||||
* override ones that are already done. */
|
||||
blender::Vector<bool> adjusted(tc->data_len, false);
|
||||
|
||||
/* For each beztmap item, find if it is used anywhere. */
|
||||
const BeztMap *bezm;
|
||||
for (const int i : bezms.index_range()) {
|
||||
bezm = &bezms[i];
|
||||
/* Loop through transdata, testing if we have a hit
|
||||
* for the handles (vec[0]/vec[2]), we must also check if they need to be swapped. */
|
||||
td2d = tc->data_2d;
|
||||
td = tc->data;
|
||||
for (int j = 0; j < tc->data_len; j++, td2d++, td++) {
|
||||
/* Skip item if already marked. */
|
||||
if (adjusted[j]) {
|
||||
continue;
|
||||
if (const int *trans_data_index = trans_data_map.lookup_ptr(bezm.bezt->vec[0])) {
|
||||
td2d = &tc->data_2d[*trans_data_index];
|
||||
if (bezm.swap_handles) {
|
||||
td2d->loc2d = fcu->bezt[new_index].vec[2];
|
||||
}
|
||||
|
||||
/* Update all transdata pointers, no need to check for selections etc,
|
||||
* since only points that are really needed were created as transdata. */
|
||||
if (td2d->loc2d == bezm->bezt->vec[0]) {
|
||||
if (bezm->swap_handles) {
|
||||
td2d->loc2d = fcu->bezt[i].vec[2];
|
||||
}
|
||||
else {
|
||||
td2d->loc2d = fcu->bezt[i].vec[0];
|
||||
}
|
||||
adjusted[j] = true;
|
||||
else {
|
||||
td2d->loc2d = fcu->bezt[new_index].vec[0];
|
||||
}
|
||||
else if (td2d->loc2d == bezm->bezt->vec[2]) {
|
||||
if (bezm->swap_handles) {
|
||||
td2d->loc2d = fcu->bezt[i].vec[0];
|
||||
}
|
||||
else {
|
||||
td2d->loc2d = fcu->bezt[i].vec[2];
|
||||
}
|
||||
adjusted[j] = true;
|
||||
td = &tc->data[*trans_data_index];
|
||||
update_trans_data(td, fcu, new_index, bezm.swap_handles);
|
||||
}
|
||||
if (const int *trans_data_index = trans_data_map.lookup_ptr(bezm.bezt->vec[2])) {
|
||||
td2d = &tc->data_2d[*trans_data_index];
|
||||
if (bezm.swap_handles) {
|
||||
td2d->loc2d = fcu->bezt[new_index].vec[0];
|
||||
}
|
||||
else if (td2d->loc2d == bezm->bezt->vec[1]) {
|
||||
td2d->loc2d = fcu->bezt[i].vec[1];
|
||||
|
||||
/* If only control point is selected, the handle pointers need to be updated as well. */
|
||||
if (td2d->h1) {
|
||||
td2d->h1 = fcu->bezt[i].vec[0];
|
||||
}
|
||||
if (td2d->h2) {
|
||||
td2d->h2 = fcu->bezt[i].vec[2];
|
||||
}
|
||||
|
||||
adjusted[j] = true;
|
||||
else {
|
||||
td2d->loc2d = fcu->bezt[new_index].vec[2];
|
||||
}
|
||||
td = &tc->data[*trans_data_index];
|
||||
update_trans_data(td, fcu, new_index, bezm.swap_handles);
|
||||
}
|
||||
if (const int *trans_data_index = trans_data_map.lookup_ptr(bezm.bezt->vec[1])) {
|
||||
td2d = &tc->data_2d[*trans_data_index];
|
||||
td2d->loc2d = fcu->bezt[new_index].vec[1];
|
||||
|
||||
/* The handle type pointer has to be updated too. */
|
||||
if (adjusted[j] && td->flag & TD_BEZTRIPLE && td->hdata) {
|
||||
if (bezm->swap_handles) {
|
||||
td->hdata->h1 = &fcu->bezt[i].h2;
|
||||
td->hdata->h2 = &fcu->bezt[i].h1;
|
||||
}
|
||||
else {
|
||||
td->hdata->h1 = &fcu->bezt[i].h1;
|
||||
td->hdata->h2 = &fcu->bezt[i].h2;
|
||||
}
|
||||
/* If only control point is selected, the handle pointers need to be updated as well. */
|
||||
if (td2d->h1) {
|
||||
td2d->h1 = fcu->bezt[new_index].vec[0];
|
||||
}
|
||||
if (td2d->h2) {
|
||||
td2d->h2 = fcu->bezt[new_index].vec[2];
|
||||
}
|
||||
td = &tc->data[*trans_data_index];
|
||||
update_trans_data(td, fcu, new_index, bezm.swap_handles);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -869,6 +867,15 @@ static void remake_graph_transdata(TransInfo *t, const blender::Span<FCurve *> f
|
||||
SpaceGraph *sipo = (SpaceGraph *)t->area->spacedata.first;
|
||||
const bool use_handle = (sipo->flag & SIPO_NOHANDLES) == 0;
|
||||
|
||||
TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);
|
||||
|
||||
/* Build a map from the data that is being modified to its index. This is used to quickly update
|
||||
* the pointers to where the data ends up after sorting. */
|
||||
blender::Map<float *, int> trans_data_map;
|
||||
for (int i = 0; i < tc->data_len; i++) {
|
||||
trans_data_map.add(tc->data_2d[i].loc2d, i);
|
||||
}
|
||||
|
||||
/* The grain size of 8 was chosen based on measured runtimes of this function. While 1 is the
|
||||
* fastest, larger grain sizes are generally preferred and the difference between 1 and 8 was
|
||||
* only minimal (~330ms to ~336ms). */
|
||||
@@ -884,7 +891,7 @@ static void remake_graph_transdata(TransInfo *t, const blender::Span<FCurve *> f
|
||||
/* NOTE: none of these functions use 'use_handle', it could be removed. */
|
||||
blender::Vector<BeztMap> bezms = bezt_to_beztmaps(fcu->bezt, fcu->totvert);
|
||||
sort_time_beztmaps(bezms);
|
||||
update_transdata_bezt_pointers(t, fcu, bezms);
|
||||
update_transdata_bezt_pointers(tc, trans_data_map, fcu, bezms);
|
||||
|
||||
/* Re-sort actual beztriples
|
||||
* (perhaps this could be done using the beztmaps to save time?). */
|
||||
|
||||
Reference in New Issue
Block a user