diff --git a/source/blender/blenkernel/BKE_bake_items_paths.hh b/source/blender/blenkernel/BKE_bake_items_paths.hh new file mode 100644 index 00000000000..c1b1d664aa1 --- /dev/null +++ b/source/blender/blenkernel/BKE_bake_items_paths.hh @@ -0,0 +1,43 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include +#include + +#include "BLI_string_ref.hh" +#include "BLI_sub_frame.hh" +#include "BLI_vector.hh" + +namespace blender::bke::bake_paths { + +struct MetaFile { + SubFrame frame; + std::string path; +}; + +struct BakePath { + /** Path to the directory containing the meta data per frame. */ + std::string meta_dir; + /** + * Path to the directory that contains the binary data. Could be shared between multiple bakes + * to reduce memory consumption. + */ + std::string bdata_dir; + /** + * Folder that is allowed to be deleted when the bake is deleted and it doesn't contain anything + * else. Typically, this contains the meta and bdata directories. + */ + std::optional bake_dir; + + static BakePath from_single_root(StringRefNull root_dir); +}; + +std::string frame_to_file_name(const SubFrame &frame); +std::optional file_name_to_frame(const StringRefNull file_name); + +Vector find_sorted_meta_files(const StringRefNull meta_dir); + +} // namespace blender::bke::bake_paths diff --git a/source/blender/blenkernel/BKE_simulation_state.hh b/source/blender/blenkernel/BKE_simulation_state.hh index 7867f6e9e0d..a87b4051aaa 100644 --- a/source/blender/blenkernel/BKE_simulation_state.hh +++ b/source/blender/blenkernel/BKE_simulation_state.hh @@ -4,6 +4,7 @@ #pragma once +#include "BKE_bake_items_paths.hh" #include "BKE_bake_items_serialize.hh" #include "BKE_geometry_set.hh" @@ -11,65 +12,11 @@ #include "BLI_sub_frame.hh" struct bNodeTree; +struct NodesModifierData; +struct Main; namespace blender::bke::sim { -class ModifierSimulationCache; - -/** - * Storage of values for a single simulation input and output node pair. - * Used as a cache to allow random access in time, and as an intermediate form before data is - * baked. - */ -class SimulationZoneState { - public: - Map> item_by_identifier; -}; - -/** Identifies a simulation zone (input and output node pair) used by a modifier. */ -struct SimulationZoneID { - /** ID of the #bNestedNodeRef that references the output node of the zone. */ - int32_t nested_node_id; - - uint64_t hash() const - { - return this->nested_node_id; - } - - friend bool operator==(const SimulationZoneID &a, const SimulationZoneID &b) - { - return a.nested_node_id == b.nested_node_id; - } -}; - -/** - * Stores a single frame of simulation states for all simulation zones in a modifier's node - * hierarchy. - */ -class ModifierSimulationState { - private: - mutable bool bake_loaded_; - - public: - ModifierSimulationCache *owner_; - mutable std::mutex mutex_; - Map> zone_states_; - /** File path to folder containing baked meta-data. */ - std::optional meta_path_; - /** File path to folder containing baked data. */ - std::optional bdata_dir_; - - SimulationZoneState *get_zone_state(const SimulationZoneID &zone_id); - const SimulationZoneState *get_zone_state(const SimulationZoneID &zone_id) const; - SimulationZoneState &get_zone_state_for_write(const SimulationZoneID &zone_id); - void ensure_bake_loaded(const bNodeTree &ntree) const; -}; - -struct ModifierSimulationStateAtFrame { - SubFrame frame; - ModifierSimulationState state; -}; - enum class CacheState { /** The cache is up-to-date with the inputs. */ Valid, @@ -82,56 +29,34 @@ enum class CacheState { Baked, }; -struct StatesAroundFrame { - const ModifierSimulationStateAtFrame *prev = nullptr; - const ModifierSimulationStateAtFrame *current = nullptr; - const ModifierSimulationStateAtFrame *next = nullptr; +struct SimulationZoneFrameCache { + SubFrame frame; + Map> items; + /** Used when the baked data is loaded lazily. */ + std::optional meta_path; }; -struct ModifierSimulationCacheRealtime { - std::unique_ptr prev_state; - std::unique_ptr current_state; - SubFrame prev_frame; - SubFrame current_frame; +struct SimulationZonePrevState { + Map> items; + SubFrame frame; +}; + +struct SimulationZoneCache { + Vector> frame_caches; + std::optional prev_state; + + std::optional bdata_dir; + std::unique_ptr bdata_sharing; + bool failed_finding_bake = false; + CacheState cache_state = CacheState::Valid; + + void reset(); }; class ModifierSimulationCache { - private: - mutable std::mutex states_at_frames_mutex_; - /** - * All simulation states, sorted by frame. - */ - Vector> states_at_frames_; - /** - * Used for baking to deduplicate arrays when writing and writing from storage. Sharing info - * must be kept alive for multiple frames to detect if each data array's version has changed. - */ - std::unique_ptr bdata_sharing_; - - friend ModifierSimulationState; - - bool failed_finding_bake_ = false; - public: - CacheState cache_state = CacheState::Valid; - - /** A non-persistent cache used only to pass simulation state data from one frame to the next. */ - ModifierSimulationCacheRealtime realtime_cache; - - void try_discover_bake(StringRefNull absolute_bake_dir); - - bool has_state_at_frame(const SubFrame &frame) const; - bool has_states() const; - const ModifierSimulationState *get_state_at_exact_frame(const SubFrame &frame) const; - ModifierSimulationState &get_state_at_frame_for_write(const SubFrame &frame); - StatesAroundFrame get_states_around_frame(const SubFrame &frame) const; - - void invalidate() - { - this->cache_state = CacheState::Invalid; - } - - void reset(); + mutable std::mutex mutex; + Map> cache_by_zone_id; }; /** @@ -140,4 +65,12 @@ class ModifierSimulationCache { */ void scene_simulation_states_reset(Scene &scene); +std::optional get_simulation_zone_bake_path(const Main &bmain, + const Object &object, + const NodesModifierData &nmd, + int zone_id); +std::optional get_modifier_simulation_bake_path(const Main &bmain, + const Object &object, + const NodesModifierData &nmd); + } // namespace blender::bke::sim diff --git a/source/blender/blenkernel/BKE_simulation_state_serialize.hh b/source/blender/blenkernel/BKE_simulation_state_serialize.hh index 42b2e26f422..ef95dbee15a 100644 --- a/source/blender/blenkernel/BKE_simulation_state_serialize.hh +++ b/source/blender/blenkernel/BKE_simulation_state_serialize.hh @@ -29,22 +29,6 @@ std::string get_default_modifier_bake_directory(const Main &bmain, const Object &object, const ModifierData &md); -/** - * Encode the simulation state in a #DictionaryValue which also contains references to external - * binary data that has been written using #bdata_writer. - */ -void serialize_modifier_simulation_state(const ModifierSimulationState &state, - BDataWriter &bdata_writer, - BDataSharing &bdata_sharing, - DictionaryValue &r_io_root); -/** - * Fill the simulation state by parsing the provided #DictionaryValue which also contains - * references to external binary data that is read using #bdata_reader. - */ -void deserialize_modifier_simulation_state(const bNodeTree &ntree, - const DictionaryValue &io_root, - const BDataReader &bdata_reader, - const BDataSharing &bdata_sharing, - ModifierSimulationState &r_state); +constexpr int simulation_file_storage_version = 3; } // namespace blender::bke::sim diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 4f59e949a8c..3dfad7ced86 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -73,6 +73,7 @@ set(SRC intern/attribute_math.cc intern/autoexec.cc intern/bake_items.cc + intern/bake_items_paths.cc intern/bake_items_serialize.cc intern/bake_items_socket.cc intern/blender.cc @@ -333,6 +334,7 @@ set(SRC BKE_attribute_math.hh BKE_autoexec.h BKE_bake_items.hh + BKE_bake_items_paths.hh BKE_bake_items_serialize.hh BKE_bake_items_socket.hh BKE_blender.h diff --git a/source/blender/blenkernel/intern/bake_items_paths.cc b/source/blender/blenkernel/intern/bake_items_paths.cc new file mode 100644 index 00000000000..ddd1ddbe164 --- /dev/null +++ b/source/blender/blenkernel/intern/bake_items_paths.cc @@ -0,0 +1,76 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_bake_items_paths.hh" + +#include "BLI_fileops.hh" +#include "BLI_path_util.h" +#include "BLI_string.h" +#include "BLI_string_utils.h" + +namespace blender::bke::bake_paths { + +std::string frame_to_file_name(const SubFrame &frame) +{ + char file_name_c[FILE_MAX]; + SNPRINTF(file_name_c, "%011.5f", double(frame)); + BLI_string_replace_char(file_name_c, '.', '_'); + return file_name_c; +} + +std::optional file_name_to_frame(const StringRefNull file_name) +{ + char modified_file_name[FILE_MAX]; + STRNCPY(modified_file_name, file_name.c_str()); + BLI_string_replace_char(modified_file_name, '_', '.'); + const SubFrame frame = std::stof(modified_file_name); + return frame; +} + +Vector find_sorted_meta_files(const StringRefNull meta_dir) +{ + if (!BLI_is_dir(meta_dir.c_str())) { + return {}; + } + + direntry *dir_entries = nullptr; + const int dir_entries_num = BLI_filelist_dir_contents(meta_dir.c_str(), &dir_entries); + BLI_SCOPED_DEFER([&]() { BLI_filelist_free(dir_entries, dir_entries_num); }); + + Vector meta_files; + 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; + } + const std::optional frame = file_name_to_frame(dir_entry.relname); + if (!frame) { + continue; + } + meta_files.append({*frame, dir_entry_path}); + } + + std::sort(meta_files.begin(), meta_files.end(), [](const MetaFile &a, const MetaFile &b) { + return a.frame < b.frame; + }); + + return meta_files; +} + +BakePath BakePath::from_single_root(StringRefNull root_dir) +{ + char meta_dir[FILE_MAX]; + BLI_path_join(meta_dir, sizeof(meta_dir), root_dir.c_str(), "meta"); + char bdata_dir[FILE_MAX]; + BLI_path_join(bdata_dir, sizeof(bdata_dir), root_dir.c_str(), "bdata"); + + BakePath bake_path; + bake_path.meta_dir = meta_dir; + bake_path.bdata_dir = bdata_dir; + bake_path.bake_dir = root_dir; + return bake_path; +} + +} // namespace blender::bke::bake_paths diff --git a/source/blender/blenkernel/intern/simulation_state.cc b/source/blender/blenkernel/intern/simulation_state.cc index 598c84585dd..5ab7396657a 100644 --- a/source/blender/blenkernel/intern/simulation_state.cc +++ b/source/blender/blenkernel/intern/simulation_state.cc @@ -4,6 +4,7 @@ #include "BKE_collection.h" #include "BKE_curves.hh" +#include "BKE_main.h" #include "BKE_simulation_state.hh" #include "BKE_simulation_state_serialize.hh" @@ -21,213 +22,13 @@ namespace blender::bke::sim { -void ModifierSimulationCache::try_discover_bake(const StringRefNull absolute_bake_dir) +void SimulationZoneCache::reset() { - 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_string_replace_char(modified_file_name, '_', '.'); - - const SubFrame frame = std::stof(modified_file_name); - - auto new_state_at_frame = std::make_unique(); - 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(); - this->cache_state = CacheState::Baked; - } -} - -static int64_t find_state_at_frame( - const Span> 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> 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()); - 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; -} - -SimulationZoneState *ModifierSimulationState::get_zone_state(const SimulationZoneID &zone_id) -{ - std::lock_guard lock{mutex_}; - if (auto *ptr = zone_states_.lookup_ptr(zone_id)) { - return ptr->get(); - } - return nullptr; -} - -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(); }); -} - -void ModifierSimulationState::ensure_bake_loaded(const bNodeTree &ntree) const -{ - std::scoped_lock lock{mutex_}; - if (bake_loaded_) { - return; - } - if (!meta_path_ || !bdata_dir_) { - return; - } - - const std::shared_ptr 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(ntree, - *io_root, - bdata_reader, - *owner_->bdata_sharing_, - const_cast(*this)); - bake_loaded_ = true; -} - -void ModifierSimulationCache::reset() -{ - std::lock_guard lock(states_at_frames_mutex_); - states_at_frames_.clear(); - bdata_sharing_.reset(); - this->realtime_cache.current_state.reset(); - this->realtime_cache.prev_state.reset(); + this->frame_caches.clear(); + this->prev_state.reset(); + this->bdata_dir.reset(); + this->bdata_sharing.reset(); + this->failed_finding_bake = false; this->cache_state = CacheState::Valid; } @@ -239,10 +40,52 @@ void scene_simulation_states_reset(Scene &scene) continue; } NodesModifierData *nmd = reinterpret_cast(md); - nmd->runtime->simulation_cache->reset(); + if (!nmd->runtime->simulation_cache) { + continue; + } + for (auto item : nmd->runtime->simulation_cache->cache_by_zone_id.items()) { + item.value->reset(); + } } } FOREACH_SCENE_OBJECT_END; } +std::optional get_modifier_simulation_bake_path(const Main &bmain, + const Object &object, + const NodesModifierData &nmd) +{ + const StringRefNull bmain_path = BKE_main_blendfile_path(&bmain); + if (bmain_path.is_empty()) { + return std::nullopt; + } + if (StringRef(nmd.simulation_bake_directory).is_empty()) { + return std::nullopt; + } + const char *base_path = ID_BLEND_PATH(&bmain, &object.id); + char absolute_bake_dir[FILE_MAX]; + STRNCPY(absolute_bake_dir, nmd.simulation_bake_directory); + BLI_path_abs(absolute_bake_dir, base_path); + return absolute_bake_dir; +} + +std::optional get_simulation_zone_bake_path(const Main &bmain, + const Object &object, + const NodesModifierData &nmd, + int zone_id) +{ + const std::optional modifier_bake_path = get_modifier_simulation_bake_path( + bmain, object, nmd); + if (!modifier_bake_path) { + return std::nullopt; + } + + char zone_bake_dir[FILE_MAX]; + BLI_path_join(zone_bake_dir, + sizeof(zone_bake_dir), + modifier_bake_path->c_str(), + std::to_string(zone_id).c_str()); + return bake_paths::BakePath::from_single_root(zone_bake_dir); +} + } // namespace blender::bke::sim diff --git a/source/blender/blenkernel/intern/simulation_state_serialize.cc b/source/blender/blenkernel/intern/simulation_state_serialize.cc index 12b7143165f..4e58a8f8451 100644 --- a/source/blender/blenkernel/intern/simulation_state_serialize.cc +++ b/source/blender/blenkernel/intern/simulation_state_serialize.cc @@ -83,111 +83,4 @@ std::string get_default_modifier_bake_directory(const Main &bmain, return dir; } -/** - * Version written to the baked data. - */ -static constexpr int serialize_format_version = 2; - -void serialize_modifier_simulation_state(const ModifierSimulationState &state, - BDataWriter &bdata_writer, - BDataSharing &bdata_sharing, - DictionaryValue &r_io_root) -{ - r_io_root.append_int("version", serialize_format_version); - auto io_zones = r_io_root.append_array("zones"); - - for (const auto item : state.zone_states_.items()) { - const SimulationZoneID &zone_id = item.key; - const SimulationZoneState &zone_state = *item.value; - - auto io_zone = io_zones->append_dict(); - - io_zone->append_int("state_id", zone_id.nested_node_id); - - auto io_state_items = io_zone->append_array("state_items"); - for (const MapItem> &state_item_with_id : - zone_state.item_by_identifier.items()) - { - auto io_state_item = io_state_items->append_dict(); - - io_state_item->append_int("id", state_item_with_id.key); - serialize_bake_item(*state_item_with_id.value, bdata_writer, bdata_sharing, *io_state_item); - } - } -} - -void deserialize_modifier_simulation_state(const bNodeTree &ntree, - const DictionaryValue &io_root, - const BDataReader &bdata_reader, - const BDataSharing &bdata_sharing, - ModifierSimulationState &r_state) -{ - io::serialize::JsonFormatter formatter; - const std::optional version = io_root.lookup_int("version"); - if (!version) { - return; - } - if (*version > serialize_format_version) { - return; - } - const io::serialize::ArrayValue *io_zones = io_root.lookup_array("zones"); - if (!io_zones) { - return; - } - for (const auto &io_zone_value : io_zones->elements()) { - const DictionaryValue *io_zone = io_zone_value->as_dictionary_value(); - if (!io_zone) { - continue; - } - bke::sim::SimulationZoneID zone_id; - if (const std::optional state_id = io_zone->lookup_int("state_id")) { - zone_id.nested_node_id = *state_id; - } - else if (const io::serialize::ArrayValue *io_zone_id = io_zone->lookup_array("zone_id")) { - /* In the initial release of simulation nodes, the entire node id path was written to the - * baked data. For backward compatibility the node ids are read here and then the nested node - * id is looked up. */ - Vector node_ids; - for (const auto &io_zone_id_element : io_zone_id->elements()) { - const io::serialize::IntValue *io_node_id = io_zone_id_element->as_int_value(); - if (!io_node_id) { - continue; - } - node_ids.append(io_node_id->value()); - } - const bNestedNodeRef *nested_node_ref = ntree.nested_node_ref_from_node_id_path(node_ids); - if (!nested_node_ref) { - continue; - } - zone_id.nested_node_id = nested_node_ref->id; - } - - const io::serialize::ArrayValue *io_state_items = io_zone->lookup_array("state_items"); - if (!io_state_items) { - continue; - } - - auto zone_state = std::make_unique(); - - for (const auto &io_state_item_value : io_state_items->elements()) { - const DictionaryValue *io_state_item = io_state_item_value->as_dictionary_value(); - if (!io_state_item) { - continue; - } - const std::optional state_item_id = io_state_item->lookup_int("id"); - if (!state_item_id) { - continue; - } - - std::unique_ptr new_state_item = deserialize_bake_item( - *io_state_item, bdata_reader, bdata_sharing); - - BLI_assert(new_state_item); - zone_state->item_by_identifier.add(*state_item_id, std::move(new_state_item)); - } - - r_state.zone_states_.add_overwrite(zone_id, std::move(zone_state)); - } -} - } // namespace blender::bke::sim diff --git a/source/blender/blenlib/BLI_sub_frame.hh b/source/blender/blenlib/BLI_sub_frame.hh index 3c2acf91feb..98166cf6172 100644 --- a/source/blender/blenlib/BLI_sub_frame.hh +++ b/source/blender/blenlib/BLI_sub_frame.hh @@ -85,11 +85,6 @@ struct SubFrame { { return a.frame_ >= b.frame_ || (a.frame_ == b.frame_ && a.subframe_ >= b.subframe_); } - - friend std::ostream &operator<<(std::ostream &stream, const SubFrame &a) - { - return stream << float(a); - } }; } // namespace blender diff --git a/source/blender/editors/object/object_bake_simulation.cc b/source/blender/editors/object/object_bake_simulation.cc index 8dd9d990ac2..86c3817f063 100644 --- a/source/blender/editors/object/object_bake_simulation.cc +++ b/source/blender/editors/object/object_bake_simulation.cc @@ -36,6 +36,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_mesh.hh" +#include "BKE_node_runtime.hh" #include "BKE_object.h" #include "BKE_pointcloud.h" #include "BKE_report.h" @@ -98,7 +99,14 @@ static void calculate_simulation_job_startjob(void *customdata, LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { if (md->type == eModifierType_Nodes) { NodesModifierData *nmd = reinterpret_cast(md); - nmd->runtime->simulation_cache->reset(); + if (!nmd->runtime->simulation_cache) { + continue; + } + for (auto item : nmd->runtime->simulation_cache->cache_by_zone_id.items()) { + if (item.value->cache_state != CacheState::Baked) { + item.value->reset(); + } + } } } objects_to_calc.append(object); @@ -214,10 +222,15 @@ static bool bake_simulation_poll(bContext *C) return true; } +struct ZoneBakeData { + int zone_id; + bke::bake_paths::BakePath path; + std::unique_ptr bdata_sharing; +}; + struct ModifierBakeData { NodesModifierData *nmd; - std::string absolute_bake_dir; - std::unique_ptr bdata_sharing; + Vector zones; }; struct ObjectBakeData { @@ -251,19 +264,38 @@ static void bake_simulation_job_startjob(void *customdata, continue; } - const char *base_path = ID_BLEND_PATH(job.bmain, &object->id); - ObjectBakeData bake_data; bake_data.object = object; LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { if (md->type == eModifierType_Nodes) { NodesModifierData *nmd = reinterpret_cast(md); - nmd->runtime->simulation_cache->reset(); - char absolute_bake_dir[FILE_MAX]; - STRNCPY(absolute_bake_dir, nmd->simulation_bake_directory); - BLI_path_abs(absolute_bake_dir, base_path); - bake_data.modifiers.append( - {nmd, absolute_bake_dir, std::make_unique()}); + if (!nmd->node_group) { + continue; + } + if (!nmd->runtime->simulation_cache) { + continue; + } + ModifierBakeData modifier_bake_data; + modifier_bake_data.nmd = nmd; + + for (auto item : nmd->runtime->simulation_cache->cache_by_zone_id.items()) { + item.value->reset(); + } + + for (const bNestedNodeRef &nested_node_ref : nmd->node_group->nested_node_refs_span()) { + ZoneBakeData zone_bake_data; + zone_bake_data.zone_id = nested_node_ref.id; + zone_bake_data.bdata_sharing = std::make_unique(); + if (std::optional path = + bke::sim::get_simulation_zone_bake_path( + *job.bmain, *object, *nmd, nested_node_ref.id)) + { + zone_bake_data.path = std::move(*path); + modifier_bake_data.zones.append(std::move(zone_bake_data)); + } + } + + bake_data.modifiers.append(std::move(modifier_bake_data)); } } objects_to_bake.append(std::move(bake_data)); @@ -288,51 +320,56 @@ static void bake_simulation_job_startjob(void *customdata, job.scene->r.cfra = frame.frame(); job.scene->r.subframe = frame.subframe(); - char frame_file_c_str[64]; - SNPRINTF(frame_file_c_str, "%011.5f", double(frame)); - BLI_string_replace_char(frame_file_c_str, '.', '_'); - const StringRefNull frame_file_str = frame_file_c_str; - BKE_scene_graph_update_for_newframe(job.depsgraph); + const std::string frame_file_name = bke::bake_paths::frame_to_file_name(frame); + for (ObjectBakeData &object_bake_data : objects_to_bake) { for (ModifierBakeData &modifier_bake_data : object_bake_data.modifiers) { NodesModifierData &nmd = *modifier_bake_data.nmd; - if (!nmd.runtime->simulation_cache) { - continue; + const ModifierSimulationCache &simulation_cache = *nmd.runtime->simulation_cache; + for (ZoneBakeData &zone_bake_data : modifier_bake_data.zones) { + if (!simulation_cache.cache_by_zone_id.contains(zone_bake_data.zone_id)) { + continue; + } + const SimulationZoneCache &zone_cache = *simulation_cache.cache_by_zone_id.lookup( + zone_bake_data.zone_id); + if (zone_cache.frame_caches.is_empty()) { + continue; + } + const SimulationZoneFrameCache &frame_cache = *zone_cache.frame_caches.last(); + if (frame_cache.frame != frame) { + continue; + } + + const bke::bake_paths::BakePath path = zone_bake_data.path; + + const std::string bdata_file_name = frame_file_name + ".bdata"; + + char bdata_path[FILE_MAX]; + BLI_path_join( + bdata_path, sizeof(bdata_path), path.bdata_dir.c_str(), bdata_file_name.c_str()); + char meta_path[FILE_MAX]; + BLI_path_join(meta_path, + sizeof(meta_path), + path.meta_dir.c_str(), + (frame_file_name + ".json").c_str()); + BLI_file_ensure_parent_dir_exists(meta_path); + BLI_file_ensure_parent_dir_exists(bdata_path); + fstream bdata_file{bdata_path, std::ios::out | std::ios::binary}; + bke::DiskBDataWriter bdata_writer{bdata_file_name, bdata_file, 0}; + + io::serialize::DictionaryValue io_root; + io_root.append_int("version", simulation_file_storage_version); + io::serialize::DictionaryValue &io_items = *io_root.append_dict("items"); + for (auto item : frame_cache.items.items()) { + io::serialize::DictionaryValue &io_item = *io_items.append_dict( + std::to_string(item.key)); + bke::serialize_bake_item( + *item.value, bdata_writer, *zone_bake_data.bdata_sharing, io_item); + } + io::serialize::write_json_file(meta_path, io_root); } - ModifierSimulationCache &sim_cache = *nmd.runtime->simulation_cache; - const ModifierSimulationState *sim_state = sim_cache.get_state_at_exact_frame(frame); - if (sim_state == nullptr || sim_state->zone_states_.is_empty()) { - continue; - } - - const std::string bdata_file_name = frame_file_str + ".bdata"; - const std::string meta_file_name = frame_file_str + ".json"; - - char bdata_path[FILE_MAX]; - BLI_path_join(bdata_path, - sizeof(bdata_path), - modifier_bake_data.absolute_bake_dir.c_str(), - "bdata", - bdata_file_name.c_str()); - char meta_path[FILE_MAX]; - BLI_path_join(meta_path, - sizeof(meta_path), - modifier_bake_data.absolute_bake_dir.c_str(), - "meta", - meta_file_name.c_str()); - - BLI_file_ensure_parent_dir_exists(bdata_path); - fstream bdata_file{bdata_path, std::ios::out | std::ios::binary}; - bke::DiskBDataWriter bdata_writer{bdata_file_name, bdata_file, 0}; - - io::serialize::DictionaryValue io_root; - bke::sim::serialize_modifier_simulation_state( - *sim_state, bdata_writer, *modifier_bake_data.bdata_sharing, io_root); - - BLI_file_ensure_parent_dir_exists(meta_path); - io::serialize::write_json_file(meta_path, io_root); } } @@ -343,9 +380,13 @@ static void bake_simulation_job_startjob(void *customdata, for (ObjectBakeData &object_bake_data : objects_to_bake) { for (ModifierBakeData &modifier_bake_data : object_bake_data.modifiers) { NodesModifierData &nmd = *modifier_bake_data.nmd; - if (nmd.runtime->simulation_cache) { - /* Tag the caches as being baked so that they are not changed anymore. */ - nmd.runtime->simulation_cache->cache_state = CacheState::Baked; + for (ZoneBakeData &zone_bake_data : modifier_bake_data.zones) { + if (std::unique_ptr &zone_cache = + nmd.runtime->simulation_cache->cache_by_zone_id.lookup(zone_bake_data.zone_id)) + { + /* Tag the caches as being baked so that they are not changed anymore. */ + zone_cache->cache_state = CacheState::Baked; + } } } DEG_id_tag_update(&object_bake_data.object->id, ID_RECALC_GEOMETRY); @@ -593,40 +634,45 @@ static int delete_baked_simulation_exec(bContext *C, wmOperator *op) } for (Object *object : objects) { - const char *base_path = ID_BLEND_PATH(bmain, &object->id); LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { if (md->type == eModifierType_Nodes) { NodesModifierData *nmd = reinterpret_cast(md); - nmd->runtime->simulation_cache->reset(); - if (StringRef(nmd->simulation_bake_directory).is_empty()) { + if (!nmd->runtime->simulation_cache) { continue; } - char absolute_bake_dir[FILE_MAX]; - STRNCPY(absolute_bake_dir, nmd->simulation_bake_directory); - BLI_path_abs(absolute_bake_dir, base_path); + for (auto item : nmd->runtime->simulation_cache->cache_by_zone_id.items()) { + item.value->reset(); - char meta_dir[FILE_MAX]; - BLI_path_join(meta_dir, sizeof(meta_dir), absolute_bake_dir, "meta"); - char bdata_dir[FILE_MAX]; - BLI_path_join(bdata_dir, sizeof(bdata_dir), absolute_bake_dir, "bdata"); + const std::optional bake_path = + bke::sim::get_simulation_zone_bake_path(*bmain, *object, *nmd, item.key); + if (!bake_path) { + continue; + } - if (BLI_exists(absolute_bake_dir)) { + const char *meta_dir = bake_path->meta_dir.c_str(); if (BLI_exists(meta_dir)) { if (BLI_delete(meta_dir, true, true)) { BKE_reportf(op->reports, RPT_ERROR, "Failed to remove meta directory %s", meta_dir); } } + const char *bdata_dir = bake_path->bdata_dir.c_str(); if (BLI_exists(bdata_dir)) { if (BLI_delete(bdata_dir, true, true)) { BKE_reportf( op->reports, RPT_ERROR, "Failed to remove bdata directory %s", bdata_dir); } } - /* Delete the folder if it's empty. */ - BLI_delete(absolute_bake_dir, true, false); + if (bake_path->bake_dir.has_value()) { + const char *zone_bake_dir = bake_path->bake_dir->c_str(); + /* Try to delete zone bake directory if it is empty. */ + BLI_delete(zone_bake_dir, true, false); + } } - else { - BKE_reportf(op->reports, RPT_ERROR, "Bake directory %s not found", absolute_bake_dir); + if (const std::optional modifier_bake_dir = + bke::sim::get_modifier_simulation_bake_path(*bmain, *object, *nmd)) + { + /* Try to delete modifier bake directory if it is empty. */ + BLI_delete(modifier_bake_dir->c_str(), true, false); } } } diff --git a/source/blender/editors/space_action/action_draw.cc b/source/blender/editors/space_action/action_draw.cc index 971a364d18c..6c5b99dcb7e 100644 --- a/source/blender/editors/space_action/action_draw.cc +++ b/source/blender/editors/space_action/action_draw.cc @@ -748,19 +748,30 @@ static void timeline_cache_draw_single(PTCacheID *pid, float y_offset, float hei } static void timeline_cache_draw_simulation_nodes( - const Scene &scene, const blender::bke::sim::ModifierSimulationCache &cache, const float y_offset, const float height, const uint pos_id) { + std::lock_guard lock{cache.mutex}; + if (cache.cache_by_zone_id.is_empty()) { + return; + } + /* Draw the state if one of the simulation zones. This is fine for now, because there is no ui + * that allows caching zones independently. */ + const blender::bke::sim::SimulationZoneCache &zone_cache = + **cache.cache_by_zone_id.values().begin(); + if (zone_cache.frame_caches.is_empty()) { + return; + } + GPU_matrix_push(); GPU_matrix_translate_2f(0.0, float(V2D_SCROLL_HANDLE_HEIGHT) + y_offset); GPU_matrix_scale_2f(1.0, height); float color[4]; UI_GetThemeColor4fv(TH_SIMULATED_FRAMES, color); - switch (cache.cache_state) { + switch (zone_cache.cache_state) { case blender::bke::sim::CacheState::Invalid: { color[3] = 0.4f; break; @@ -777,16 +788,13 @@ static void timeline_cache_draw_simulation_nodes( immUniformColor4fv(color); - const int start_frame = scene.r.sfra; - const int end_frame = scene.r.efra; - const int frames_num = end_frame - start_frame + 1; - const blender::IndexRange frames_range(start_frame, frames_num); + immBeginAtMost(GPU_PRIM_TRIS, zone_cache.frame_caches.size() * 6); - immBeginAtMost(GPU_PRIM_TRIS, frames_num * 6); - for (const int frame : frames_range) { - if (cache.has_state_at_frame(frame)) { - immRectf_fast(pos_id, frame - 0.5f, 0, frame + 0.5f, 1.0f); - } + for (const std::unique_ptr &frame_cache : + zone_cache.frame_caches.as_span()) + { + const int frame = frame_cache->frame.frame(); + immRectf_fast(pos_id, frame - 0.5f, 0, frame + 0.5f, 1.0f); } immEnd(); @@ -840,7 +848,7 @@ void timeline_draw_cache(const SpaceAction *saction, const Object *ob, const Sce continue; } timeline_cache_draw_simulation_nodes( - *scene, *nmd->runtime->simulation_cache, y_offset, cache_draw_height, pos_id); + *nmd->runtime->simulation_cache, y_offset, cache_draw_height, pos_id); y_offset += cache_draw_height; } } diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 3c7a5f83a8a..a03b73c2714 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -660,154 +660,400 @@ static void check_property_socket_sync(const Object *ob, ModifierData *md) } } -static void prepare_simulation_states_for_evaluation(const NodesModifierData &nmd, - const ModifierEvalContext &ctx, - nodes::GeoNodesModifierData &exec_data) -{ - if (!nmd.runtime->simulation_cache) { - return; - } - const Main *bmain = DEG_get_bmain(ctx.depsgraph); - const SubFrame current_frame = DEG_get_ctime(ctx.depsgraph); - const Scene *scene = DEG_get_input_scene(ctx.depsgraph); - const SubFrame start_frame = scene->r.sfra; - const bool is_start_frame = current_frame == start_frame; +namespace sim_input = nodes::sim_input; +namespace sim_output = nodes::sim_output; - /* This cache may be shared between original and evaluated modifiers. */ - blender::bke::sim::ModifierSimulationCache &simulation_cache = *nmd.runtime->simulation_cache; +class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams { + private: + static constexpr float max_delta_frames = 1.0f; + mutable Map> behavior_by_zone_id_; + const NodesModifierData &nmd_; + const ModifierEvalContext &ctx_; + const Main *bmain_; + SubFrame current_frame_; + SubFrame start_frame_; + bool is_start_frame_; + bool use_frame_cache_; + bool depsgraph_is_active_; + bke::sim::ModifierSimulationCache *simulation_cache_; + float fps_; + + public: + NodesModifierSimulationParams(NodesModifierData &nmd, const ModifierEvalContext &ctx) + : nmd_(nmd), ctx_(ctx) { - /* Try to use baked data. */ - const StringRefNull bmain_path = BKE_main_blendfile_path(bmain); - if (simulation_cache.cache_state != bke::sim::CacheState::Baked && !bmain_path.is_empty()) { - if (!StringRef(nmd.simulation_bake_directory).is_empty()) { - if (const char *base_path = ID_BLEND_PATH(bmain, &ctx.object->id)) { - char absolute_bake_dir[FILE_MAX]; - STRNCPY(absolute_bake_dir, nmd.simulation_bake_directory); - BLI_path_abs(absolute_bake_dir, base_path); - simulation_cache.try_discover_bake(absolute_bake_dir); - } - } + const Depsgraph *depsgraph = ctx_.depsgraph; + bmain_ = DEG_get_bmain(depsgraph); + current_frame_ = DEG_get_ctime(depsgraph); + const Scene *scene = DEG_get_input_scene(depsgraph); + start_frame_ = scene->r.sfra; + is_start_frame_ = current_frame_ == start_frame_; + use_frame_cache_ = ctx_.object->flag & OB_FLAG_USE_SIMULATION_CACHE; + depsgraph_is_active_ = DEG_is_active(depsgraph); + simulation_cache_ = nmd.runtime->simulation_cache.get(); + fps_ = FPS; + + if (!simulation_cache_) { + return; } - } - - if (ctx.object->flag & OB_FLAG_USE_SIMULATION_CACHE) { - if (DEG_is_active(ctx.depsgraph)) { - - { - /* Invalidate cached data on user edits. */ - if (nmd.modifier.flag & eModifierFlag_UserModified) { - if (simulation_cache.cache_state != bke::sim::CacheState::Baked) { - simulation_cache.invalidate(); - } - } - } - - { - /* Reset cached data if necessary. */ - const bke::sim::StatesAroundFrame sim_states = simulation_cache.get_states_around_frame( - current_frame); - if (simulation_cache.cache_state == bke::sim::CacheState::Invalid && - (current_frame == start_frame || - (sim_states.current == nullptr && sim_states.prev == nullptr && - sim_states.next != nullptr))) + std::lock_guard lock{simulation_cache_->mutex}; + if (depsgraph_is_active_) { + /* Invalidate data on user edits. */ + if (nmd.modifier.flag & eModifierFlag_UserModified) { + for (std::unique_ptr &zone_cache : + simulation_cache_->cache_by_zone_id.values()) { - simulation_cache.reset(); - } - } - /* Decide if a new simulation state should be created in this evaluation. */ - const bke::sim::StatesAroundFrame sim_states = simulation_cache.get_states_around_frame( - current_frame); - if (simulation_cache.cache_state != bke::sim::CacheState::Baked) { - if (sim_states.current == nullptr) { - if (is_start_frame || !simulation_cache.has_states()) { - bke::sim::ModifierSimulationState ¤t_sim_state = - simulation_cache.get_state_at_frame_for_write(current_frame); - exec_data.current_simulation_state_for_write = ¤t_sim_state; - exec_data.simulation_time_delta = 0.0f; - if (!is_start_frame) { - /* When starting a new simulation at another frame than the start frame, - * it can't match what would be baked, so invalidate it immediately. */ - simulation_cache.invalidate(); - } - } - else if (sim_states.prev != nullptr && sim_states.next == nullptr) { - const float max_delta_frames = 1.0f; - const float scene_delta_frames = float(current_frame) - float(sim_states.prev->frame); - const float delta_frames = std::min(max_delta_frames, scene_delta_frames); - if (delta_frames != scene_delta_frames) { - simulation_cache.invalidate(); - } - bke::sim::ModifierSimulationState ¤t_sim_state = - simulation_cache.get_state_at_frame_for_write(current_frame); - exec_data.current_simulation_state_for_write = ¤t_sim_state; - const float delta_seconds = delta_frames / FPS; - exec_data.simulation_time_delta = delta_seconds; + if (zone_cache->cache_state != bke::sim::CacheState::Baked) { + zone_cache->cache_state = bke::sim::CacheState::Invalid; } } } - } - - /* Load read-only states to give nodes access to cached data. */ - const bke::sim::StatesAroundFrame sim_states = simulation_cache.get_states_around_frame( - current_frame); - if (sim_states.current) { - sim_states.current->state.ensure_bake_loaded(*nmd.node_group); - exec_data.current_simulation_state = &sim_states.current->state; - } - if (sim_states.prev) { - sim_states.prev->state.ensure_bake_loaded(*nmd.node_group); - exec_data.prev_simulation_state = &sim_states.prev->state; - if (sim_states.next) { - sim_states.next->state.ensure_bake_loaded(*nmd.node_group); - exec_data.next_simulation_state = &sim_states.next->state; - exec_data.simulation_state_mix_factor = - (float(current_frame) - float(sim_states.prev->frame)) / - (float(sim_states.next->frame) - float(sim_states.prev->frame)); + /* Reset cached data if necessary. */ + if (is_start_frame_) { + for (std::unique_ptr &zone_cache : + simulation_cache_->cache_by_zone_id.values()) + { + if (zone_cache->cache_state == bke::sim::CacheState::Invalid) { + zone_cache->reset(); + } + } } } } - else { - if (DEG_is_active(ctx.depsgraph)) { - bke::sim::ModifierSimulationCacheRealtime &realtime_cache = simulation_cache.realtime_cache; - if (current_frame < realtime_cache.prev_frame) { - /* Reset the cache when going backwards in time. */ - simulation_cache.reset(); + nodes::SimulationZoneBehavior *get(const int zone_id) const override + { + if (!simulation_cache_) { + return nullptr; + } + std::lock_guard lock{simulation_cache_->mutex}; + return behavior_by_zone_id_ + .lookup_or_add_cb(zone_id, + [&]() { + auto info = std::make_unique(); + this->init_simulation_info(zone_id, *info); + return info; + }) + .get(); + } + + struct FrameIndices { + std::optional prev; + std::optional current; + std::optional next; + }; + + void init_simulation_info(const int zone_id, nodes::SimulationZoneBehavior &zone_behavior) const + { + using namespace bke::sim; + + SimulationZoneCache &zone_cache = *simulation_cache_->cache_by_zone_id.lookup_or_add_cb( + zone_id, []() { return std::make_unique(); }); + + /* Try load baked data. */ + if (!zone_cache.failed_finding_bake) { + if (zone_cache.cache_state != CacheState::Baked) { + if (std::optional zone_bake_path = + get_simulation_zone_bake_path(*bmain_, *ctx_.object, nmd_, zone_id)) + { + + Vector meta_files = bke::bake_paths::find_sorted_meta_files( + zone_bake_path->meta_dir); + if (!meta_files.is_empty()) { + zone_cache.reset(); + + for (const bke::bake_paths::MetaFile &meta_file : meta_files) { + auto frame_cache = std::make_unique(); + frame_cache->frame = meta_file.frame; + frame_cache->meta_path = meta_file.path; + zone_cache.frame_caches.append(std::move(frame_cache)); + } + zone_cache.bdata_dir = zone_bake_path->bdata_dir; + zone_cache.bdata_sharing = std::make_unique(); + zone_cache.cache_state = CacheState::Baked; + } + } } - if (realtime_cache.current_frame == current_frame && realtime_cache.current_state) { - /* Don't simulate in the same frame again. */ - exec_data.current_simulation_state = realtime_cache.current_state.get(); + if (zone_cache.cache_state != CacheState::Baked) { + zone_cache.failed_finding_bake = true; + } + } + + const FrameIndices frame_indices = this->get_frame_indices(zone_cache); + if (zone_cache.cache_state == CacheState::Baked) { + this->read_from_cache(frame_indices, zone_cache, zone_behavior); + return; + } + if (use_frame_cache_) { + /* If the depsgraph is active, we allow creating new simulation states. Otherwise, the access + * is read-only. */ + if (depsgraph_is_active_) { + if (zone_cache.frame_caches.is_empty()) { + /* Initialize the simulation. */ + this->input_pass_through(zone_behavior); + this->output_store_frame_cache(zone_cache, zone_behavior); + if (!is_start_frame_) { + /* If we initialize at a frame that is not the start frame, the simulation is not + * valid. */ + zone_cache.cache_state = CacheState::Invalid; + } + return; + } + if (frame_indices.prev && !frame_indices.current && !frame_indices.next) { + /* Read the previous frame's data and store the newly computed simulation state. */ + auto &output_copy_info = zone_behavior.input.emplace(); + const bke::sim::SimulationZoneFrameCache &prev_frame_cache = + *zone_cache.frame_caches[*frame_indices.prev]; + const float delta_frames = std::min( + max_delta_frames, float(current_frame_) - float(prev_frame_cache.frame)); + if (delta_frames != 1) { + zone_cache.cache_state = CacheState::Invalid; + } + output_copy_info.delta_time = delta_frames / fps_; + output_copy_info.items_by_id = this->to_readonly_items_map(prev_frame_cache.items); + this->output_store_frame_cache(zone_cache, zone_behavior); + return; + } + } + this->read_from_cache(frame_indices, zone_cache, zone_behavior); + return; + } + + /* When there is no per-frame cache, check if there is a previous state. */ + if (zone_cache.prev_state) { + if (zone_cache.prev_state->frame < current_frame_) { + /* Do a simulation step. */ + const float delta_frames = std::min( + max_delta_frames, float(zone_cache.prev_state->frame) - float(current_frame_)); + auto &output_move_info = zone_behavior.input.emplace(); + output_move_info.delta_time = delta_frames / fps_; + output_move_info.items_by_id = std::move(zone_cache.prev_state->items); + this->store_as_prev_items(zone_cache, zone_behavior); return; } - - /* Advance in time, making the last "current" state the new "previous" state. */ - realtime_cache.prev_frame = realtime_cache.current_frame; - realtime_cache.prev_state = std::move(realtime_cache.current_state); - if (realtime_cache.prev_state) { - exec_data.prev_simulation_state_mutable = realtime_cache.prev_state.get(); + if (zone_cache.prev_state->frame == current_frame_) { + /* Just read from the previous state if the frame has not changed. */ + auto &output_copy_info = zone_behavior.input.emplace(); + output_copy_info.delta_time = 0.0f; + output_copy_info.items_by_id = this->to_readonly_items_map(zone_cache.prev_state->items); + auto &read_single_info = zone_behavior.output.emplace(); + read_single_info.items_by_id = this->to_readonly_items_map(zone_cache.prev_state->items); + return; } + if (!depsgraph_is_active_) { + /* There is no previous state, and it's not possible to initialize the simulation because + * the depsgraph is not active. */ + zone_behavior.input.emplace(); + zone_behavior.output.emplace(); + return; + } + /* Reset the simulation when the scene time moved backwards. */ + zone_cache.prev_state.reset(); + } + zone_behavior.input.emplace(); + if (depsgraph_is_active_) { + /* Initialize the simulation. */ + this->store_as_prev_items(zone_cache, zone_behavior); + } + else { + zone_behavior.output.emplace(); + } + } - /* Create a new current state used to pass the data to the next frame. */ - realtime_cache.current_state = std::make_unique(); - realtime_cache.current_frame = current_frame; - exec_data.current_simulation_state_for_write = realtime_cache.current_state.get(); - exec_data.current_simulation_state = exec_data.current_simulation_state_for_write; + FrameIndices get_frame_indices(const bke::sim::SimulationZoneCache &zone_cache) const + { + FrameIndices frame_indices; + if (!zone_cache.frame_caches.is_empty()) { + const int first_future_frame_index = binary_search::find_predicate_begin( + zone_cache.frame_caches, + [&](const std::unique_ptr &value) { + return value->frame > current_frame_; + }); + frame_indices.next = (first_future_frame_index == zone_cache.frame_caches.size()) ? + std::nullopt : + std::optional(first_future_frame_index); + if (first_future_frame_index > 0) { + const int index = first_future_frame_index - 1; + if (zone_cache.frame_caches[index]->frame < current_frame_) { + frame_indices.prev = index; + } + else { + BLI_assert(zone_cache.frame_caches[index]->frame == current_frame_); + frame_indices.current = index; + if (index > 0) { + frame_indices.prev = index - 1; + } + } + } + } + return frame_indices; + } - /* Calculate the delta time. */ - if (realtime_cache.prev_state) { - const float max_delta_frames = 1.0f; - const float scene_delta_frames = float(current_frame) - float(realtime_cache.prev_frame); - const float delta_frames = std::min(max_delta_frames, scene_delta_frames); - const float delta_seconds = delta_frames / FPS; - exec_data.simulation_time_delta = delta_seconds; + void input_pass_through(nodes::SimulationZoneBehavior &zone_behavior) const + { + zone_behavior.input.emplace(); + } + + void output_store_frame_cache(bke::sim::SimulationZoneCache &zone_cache, + nodes::SimulationZoneBehavior &zone_behavior) const + { + auto &store_and_pass_through_info = + zone_behavior.output.emplace(); + store_and_pass_through_info.store_fn = + [simulation_cache = simulation_cache_, + zone_cache = &zone_cache, + current_frame = current_frame_](Map> items) { + std::lock_guard lock{simulation_cache->mutex}; + auto frame_cache = std::make_unique(); + frame_cache->frame = current_frame; + frame_cache->items = std::move(items); + zone_cache->frame_caches.append(std::move(frame_cache)); + }; + } + + void store_as_prev_items(bke::sim::SimulationZoneCache &zone_cache, + nodes::SimulationZoneBehavior &zone_behavior) const + { + auto &store_and_pass_through_info = + zone_behavior.output.emplace(); + store_and_pass_through_info.store_fn = + [simulation_cache = simulation_cache_, + zone_cache = &zone_cache, + current_frame = current_frame_](Map> items) { + std::lock_guard lock{simulation_cache->mutex}; + if (!zone_cache->prev_state) { + zone_cache->prev_state.emplace(); + } + zone_cache->prev_state->items = std::move(items); + zone_cache->prev_state->frame = current_frame; + }; + } + + void read_from_cache(const FrameIndices &frame_indices, + bke::sim::SimulationZoneCache &zone_cache, + nodes::SimulationZoneBehavior &zone_behavior) const + { + if (frame_indices.prev) { + auto &output_copy_info = zone_behavior.input.emplace(); + bke::sim::SimulationZoneFrameCache &frame_cache = + *zone_cache.frame_caches[*frame_indices.prev]; + const float delta_frames = std::min(max_delta_frames, + float(current_frame_) - float(frame_cache.frame)); + output_copy_info.delta_time = delta_frames / fps_; + for (auto item : frame_cache.items.items()) { + output_copy_info.items_by_id.add_new(item.key, item.value.get()); + } + } + else { + zone_behavior.input.emplace(); + } + if (frame_indices.current) { + this->read_single(*frame_indices.current, zone_cache, zone_behavior); + } + else if (frame_indices.next) { + if (frame_indices.prev) { + this->read_interpolated( + *frame_indices.prev, *frame_indices.next, zone_cache, zone_behavior); } else { - exec_data.simulation_time_delta = 0.0f; + this->read_empty(zone_behavior); + } + } + else if (frame_indices.prev) { + this->read_single(*frame_indices.prev, zone_cache, zone_behavior); + } + else { + this->read_empty(zone_behavior); + } + } + + void read_empty(nodes::SimulationZoneBehavior &zone_behavior) const + { + zone_behavior.output.emplace(); + } + + void read_single(const int frame_index, + bke::sim::SimulationZoneCache &zone_cache, + nodes::SimulationZoneBehavior &zone_behavior) const + { + bke::sim::SimulationZoneFrameCache &frame_cache = *zone_cache.frame_caches[frame_index]; + this->ensure_bake_loaded(zone_cache, frame_cache); + auto &read_single_info = zone_behavior.output.emplace(); + read_single_info.items_by_id = this->to_readonly_items_map(frame_cache.items); + } + + void read_interpolated(const int prev_frame_index, + const int next_frame_index, + bke::sim::SimulationZoneCache &zone_cache, + nodes::SimulationZoneBehavior &zone_behavior) const + { + bke::sim::SimulationZoneFrameCache &prev_frame_cache = + *zone_cache.frame_caches[prev_frame_index]; + bke::sim::SimulationZoneFrameCache &next_frame_cache = + *zone_cache.frame_caches[next_frame_index]; + this->ensure_bake_loaded(zone_cache, prev_frame_cache); + this->ensure_bake_loaded(zone_cache, next_frame_cache); + auto &read_interpolated_info = zone_behavior.output.emplace(); + read_interpolated_info.mix_factor = (float(current_frame_) - float(prev_frame_cache.frame)) / + (float(next_frame_cache.frame) - + float(prev_frame_cache.frame)); + read_interpolated_info.prev_items_by_id = this->to_readonly_items_map(prev_frame_cache.items); + read_interpolated_info.next_items_by_id = this->to_readonly_items_map(next_frame_cache.items); + } + + Map to_readonly_items_map( + const Map> &items) const + { + Map map; + for (auto item : items.items()) { + map.add_new(item.key, item.value.get()); + } + return map; + } + + void ensure_bake_loaded(bke::sim::SimulationZoneCache &zone_cache, + bke::sim::SimulationZoneFrameCache &frame_cache) const + { + if (!frame_cache.items.is_empty()) { + return; + } + if (!zone_cache.bdata_dir) { + return; + } + if (!frame_cache.meta_path) { + return; + } + std::shared_ptr io_root_value = io::serialize::read_json_file( + *frame_cache.meta_path); + const io::serialize::DictionaryValue *io_root = io_root_value->as_dictionary_value(); + if (!io_root) { + return; + } + if (io_root->lookup_int("version").value_or(0) != bke::sim::simulation_file_storage_version) { + return; + } + const io::serialize::DictionaryValue *io_items = io_root->lookup_dict("items"); + if (!io_items) { + return; + } + + bke::DiskBDataReader bdata_reader{*zone_cache.bdata_dir}; + + for (const auto &io_item_value : io_items->elements()) { + const io::serialize::DictionaryValue *io_item = io_item_value.second->as_dictionary_value(); + if (!io_item) { + continue; + } + const int zone_id = std::stoi(io_item_value.first); + std::unique_ptr item = bke::deserialize_bake_item( + *io_item, bdata_reader, *zone_cache.bdata_sharing); + if (item) { + frame_cache.items.add(zone_id, std::move(item)); } } } -} +}; static void modifyGeometry(ModifierData *md, const ModifierEvalContext *ctx, @@ -868,7 +1114,8 @@ static void modifyGeometry(ModifierData *md, modifier_eval_data.self_object = ctx->object; auto eval_log = std::make_unique(); - prepare_simulation_states_for_evaluation(*nmd, *ctx, modifier_eval_data); + NodesModifierSimulationParams simulation_params(*nmd, *ctx); + modifier_eval_data.simulation_params = &simulation_params; Set socket_log_contexts; if (logging_enabled(ctx)) { diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index e0ce42950db..4c1863f1d2a 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -20,6 +20,8 @@ * #lazy_function::Graph is build that can be used when evaluating the graph (e.g. for logging). */ +#include + #include "FN_lazy_function_graph.hh" #include "FN_lazy_function_graph_executor.hh" @@ -40,6 +42,92 @@ namespace blender::nodes { using lf::LazyFunction; using mf::MultiFunction; +/** The structs in here describe the different possible behaviors of a simulation input node. */ +namespace sim_input { + +/** + * The data is just passed through the node. Data that is incompatible with simulations (like + * anonymous attributes), is removed though. + */ +struct PassThrough { +}; + +/** + * The input is not evaluated, instead the values provided here are output by the node. + */ +struct OutputCopy { + float delta_time; + Map items_by_id; +}; + +/** + * Same as above, but the values can be output by move, instead of copy. This can reduce the amount + * of unnecessary copies, when the old simulation state is not needed anymore. + */ +struct OutputMove { + float delta_time; + Map> items_by_id; +}; + +using Behavior = std::variant; + +} // namespace sim_input + +/** The structs in here describe the different possible behaviors of a simulation output node. */ +namespace sim_output { + +/** + * The data is just passed through the node. Data that is incompatible with simulations (like + * anonymous attributes), is removed though. + */ +struct PassThrough { +}; + +/** + * Same as above, but also calls the given function with the data that is passed through the node. + * This allows the caller of geometry nodes (e.g. the modifier), to cache the new simulation state. + */ +struct StoreAndPassThrough { + std::function> items_by_id)> store_fn; +}; + +/** + * The inputs are not evaluated, instead the given cached items are output directly. + */ +struct ReadSingle { + Map items_by_id; +}; + +/** + * The inputs are not evaluated, instead of a mix of the two given states is output. + */ +struct ReadInterpolated { + /** Factor between 0 and 1 that determines the influence of the two simulation states. */ + float mix_factor; + Map prev_items_by_id; + Map next_items_by_id; +}; + +using Behavior = std::variant; + +} // namespace sim_output + +/** Controls the behavior of one simulation zone. */ +struct SimulationZoneBehavior { + sim_input::Behavior input; + sim_output::Behavior output; +}; + +class GeoNodesSimulationParams { + public: + /** + * Get the expected behavior for the simulation zone with the given id (see #bNestedNodeRef). + * It's possible that this method called multiple times for the same id. In this case, the same + * pointer should be returned in each call. + */ + virtual SimulationZoneBehavior *get(const int zone_id) const = 0; +}; + /** * Data that is passed into geometry nodes evaluation from the modifier. */ @@ -51,20 +139,7 @@ struct GeoNodesModifierData { /** Optional logger. */ geo_eval_log::GeoModifierLog *eval_log = nullptr; - /** Read-only simulation states around the current frame. */ - const bke::sim::ModifierSimulationState *current_simulation_state = nullptr; - const bke::sim::ModifierSimulationState *prev_simulation_state = nullptr; - const bke::sim::ModifierSimulationState *next_simulation_state = nullptr; - float simulation_state_mix_factor = 0.0f; - /** Used when the evaluation should create a new simulation state. */ - bke::sim::ModifierSimulationState *current_simulation_state_for_write = nullptr; - float simulation_time_delta = 0.0f; - - /** - * The same as #prev_simulation_state, but the cached values can be moved from, - * to keep data managed by implicit sharing mutable. - */ - bke::sim::ModifierSimulationState *prev_simulation_state_mutable = nullptr; + GeoNodesSimulationParams *simulation_params = nullptr; /** * Some nodes should be executed even when their output is not used (e.g. active viewer nodes and @@ -264,8 +339,14 @@ std::unique_ptr get_simulation_input_lazy_function( GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info); std::unique_ptr get_switch_node_lazy_function(const bNode &node); -std::optional get_simulation_zone_id( - const GeoNodesLFUserData &user_data, const int output_node_id); +struct FoundNestedNodeID { + int id; + bool is_in_simulation = false; + bool is_in_loop = false; +}; + +std::optional find_nested_node_id(const GeoNodesLFUserData &user_data, + const int node_id); /** * An anonymous attribute created by a node. diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 7c0663aef4f..116e0985f16 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -136,17 +136,17 @@ void socket_declarations_for_simulation_items(Span items, NodeDeclaration &r_declaration); const CPPType &get_simulation_item_cpp_type(eNodeSocketDatatype socket_type); const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item); -void move_values_to_simulation_state(const Span node_simulation_items, - const Span input_values, - bke::sim::SimulationZoneState &r_zone_state); + +Map> move_values_to_simulation_state( + const Span node_simulation_items, const Span input_values); void move_simulation_state_to_values(const Span node_simulation_items, - bke::sim::SimulationZoneState &zone_state, + Map> zone_state, const Object &self_object, const ComputeContext &compute_context, const bNode &sim_output_node, Span r_output_values); void copy_simulation_state_to_values(const Span node_simulation_items, - const bke::sim::SimulationZoneState &zone_state, + const Map &zone_state, const Object &self_object, const ComputeContext &compute_context, const bNode &sim_output_node, diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc index 9934cba5f09..b5c6e8ba675 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc @@ -64,54 +64,56 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction { return; } const GeoNodesModifierData &modifier_data = *user_data.modifier_data; - - if (!params.output_was_set(0)) { - const float delta_time = modifier_data.simulation_time_delta; - params.set_output(0, fn::ValueOrField(delta_time)); - } - - if (modifier_data.current_simulation_state == nullptr) { - this->pass_through(params, user_data); - return; - } - - const std::optional zone_id = get_simulation_zone_id( - user_data, output_node_id_); - if (!zone_id) { + if (!modifier_data.simulation_params) { params.set_default_remaining_outputs(); return; } - - /* When caching is turned off and the old state doesn't need to persist, moving data - * from the last state instead of copying it can avoid copies of geometry data arrays. */ - if (auto *state = modifier_data.prev_simulation_state_mutable) { - if (bke::sim::SimulationZoneState *zone = state->get_zone_state(*zone_id)) { - this->output_simulation_state_move(params, user_data, *zone); - return; - } + std::optional found_id = find_nested_node_id(user_data, output_node_id_); + if (!found_id) { + params.set_default_remaining_outputs(); + return; } - - /* If there is a read-only state from the last frame, output that directly. */ - if (const auto *state = modifier_data.prev_simulation_state) { - if (const bke::sim::SimulationZoneState *zone = state->get_zone_state(*zone_id)) { - this->output_simulation_state_copy(params, user_data, *zone); - return; - } + if (found_id->is_in_loop) { + params.set_default_remaining_outputs(); + return; + } + SimulationZoneBehavior *zone_behavior = modifier_data.simulation_params->get(found_id->id); + if (!zone_behavior) { + params.set_default_remaining_outputs(); + return; + } + sim_input::Behavior &input_behavior = zone_behavior->input; + float delta_time = 0.0f; + if (auto *info = std::get_if(&input_behavior)) { + delta_time = info->delta_time; + this->output_simulation_state_copy(params, user_data, info->items_by_id); + } + else if (auto *info = std::get_if(&input_behavior)) { + delta_time = info->delta_time; + this->output_simulation_state_move(params, user_data, std::move(info->items_by_id)); + } + else if (std::get_if(&input_behavior)) { + delta_time = 0.0f; + this->pass_through(params, user_data); + } + else { + BLI_assert_unreachable(); + } + if (!params.output_was_set(0)) { + params.set_output(0, fn::ValueOrField(delta_time)); } - - this->pass_through(params, user_data); } void output_simulation_state_copy(lf::Params ¶ms, const GeoNodesLFUserData &user_data, - const bke::sim::SimulationZoneState &state) const + const Map &zone_state) const { Array outputs(simulation_items_.size()); for (const int i : simulation_items_.index_range()) { outputs[i] = params.get_output_data_ptr(i + 1); } copy_simulation_state_to_values(simulation_items_, - state, + std::move(zone_state), *user_data.modifier_data->self_object, *user_data.compute_context, node_, @@ -123,14 +125,14 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction { void output_simulation_state_move(lf::Params ¶ms, const GeoNodesLFUserData &user_data, - bke::sim::SimulationZoneState &state) const + Map> zone_state) const { Array outputs(simulation_items_.size()); for (const int i : simulation_items_.index_range()) { outputs[i] = params.get_output_data_ptr(i + 1); } move_simulation_state_to_values(simulation_items_, - state, + std::move(zone_state), *user_data.modifier_data->self_object, *user_data.compute_context, node_, @@ -153,9 +155,9 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction { /* Instead of outputting the initial values directly, convert them to a simulation state and * then back. This ensures that some geometry processing happens on the data consistently (e.g. * removing anonymous attributes). */ - bke::sim::SimulationZoneState state; - move_values_to_simulation_state(simulation_items_, input_values, state); - this->output_simulation_state_move(params, user_data, state); + Map> bake_items = move_values_to_simulation_state( + simulation_items_, input_values); + this->output_simulation_state_move(params, user_data, std::move(bake_items)); } }; diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc index 59b9b326a19..c470a4ef05c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc @@ -185,7 +185,7 @@ static std::shared_ptr make_attribute_field( } void move_simulation_state_to_values(const Span node_simulation_items, - bke::sim::SimulationZoneState &zone_state, + Map> zone_state, const Object &self_object, const ComputeContext &compute_context, const bNode &node, @@ -194,7 +194,7 @@ void move_simulation_state_to_values(const Span node_simulat const bke::BakeSocketConfig config = make_bake_socket_config(node_simulation_items); Vector bake_items; for (const NodeSimulationItem &item : node_simulation_items) { - auto *bake_item = zone_state.item_by_identifier.lookup_ptr(item.identifier); + auto *bake_item = zone_state.lookup_ptr(item.identifier); bake_items.append(bake_item ? bake_item->get() : nullptr); } @@ -209,17 +209,17 @@ void move_simulation_state_to_values(const Span node_simulat } void copy_simulation_state_to_values(const Span node_simulation_items, - const bke::sim::SimulationZoneState &zone_state, + const Map &zone_state, const Object &self_object, const ComputeContext &compute_context, const bNode &node, Span r_output_values) { const bke::BakeSocketConfig config = make_bake_socket_config(node_simulation_items); - Vector bake_items; + Vector bake_items; for (const NodeSimulationItem &item : node_simulation_items) { - auto *bake_item = zone_state.item_by_identifier.lookup_ptr(item.identifier); - bake_items.append(bake_item ? bake_item->get() : nullptr); + const bke::BakeItem *const *bake_item = zone_state.lookup_ptr(item.identifier); + bake_items.append(bake_item ? *bake_item : nullptr); } bke::copy_bake_items_to_socket_values( @@ -232,22 +232,23 @@ void copy_simulation_state_to_values(const Span node_simulat r_output_values); } -void move_values_to_simulation_state(const Span node_simulation_items, - const Span input_values, - bke::sim::SimulationZoneState &r_zone_state) +Map> move_values_to_simulation_state( + const Span node_simulation_items, const Span input_values) { const bke::BakeSocketConfig config = make_bake_socket_config(node_simulation_items); Array> bake_items = bke::move_socket_values_to_bake_items( input_values, config); + Map> bake_items_map; for (const int i : node_simulation_items.index_range()) { const NodeSimulationItem &item = node_simulation_items[i]; std::unique_ptr &bake_item = bake_items[i]; if (bake_item) { - r_zone_state.item_by_identifier.add_new(item.identifier, std::move(bake_item)); + bake_items_map.add_new(item.identifier, std::move(bake_item)); } } + return bake_items_map; } } // namespace blender::nodes @@ -256,10 +257,6 @@ namespace blender::nodes::node_geo_simulation_output_cc { NODE_STORAGE_FUNCS(NodeGeometrySimulationOutput); -struct EvalData { - bool is_first_evaluation = true; -}; - static bool sharing_info_equal(const ImplicitSharingInfo *a, const ImplicitSharingInfo *b) { if (!a || !b) { @@ -542,16 +539,6 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { } } - void *init_storage(LinearAllocator<> &allocator) const - { - return allocator.construct().release(); - } - - void destruct_storage(void *storage) const - { - std::destroy_at(static_cast(storage)); - } - void execute_impl(lf::Params ¶ms, const lf::Context &context) const final { GeoNodesLFUserData &user_data = *static_cast(context.user_data); @@ -559,78 +546,51 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { params.set_default_remaining_outputs(); return; } - GeoNodesModifierData &modifier_data = *user_data.modifier_data; - EvalData &eval_data = *static_cast(context.storage); - BLI_SCOPED_DEFER([&]() { eval_data.is_first_evaluation = false; }); - - const std::optional zone_id = get_simulation_zone_id( - user_data, node_.identifier); - if (!zone_id) { + const GeoNodesModifierData &modifier_data = *user_data.modifier_data; + if (!modifier_data.simulation_params) { params.set_default_remaining_outputs(); return; } - - const bke::sim::SimulationZoneState *current_zone_state = - modifier_data.current_simulation_state ? - modifier_data.current_simulation_state->get_zone_state(*zone_id) : - nullptr; - if (eval_data.is_first_evaluation && current_zone_state != nullptr) { - /* Common case when data is cached already. */ - this->output_cached_state(params, user_data, *current_zone_state); + std::optional found_id = find_nested_node_id(user_data, node_.identifier); + if (!found_id) { + params.set_default_remaining_outputs(); return; } - - if (modifier_data.current_simulation_state_for_write == nullptr) { - const bke::sim::SimulationZoneState *prev_zone_state = - modifier_data.prev_simulation_state ? - modifier_data.prev_simulation_state->get_zone_state(*zone_id) : - nullptr; - if (prev_zone_state == nullptr) { - /* There is no previous simulation state and we also don't create a new one, so just pass - * the data through. */ - this->pass_through(params, user_data); - return; - } - const bke::sim::SimulationZoneState *next_zone_state = - modifier_data.next_simulation_state ? - modifier_data.next_simulation_state->get_zone_state(*zone_id) : - nullptr; - if (next_zone_state == nullptr) { - /* Output the last cached simulation state. */ - this->output_cached_state(params, user_data, *prev_zone_state); - return; - } - /* A previous and next frame is cached already, but the current frame is not. */ + if (found_id->is_in_loop) { + params.set_default_remaining_outputs(); + return; + } + SimulationZoneBehavior *zone_behavior = modifier_data.simulation_params->get(found_id->id); + if (!zone_behavior) { + params.set_default_remaining_outputs(); + return; + } + sim_output::Behavior &output_behavior = zone_behavior->output; + if (auto *info = std::get_if(&output_behavior)) { + this->output_cached_state(params, user_data, info->items_by_id); + } + else if (auto *info = std::get_if(&output_behavior)) { this->output_mixed_cached_state(params, *modifier_data.self_object, *user_data.compute_context, - *prev_zone_state, - *next_zone_state, - modifier_data.simulation_state_mix_factor); - return; + info->prev_items_by_id, + info->next_items_by_id, + info->mix_factor); } - - bke::sim::SimulationZoneState &new_zone_state = - modifier_data.current_simulation_state_for_write->get_zone_state_for_write(*zone_id); - if (eval_data.is_first_evaluation) { - new_zone_state.item_by_identifier.clear(); + else if (std::get_if(&output_behavior)) { + this->pass_through(params, user_data); } - - Array input_values(simulation_items_.size(), nullptr); - for (const int i : simulation_items_.index_range()) { - input_values[i] = params.try_get_input_data_ptr_or_request(i); + else if (auto *info = std::get_if(&output_behavior)) { + this->store_and_pass_through(params, user_data, *info); } - if (input_values.as_span().contains(nullptr)) { - /* Wait until all inputs are available. */ - return; + else { + BLI_assert_unreachable(); } - move_values_to_simulation_state(simulation_items_, input_values, new_zone_state); - this->output_cached_state(params, user_data, new_zone_state); } void output_cached_state(lf::Params ¶ms, GeoNodesLFUserData &user_data, - const bke::sim::SimulationZoneState &state) const + const Map &state) const { Array output_values(simulation_items_.size()); for (const int i : simulation_items_.index_range()) { @@ -650,8 +610,8 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { void output_mixed_cached_state(lf::Params ¶ms, const Object &self_object, const ComputeContext &compute_context, - const bke::sim::SimulationZoneState &prev_state, - const bke::sim::SimulationZoneState &next_state, + const Map &prev_state, + const Map &next_state, const float mix_factor) const { Array output_values(simulation_items_.size()); @@ -685,6 +645,52 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { } void pass_through(lf::Params ¶ms, GeoNodesLFUserData &user_data) const + { + /* Instead of outputting the initial values directly, convert them to a simulation state and + * then back. This ensures that some geometry processing happens on the data consistently (e.g. + * removing anonymous attributes). */ + std::optional>> bake_items = + this->get_bake_items_from_inputs(params); + if (!bake_items) { + /* Wait for inputs to be computed. */ + return; + } + + Array output_values(simulation_items_.size()); + for (const int i : simulation_items_.index_range()) { + output_values[i] = params.get_output_data_ptr(i); + } + move_simulation_state_to_values(simulation_items_, + std::move(*bake_items), + *user_data.modifier_data->self_object, + *user_data.compute_context, + node_, + output_values); + for (const int i : simulation_items_.index_range()) { + params.output_set(i); + } + } + + void store_and_pass_through(lf::Params ¶ms, + GeoNodesLFUserData &user_data, + const sim_output::StoreAndPassThrough &info) const + { + std::optional>> bake_items = + this->get_bake_items_from_inputs(params); + if (!bake_items) { + /* Wait for inputs to be computed. */ + return; + } + Map bake_item_pointers; + for (const auto item : bake_items->items()) { + bake_item_pointers.add_new(item.key, item.value.get()); + } + this->output_cached_state(params, user_data, bake_item_pointers); + info.store_fn(std::move(*bake_items)); + } + + std::optional>> get_bake_items_from_inputs( + lf::Params ¶ms) const { Array input_values(inputs_.size()); for (const int i : inputs_.index_range()) { @@ -692,14 +698,10 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { } if (input_values.as_span().contains(nullptr)) { /* Wait for inputs to be computed. */ - return; + return std::nullopt; } - /* Instead of outputting the initial values directly, convert them to a simulation state and - * then back. This ensures that some geometry processing happens on the data consistently (e.g. - * removing anonymous attributes). */ - bke::sim::SimulationZoneState state; - move_values_to_simulation_state(simulation_items_, input_values, state); - this->output_cached_state(params, user_data, state); + + return move_values_to_simulation_state(simulation_items_, input_values); } }; @@ -715,30 +717,6 @@ std::unique_ptr get_simulation_output_lazy_function( return std::make_unique(node, own_lf_graph_info); } -std::optional get_simulation_zone_id( - const GeoNodesLFUserData &user_data, const int output_node_id) -{ - Vector node_ids; - for (const ComputeContext *context = user_data.compute_context; context != nullptr; - context = context->parent()) - { - if (const auto *node_context = dynamic_cast(context)) { - node_ids.append(node_context->node_id()); - } - else if (dynamic_cast(context) != nullptr) { - /* Simulation can't be used in a repeat zone. */ - return std::nullopt; - } - } - std::reverse(node_ids.begin(), node_ids.end()); - node_ids.append(output_node_id); - const bNestedNodeRef *nested_node_ref = user_data.root_ntree->nested_node_ref_from_node_id_path( - node_ids); - bke::sim::SimulationZoneID zone_id; - zone_id.nested_node_id = nested_node_ref->id; - return zone_id; -} - } // namespace blender::nodes namespace blender::nodes::node_geo_simulation_output_cc { diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index 45d09c300be..591eceb6f67 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -866,13 +866,15 @@ class LazyFunctionForViewerInputUsage : public LazyFunction { }; class LazyFunctionForSimulationInputsUsage : public LazyFunction { + private: + const bNode *output_bnode_; public: - LazyFunctionForSimulationInputsUsage() + LazyFunctionForSimulationInputsUsage(const bNode &output_bnode) : output_bnode_(&output_bnode) { debug_name_ = "Simulation Inputs Usage"; - outputs_.append_as("Is Initialization", CPPType::get()); - outputs_.append_as("Do Simulation Step", CPPType::get()); + outputs_.append_as("Need Input Inputs", CPPType::get()); + outputs_.append_as("Need Output Inputs", CPPType::get()); } void execute_impl(lf::Params ¶ms, const lf::Context &context) const override @@ -883,11 +885,39 @@ class LazyFunctionForSimulationInputsUsage : public LazyFunction { return; } const GeoNodesModifierData &modifier_data = *user_data.modifier_data; + if (!modifier_data.simulation_params) { + params.set_default_remaining_outputs(); + return; + } + const std::optional found_id = find_nested_node_id( + user_data, output_bnode_->identifier); + if (!found_id) { + params.set_default_remaining_outputs(); + return; + } + if (found_id->is_in_loop) { + params.set_default_remaining_outputs(); + return; + } + SimulationZoneBehavior *zone_behavior = modifier_data.simulation_params->get(found_id->id); + if (!zone_behavior) { + params.set_default_remaining_outputs(); + return; + } - params.set_output(0, - modifier_data.current_simulation_state_for_write != nullptr && - modifier_data.prev_simulation_state == nullptr); - params.set_output(1, modifier_data.current_simulation_state_for_write != nullptr); + bool solve_contains_side_effect = false; + if (modifier_data.side_effect_nodes) { + const Span side_effect_nodes = + modifier_data.side_effect_nodes->lookup(user_data.compute_context->hash()); + solve_contains_side_effect = !side_effect_nodes.is_empty(); + } + + params.set_output(0, std::holds_alternative(zone_behavior->input)); + params.set_output( + 1, + solve_contains_side_effect || + std::holds_alternative(zone_behavior->output) || + std::holds_alternative(zone_behavior->output)); } }; @@ -1869,7 +1899,8 @@ struct GeometryNodesLazyFunctionGraphBuilder { lf::Node &lf_border_link_usage_node = this->build_border_link_input_usage_node(zone, lf_graph); lf::Node &lf_simulation_usage_node = [&]() -> lf::Node & { - auto &lazy_function = scope_.construct(); + auto &lazy_function = scope_.construct( + *zone.output_node); lf::Node &lf_node = lf_graph.add_function(lazy_function); if (lf_main_input_usage_node) { @@ -4006,4 +4037,33 @@ GeoNodesLFLocalUserData::GeoNodesLFLocalUserData(GeoNodesLFUserData &user_data) } } +std::optional find_nested_node_id(const GeoNodesLFUserData &user_data, + const int node_id) +{ + FoundNestedNodeID found; + Vector node_ids; + for (const ComputeContext *context = user_data.compute_context; context != nullptr; + context = context->parent()) + { + if (const auto *node_context = dynamic_cast(context)) { + node_ids.append(node_context->node_id()); + } + else if (dynamic_cast(context) != nullptr) { + found.is_in_loop = true; + } + else if (dynamic_cast(context) != nullptr) { + found.is_in_simulation = true; + } + } + std::reverse(node_ids.begin(), node_ids.end()); + node_ids.append(node_id); + const bNestedNodeRef *nested_node_ref = user_data.root_ntree->nested_node_ref_from_node_id_path( + node_ids); + if (nested_node_ref == nullptr) { + return std::nullopt; + } + found.id = nested_node_ref->id; + return found; +} + } // namespace blender::nodes