Fix: Quadratic performance of simulation state frame lookup
Searching for a simulation state at a particular frame was implemented with a linear loop. The timeline did that for every visible frame, giving quadratic performance overall when zoomed out. Since the states are already assumed to be sorted by frame, we can use binary search instead giving logarithmic performance for each lookup instead. In the test file from #108097, instead of dropping to 20-30 FPS after about 4000 frames, I observed the original 70 FPS. Pull Request: https://projects.blender.org/blender/blender/pulls/109037
This commit is contained in:
parent
23eef18b65
commit
6a05e5161b
|
@ -149,6 +149,9 @@ struct StatesAroundFrame {
|
|||
class ModifierSimulationCache {
|
||||
private:
|
||||
mutable std::mutex states_at_frames_mutex_;
|
||||
/**
|
||||
* All simulation states, sorted by frame.
|
||||
*/
|
||||
Vector<std::unique_ptr<ModifierSimulationStateAtFrame>> states_at_frames_;
|
||||
/**
|
||||
* Used for baking to deduplicate arrays when writing and writing from storage. Sharing info
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "DNA_node_types.h"
|
||||
#include "DNA_pointcloud_types.h"
|
||||
|
||||
#include "BLI_binary_search.hh"
|
||||
#include "BLI_fileops.hh"
|
||||
#include "BLI_hash_md5.h"
|
||||
#include "BLI_path_util.h"
|
||||
|
@ -92,15 +93,34 @@ void ModifierSimulationCache::try_discover_bake(const StringRefNull absolute_bak
|
|||
}
|
||||
}
|
||||
|
||||
static int64_t find_state_at_frame(
|
||||
const Span<std::unique_ptr<ModifierSimulationStateAtFrame>> states, const SubFrame &frame)
|
||||
{
|
||||
const int64_t i = binary_search::find_predicate_begin(
|
||||
states, [&](const auto &item) { return item->frame >= frame; });
|
||||
if (i == states.size()) {
|
||||
return -1;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static int64_t find_state_at_frame_exact(
|
||||
const Span<std::unique_ptr<ModifierSimulationStateAtFrame>> states, const SubFrame &frame)
|
||||
{
|
||||
const int64_t i = find_state_at_frame(states, frame);
|
||||
if (i == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (states[i]->frame != frame) {
|
||||
return -1;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
bool ModifierSimulationCache::has_state_at_frame(const SubFrame &frame) const
|
||||
{
|
||||
std::lock_guard lock(states_at_frames_mutex_);
|
||||
for (const auto &item : states_at_frames_) {
|
||||
if (item->frame == frame) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return find_state_at_frame_exact(states_at_frames_, frame) != -1;
|
||||
}
|
||||
|
||||
bool ModifierSimulationCache::has_states() const
|
||||
|
@ -113,23 +133,26 @@ const ModifierSimulationState *ModifierSimulationCache::get_state_at_exact_frame
|
|||
const SubFrame &frame) const
|
||||
{
|
||||
std::lock_guard lock(states_at_frames_mutex_);
|
||||
for (const auto &item : states_at_frames_) {
|
||||
if (item->frame == frame) {
|
||||
return &item->state;
|
||||
}
|
||||
const int64_t i = find_state_at_frame_exact(states_at_frames_, frame);
|
||||
if (i == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
return &states_at_frames_[i]->state;
|
||||
}
|
||||
|
||||
ModifierSimulationState &ModifierSimulationCache::get_state_at_frame_for_write(
|
||||
const SubFrame &frame)
|
||||
{
|
||||
std::lock_guard lock(states_at_frames_mutex_);
|
||||
for (const auto &item : states_at_frames_) {
|
||||
if (item->frame == frame) {
|
||||
return item->state;
|
||||
}
|
||||
const int64_t i = find_state_at_frame_exact(states_at_frames_, frame);
|
||||
if (i != -1) {
|
||||
return states_at_frames_[i]->state;
|
||||
}
|
||||
|
||||
if (!states_at_frames_.is_empty()) {
|
||||
BLI_assert(frame > states_at_frames_.last()->frame);
|
||||
}
|
||||
|
||||
states_at_frames_.append(std::make_unique<ModifierSimulationStateAtFrame>());
|
||||
states_at_frames_.last()->frame = frame;
|
||||
states_at_frames_.last()->state.owner_ = this;
|
||||
|
@ -139,23 +162,22 @@ ModifierSimulationState &ModifierSimulationCache::get_state_at_frame_for_write(
|
|||
StatesAroundFrame ModifierSimulationCache::get_states_around_frame(const SubFrame &frame) const
|
||||
{
|
||||
std::lock_guard lock(states_at_frames_mutex_);
|
||||
StatesAroundFrame states_around_frame;
|
||||
for (const auto &item : states_at_frames_) {
|
||||
if (item->frame < frame) {
|
||||
if (states_around_frame.prev == nullptr || item->frame > states_around_frame.prev->frame) {
|
||||
states_around_frame.prev = item.get();
|
||||
}
|
||||
}
|
||||
if (item->frame == frame) {
|
||||
if (states_around_frame.current == nullptr) {
|
||||
states_around_frame.current = item.get();
|
||||
}
|
||||
}
|
||||
if (item->frame > frame) {
|
||||
if (states_around_frame.next == nullptr || item->frame < states_around_frame.next->frame) {
|
||||
states_around_frame.next = item.get();
|
||||
}
|
||||
const int64_t i = find_state_at_frame(states_at_frames_, frame);
|
||||
StatesAroundFrame states_around_frame{};
|
||||
if (i == -1) {
|
||||
if (!states_at_frames_.is_empty() && states_at_frames_.last()->frame < frame) {
|
||||
states_around_frame.prev = states_at_frames_.last().get();
|
||||
}
|
||||
return states_around_frame;
|
||||
}
|
||||
if (states_at_frames_[i]->frame == frame) {
|
||||
states_around_frame.current = states_at_frames_[i].get();
|
||||
}
|
||||
if (i > 0) {
|
||||
states_around_frame.prev = states_at_frames_[i - 1].get();
|
||||
}
|
||||
if (i < states_at_frames_.size() - 2) {
|
||||
states_around_frame.next = states_at_frames_[i + 1].get();
|
||||
}
|
||||
return states_around_frame;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue