From 2d2b087fcf40bdae4f88c5fbdb83b87e623a79a2 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 1 Feb 2024 09:21:55 +0100 Subject: [PATCH] Geometry Nodes: support baking data block references With this patch, materials are kept intact in simulation zones and bake nodes without any additional user action. This implements the design proposed in #108410 to support referencing data-blocks (only materials for now) in the baked data. The task also describes why this is not a trivial issue. A previous attempt was implemented in #109703 but it didn't work well-enough. The solution is to have an explicit `name (+ library name) -> data-block` mapping that is stored in the modifier for each bake node and simulation zone. The `library name` is necessary for it to be unique within a .blend file. Note that this refers to the name of the `Library` data-block and not a file path. The baked data only contains the names of the used data-blocks. When the baked data is loaded, the correct material data-block is looked up from the mapping. ### Automatic Mapping Generation The most tricky aspect of this approach is to make it feel mostly automatic. From the user point-of-view, it should just work. Therefore, we don't want the user to have to create the mapping manually in the majority of cases. Creating the mapping automatically is difficult because the data-blocks that should become part of the mapping are only known during depsgraph evaluation. So we somehow have to gather the missing data blocks during evaluation and then write the new mappings back to the original data. While writing back to original data is something we do in some cases already, the situation here is different, because we are actually creating new relations between data-blocks. This also means that we'll have to do user-counting. Since user counts in data-blocks are *not* atomic, we can't do that from multiple threads at the same time. Also, under some circumstances, it may be necessary to trigger depsgraph evaluation again after the write-back because it actually affects the result. To solve this, a small new API is added in `DEG_depsgraph_writeback_sync.hh`. It allows gathering tasks which write back to original data in a synchronous way which may also require a reevaluation. ### Accessing the Mapping A new `BakeDataBlockMap` is passed to geometry nodes evaluation by the modifier. This map allows getting the `ID` pointer that should be used for a specific data-block name that is stored in baked data. It's also used to gather all the missing data mappings during evaluation. ### Weak ID References The baked/cached geometries may have references to other data-blocks (currently only materials, but in the future also e.g. instanced objects/collections). However, the pointers of these data-blocks are not stable over time. That is especially true when storing/loading the data from disk, but also just when playing back the animation. Therefore, the used data-blocks have to referenced in a different way at run-time. This is solved by adding `std::unique_ptr` to the run-time data of various geometry data-blocks. If the data-block is cached over a longer period of time (such that material pointers can't be used directly), it stores the material name (+ library name) used by each material slot. When the geometry is used again, the material pointers are restored using these weak name references and the `BakeDataBlockMap`. ### Manual Mapping Management There is a new `Data-Blocks` panel in the bake settings in the node editor sidebar that allows inspecting and modifying the data-blocks that are used when baking. The user can change what data-block a specific name is mapped to. Pull Request: https://projects.blender.org/blender/blender/pulls/117043 --- .../blenkernel/BKE_bake_data_block_id.hh | 56 +++ .../blenkernel/BKE_bake_data_block_map.hh | 43 +++ source/blender/blenkernel/BKE_bake_items.hh | 18 +- .../blenkernel/BKE_bake_items_socket.hh | 6 +- source/blender/blenkernel/BKE_curves.hh | 6 + source/blender/blenkernel/BKE_lib_id.hh | 4 + source/blender/blenkernel/BKE_mesh_types.hh | 6 + source/blender/blenkernel/BKE_pointcloud.hh | 6 + source/blender/blenkernel/CMakeLists.txt | 2 + .../blenkernel/intern/bake_data_block_map.cc | 47 +++ .../blender/blenkernel/intern/bake_items.cc | 79 +++- .../blenkernel/intern/bake_items_serialize.cc | 73 +++- .../blenkernel/intern/bake_items_socket.cc | 19 +- .../blenkernel/intern/curves_geometry.cc | 6 + source/blender/blenkernel/intern/lib_id.cc | 25 ++ source/blender/blenkernel/intern/mesh.cc | 5 + .../blender/blenkernel/intern/mesh_runtime.cc | 1 + .../blender/blenkernel/intern/pointcloud.cc | 6 + source/blender/blenkernel/intern/scene.cc | 9 +- source/blender/depsgraph/CMakeLists.txt | 2 + source/blender/depsgraph/DEG_depsgraph.hh | 18 +- .../depsgraph/DEG_depsgraph_writeback_sync.hh | 31 ++ source/blender/depsgraph/intern/depsgraph.hh | 10 + .../depsgraph/intern/depsgraph_eval.cc | 23 +- .../intern/depsgraph_writeback_sync.cc | 27 ++ .../editors/object/object_bake_simulation.cc | 12 + .../blender/editors/object/object_modifier.cc | 1 + source/blender/makesdna/DNA_modifier_types.h | 37 ++ .../blender/makesrna/intern/rna_modifier.cc | 69 ++++ source/blender/modifiers/CMakeLists.txt | 2 + source/blender/modifiers/MOD_nodes.hh | 2 + source/blender/modifiers/intern/MOD_nodes.cc | 351 ++++++++++++++++-- .../nodes/NOD_geometry_nodes_lazy_function.hh | 8 +- .../nodes/geometry/node_geometry_util.hh | 8 +- .../nodes/geometry/nodes/node_geo_bake.cc | 110 +++++- .../nodes/node_geo_simulation_input.cc | 22 +- .../nodes/node_geo_simulation_output.cc | 62 +++- .../intern/geometry_nodes_lazy_function.cc | 4 +- 38 files changed, 1105 insertions(+), 111 deletions(-) create mode 100644 source/blender/blenkernel/BKE_bake_data_block_id.hh create mode 100644 source/blender/blenkernel/BKE_bake_data_block_map.hh create mode 100644 source/blender/blenkernel/intern/bake_data_block_map.cc create mode 100644 source/blender/depsgraph/DEG_depsgraph_writeback_sync.hh create mode 100644 source/blender/depsgraph/intern/depsgraph_writeback_sync.cc diff --git a/source/blender/blenkernel/BKE_bake_data_block_id.hh b/source/blender/blenkernel/BKE_bake_data_block_id.hh new file mode 100644 index 00000000000..4df8b1a1ae0 --- /dev/null +++ b/source/blender/blenkernel/BKE_bake_data_block_id.hh @@ -0,0 +1,56 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bke + */ + +#include "BLI_string_ref.hh" +#include "BLI_struct_equality_utils.hh" +#include "BLI_vector.hh" + +#include "DNA_ID_enums.h" + +struct ID; +struct NodesModifierDataBlock; + +namespace blender::bke::bake { + +/** + * Unique weak reference to a data block within a #Main. It's used when caching/baking data-block + * references. Data-block pointers can't be used directly, because they are not stable over time + * and between Blender sessions. + */ +struct BakeDataBlockID { + ID_Type type; + /** + * Name of the data-block, without the type prefix. + */ + std::string id_name; + /** + * Name of the library data-block that the data-block is in. This refers to `Library.id.name` and + * not the file path. The type prefix of the name is omitted. If this is empty, the data-block is + * expected to be local and not linked. + */ + std::string lib_name; + + BakeDataBlockID(ID_Type type, std::string id_name, std::string lib_name); + BakeDataBlockID(const ID &id); + BakeDataBlockID(const NodesModifierDataBlock &data_block); + + uint64_t hash() const; + + friend std::ostream &operator<<(std::ostream &stream, const BakeDataBlockID &id); + + BLI_STRUCT_EQUALITY_OPERATORS_3(BakeDataBlockID, type, id_name, lib_name) +}; + +/** + * A list of weak data-block references for material slots. + */ +struct BakeMaterialsList : public Vector> {}; + +} // namespace blender::bke::bake diff --git a/source/blender/blenkernel/BKE_bake_data_block_map.hh b/source/blender/blenkernel/BKE_bake_data_block_map.hh new file mode 100644 index 00000000000..d67e0da4f47 --- /dev/null +++ b/source/blender/blenkernel/BKE_bake_data_block_map.hh @@ -0,0 +1,43 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bke + */ + +#include + +#include "BLI_string_ref.hh" +#include "BLI_struct_equality_utils.hh" + +#include "BKE_bake_data_block_id.hh" + +#include "DNA_ID_enums.h" + +namespace blender::bke::bake { + +/** + * Maps #BakeDataBlockID to the corresponding data-blocks. This is used during depsgraph evaluation + * to remap weak data-block references stored in baked data to the actual data-blocks at run-time. + * + * Also it keeps track of missing data-blocks, so that they can be added later. + */ +struct BakeDataBlockMap { + public: + /** + * Tries to retrieve the data block for the given key. If it's not explicitly mapped, it might be + * added to the mapping. If it's still not found, null is returned. + */ + virtual ID *lookup_or_remember_missing(const BakeDataBlockID &key) = 0; + + /** + * Tries to add the data block to the map. This may not succeed in all cases, e.g. if the + * implementation does not allow inserting new mapping items. + */ + virtual void try_add(ID &id) = 0; +}; + +} // namespace blender::bke::bake diff --git a/source/blender/blenkernel/BKE_bake_items.hh b/source/blender/blenkernel/BKE_bake_items.hh index 41f2f07f0ce..71da8cb7ae4 100644 --- a/source/blender/blenkernel/BKE_bake_items.hh +++ b/source/blender/blenkernel/BKE_bake_items.hh @@ -4,6 +4,7 @@ #pragma once +#include "BKE_bake_data_block_map.hh" #include "BKE_geometry_set.hh" namespace blender::bke::bake { @@ -42,13 +43,18 @@ class GeometryBakeItem : public BakeItem { GeometryBakeItem(GeometrySet geometry); /** - * Removes parts of the geometry that can't be stored in the simulation state: - * - Anonymous attributes can't be stored because it is not known which of them will or will not - * be used in the future. - * - Materials can't be stored directly, because they are linked ID data blocks that can't be - * restored from baked data currently. + * Removes parts of the geometry that can't be baked/cached (anonymous attributes) and replaces + * data-block pointers with #BakeDataBlockID. */ - static void cleanup_geometry(GeometrySet &geometry); + static void prepare_geometry_for_bake(GeometrySet &geometry, BakeDataBlockMap *data_block_map); + + /** + * The baked data does not have raw pointers to referenced data-blocks because those would become + * dangling quickly. Instead it has weak name-based references (#BakeDataBlockID). This function + * attempts to restore the actual data block pointers based on the weak references using the + * given mapping. + */ + static void try_restore_data_blocks(GeometrySet &geometry, BakeDataBlockMap *data_block_map); }; /** diff --git a/source/blender/blenkernel/BKE_bake_items_socket.hh b/source/blender/blenkernel/BKE_bake_items_socket.hh index c01419e3287..3d3cef579c0 100644 --- a/source/blender/blenkernel/BKE_bake_items_socket.hh +++ b/source/blender/blenkernel/BKE_bake_items_socket.hh @@ -35,8 +35,8 @@ struct BakeSocketConfig { * Create new bake items from the socket values. The socket values are not destructed, but they may * be in a moved-from state afterwards. */ -Array> move_socket_values_to_bake_items(Span socket_values, - const BakeSocketConfig &config); +Array> move_socket_values_to_bake_items( + Span socket_values, const BakeSocketConfig &config, BakeDataBlockMap *data_block_map); /** * Create socket values from bake items. @@ -52,6 +52,7 @@ Array> move_socket_values_to_bake_items(Span s void move_bake_items_to_socket_values( Span bake_items, const BakeSocketConfig &config, + BakeDataBlockMap *data_block_map, FunctionRef(int socket_index, const CPPType &)> make_attribute_field, Span r_socket_values); @@ -63,6 +64,7 @@ void move_bake_items_to_socket_values( void copy_bake_items_to_socket_values( Span bake_items, const BakeSocketConfig &config, + BakeDataBlockMap *data_block_map, FunctionRef(int, const CPPType &)> make_attribute_field, Span r_socket_values); diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index 7e62c8b1cb6..1ff4aad4b2b 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -33,6 +33,9 @@ class AttributeAccessor; class MutableAttributeAccessor; enum class AttrDomain : int8_t; } // namespace blender::bke +namespace blender::bke::bake { +struct BakeMaterialsList; +} namespace blender::bke { @@ -111,6 +114,9 @@ class CurvesGeometryRuntime { /** Normal direction vectors for each evaluated point. */ mutable SharedCache> evaluated_normal_cache; + + /** Stores weak references to material data blocks. */ + std::unique_ptr bake_materials; }; /** diff --git a/source/blender/blenkernel/BKE_lib_id.hh b/source/blender/blenkernel/BKE_lib_id.hh index 8625c40d3de..3958d4d497d 100644 --- a/source/blender/blenkernel/BKE_lib_id.hh +++ b/source/blender/blenkernel/BKE_lib_id.hh @@ -204,6 +204,10 @@ void BKE_libblock_ensure_unique_name(Main *bmain, ID *id) ATTR_NONNULL(); ID *BKE_libblock_find_name(Main *bmain, short type, const char *name) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); ID *BKE_libblock_find_session_uid(Main *bmain, short type, uint32_t session_uid); +ID *BKE_libblock_find_name_and_library(Main *bmain, + short type, + const char *name, + const char *lib_name); /** * Duplicate (a.k.a. deep copy) common processing options. * See also eDupli_ID_Flags for options controlling what kind of IDs to duplicate. diff --git a/source/blender/blenkernel/BKE_mesh_types.hh b/source/blender/blenkernel/BKE_mesh_types.hh index 3148e4e3ea0..3a869c079de 100644 --- a/source/blender/blenkernel/BKE_mesh_types.hh +++ b/source/blender/blenkernel/BKE_mesh_types.hh @@ -29,6 +29,9 @@ struct SubsurfRuntimeData; namespace blender::bke { struct EditMeshData; } +namespace blender::bke::bake { +struct BakeMaterialsList; +} /** #MeshRuntime.wrapper_type */ enum eMeshWrapperType { @@ -207,6 +210,9 @@ struct MeshRuntime { */ BitVector<> subsurf_optimal_display_edges; + /** Stores weak references to material data blocks. */ + std::unique_ptr bake_materials; + MeshRuntime(); ~MeshRuntime(); }; diff --git a/source/blender/blenkernel/BKE_pointcloud.hh b/source/blender/blenkernel/BKE_pointcloud.hh index 7ee442f9b0a..78a695db656 100644 --- a/source/blender/blenkernel/BKE_pointcloud.hh +++ b/source/blender/blenkernel/BKE_pointcloud.hh @@ -22,6 +22,9 @@ struct Main; struct Object; struct PointCloud; struct Scene; +namespace blender::bke::bake { +struct BakeMaterialsList; +} /* PointCloud datablock */ extern const char *POINTCLOUD_ATTR_POSITION; @@ -37,6 +40,9 @@ struct PointCloudRuntime { */ mutable SharedCache> bounds_cache; + /** Stores weak references to material data blocks. */ + std::unique_ptr bake_materials; + MEM_CXX_CLASS_ALLOC_FUNCS("PointCloudRuntime"); }; diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 7c487a29b05..39f390f379c 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -70,6 +70,7 @@ set(SRC intern/attribute_math.cc intern/autoexec.cc intern/bake_geometry_nodes_modifier.cc + intern/bake_data_block_map.cc intern/bake_items.cc intern/bake_items_paths.cc intern/bake_items_serialize.cc @@ -336,6 +337,7 @@ set(SRC BKE_attribute_math.hh BKE_autoexec.hh BKE_bake_geometry_nodes_modifier.hh + BKE_bake_data_block_map.hh BKE_bake_items.hh BKE_bake_items_paths.hh BKE_bake_items_serialize.hh diff --git a/source/blender/blenkernel/intern/bake_data_block_map.cc b/source/blender/blenkernel/intern/bake_data_block_map.cc new file mode 100644 index 00000000000..e56a9e266a5 --- /dev/null +++ b/source/blender/blenkernel/intern/bake_data_block_map.cc @@ -0,0 +1,47 @@ +/* SPDX-FileCopyrightText: 2005 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "BLI_hash.hh" + +#include "BKE_bake_data_block_map.hh" + +#include "DNA_ID.h" +#include "DNA_modifier_types.h" + +namespace blender::bke::bake { + +BakeDataBlockID::BakeDataBlockID(ID_Type type, std::string id_name, std::string lib_name) + : type(type), id_name(std::move(id_name)), lib_name(std::move(lib_name)) +{ +} + +BakeDataBlockID::BakeDataBlockID(const ID &id) +{ + this->type = GS(id.name); + this->id_name = id.name + 2; + if (id.lib) { + this->lib_name = id.lib->id.name + 2; + } +} + +BakeDataBlockID::BakeDataBlockID(const NodesModifierDataBlock &data_block) + : BakeDataBlockID(ID_Type(data_block.id_type), + StringRef(data_block.id_name), + StringRef(data_block.lib_name)) +{ +} + +std::ostream &operator<<(std::ostream &stream, const BakeDataBlockID &id) +{ + return stream << "(" << id.id_name << ", Lib: " << id.lib_name << ")"; +} + +uint64_t BakeDataBlockID::hash() const +{ + return get_default_hash(this->type, this->id_name, this->lib_name); +} + +} // namespace blender::bke::bake diff --git a/source/blender/blenkernel/intern/bake_items.cc b/source/blender/blenkernel/intern/bake_items.cc index 26652042fda..2cd8d9e2507 100644 --- a/source/blender/blenkernel/intern/bake_items.cc +++ b/source/blender/blenkernel/intern/bake_items.cc @@ -27,27 +27,49 @@ using DictionaryValuePtr = std::shared_ptr; GeometryBakeItem::GeometryBakeItem(GeometrySet geometry) : geometry(std::move(geometry)) {} -static void remove_materials(Material ***materials, short *materials_num) +static std::unique_ptr materials_to_weak_references( + Material ***materials, short *materials_num, BakeDataBlockMap *data_block_map) { + if (*materials_num == 0) { + return {}; + } + auto materials_list = std::make_unique(); + materials_list->resize(*materials_num); + for (const int i : materials_list->index_range()) { + Material *material = (*materials)[i]; + if (material) { + (*materials_list)[i] = BakeDataBlockID(material->id); + if (data_block_map) { + data_block_map->try_add(material->id); + } + } + } + MEM_SAFE_FREE(*materials); *materials_num = 0; + + return materials_list; } -void GeometryBakeItem::cleanup_geometry(GeometrySet &main_geometry) +void GeometryBakeItem::prepare_geometry_for_bake(GeometrySet &main_geometry, + BakeDataBlockMap *data_block_map) { main_geometry.ensure_owns_all_data(); main_geometry.modify_geometry_sets([&](GeometrySet &geometry) { if (Mesh *mesh = geometry.get_mesh_for_write()) { mesh->attributes_for_write().remove_anonymous(); - remove_materials(&mesh->mat, &mesh->totcol); + mesh->runtime->bake_materials = materials_to_weak_references( + &mesh->mat, &mesh->totcol, data_block_map); } if (Curves *curves = geometry.get_curves_for_write()) { curves->geometry.wrap().attributes_for_write().remove_anonymous(); - remove_materials(&curves->mat, &curves->totcol); + curves->geometry.runtime->bake_materials = materials_to_weak_references( + &curves->mat, &curves->totcol, data_block_map); } if (PointCloud *pointcloud = geometry.get_pointcloud_for_write()) { pointcloud->attributes_for_write().remove_anonymous(); - remove_materials(&pointcloud->mat, &pointcloud->totcol); + pointcloud->runtime->bake_materials = materials_to_weak_references( + &pointcloud->mat, &pointcloud->totcol, data_block_map); } if (bke::Instances *instances = geometry.get_instances_for_write()) { instances->attributes_for_write().remove_anonymous(); @@ -59,6 +81,53 @@ void GeometryBakeItem::cleanup_geometry(GeometrySet &main_geometry) }); } +static void restore_materials(Material ***materials, + short *materials_num, + std::unique_ptr materials_list, + BakeDataBlockMap *data_block_map) +{ + if (!materials_list) { + return; + } + BLI_assert(*materials == nullptr); + *materials_num = materials_list->size(); + *materials = MEM_cnew_array(materials_list->size(), __func__); + if (!data_block_map) { + return; + } + + for (const int i : materials_list->index_range()) { + const std::optional &data_block_id = (*materials_list)[i]; + if (data_block_id) { + (*materials)[i] = reinterpret_cast( + data_block_map->lookup_or_remember_missing(*data_block_id)); + } + } +} + +void GeometryBakeItem::try_restore_data_blocks(GeometrySet &main_geometry, + BakeDataBlockMap *data_block_map) +{ + main_geometry.modify_geometry_sets([&](GeometrySet &geometry) { + if (Mesh *mesh = geometry.get_mesh_for_write()) { + restore_materials( + &mesh->mat, &mesh->totcol, std::move(mesh->runtime->bake_materials), data_block_map); + } + if (Curves *curves = geometry.get_curves_for_write()) { + restore_materials(&curves->mat, + &curves->totcol, + std::move(curves->geometry.runtime->bake_materials), + data_block_map); + } + if (PointCloud *pointcloud = geometry.get_pointcloud_for_write()) { + restore_materials(&pointcloud->mat, + &pointcloud->totcol, + std::move(pointcloud->runtime->bake_materials), + data_block_map); + } + }); +} + PrimitiveBakeItem::PrimitiveBakeItem(const CPPType &type, const void *value) : type_(type) { value_ = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); diff --git a/source/blender/blenkernel/intern/bake_items_serialize.cc b/source/blender/blenkernel/intern/bake_items_serialize.cc index 92170da7131..ddb6719340d 100644 --- a/source/blender/blenkernel/intern/bake_items_serialize.cc +++ b/source/blender/blenkernel/intern/bake_items_serialize.cc @@ -365,6 +365,32 @@ template return *r_data != nullptr; } +[[nodiscard]] static bool load_materials(const io::serialize::ArrayValue &io_materials, + std::unique_ptr &materials) +{ + if (io_materials.elements().is_empty()) { + return true; + } + materials = std::make_unique(); + for (const auto &io_material_value : io_materials.elements()) { + if (io_material_value->type() == io::serialize::eValueType::Null) { + materials->append(std::nullopt); + continue; + } + const auto *io_material = io_material_value->as_dictionary_value(); + if (!io_material) { + return false; + } + std::optional id_name = io_material->lookup_str("name"); + if (!id_name) { + return false; + } + std::string lib_name = io_material->lookup_str("lib_name").value_or(""); + materials->append(BakeDataBlockID(ID_MA, std::move(*id_name), std::move(lib_name))); + } + return true; +} + [[nodiscard]] static bool load_attributes(const io::serialize::ArrayValue &io_attributes, MutableAttributeAccessor &attributes, const BlobReader &blob_reader, @@ -450,6 +476,12 @@ static PointCloud *try_load_pointcloud(const DictionaryValue &io_geometry, if (!load_attributes(*io_attributes, attributes, blob_reader, blob_sharing)) { return cancel(); } + + if (const io::serialize::ArrayValue *io_materials = io_pointcloud->lookup_array("materials")) { + if (!load_materials(*io_materials, pointcloud->runtime->bake_materials)) { + return cancel(); + } + } return pointcloud; } @@ -499,6 +531,12 @@ static Curves *try_load_curves(const DictionaryValue &io_geometry, return cancel(); } + if (const io::serialize::ArrayValue *io_materials = io_curves->lookup_array("materials")) { + if (!load_materials(*io_materials, curves.runtime->bake_materials)) { + return cancel(); + } + } + curves.update_curve_types(); return curves_id; @@ -554,6 +592,12 @@ static Mesh *try_load_mesh(const DictionaryValue &io_geometry, return cancel(); } + if (const io::serialize::ArrayValue *io_materials = io_mesh->lookup_array("materials")) { + if (!load_materials(*io_materials, mesh->runtime->bake_materials)) { + return cancel(); + } + } + return mesh; } @@ -630,20 +674,23 @@ static GeometrySet load_geometry(const DictionaryValue &io_geometry, return geometry; } -static std::shared_ptr serialize_material_slots( - const Span material_slots) +static std::shared_ptr serialize_materials( + const std::unique_ptr &materials) { auto io_materials = std::make_shared(); - for (const Material *material : material_slots) { - if (material == nullptr) { - io_materials->append_null(); + if (!materials) { + return io_materials; + } + for (const std::optional &material : *materials) { + if (material) { + auto io_material = io_materials->append_dict(); + io_material->append_str("name", material->id_name); + if (!material->lib_name.empty()) { + io_material->append_str("lib_name", material->lib_name); + } } else { - auto io_material = io_materials->append_dict(); - io_material->append_str("name", material->id.name + 2); - if (material->id.lib != nullptr) { - io_material->append_str("lib_name", material->id.lib->id.name + 2); - } + io_materials->append_null(); } } return io_materials; @@ -707,7 +754,7 @@ static std::shared_ptr serialize_geometry_set(const GeometrySet mesh.runtime->face_offsets_sharing_info)); } - auto io_materials = serialize_material_slots({mesh.mat, mesh.totcol}); + auto io_materials = serialize_materials(mesh.runtime->bake_materials); io_mesh->append("materials", io_materials); auto io_attributes = serialize_attributes(mesh.attributes(), blob_writer, blob_sharing, {}); @@ -719,7 +766,7 @@ static std::shared_ptr serialize_geometry_set(const GeometrySet io_pointcloud->append_int("num_points", pointcloud.totpoint); - auto io_materials = serialize_material_slots({pointcloud.mat, pointcloud.totcol}); + auto io_materials = serialize_materials(pointcloud.runtime->bake_materials); io_pointcloud->append("materials", io_materials); auto io_attributes = serialize_attributes( @@ -744,7 +791,7 @@ static std::shared_ptr serialize_geometry_set(const GeometrySet curves.runtime->curve_offsets_sharing_info)); } - auto io_materials = serialize_material_slots({curves_id.mat, curves_id.totcol}); + auto io_materials = serialize_materials(curves.runtime->bake_materials); io_curves->append("materials", io_materials); auto io_attributes = serialize_attributes(curves.attributes(), blob_writer, blob_sharing, {}); diff --git a/source/blender/blenkernel/intern/bake_items_socket.cc b/source/blender/blenkernel/intern/bake_items_socket.cc index 204e4f0814f..2c706dcd93c 100644 --- a/source/blender/blenkernel/intern/bake_items_socket.cc +++ b/source/blender/blenkernel/intern/bake_items_socket.cc @@ -3,16 +3,15 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BKE_bake_items_socket.hh" - #include "BKE_geometry_fields.hh" #include "BKE_node.hh" - #include "BKE_node_socket_value.hh" namespace blender::bke::bake { Array> move_socket_values_to_bake_items(const Span socket_values, - const BakeSocketConfig &config) + const BakeSocketConfig &config, + BakeDataBlockMap *data_block_map) { BLI_assert(socket_values.size() == config.types.size()); BLI_assert(socket_values.size() == config.geometries_by_attribute.size()); @@ -99,7 +98,7 @@ Array> move_socket_values_to_bake_items(const Span(bake_items[i].get())->geometry; - GeometryBakeItem::cleanup_geometry(geometry); + GeometryBakeItem::prepare_geometry_for_bake(geometry, data_block_map); } return bake_items; @@ -192,6 +191,14 @@ static void rename_attributes(const Span geometries, } } +static void restore_data_blocks(const Span geometries, + BakeDataBlockMap *data_block_map) +{ + for (GeometrySet *main_geometry : geometries) { + GeometryBakeItem::try_restore_data_blocks(*main_geometry, data_block_map); + } +} + static void default_initialize_socket_value(const eNodeSocketDatatype socket_type, void *r_value) { const char *socket_idname = nodeStaticSocketType(socket_type, 0); @@ -203,6 +210,7 @@ static void default_initialize_socket_value(const eNodeSocketDatatype socket_typ void move_bake_items_to_socket_values( const Span bake_items, const BakeSocketConfig &config, + BakeDataBlockMap *data_block_map, FunctionRef(int, const CPPType &)> make_attribute_field, const Span r_socket_values) @@ -237,11 +245,13 @@ void move_bake_items_to_socket_values( } rename_attributes(geometries, attribute_map); + restore_data_blocks(geometries, data_block_map); } void copy_bake_items_to_socket_values( const Span bake_items, const BakeSocketConfig &config, + BakeDataBlockMap *data_block_map, FunctionRef(int, const CPPType &)> make_attribute_field, const Span r_socket_values) @@ -273,6 +283,7 @@ void copy_bake_items_to_socket_values( } rename_attributes(geometries, attribute_map); + restore_data_blocks(geometries, data_block_map); } } // namespace blender::bke::bake diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index 90fc1e0ab61..9461d354abf 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -26,6 +26,7 @@ #include "BKE_attribute.hh" #include "BKE_attribute_math.hh" +#include "BKE_bake_data_block_id.hh" #include "BKE_curves.hh" #include "BKE_curves_utils.hh" #include "BKE_customdata.hh" @@ -119,6 +120,11 @@ static void copy_curves_geometry(CurvesGeometry &dst, const CurvesGeometry &src) dst.runtime->evaluated_length_cache = src.runtime->evaluated_length_cache; dst.runtime->evaluated_tangent_cache = src.runtime->evaluated_tangent_cache; dst.runtime->evaluated_normal_cache = src.runtime->evaluated_normal_cache; + + if (src.runtime->bake_materials) { + dst.runtime->bake_materials = std::make_unique( + *src.runtime->bake_materials); + } } CurvesGeometry::CurvesGeometry(const CurvesGeometry &other) : CurvesGeometry() diff --git a/source/blender/blenkernel/intern/lib_id.cc b/source/blender/blenkernel/intern/lib_id.cc index 5ac7168679b..5c7d6261c53 100644 --- a/source/blender/blenkernel/intern/lib_id.cc +++ b/source/blender/blenkernel/intern/lib_id.cc @@ -1489,6 +1489,31 @@ ID *BKE_libblock_find_session_uid(Main *bmain, const short type, const uint32_t return nullptr; } +ID *BKE_libblock_find_name_and_library(Main *bmain, + const short type, + const char *name, + const char *lib_name) +{ + ListBase *lb = which_libbase(bmain, type); + BLI_assert(lb != nullptr); + LISTBASE_FOREACH (ID *, id, lb) { + if (!STREQ(id->name + 2, name)) { + continue; + } + if (lib_name == nullptr || lib_name[0] == '\0') { + if (id->lib == nullptr) { + return id; + } + return nullptr; + } + if (!STREQ(id->lib->id.name + 2, lib_name)) { + continue; + } + return id; + } + return nullptr; +} + void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) { #define ID_SORT_STEP_SIZE 512 diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index a8ec4cadc1c..1d08cc667ad 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -44,6 +44,7 @@ #include "BKE_anim_data.h" #include "BKE_attribute.hh" +#include "BKE_bake_data_block_id.hh" #include "BKE_bpath.h" #include "BKE_deform.hh" #include "BKE_editmesh.hh" @@ -146,6 +147,10 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int mesh_dst->runtime->vert_to_face_map_cache = mesh_src->runtime->vert_to_face_map_cache; mesh_dst->runtime->vert_to_corner_map_cache = mesh_src->runtime->vert_to_corner_map_cache; mesh_dst->runtime->corner_to_face_map_cache = mesh_src->runtime->corner_to_face_map_cache; + if (mesh_src->runtime->bake_materials) { + mesh_dst->runtime->bake_materials = std::make_unique( + *mesh_src->runtime->bake_materials); + } /* Only do tessface if we have no faces. */ const bool do_tessface = ((mesh_src->totface_legacy != 0) && (mesh_src->faces_num == 0)); diff --git a/source/blender/blenkernel/intern/mesh_runtime.cc b/source/blender/blenkernel/intern/mesh_runtime.cc index c9bac73b546..6301bc67fd7 100644 --- a/source/blender/blenkernel/intern/mesh_runtime.cc +++ b/source/blender/blenkernel/intern/mesh_runtime.cc @@ -17,6 +17,7 @@ #include "BLI_task.hh" #include "BLI_timeit.hh" +#include "BKE_bake_data_block_id.hh" #include "BKE_bvhutils.hh" #include "BKE_customdata.hh" #include "BKE_editmesh_cache.hh" diff --git a/source/blender/blenkernel/intern/pointcloud.cc b/source/blender/blenkernel/intern/pointcloud.cc index bf5ef45bc11..daa2ccc412a 100644 --- a/source/blender/blenkernel/intern/pointcloud.cc +++ b/source/blender/blenkernel/intern/pointcloud.cc @@ -25,6 +25,7 @@ #include "BLI_vector.hh" #include "BKE_anim_data.h" +#include "BKE_bake_data_block_id.hh" #include "BKE_customdata.hh" #include "BKE_geometry_set.hh" #include "BKE_global.h" @@ -86,6 +87,11 @@ static void pointcloud_copy_data(Main * /*bmain*/, pointcloud_dst->runtime = new blender::bke::PointCloudRuntime(); pointcloud_dst->runtime->bounds_cache = pointcloud_src->runtime->bounds_cache; + if (pointcloud_src->runtime->bake_materials) { + pointcloud_dst->runtime->bake_materials = + std::make_unique( + *pointcloud_src->runtime->bake_materials); + } pointcloud_dst->batch_cache = nullptr; } diff --git a/source/blender/blenkernel/intern/scene.cc b/source/blender/blenkernel/intern/scene.cc index 9fd7440fcc2..190848c0c1c 100644 --- a/source/blender/blenkernel/intern/scene.cc +++ b/source/blender/blenkernel/intern/scene.cc @@ -24,6 +24,7 @@ #include "DNA_mask_types.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" +#include "DNA_modifier_types.h" #include "DNA_node_types.h" #include "DNA_object_types.h" #include "DNA_rigidbody_types.h" @@ -79,6 +80,7 @@ #include "BKE_linestyle.h" #include "BKE_main.hh" #include "BKE_mask.h" +#include "BKE_modifier.hh" #include "BKE_node.hh" #include "BKE_node_runtime.hh" #include "BKE_object.hh" @@ -97,6 +99,7 @@ #include "DEG_depsgraph_build.hh" #include "DEG_depsgraph_debug.hh" #include "DEG_depsgraph_query.hh" +#include "DEG_depsgraph_writeback_sync.hh" #include "RE_engine.h" @@ -2567,7 +2570,7 @@ static void scene_graph_update_tagged(Depsgraph *depsgraph, Main *bmain, bool on prepare_mesh_for_viewport_render(bmain, scene, view_layer); /* Update all objects: drivers, matrices, etc. flags set * by depsgraph or manual, no layer check here, gets correct flushed. */ - DEG_evaluate_on_refresh(depsgraph); + DEG_evaluate_on_refresh(depsgraph, DEG_EVALUATE_SYNC_WRITEBACK_YES); /* Update sound system. */ BKE_scene_update_sound(depsgraph, bmain); /* Notify python about depsgraph update. */ @@ -2647,10 +2650,10 @@ void BKE_scene_graph_update_for_newframe_ex(Depsgraph *depsgraph, const bool cle * lose any possible unkeyed changes made by the handler. */ if (pass == 0) { const float frame = BKE_scene_frame_get(scene); - DEG_evaluate_on_framechange(depsgraph, frame); + DEG_evaluate_on_framechange(depsgraph, frame, DEG_EVALUATE_SYNC_WRITEBACK_YES); } else { - DEG_evaluate_on_refresh(depsgraph); + DEG_evaluate_on_refresh(depsgraph, DEG_EVALUATE_SYNC_WRITEBACK_YES); } /* Update sound system animation. */ BKE_scene_update_sound(depsgraph, bmain); diff --git a/source/blender/depsgraph/CMakeLists.txt b/source/blender/depsgraph/CMakeLists.txt index 35d7da7fc83..00420e0085e 100644 --- a/source/blender/depsgraph/CMakeLists.txt +++ b/source/blender/depsgraph/CMakeLists.txt @@ -88,6 +88,7 @@ set(SRC intern/depsgraph_tag.cc intern/depsgraph_type.cc intern/depsgraph_update.cc + intern/depsgraph_writeback_sync.cc DEG_depsgraph.hh DEG_depsgraph_build.hh @@ -95,6 +96,7 @@ set(SRC DEG_depsgraph_light_linking.hh DEG_depsgraph_physics.hh DEG_depsgraph_query.hh + DEG_depsgraph_writeback_sync.hh intern/builder/deg_builder.h intern/builder/deg_builder_cache.h diff --git a/source/blender/depsgraph/DEG_depsgraph.hh b/source/blender/depsgraph/DEG_depsgraph.hh index 0a251552ece..478c16221d4 100644 --- a/source/blender/depsgraph/DEG_depsgraph.hh +++ b/source/blender/depsgraph/DEG_depsgraph.hh @@ -173,18 +173,32 @@ void DEG_ids_restore_recalc(Depsgraph *depsgraph); /** \name Graph Evaluation * \{ */ +enum DepsgraphEvaluateSyncWriteback { + DEG_EVALUATE_SYNC_WRITEBACK_NO, + /** + * Allow writing back to original data after depsgraph evaluation. The change to original data + * may add new ID relations and may tag the depsgraph as changed again. + */ + DEG_EVALUATE_SYNC_WRITEBACK_YES, +}; + /** * Frame changed recalculation entry point. * * \note The frame-change happened for root scene that graph belongs to. */ -void DEG_evaluate_on_framechange(Depsgraph *graph, float frame); +void DEG_evaluate_on_framechange( + Depsgraph *graph, + float frame, + DepsgraphEvaluateSyncWriteback sync_writeback = DEG_EVALUATE_SYNC_WRITEBACK_NO); /** * Data changed recalculation entry point. * Evaluate all nodes tagged for updating. */ -void DEG_evaluate_on_refresh(Depsgraph *graph); +void DEG_evaluate_on_refresh( + Depsgraph *graph, + DepsgraphEvaluateSyncWriteback sync_writeback = DEG_EVALUATE_SYNC_WRITEBACK_NO); /** \} */ diff --git a/source/blender/depsgraph/DEG_depsgraph_writeback_sync.hh b/source/blender/depsgraph/DEG_depsgraph_writeback_sync.hh new file mode 100644 index 00000000000..bc49954c348 --- /dev/null +++ b/source/blender/depsgraph/DEG_depsgraph_writeback_sync.hh @@ -0,0 +1,31 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bke + * + * This file provides an API that can be used to modify original (as opposed to evaluated) + * data-blocks after depsgraph evaluation. For some data (e.g. animated properties), this is done + * during depsgraph evaluation. However, this is not possible in all cases. For example, if the + * change to the original data adds a new relation between data-blocks, a user-count (#ID.us) has + * to be increased. This counter is not atomic and can therefore not be modified arbitrarily from + * different threads. + */ + +#include + +struct Depsgraph; + +namespace blender::deg::sync_writeback { + +/** + * Add a writeback task during depsgraph evaluation. The given function is called after depsgraph + * evaluation is done if the depsgraph is active. It is allowed to change original data blocks and + * even to add new relations. + */ +void add(Depsgraph &depsgraph, std::function fn); + +} // namespace blender::deg::sync_writeback diff --git a/source/blender/depsgraph/intern/depsgraph.hh b/source/blender/depsgraph/intern/depsgraph.hh index b14035e324c..a4bc13ba10f 100644 --- a/source/blender/depsgraph/intern/depsgraph.hh +++ b/source/blender/depsgraph/intern/depsgraph.hh @@ -14,6 +14,8 @@ #pragma once +#include +#include #include #include "MEM_guardedalloc.h" @@ -174,6 +176,14 @@ struct Depsgraph { /* The number of times this graph has been evaluated. */ uint64_t update_count; + /** + * Stores functions that can be called after depsgraph evaluation to writeback some changes to + * original data. Also see `DEG_depsgraph_writeback_sync.hh`. + */ + Vector> sync_writeback_callbacks; + /** Needs to be locked when adding a writeback callback during evaluation. */ + std::mutex sync_writeback_callbacks_mutex; + MEM_CXX_CLASS_ALLOC_FUNCS("Depsgraph"); }; diff --git a/source/blender/depsgraph/intern/depsgraph_eval.cc b/source/blender/depsgraph/intern/depsgraph_eval.cc index 3a1966a818e..3ed12e3a39d 100644 --- a/source/blender/depsgraph/intern/depsgraph_eval.cc +++ b/source/blender/depsgraph/intern/depsgraph_eval.cc @@ -20,6 +20,7 @@ #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" +#include "DEG_depsgraph_writeback_sync.hh" #include "intern/eval/deg_eval.h" #include "intern/eval/deg_eval_flush.h" @@ -33,7 +34,8 @@ namespace deg = blender::deg; -static void deg_flush_updates_and_refresh(deg::Depsgraph *deg_graph) +static void deg_flush_updates_and_refresh(deg::Depsgraph *deg_graph, + const DepsgraphEvaluateSyncWriteback sync_writeback) { /* Update the time on the cow scene. */ if (deg_graph->scene_cow) { @@ -43,9 +45,18 @@ static void deg_flush_updates_and_refresh(deg::Depsgraph *deg_graph) deg::graph_tag_ids_for_visible_update(deg_graph); deg::deg_graph_flush_updates(deg_graph); deg::deg_evaluate_on_refresh(deg_graph); + + if (sync_writeback == DEG_EVALUATE_SYNC_WRITEBACK_YES) { + if (deg_graph->is_active) { + for (std::function &fn : deg_graph->sync_writeback_callbacks) { + fn(); + } + } + } + deg_graph->sync_writeback_callbacks.clear(); } -void DEG_evaluate_on_refresh(Depsgraph *graph) +void DEG_evaluate_on_refresh(Depsgraph *graph, const DepsgraphEvaluateSyncWriteback sync_writeback) { deg::Depsgraph *deg_graph = reinterpret_cast(graph); const Scene *scene = DEG_get_input_scene(graph); @@ -66,10 +77,12 @@ void DEG_evaluate_on_refresh(Depsgraph *graph) deg_graph->tag_time_source(); } - deg_flush_updates_and_refresh(deg_graph); + deg_flush_updates_and_refresh(deg_graph, sync_writeback); } -void DEG_evaluate_on_framechange(Depsgraph *graph, float frame) +void DEG_evaluate_on_framechange(Depsgraph *graph, + float frame, + const DepsgraphEvaluateSyncWriteback sync_writeback) { deg::Depsgraph *deg_graph = reinterpret_cast(graph); const Scene *scene = DEG_get_input_scene(graph); @@ -77,5 +90,5 @@ void DEG_evaluate_on_framechange(Depsgraph *graph, float frame) deg_graph->tag_time_source(); deg_graph->frame = frame; deg_graph->ctime = BKE_scene_frame_to_ctime(scene, frame); - deg_flush_updates_and_refresh(deg_graph); + deg_flush_updates_and_refresh(deg_graph, sync_writeback); } diff --git a/source/blender/depsgraph/intern/depsgraph_writeback_sync.cc b/source/blender/depsgraph/intern/depsgraph_writeback_sync.cc new file mode 100644 index 00000000000..bcc56947042 --- /dev/null +++ b/source/blender/depsgraph/intern/depsgraph_writeback_sync.cc @@ -0,0 +1,27 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "DEG_depsgraph.hh" +#include "DEG_depsgraph_writeback_sync.hh" + +#include "BLI_map.hh" + +#include "depsgraph.hh" + +namespace blender::deg::sync_writeback { + +void add(::Depsgraph &depsgraph, std::function fn) +{ + deg::Depsgraph °_graph = reinterpret_cast(depsgraph); + if (!deg_graph.is_active) { + return; + } + + std::lock_guard lock{deg_graph.sync_writeback_callbacks_mutex}; + deg_graph.sync_writeback_callbacks.append(std::move(fn)); +} + +} // namespace blender::deg::sync_writeback diff --git a/source/blender/editors/object/object_bake_simulation.cc b/source/blender/editors/object/object_bake_simulation.cc index 980d809cddf..49de4a4dd32 100644 --- a/source/blender/editors/object/object_bake_simulation.cc +++ b/source/blender/editors/object/object_bake_simulation.cc @@ -21,6 +21,7 @@ #include "ED_screen.hh" +#include "DNA_array_utils.hh" #include "DNA_curves_types.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" @@ -706,6 +707,17 @@ static void try_delete_bake( else if (auto *node_cache = modifier_cache.bake_cache_by_id.lookup_ptr(bake_id)) { (*node_cache)->reset(); } + NodesModifierBake *bake = nmd.find_bake(bake_id); + if (!bake) { + return; + } + dna::array::clear(&bake->data_blocks, + &bake->data_blocks_num, + &bake->active_data_block, + [](NodesModifierDataBlock *data_block) { + nodes_modifier_data_block_destruct(data_block, true); + }); + const std::optional bake_path = bake::get_node_bake_path( *bmain, object, nmd, bake_id); if (!bake_path) { diff --git a/source/blender/editors/object/object_modifier.cc b/source/blender/editors/object/object_modifier.cc index b701b4ce048..129942afbac 100644 --- a/source/blender/editors/object/object_modifier.cc +++ b/source/blender/editors/object/object_modifier.cc @@ -16,6 +16,7 @@ #include "DNA_anim_types.h" #include "DNA_armature_types.h" +#include "DNA_array_utils.hh" #include "DNA_curve_types.h" #include "DNA_dynamicpaint_types.h" #include "DNA_fluid_types.h" diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index c53dc3d90e9..ed2c7b7c387 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -2342,6 +2342,31 @@ typedef struct NodesModifierSettings { struct IDProperty *properties; } NodesModifierSettings; +/** + * Maps a name (+ optional library name) to a data-block. The name can be stored on disk and is + * remapped to the data-block when the data is loaded. + * + * At run-time, #BakeDataBlockID is used to pair up the data-block and library name. + */ +typedef struct NodesModifierDataBlock { + /** + * Name of the data-block. Can be empty in which case the name of the `id` below is used. + * This only needs to be set manually when the name stored on disk does not exist in the .blend + * file anymore, because e.g. the ID has been renamed. + */ + char *id_name; + /** + * Name of the library the ID is in. Can be empty when the ID is not linked or when `id_name` is + * empty as well and thus the names from the `id` below are used. + */ + char *lib_name; + /** ID that this is mapped to. */ + struct ID *id; + /** Type of ID that is referenced by this mapping. */ + int id_type; + char _pad[4]; +} NodesModifierDataBlock; + typedef struct NodesModifierBake { /** An id that references a nested node in the node tree. Also see #bNestedNodeRef. */ int id; @@ -2361,6 +2386,17 @@ typedef struct NodesModifierBake { */ int frame_start; int frame_end; + + /** + * Maps data-block names to actual data-blocks, so that names stored in caches or on disk can be + * remapped to actual IDs on load. The mapping also makes sure that IDs referenced by baked data + * are not automatically removed because they are not referenced anymore. Furthermore, it allows + * the modifier to add all required IDs to the dependency graph before actually loading the baked + * data. + */ + int data_blocks_num; + int active_data_block; + NodesModifierDataBlock *data_blocks; } NodesModifierBake; typedef struct NodesModifierPanel { @@ -2398,6 +2434,7 @@ typedef struct NodesModifierData { char _pad[3]; int bakes_num; NodesModifierBake *bakes; + char _pad2[4]; int panels_num; NodesModifierPanel *panels; diff --git a/source/blender/makesrna/intern/rna_modifier.cc b/source/blender/makesrna/intern/rna_modifier.cc index ebffc50ccff..bf73298d827 100644 --- a/source/blender/makesrna/intern/rna_modifier.cc +++ b/source/blender/makesrna/intern/rna_modifier.cc @@ -1791,6 +1791,12 @@ static PointerRNA rna_NodesModifierBake_node_get(PointerRNA *ptr) return RNA_pointer_create(const_cast(&tree->id), &RNA_Node, const_cast(node)); } +static StructRNA *rna_NodesModifierBake_data_block_typef(PointerRNA *ptr) +{ + NodesModifierDataBlock *data_block = static_cast(ptr->data); + return ID_code_to_RNA_type(data_block->id_type); +} + bool rna_GreasePencilModifier_material_poll(PointerRNA *ptr, PointerRNA value) { Object *ob = reinterpret_cast(ptr->owner_id); @@ -7269,8 +7275,63 @@ static void rna_def_modifier_weightednormal(BlenderRNA *brna) RNA_define_lib_overridable(false); } +static void rna_def_modifier_nodes_data_block(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "NodesModifierDataBlock", nullptr); + RNA_def_struct_sdna(srna, "NodesModifierDataBlock"); + + RNA_define_lib_overridable(true); + + prop = RNA_def_property(srna, "id_name", PROP_STRING, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text( + prop, "Data-Block Name", "Name that is mapped to the referenced data-block"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "lib_name", PROP_STRING, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, + "Library Name", + "Used when the data block is not local to the current .blend file but " + "is linked from some library"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "id", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "ID"); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_pointer_funcs( + prop, nullptr, nullptr, "rna_NodesModifierBake_data_block_typef", nullptr); + RNA_def_property_ui_text(prop, "Data-Block", ""); + RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); + + prop = RNA_def_property(srna, "id_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_id_type_items); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE | PROP_EDITABLE); + + RNA_define_lib_overridable(false); +} + +static void rna_def_modifier_nodes_bake_data_blocks(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "NodesModifierBakeDataBlocks", nullptr); + RNA_def_struct_sdna(srna, "NodesModifierBake"); + RNA_def_struct_ui_text( + srna, "Data-Blocks", "Collection of data-blocks that can be referenced by baked data"); + + prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, nullptr, "active_data_block"); +} + static void rna_def_modifier_nodes_bake(BlenderRNA *brna) { + rna_def_modifier_nodes_bake_data_blocks(brna); + static EnumPropertyItem bake_mode_items[] = { {NODES_MODIFIER_BAKE_MODE_ANIMATION, "ANIMATION", 0, "Animation", "Bake a frame range"}, {NODES_MODIFIER_BAKE_MODE_STILL, "STILL", 0, "Still", "Bake a single frame"}, @@ -7330,6 +7391,11 @@ static void rna_def_modifier_nodes_bake(BlenderRNA *brna) "none in some cases like missing linked data blocks"); RNA_def_property_pointer_funcs( prop, "rna_NodesModifierBake_node_get", nullptr, nullptr, nullptr); + + prop = RNA_def_property(srna, "data_blocks", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "NodesModifierDataBlock"); + RNA_def_property_collection_sdna(prop, nullptr, "data_blocks", "data_blocks_num"); + RNA_def_property_srna(prop, "NodesModifierBakeDataBlocks"); } static void rna_def_modifier_nodes_bakes(BlenderRNA *brna) @@ -7370,6 +7436,8 @@ static void rna_def_modifier_nodes(BlenderRNA *brna) StructRNA *srna; PropertyRNA *prop; + rna_def_modifier_nodes_data_block(brna); + rna_def_modifier_nodes_bake(brna); rna_def_modifier_nodes_bakes(brna); @@ -7417,6 +7485,7 @@ static void rna_def_modifier_nodes(BlenderRNA *brna) rna_def_modifier_panel_open_prop(srna, "open_manage_panel", 1); rna_def_modifier_panel_open_prop(srna, "open_bake_panel", 2); rna_def_modifier_panel_open_prop(srna, "open_named_attributes_panel", 3); + rna_def_modifier_panel_open_prop(srna, "open_bake_data_blocks_panel", 4); RNA_define_lib_overridable(false); } diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index 1c78f7895c1..cad608cfe13 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -17,6 +17,7 @@ set(INC ../render ../windowmanager ../../../intern/eigen + ../../../extern/fmtlib/include # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna @@ -122,6 +123,7 @@ set(LIB PRIVATE bf::depsgraph PUBLIC bf::dna PRIVATE bf::intern::guardedalloc + extern_fmtlib ) if(WITH_ALEMBIC) diff --git a/source/blender/modifiers/MOD_nodes.hh b/source/blender/modifiers/MOD_nodes.hh index b2ac4394643..3f288c9c0c4 100644 --- a/source/blender/modifiers/MOD_nodes.hh +++ b/source/blender/modifiers/MOD_nodes.hh @@ -37,4 +37,6 @@ struct NodesModifierRuntime { std::shared_ptr cache; }; +void nodes_modifier_data_block_destruct(NodesModifierDataBlock *data_block, bool do_id_user); + } // namespace blender diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index ff447d7568f..86e7b4097f9 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -22,6 +23,7 @@ #include "BLI_string.h" #include "BLI_utildefines.h" +#include "DNA_array_utils.hh" #include "DNA_collection_types.h" #include "DNA_curves_types.h" #include "DNA_defaults.h" @@ -39,6 +41,7 @@ #include "DNA_windowmanager_types.h" #include "BKE_attribute_math.hh" +#include "BKE_bake_data_block_map.hh" #include "BKE_bake_geometry_nodes_modifier.hh" #include "BKE_compute_contexts.hh" #include "BKE_customdata.hh" @@ -65,6 +68,7 @@ #include "BLT_translation.h" +#include "WM_api.hh" #include "WM_types.hh" #include "RNA_access.hh" @@ -73,6 +77,7 @@ #include "DEG_depsgraph_build.hh" #include "DEG_depsgraph_query.hh" +#include "DEG_depsgraph_writeback_sync.hh" #include "MOD_modifiertypes.hh" #include "MOD_nodes.hh" @@ -177,6 +182,14 @@ static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphCont } } + for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) { + for (const NodesModifierDataBlock &data_block : Span(bake.data_blocks, bake.data_blocks_num)) { + if (data_block.id) { + used_ids.add(data_block.id); + } + } + } + for (ID *id : used_ids) { switch ((ID_Type)GS(id->name)) { case ID_OB: { @@ -270,6 +283,13 @@ static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void settings->user_data, settings->ob, (ID **)&id_prop->data.pointer, IDWALK_CB_USER); }, &settings); + + for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) { + for (NodesModifierDataBlock &data_block : MutableSpan(bake.data_blocks, bake.data_blocks_num)) + { + walk(user_data, ob, &data_block.id, IDWALK_CB_USER); + } + } } static void foreach_tex_link(ModifierData *md, Object *ob, TexWalkFunc walk, void *user_data) @@ -832,6 +852,53 @@ static void check_property_socket_sync(const Object *ob, ModifierData *md) } } +class NodesModifierBakeDataBlockMap : public bake::BakeDataBlockMap { + /** Protects access to `new_mappings` which may be added to from multiple threads. */ + std::mutex mutex_; + + public: + Map old_mappings; + Map new_mappings; + + ID *lookup_or_remember_missing(const bake::BakeDataBlockID &key) override + { + if (ID *id = this->old_mappings.lookup_default(key, nullptr)) { + return id; + } + if (this->old_mappings.contains(key)) { + /* Don't allow overwriting old mappings. */ + return nullptr; + } + std::lock_guard lock{mutex_}; + return this->new_mappings.lookup_or_add(key, nullptr); + } + + void try_add(ID &id) override + { + bake::BakeDataBlockID key{id}; + if (this->old_mappings.contains(key)) { + return; + } + std::lock_guard lock{mutex_}; + this->new_mappings.add_overwrite(std::move(key), &id); + } + + private: + ID *lookup_in_map(Map &map, + const bake::BakeDataBlockID &key, + const std::optional &type) + { + ID *id = map.lookup_default(key, nullptr); + if (!id) { + return nullptr; + } + if (type && GS(id->name) != *type) { + return nullptr; + } + return id; + } +}; + namespace sim_input = nodes::sim_input; namespace sim_output = nodes::sim_output; @@ -920,7 +987,6 @@ 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_; @@ -933,6 +999,13 @@ class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams { bool has_invalid_simulation_ = false; public: + struct DataPerZone { + nodes::SimulationZoneBehavior behavior; + NodesModifierBakeDataBlockMap data_block_map; + }; + + mutable Map> data_by_zone_id; + NodesModifierSimulationParams(NodesModifierData &nmd, const ModifierEvalContext &ctx) : nmd_(nmd), ctx_(ctx) { @@ -1005,21 +1078,26 @@ class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams { return nullptr; } std::lock_guard lock{modifier_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(); + return &this->data_by_zone_id + .lookup_or_add_cb(zone_id, + [&]() { + auto data = std::make_unique(); + data->behavior.data_block_map = &data->data_block_map; + this->init_simulation_info( + zone_id, data->behavior, data->data_block_map); + return data; + }) + ->behavior; } - void init_simulation_info(const int zone_id, nodes::SimulationZoneBehavior &zone_behavior) const + void init_simulation_info(const int zone_id, + nodes::SimulationZoneBehavior &zone_behavior, + NodesModifierBakeDataBlockMap &data_block_map) const { bake::SimulationNodeCache &node_cache = *modifier_cache_->simulation_cache_by_id.lookup_or_add_cb( zone_id, []() { return std::make_unique(); }); + const NodesModifierBake &bake = *nmd_.find_bake(zone_id); const IndexRange sim_frame_range = *bake::get_node_bake_frame_range( *scene_, *ctx_.object, nmd_, zone_id); const SubFrame sim_start_frame{int(sim_frame_range.first())}; @@ -1037,6 +1115,14 @@ class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams { } } + /* If there are no baked frames, we don't need keep track of the data-blocks. */ + if (!node_cache.bake.frames.is_empty()) { + for (const NodesModifierDataBlock &data_block : Span{bake.data_blocks, bake.data_blocks_num}) + { + data_block_map.old_mappings.add(data_block, data_block.id); + } + } + const BakeFrameIndices frame_indices = get_bake_frame_indices(node_cache.bake.frames, current_frame_); if (node_cache.cache_status == bake::CacheStatus::Baked) { @@ -1230,7 +1316,6 @@ class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams { class NodesModifierBakeParams : public nodes::GeoNodesBakeParams { private: - mutable Map> behavior_by_node_id_; const NodesModifierData &nmd_; const ModifierEvalContext &ctx_; Main *bmain_; @@ -1239,6 +1324,13 @@ class NodesModifierBakeParams : public nodes::GeoNodesBakeParams { bool depsgraph_is_active_; public: + struct DataPerNode { + nodes::BakeNodeBehavior behavior; + NodesModifierBakeDataBlockMap data_block_map; + }; + + mutable Map> data_by_node_id; + NodesModifierBakeParams(NodesModifierData &nmd, const ModifierEvalContext &ctx) : nmd_(nmd), ctx_(ctx) { @@ -1255,27 +1347,36 @@ class NodesModifierBakeParams : public nodes::GeoNodesBakeParams { return nullptr; } std::lock_guard lock{modifier_cache_->mutex}; - return behavior_by_node_id_ - .lookup_or_add_cb(id, - [&]() { - auto info = std::make_unique(); - this->init_bake_behavior(id, *info); - return info; - }) - .get(); + return &this->data_by_node_id + .lookup_or_add_cb(id, + [&]() { + auto data = std::make_unique(); + data->behavior.data_block_map = &data->data_block_map; + this->init_bake_behavior( + id, data->behavior, data->data_block_map); + return data; + }) + ->behavior; return nullptr; } private: - void init_bake_behavior(const int id, nodes::BakeNodeBehavior &behavior) const + void init_bake_behavior(const int id, + nodes::BakeNodeBehavior &behavior, + NodesModifierBakeDataBlockMap &data_block_map) const { bake::BakeNodeCache &node_cache = *modifier_cache_->bake_cache_by_id.lookup_or_add_cb( id, []() { return std::make_unique(); }); + const NodesModifierBake &bake = *nmd_.find_bake(id); + + for (const NodesModifierDataBlock &data_block : Span{bake.data_blocks, bake.data_blocks_num}) { + data_block_map.old_mappings.add(data_block, data_block.id); + } if (depsgraph_is_active_) { if (modifier_cache_->requested_bakes.contains(id)) { /* This node is baked during the current evaluation. */ - auto &store_info = behavior.emplace(); + auto &store_info = behavior.behavior.emplace(); store_info.store_fn = [modifier_cache = modifier_cache_, node_cache = &node_cache, current_frame = current_frame_](bake::BakeState state) { @@ -1304,7 +1405,7 @@ class NodesModifierBakeParams : public nodes::GeoNodesBakeParams { } if (node_cache.bake.frames.is_empty()) { - behavior.emplace(); + behavior.behavior.emplace(); return; } const BakeFrameIndices frame_indices = get_bake_frame_indices(node_cache.bake.frames, @@ -1337,7 +1438,7 @@ class NodesModifierBakeParams : public nodes::GeoNodesBakeParams { if (this->check_read_error(frame_cache, behavior)) { return; } - auto &read_single_info = behavior.emplace(); + auto &read_single_info = behavior.behavior.emplace(); read_single_info.state = frame_cache.state; } @@ -1355,7 +1456,7 @@ class NodesModifierBakeParams : public nodes::GeoNodesBakeParams { { return; } - auto &read_interpolated_info = behavior.emplace(); + auto &read_interpolated_info = behavior.behavior.emplace(); read_interpolated_info.mix_factor = (float(current_frame_) - float(prev_frame_cache.frame)) / (float(next_frame_cache.frame) - float(prev_frame_cache.frame)); @@ -1367,7 +1468,7 @@ class NodesModifierBakeParams : public nodes::GeoNodesBakeParams { nodes::BakeNodeBehavior &behavior) const { if (frame_cache.meta_path && frame_cache.state.items_by_id.is_empty()) { - auto &read_error_info = behavior.emplace(); + auto &read_error_info = behavior.behavior.emplace(); read_error_info.message = RPT_("Can not load the baked data"); return true; } @@ -1375,6 +1476,169 @@ class NodesModifierBakeParams : public nodes::GeoNodesBakeParams { } }; +static void add_missing_data_block_mappings( + NodesModifierBake &bake, + const Span missing, + FunctionRef get_data_block) +{ + const int old_num = bake.data_blocks_num; + const int new_num = old_num + missing.size(); + bake.data_blocks = reinterpret_cast( + MEM_recallocN(bake.data_blocks, sizeof(NodesModifierDataBlock) * new_num)); + for (const int i : missing.index_range()) { + NodesModifierDataBlock &data_block = bake.data_blocks[old_num + i]; + const blender::bke::bake::BakeDataBlockID &key = missing[i]; + + data_block.id_name = BLI_strdup(key.id_name.c_str()); + if (!key.lib_name.empty()) { + data_block.lib_name = BLI_strdup(key.lib_name.c_str()); + } + data_block.id_type = int(key.type); + ID *id = get_data_block(key); + if (id) { + data_block.id = id; + } + } + bake.data_blocks_num = new_num; +} + +void nodes_modifier_data_block_destruct(NodesModifierDataBlock *data_block, const bool do_id_user) +{ + MEM_SAFE_FREE(data_block->id_name); + MEM_SAFE_FREE(data_block->lib_name); + if (do_id_user) { + id_us_min(data_block->id); + } +} + +/** + * During evaluation we might have baked geometry that contains references to other data-blocks + * (such as materials). We need to make sure that those data-blocks stay dependencies of the + * modifier. Otherwise, the data-block references might not work when the baked data is loaded + * again. Therefor, the dependencies are written back to the original modifier. + */ +static void add_data_block_items_writeback(const ModifierEvalContext &ctx, + NodesModifierData &nmd_eval, + NodesModifierData &nmd_orig, + NodesModifierSimulationParams &simulation_params, + NodesModifierBakeParams &bake_params) +{ + Depsgraph *depsgraph = ctx.depsgraph; + Main *bmain = DEG_get_bmain(depsgraph); + + struct DataPerBake { + bool reset_first = false; + Map new_mappings; + }; + Map writeback_data; + for (auto item : simulation_params.data_by_zone_id.items()) { + DataPerBake data; + NodesModifierBake &bake = *nmd_eval.find_bake(item.key); + if (item.value->data_block_map.old_mappings.size() < bake.data_blocks_num) { + data.reset_first = true; + } + if (bake::SimulationNodeCache *node_cache = nmd_eval.runtime->cache->get_simulation_node_cache( + item.key)) + { + /* Only writeback if the bake node has actually baked anything. */ + if (!node_cache->bake.frames.is_empty()) { + data.new_mappings = std::move(item.value->data_block_map.new_mappings); + } + } + if (data.reset_first || !data.new_mappings.is_empty()) { + writeback_data.add(item.key, std::move(data)); + } + } + for (auto item : bake_params.data_by_node_id.items()) { + if (bake::BakeNodeCache *node_cache = nmd_eval.runtime->cache->get_bake_node_cache(item.key)) { + /* Only writeback if the bake node has actually baked anything. */ + if (!node_cache->bake.frames.is_empty()) { + DataPerBake data; + data.new_mappings = std::move(item.value->data_block_map.new_mappings); + writeback_data.add(item.key, std::move(data)); + } + } + } + + if (writeback_data.is_empty()) { + /* Nothing to do. */ + return; + } + + deg::sync_writeback::add( + *depsgraph, + [depsgraph = depsgraph, + object_eval = ctx.object, + bmain, + &nmd_orig, + &nmd_eval, + writeback_data = std::move(writeback_data)]() { + for (auto item : writeback_data.items()) { + const int bake_id = item.key; + DataPerBake data = item.value; + + NodesModifierBake &bake_orig = *nmd_orig.find_bake(bake_id); + NodesModifierBake &bake_eval = *nmd_eval.find_bake(bake_id); + + if (data.reset_first) { + /* Reset data-block list on original data. */ + dna::array::clear(&bake_orig.data_blocks, + &bake_orig.data_blocks_num, + &bake_orig.active_data_block, + [](NodesModifierDataBlock *data_block) { + nodes_modifier_data_block_destruct( + data_block, true); + }); + /* Reset data-block list on evaluated data. */ + dna::array::clear(&bake_eval.data_blocks, + &bake_eval.data_blocks_num, + &bake_eval.active_data_block, + [](NodesModifierDataBlock *data_block) { + nodes_modifier_data_block_destruct( + data_block, false); + }); + } + + Vector sorted_new_mappings; + sorted_new_mappings.extend(data.new_mappings.keys().begin(), + data.new_mappings.keys().end()); + bool needs_reevaluation = false; + /* Add new data block mappings to the original modifier. This may do a name lookup in + * bmain to find the data block if there is not faster way to get it. */ + add_missing_data_block_mappings( + bake_orig, sorted_new_mappings, [&](const bake::BakeDataBlockID &key) -> ID * { + ID *id_orig = nullptr; + if (ID *id_eval = data.new_mappings.lookup_default(key, nullptr)) { + id_orig = DEG_get_original_id(id_eval); + } + else { + needs_reevaluation = true; + id_orig = BKE_libblock_find_name_and_library( + bmain, short(key.type), key.id_name.c_str(), key.lib_name.c_str()); + } + if (id_orig) { + id_us_plus(id_orig); + } + return id_orig; + }); + /* Add new data block mappings to the evaluated modifier. In most cases this makes it so + * the evaluated modifier is in the same state as if it were copied from the updated + * original again. The exception is when a missing data block was found that is not in + * the depsgraph currently. */ + add_missing_data_block_mappings( + bake_eval, sorted_new_mappings, [&](const bake::BakeDataBlockID &key) -> ID * { + return data.new_mappings.lookup_default(key, nullptr); + }); + + if (needs_reevaluation) { + Object *object_orig = DEG_get_original_object(object_eval); + DEG_id_tag_update(&object_orig->id, ID_RECALC_GEOMETRY); + DEG_relations_tag_update(bmain); + } + } + }); +} + static void modifyGeometry(ModifierData *md, const ModifierEvalContext *ctx, bke::GeometrySet &geometry_set) @@ -1466,6 +1730,10 @@ static void modifyGeometry(ModifierData *md, nmd_orig->runtime->eval_log = std::move(eval_log); } + if (DEG_is_active(ctx->depsgraph)) { + add_data_block_items_writeback(*ctx, *nmd, *nmd_orig, simulation_params, bake_params); + } + if (use_orig_index_verts || use_orig_index_edges || use_orig_index_faces) { if (Mesh *mesh = geometry_set.get_mesh_for_write()) { /* Add #CD_ORIGINDEX layers if they don't exist already. This is required because the @@ -2108,6 +2376,13 @@ static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const Modi BLO_write_struct_array(writer, NodesModifierBake, nmd->bakes_num, nmd->bakes); for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) { BLO_write_string(writer, bake.directory); + + BLO_write_struct_array( + writer, NodesModifierDataBlock, bake.data_blocks_num, bake.data_blocks); + for (const NodesModifierDataBlock &item : Span(bake.data_blocks, bake.data_blocks_num)) { + BLO_write_string(writer, item.id_name); + BLO_write_string(writer, item.lib_name); + } } BLO_write_struct_array(writer, NodesModifierPanel, nmd->panels_num, nmd->panels); @@ -2141,6 +2416,13 @@ static void blend_read(BlendDataReader *reader, ModifierData *md) BLO_read_data_address(reader, &nmd->bakes); for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) { BLO_read_data_address(reader, &bake.directory); + + BLO_read_data_address(reader, &bake.data_blocks); + for (NodesModifierDataBlock &data_block : MutableSpan(bake.data_blocks, bake.data_blocks_num)) + { + BLO_read_data_address(reader, &data_block.id_name); + BLO_read_data_address(reader, &data_block.lib_name); + } } BLO_read_data_address(reader, &nmd->panels); @@ -2162,6 +2444,18 @@ static void copy_data(const ModifierData *md, ModifierData *target, const int fl if (bake.directory) { bake.directory = BLI_strdup(bake.directory); } + if (bake.data_blocks) { + bake.data_blocks = static_cast(MEM_dupallocN(bake.data_blocks)); + for (const int i : IndexRange(bake.data_blocks_num)) { + NodesModifierDataBlock &data_block = bake.data_blocks[i]; + if (data_block.id_name) { + data_block.id_name = BLI_strdup(data_block.id_name); + } + if (data_block.lib_name) { + data_block.lib_name = BLI_strdup(data_block.lib_name); + } + } + } } } @@ -2198,6 +2492,13 @@ static void free_data(ModifierData *md) for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) { MEM_SAFE_FREE(bake.directory); + + for (NodesModifierDataBlock &data_block : MutableSpan(bake.data_blocks, bake.data_blocks_num)) + { + MEM_SAFE_FREE(data_block.id_name); + MEM_SAFE_FREE(data_block.lib_name); + } + MEM_SAFE_FREE(bake.data_blocks); } MEM_SAFE_FREE(nmd->bakes); diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index 2c259ee7250..f797f6c9999 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -121,6 +121,7 @@ using Behavior = std::variant node_simulation_items, Span input_values); + Span node_simulation_items, + Span input_values, + bke::bake::BakeDataBlockMap *data_block_map); void move_simulation_state_to_values(Span node_simulation_items, bke::bake::BakeState zone_state, const Object &self_object, const ComputeContext &compute_context, const bNode &sim_output_node, + bke::bake::BakeDataBlockMap *data_block_map, Span r_output_values); void copy_simulation_state_to_values(Span node_simulation_items, const bke::bake::BakeStateRef &zone_state, const Object &self_object, const ComputeContext &compute_context, const bNode &sim_output_node, + bke::bake::BakeDataBlockMap *data_block_map, Span r_output_values); void copy_with_checked_indices(const GVArray &src, @@ -155,4 +159,6 @@ const EnumPropertyItem *grid_socket_type_items_filter_fn(bContext *C, void node_geo_exec_with_missing_openvdb(GeoNodeExecParams ¶ms); +void draw_data_blocks(const bContext *C, uiLayout *layout, PointerRNA &bake_rna); + } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_bake.cc b/source/blender/nodes/geometry/nodes/node_geo_bake.cc index 8ca46fade63..71026e54c0b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_bake.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_bake.cc @@ -16,6 +16,7 @@ #include "BKE_bake_geometry_nodes_modifier.hh" #include "BKE_bake_items_socket.hh" #include "BKE_context.hh" +#include "BKE_screen.hh" #include "ED_node.hh" @@ -191,24 +192,25 @@ class LazyFunctionForBakeNode final : public LazyFunction { this->set_default_outputs(params); return; } - if (auto *info = std::get_if(behavior)) { - this->output_cached_state(params, user_data, info->state); + if (auto *info = std::get_if(&behavior->behavior)) { + this->output_cached_state(params, user_data, behavior->data_block_map, info->state); } - else if (auto *info = std::get_if(behavior)) { + else if (auto *info = std::get_if(&behavior->behavior)) { this->output_mixed_cached_state(params, + behavior->data_block_map, *user_data.call_data->self_object(), *user_data.compute_context, info->prev_state, info->next_state, info->mix_factor); } - else if (std::get_if(behavior)) { - this->pass_through(params, user_data); + else if (std::get_if(&behavior->behavior)) { + this->pass_through(params, user_data, behavior->data_block_map); } - else if (auto *info = std::get_if(behavior)) { - this->store(params, user_data, *info); + else if (auto *info = std::get_if(&behavior->behavior)) { + this->store(params, user_data, behavior->data_block_map, *info); } - else if (auto *info = std::get_if(behavior)) { + else if (auto *info = std::get_if(&behavior->behavior)) { if (geo_eval_log::GeoTreeLogger *tree_logger = local_user_data.try_get_tree_logger( user_data)) { @@ -227,9 +229,12 @@ class LazyFunctionForBakeNode final : public LazyFunction { set_default_remaining_node_outputs(params, node_); } - void pass_through(lf::Params ¶ms, GeoNodesLFUserData &user_data) const + void pass_through(lf::Params ¶ms, + GeoNodesLFUserData &user_data, + bke::bake::BakeDataBlockMap *data_block_map) const { - std::optional bake_state = this->get_bake_state_from_inputs(params); + std::optional bake_state = this->get_bake_state_from_inputs(params, + data_block_map); if (!bake_state) { /* Wait for inputs to be computed. */ return; @@ -239,6 +244,7 @@ class LazyFunctionForBakeNode final : public LazyFunction { output_values[i] = params.get_output_data_ptr(i); } this->move_bake_state_to_values(std::move(*bake_state), + data_block_map, *user_data.call_data->self_object(), *user_data.compute_context, output_values); @@ -249,19 +255,22 @@ class LazyFunctionForBakeNode final : public LazyFunction { void store(lf::Params ¶ms, GeoNodesLFUserData &user_data, + bke::bake::BakeDataBlockMap *data_block_map, const sim_output::StoreNewState &info) const { - std::optional bake_state = this->get_bake_state_from_inputs(params); + std::optional bake_state = this->get_bake_state_from_inputs(params, + data_block_map); if (!bake_state) { /* Wait for inputs to be computed. */ return; } - this->output_cached_state(params, user_data, *bake_state); + this->output_cached_state(params, user_data, data_block_map, *bake_state); info.store_fn(std::move(*bake_state)); } void output_cached_state(lf::Params ¶ms, GeoNodesLFUserData &user_data, + bke::bake::BakeDataBlockMap *data_block_map, const bake::BakeStateRef &bake_state) const { Array output_values(bake_items_.size()); @@ -269,6 +278,7 @@ class LazyFunctionForBakeNode final : public LazyFunction { output_values[i] = params.get_output_data_ptr(i); } this->copy_bake_state_to_values(bake_state, + data_block_map, *user_data.call_data->self_object(), *user_data.compute_context, output_values); @@ -278,6 +288,7 @@ class LazyFunctionForBakeNode final : public LazyFunction { } void output_mixed_cached_state(lf::Params ¶ms, + bke::bake::BakeDataBlockMap *data_block_map, const Object &self_object, const ComputeContext &compute_context, const bake::BakeStateRef &prev_state, @@ -288,7 +299,8 @@ class LazyFunctionForBakeNode final : public LazyFunction { for (const int i : bake_items_.index_range()) { output_values[i] = params.get_output_data_ptr(i); } - this->copy_bake_state_to_values(prev_state, self_object, compute_context, output_values); + this->copy_bake_state_to_values( + prev_state, data_block_map, self_object, compute_context, output_values); Array next_values(bake_items_.size()); LinearAllocator<> allocator; @@ -296,7 +308,8 @@ class LazyFunctionForBakeNode final : public LazyFunction { const CPPType &type = *outputs_[i].type; next_values[i] = allocator.allocate(type.size(), type.alignment()); } - this->copy_bake_state_to_values(next_state, self_object, compute_context, next_values); + this->copy_bake_state_to_values( + next_state, data_block_map, self_object, compute_context, next_values); for (const int i : bake_items_.index_range()) { mix_baked_data_item(eNodeSocketDatatype(bake_items_[i].socket_type), @@ -315,7 +328,8 @@ class LazyFunctionForBakeNode final : public LazyFunction { } } - std::optional get_bake_state_from_inputs(lf::Params ¶ms) const + std::optional get_bake_state_from_inputs( + lf::Params ¶ms, bke::bake::BakeDataBlockMap *data_block_map) const { Array input_values(bake_items_.size()); for (const int i : bake_items_.index_range()) { @@ -327,7 +341,7 @@ class LazyFunctionForBakeNode final : public LazyFunction { } Array> bake_items = bake::move_socket_values_to_bake_items( - input_values, bake_socket_config_); + input_values, bake_socket_config_, data_block_map); bake::BakeState bake_state; for (const int i : bake_items_.index_range()) { @@ -341,6 +355,7 @@ class LazyFunctionForBakeNode final : public LazyFunction { } void move_bake_state_to_values(bake::BakeState bake_state, + bke::bake::BakeDataBlockMap *data_block_map, const Object &self_object, const ComputeContext &compute_context, Span r_output_values) const @@ -354,6 +369,7 @@ class LazyFunctionForBakeNode final : public LazyFunction { bake::move_bake_items_to_socket_values( bake_items, bake_socket_config_, + data_block_map, [&](const int i, const CPPType &type) { return this->make_attribute_field(self_object, compute_context, bake_items_[i], type); }, @@ -361,6 +377,7 @@ class LazyFunctionForBakeNode final : public LazyFunction { } void copy_bake_state_to_values(const bake::BakeStateRef &bake_state, + bke::bake::BakeDataBlockMap *data_block_map, const Object &self_object, const ComputeContext &compute_context, Span r_output_values) const @@ -373,6 +390,7 @@ class LazyFunctionForBakeNode final : public LazyFunction { bake::copy_bake_items_to_socket_values( bake_items, bake_socket_config_, + data_block_map, [&](const int i, const CPPType &type) { return this->make_attribute_field(self_object, compute_context, bake_items_[i], type); }, @@ -591,6 +609,8 @@ static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) uiItemR(subcol, &ctx.bake_rna, "frame_end", UI_ITEM_NONE, "End", ICON_NONE); } } + + draw_data_blocks(C, layout, ctx.bake_rna); } static void node_register() @@ -613,6 +633,64 @@ NOD_REGISTER_NODE(node_register) namespace blender::nodes { +static void draw_bake_data_block_list_item(uiList * /*ui_list*/, + const bContext * /*C*/, + uiLayout *layout, + PointerRNA * /*idataptr*/, + PointerRNA *itemptr, + int /*icon*/, + PointerRNA * /*active_dataptr*/, + const char * /*active_propname*/, + int /*index*/, + int /*flt_flag*/) +{ + auto &data_block = *static_cast(itemptr->data); + uiLayout *row = uiLayoutRow(layout, true); + + std::string name; + if (StringRef(data_block.lib_name).is_empty()) { + name = data_block.id_name; + } + else { + name = fmt::format("{} [{}]", data_block.id_name, data_block.lib_name); + } + + uiItemR(row, itemptr, "id", UI_ITEM_NONE, name.c_str(), ICON_NONE); +} + +void draw_data_blocks(const bContext *C, uiLayout *layout, PointerRNA &bake_rna) +{ + static const uiListType *data_block_list = []() { + uiListType *list = MEM_cnew(__func__); + STRNCPY(list->idname, "DATA_UL_nodes_modifier_data_blocks"); + list->draw_item = draw_bake_data_block_list_item; + WM_uilisttype_add(list); + return list; + }(); + + PointerRNA data_blocks_ptr = RNA_pointer_create( + bake_rna.owner_id, &RNA_NodesModifierBakeDataBlocks, bake_rna.data); + + if (uiLayout *panel = uiLayoutPanel( + C, layout, "data_block_references", true, TIP_("Data-Block References"))) + { + uiTemplateList(panel, + C, + data_block_list->idname, + "", + &bake_rna, + "data_blocks", + &data_blocks_ptr, + "active_index", + nullptr, + 3, + 5, + UILST_LAYOUT_DEFAULT, + 0, + UI_TEMPLATE_LIST_FLAG_NONE); + } +} + std::unique_ptr get_bake_lazy_function( const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info) { 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 44fcb0a1ae0..bcb5e378f19 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc @@ -89,15 +89,17 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction { 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->state); + this->output_simulation_state_copy( + params, user_data, zone_behavior->data_block_map, info->state); } 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->state)); + this->output_simulation_state_move( + params, user_data, zone_behavior->data_block_map, std::move(info->state)); } else if (std::get_if(&input_behavior)) { delta_time = 0.0f; - this->pass_through(params, user_data); + this->pass_through(params, user_data, zone_behavior->data_block_map); } else { BLI_assert_unreachable(); @@ -114,6 +116,7 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction { void output_simulation_state_copy(lf::Params ¶ms, const GeoNodesLFUserData &user_data, + bke::bake::BakeDataBlockMap *data_block_map, const bke::bake::BakeStateRef &zone_state) const { Array outputs(simulation_items_.size()); @@ -125,6 +128,7 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction { *user_data.call_data->self_object(), *user_data.compute_context, node_, + data_block_map, outputs); for (const int i : simulation_items_.index_range()) { params.output_set(i + 1); @@ -133,6 +137,7 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction { void output_simulation_state_move(lf::Params ¶ms, const GeoNodesLFUserData &user_data, + bke::bake::BakeDataBlockMap *data_block_map, bke::bake::BakeState zone_state) const { Array outputs(simulation_items_.size()); @@ -144,13 +149,16 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction { *user_data.call_data->self_object(), *user_data.compute_context, node_, + data_block_map, outputs); for (const int i : simulation_items_.index_range()) { params.output_set(i + 1); } } - void pass_through(lf::Params ¶ms, const GeoNodesLFUserData &user_data) const + void pass_through(lf::Params ¶ms, + const GeoNodesLFUserData &user_data, + bke::bake::BakeDataBlockMap *data_block_map) const { Array input_values(inputs_.size()); for (const int i : inputs_.index_range()) { @@ -163,9 +171,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::bake::BakeState bake_state = move_values_to_simulation_state(simulation_items_, - input_values); - this->output_simulation_state_move(params, user_data, std::move(bake_state)); + bke::bake::BakeState bake_state = move_values_to_simulation_state( + simulation_items_, input_values, data_block_map); + this->output_simulation_state_move(params, user_data, data_block_map, std::move(bake_state)); } }; 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 cbc303b3f15..5bc8f6ee24d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc @@ -104,6 +104,7 @@ void move_simulation_state_to_values(const Span node_simulat const Object &self_object, const ComputeContext &compute_context, const bNode &node, + bke::bake::BakeDataBlockMap *data_block_map, Span r_output_values) { const bke::bake::BakeSocketConfig config = make_bake_socket_config(node_simulation_items); @@ -117,6 +118,7 @@ void move_simulation_state_to_values(const Span node_simulat bke::bake::move_bake_items_to_socket_values( bake_items, config, + data_block_map, [&](const int i, const CPPType &type) { return make_attribute_field( self_object, compute_context, node, node_simulation_items[i], type); @@ -129,6 +131,7 @@ void copy_simulation_state_to_values(const Span node_simulat const Object &self_object, const ComputeContext &compute_context, const bNode &node, + bke::bake::BakeDataBlockMap *data_block_map, Span r_output_values) { const bke::bake::BakeSocketConfig config = make_bake_socket_config(node_simulation_items); @@ -142,6 +145,7 @@ void copy_simulation_state_to_values(const Span node_simulat bke::bake::copy_bake_items_to_socket_values( bake_items, config, + data_block_map, [&](const int i, const CPPType &type) { return make_attribute_field( self_object, compute_context, node, node_simulation_items[i], type); @@ -150,12 +154,14 @@ void copy_simulation_state_to_values(const Span node_simulat } bke::bake::BakeState move_values_to_simulation_state( - const Span node_simulation_items, const Span input_values) + const Span node_simulation_items, + const Span input_values, + bke::bake::BakeDataBlockMap *data_block_map) { const bke::bake::BakeSocketConfig config = make_bake_socket_config(node_simulation_items); Array> bake_items = - bke::bake::move_socket_values_to_bake_items(input_values, config); + bke::bake::move_socket_values_to_bake_items(input_values, config, data_block_map); bke::bake::BakeState bake_state; for (const int i : node_simulation_items.index_range()) { @@ -525,10 +531,11 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { } sim_output::Behavior &output_behavior = zone_behavior->output; if (auto *info = std::get_if(&output_behavior)) { - this->output_cached_state(params, user_data, info->state); + this->output_cached_state(params, user_data, zone_behavior->data_block_map, info->state); } else if (auto *info = std::get_if(&output_behavior)) { this->output_mixed_cached_state(params, + zone_behavior->data_block_map, *user_data.call_data->self_object(), *user_data.compute_context, info->prev_state, @@ -536,10 +543,10 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { info->mix_factor); } else if (std::get_if(&output_behavior)) { - this->pass_through(params, user_data); + this->pass_through(params, user_data, zone_behavior->data_block_map); } else if (auto *info = std::get_if(&output_behavior)) { - this->store_new_state(params, user_data, *info); + this->store_new_state(params, user_data, zone_behavior->data_block_map, *info); } else { BLI_assert_unreachable(); @@ -553,6 +560,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { void output_cached_state(lf::Params ¶ms, GeoNodesLFUserData &user_data, + bke::bake::BakeDataBlockMap *data_block_map, const bke::bake::BakeStateRef &state) const { Array output_values(simulation_items_.size()); @@ -564,6 +572,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { *user_data.call_data->self_object(), *user_data.compute_context, node_, + data_block_map, output_values); for (const int i : simulation_items_.index_range()) { params.output_set(i); @@ -571,6 +580,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { } void output_mixed_cached_state(lf::Params ¶ms, + bke::bake::BakeDataBlockMap *data_block_map, const Object &self_object, const ComputeContext &compute_context, const bke::bake::BakeStateRef &prev_state, @@ -581,8 +591,13 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { for (const int i : simulation_items_.index_range()) { output_values[i] = params.get_output_data_ptr(i); } - copy_simulation_state_to_values( - simulation_items_, prev_state, self_object, compute_context, node_, output_values); + copy_simulation_state_to_values(simulation_items_, + prev_state, + self_object, + compute_context, + node_, + data_block_map, + output_values); Array next_values(simulation_items_.size()); LinearAllocator<> allocator; @@ -590,8 +605,13 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { const CPPType &type = *outputs_[i].type; next_values[i] = allocator.allocate(type.size(), type.alignment()); } - copy_simulation_state_to_values( - simulation_items_, next_state, self_object, compute_context, node_, next_values); + copy_simulation_state_to_values(simulation_items_, + next_state, + self_object, + compute_context, + node_, + data_block_map, + next_values); for (const int i : simulation_items_.index_range()) { mix_baked_data_item(eNodeSocketDatatype(simulation_items_[i].socket_type), @@ -610,10 +630,12 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { } } - void pass_through(lf::Params ¶ms, GeoNodesLFUserData &user_data) const + void pass_through(lf::Params ¶ms, + GeoNodesLFUserData &user_data, + bke::bake::BakeDataBlockMap *data_block_map) const { - std::optional bake_state = this->get_bake_state_from_inputs(params, - true); + std::optional bake_state = this->get_bake_state_from_inputs( + params, data_block_map, true); if (!bake_state) { /* Wait for inputs to be computed. */ return; @@ -628,6 +650,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { *user_data.call_data->self_object(), *user_data.compute_context, node_, + data_block_map, output_values); for (const int i : simulation_items_.index_range()) { params.output_set(i); @@ -636,6 +659,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { void store_new_state(lf::Params ¶ms, GeoNodesLFUserData &user_data, + bke::bake::BakeDataBlockMap *data_block_map, const sim_output::StoreNewState &info) const { const SocketValueVariant *skip_variant = @@ -649,18 +673,18 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { /* Instead of outputting the values directly, convert them to a bake state and then back. This * ensures that some geometry processing happens on the data consistently (e.g. removing * anonymous attributes). */ - std::optional bake_state = this->get_bake_state_from_inputs(params, - skip); + std::optional bake_state = this->get_bake_state_from_inputs( + params, data_block_map, skip); if (!bake_state) { /* Wait for inputs to be computed. */ return; } - this->output_cached_state(params, user_data, *bake_state); + this->output_cached_state(params, user_data, data_block_map, *bake_state); info.store_fn(std::move(*bake_state)); } - std::optional get_bake_state_from_inputs(lf::Params ¶ms, - const bool skip) const + std::optional get_bake_state_from_inputs( + lf::Params ¶ms, bke::bake::BakeDataBlockMap *data_block_map, const bool skip) const { /* Choose which set of input parameters to use. The others are ignored. */ const int params_offset = skip ? skip_inputs_offset_ : solve_inputs_offset_; @@ -673,7 +697,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { return std::nullopt; } - return move_values_to_simulation_state(simulation_items_, input_values); + return move_values_to_simulation_state(simulation_items_, input_values, data_block_map); } }; @@ -888,6 +912,8 @@ static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) uiItemR(subcol, &bake_rna, "frame_end", UI_ITEM_NONE, "End", ICON_NONE); } } + + draw_data_blocks(C, layout, bake_rna); } static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index 6ec0f7a07d2..9cc59902597 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -965,8 +965,8 @@ class LazyFunctionForBakeInputsUsage : public LazyFunction { this->set_default_outputs(params); return; } - const bool need_inputs = std::holds_alternative(*behavior) || - std::holds_alternative(*behavior); + const bool need_inputs = std::holds_alternative(behavior->behavior) || + std::holds_alternative(behavior->behavior); params.set_output(0, need_inputs); }