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); }