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
251 lines
7.1 KiB
C++
251 lines
7.1 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "BKE_curves.hh"
|
|
#include "BKE_simulation_state.hh"
|
|
#include "BKE_simulation_state_serialize.hh"
|
|
|
|
#include "DNA_curves_types.h"
|
|
#include "DNA_mesh_types.h"
|
|
#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"
|
|
|
|
namespace blender::bke::sim {
|
|
|
|
GeometrySimulationStateItem::GeometrySimulationStateItem(GeometrySet geometry)
|
|
: geometry(std::move(geometry))
|
|
{
|
|
}
|
|
|
|
PrimitiveSimulationStateItem::PrimitiveSimulationStateItem(const CPPType &type, const void *value)
|
|
: type_(type)
|
|
{
|
|
value_ = MEM_mallocN_aligned(type.size(), type.alignment(), __func__);
|
|
type.copy_construct(value, value_);
|
|
}
|
|
|
|
PrimitiveSimulationStateItem::~PrimitiveSimulationStateItem()
|
|
{
|
|
type_.destruct(value_);
|
|
MEM_freeN(value_);
|
|
}
|
|
|
|
StringSimulationStateItem::StringSimulationStateItem(std::string value) : value_(std::move(value))
|
|
{
|
|
}
|
|
|
|
void ModifierSimulationCache::try_discover_bake(const StringRefNull absolute_bake_dir)
|
|
{
|
|
if (failed_finding_bake_) {
|
|
return;
|
|
}
|
|
|
|
char meta_dir[FILE_MAX];
|
|
BLI_path_join(meta_dir, sizeof(meta_dir), absolute_bake_dir.c_str(), "meta");
|
|
char bdata_dir[FILE_MAX];
|
|
BLI_path_join(bdata_dir, sizeof(bdata_dir), absolute_bake_dir.c_str(), "bdata");
|
|
|
|
if (!BLI_is_dir(meta_dir) || !BLI_is_dir(bdata_dir)) {
|
|
failed_finding_bake_ = true;
|
|
return;
|
|
}
|
|
|
|
direntry *dir_entries = nullptr;
|
|
const int dir_entries_num = BLI_filelist_dir_contents(meta_dir, &dir_entries);
|
|
BLI_SCOPED_DEFER([&]() { BLI_filelist_free(dir_entries, dir_entries_num); });
|
|
|
|
if (dir_entries_num == 0) {
|
|
failed_finding_bake_ = true;
|
|
return;
|
|
}
|
|
|
|
this->reset();
|
|
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
|
|
for (const int i : IndexRange(dir_entries_num)) {
|
|
const direntry &dir_entry = dir_entries[i];
|
|
const StringRefNull dir_entry_path = dir_entry.path;
|
|
if (!dir_entry_path.endswith(".json")) {
|
|
continue;
|
|
}
|
|
char modified_file_name[FILE_MAX];
|
|
STRNCPY(modified_file_name, dir_entry.relname);
|
|
BLI_str_replace_char(modified_file_name, '_', '.');
|
|
|
|
const SubFrame frame = std::stof(modified_file_name);
|
|
|
|
auto new_state_at_frame = std::make_unique<ModifierSimulationStateAtFrame>();
|
|
new_state_at_frame->frame = frame;
|
|
new_state_at_frame->state.bdata_dir_ = bdata_dir;
|
|
new_state_at_frame->state.meta_path_ = dir_entry.path;
|
|
new_state_at_frame->state.owner_ = this;
|
|
states_at_frames_.append(std::move(new_state_at_frame));
|
|
}
|
|
|
|
bdata_sharing_ = std::make_unique<BDataSharing>();
|
|
cache_state_ = CacheState::Baked;
|
|
}
|
|
}
|
|
|
|
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_);
|
|
return find_state_at_frame_exact(states_at_frames_, frame) != -1;
|
|
}
|
|
|
|
bool ModifierSimulationCache::has_states() const
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
return !states_at_frames_.is_empty();
|
|
}
|
|
|
|
const ModifierSimulationState *ModifierSimulationCache::get_state_at_exact_frame(
|
|
const SubFrame &frame) const
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
const int64_t i = find_state_at_frame_exact(states_at_frames_, frame);
|
|
if (i == -1) {
|
|
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_);
|
|
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;
|
|
return states_at_frames_.last()->state;
|
|
}
|
|
|
|
StatesAroundFrame ModifierSimulationCache::get_states_around_frame(const SubFrame &frame) const
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
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;
|
|
}
|
|
|
|
const SimulationZoneState *ModifierSimulationState::get_zone_state(
|
|
const SimulationZoneID &zone_id) const
|
|
{
|
|
std::lock_guard lock{mutex_};
|
|
if (auto *ptr = zone_states_.lookup_ptr(zone_id)) {
|
|
return ptr->get();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SimulationZoneState &ModifierSimulationState::get_zone_state_for_write(
|
|
const SimulationZoneID &zone_id)
|
|
{
|
|
std::lock_guard lock{mutex_};
|
|
return *zone_states_.lookup_or_add_cb(zone_id,
|
|
[]() { return std::make_unique<SimulationZoneState>(); });
|
|
}
|
|
|
|
void ModifierSimulationState::ensure_bake_loaded() const
|
|
{
|
|
std::scoped_lock lock{mutex_};
|
|
if (bake_loaded_) {
|
|
return;
|
|
}
|
|
if (!meta_path_ || !bdata_dir_) {
|
|
return;
|
|
}
|
|
|
|
const std::shared_ptr<io::serialize::Value> io_root_value = io::serialize::read_json_file(
|
|
*meta_path_);
|
|
if (!io_root_value) {
|
|
return;
|
|
}
|
|
const DictionaryValue *io_root = io_root_value->as_dictionary_value();
|
|
if (!io_root) {
|
|
return;
|
|
}
|
|
|
|
const DiskBDataReader bdata_reader{*bdata_dir_};
|
|
deserialize_modifier_simulation_state(*io_root,
|
|
bdata_reader,
|
|
*owner_->bdata_sharing_,
|
|
const_cast<ModifierSimulationState &>(*this));
|
|
bake_loaded_ = true;
|
|
}
|
|
|
|
void ModifierSimulationCache::clear_prev_states()
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
std::unique_ptr<ModifierSimulationStateAtFrame> temp = std::move(states_at_frames_.last());
|
|
states_at_frames_.clear_and_shrink();
|
|
bdata_sharing_.reset();
|
|
states_at_frames_.append(std::move(temp));
|
|
}
|
|
|
|
void ModifierSimulationCache::reset()
|
|
{
|
|
std::lock_guard lock(states_at_frames_mutex_);
|
|
states_at_frames_.clear();
|
|
bdata_sharing_.reset();
|
|
cache_state_ = CacheState::Valid;
|
|
}
|
|
|
|
} // namespace blender::bke::sim
|