diff options
Diffstat (limited to 'source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc')
-rw-r--r-- | source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc | 1014 |
1 files changed, 1014 insertions, 0 deletions
diff --git a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc new file mode 100644 index 00000000000..2789f189f03 --- /dev/null +++ b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc @@ -0,0 +1,1014 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 20137Blender Foundation. + * All rights reserved. + * + * Original Author: Sergey Sharybin + * Contributor(s): None Yet + * + * ***** END GPL LICENSE BLOCK ***** + */ + + +/** \file blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc + * \ingroup depsgraph + */ + +/* Enable special; trickery to treat nested owned IDs (such as nodetree of + * material) to be handled in same way as "real" datablocks, even tho some + * internal BKE routines doesn't treat them like that. + * + * TODO(sergey): Re-evaluate that after new ID handling is in place. + */ +#define NESTED_ID_NASTY_WORKAROUND + +/* Silence warnings from copying deprecated fields. */ +#define DNA_DEPRECATED_ALLOW + +#include "intern/eval/deg_eval_copy_on_write.h" + +#include <cstring> + +#include "BLI_utildefines.h" +#include "BLI_listbase.h" +#include "BLI_threads.h" +#include "BLI_string.h" + +#include "BKE_curve.h" +#include "BKE_global.h" +#include "BKE_idprop.h" +#include "BKE_layer.h" +#include "BKE_library.h" +#include "BKE_main.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" + +#include "MEM_guardedalloc.h" + +extern "C" { +#include "DNA_ID.h" +#include "DNA_anim_types.h" +#include "DNA_armature_types.h" +#include "DNA_mesh_types.h" +#include "DNA_scene_types.h" +#include "DNA_object_types.h" +#include "DNA_particle_types.h" + +#ifdef NESTED_ID_NASTY_WORKAROUND +# include "DNA_curve_types.h" +# include "DNA_key_types.h" +# include "DNA_lamp_types.h" +# include "DNA_lattice_types.h" +# include "DNA_linestyle_types.h" +# include "DNA_material_types.h" +# include "DNA_meta_types.h" +# include "DNA_node_types.h" +# include "DNA_texture_types.h" +# include "DNA_world_types.h" +#endif + +#include "BKE_action.h" +#include "BKE_animsys.h" +#include "BKE_armature.h" +#include "BKE_editmesh.h" +#include "BKE_library_query.h" +#include "BKE_object.h" +} + +#include "intern/depsgraph.h" +#include "intern/builder/deg_builder_nodes.h" +#include "intern/nodes/deg_node.h" +#include "intern/nodes/deg_node_id.h" + +namespace DEG { + +#define DEBUG_PRINT if (G.debug & G_DEBUG_DEPSGRAPH_EVAL) printf + +namespace { + +#ifdef NESTED_ID_NASTY_WORKAROUND +union NestedIDHackTempStorage { + Curve curve; + FreestyleLineStyle linestyle; + Lamp lamp; + Lattice lattice; + Material material; + Mesh mesh; + Scene scene; + Tex tex; + World world; +}; + +/* Set nested owned ID pointers to NULL. */ +void nested_id_hack_discard_pointers(ID *id_cow) +{ + switch (GS(id_cow->name)) { +# define SPECIAL_CASE(id_type, dna_type, field) \ + case id_type: \ + { \ + ((dna_type *)id_cow)->field = NULL; \ + break; \ + } + + SPECIAL_CASE(ID_LS, FreestyleLineStyle, nodetree) + SPECIAL_CASE(ID_LA, Lamp, nodetree) + SPECIAL_CASE(ID_MA, Material, nodetree) + SPECIAL_CASE(ID_SCE, Scene, nodetree) + SPECIAL_CASE(ID_TE, Tex, nodetree) + SPECIAL_CASE(ID_WO, World, nodetree) + + SPECIAL_CASE(ID_CU, Curve, key) + SPECIAL_CASE(ID_LT, Lattice, key) + SPECIAL_CASE(ID_ME, Mesh, key) + + case ID_OB: + { + /* Clear the ParticleSettings pointer to prevent doubly-freeing it. */ + Object *ob = (Object *)id_cow; + LISTBASE_FOREACH(ParticleSystem *, psys, &ob->particlesystem) { + psys->part = NULL; + } + break; + } +# undef SPECIAL_CASE + + default: + break; + } +} + +/* Set ID pointer of nested owned IDs (nodetree, key) to NULL. + * + * Return pointer to a new ID to be used. + */ +const ID *nested_id_hack_get_discarded_pointers(NestedIDHackTempStorage *storage, + const ID *id) +{ + switch (GS(id->name)) { +# define SPECIAL_CASE(id_type, dna_type, field, variable) \ + case id_type: \ + { \ + storage->variable = *(dna_type *)id; \ + storage->variable.field = NULL; \ + return &storage->variable.id; \ + } + + SPECIAL_CASE(ID_LS, FreestyleLineStyle, nodetree, linestyle) + SPECIAL_CASE(ID_LA, Lamp, nodetree, lamp) + SPECIAL_CASE(ID_MA, Material, nodetree, material) + SPECIAL_CASE(ID_SCE, Scene, nodetree, scene) + SPECIAL_CASE(ID_TE, Tex, nodetree, tex) + SPECIAL_CASE(ID_WO, World, nodetree, world) + + SPECIAL_CASE(ID_CU, Curve, key, curve) + SPECIAL_CASE(ID_LT, Lattice, key, lattice) + SPECIAL_CASE(ID_ME, Mesh, key, mesh) + +# undef SPECIAL_CASE + + default: + break; + } + return id; +} + +/* Set ID pointer of nested owned IDs (nodetree, key) to the original value. */ +void nested_id_hack_restore_pointers(const ID *old_id, ID *new_id) +{ + if (new_id == NULL) { + return; + } + switch (GS(old_id->name)) { +# define SPECIAL_CASE(id_type, dna_type, field) \ + case id_type: \ + { \ + ((dna_type *)(new_id))->field = \ + ((dna_type *)(old_id))->field; \ + break; \ + } + + SPECIAL_CASE(ID_LS, FreestyleLineStyle, nodetree) + SPECIAL_CASE(ID_LA, Lamp, nodetree) + SPECIAL_CASE(ID_MA, Material, nodetree) + SPECIAL_CASE(ID_SCE, Scene, nodetree) + SPECIAL_CASE(ID_TE, Tex, nodetree) + SPECIAL_CASE(ID_WO, World, nodetree) + + SPECIAL_CASE(ID_CU, Curve, key) + SPECIAL_CASE(ID_LT, Lattice, key) + SPECIAL_CASE(ID_ME, Mesh, key) + +#undef SPECIAL_CASE + default: + break; + } +} + +/* Remap pointer of nested owned IDs (nodetree. key) to the new ID values. */ +void ntree_hack_remap_pointers(const Depsgraph *depsgraph, ID *id_cow) +{ + switch (GS(id_cow->name)) { +# define SPECIAL_CASE(id_type, dna_type, field, field_type) \ + case id_type: \ + { \ + dna_type *data = (dna_type *)id_cow; \ + if (data->field != NULL) { \ + ID *ntree_id_cow = depsgraph->get_cow_id(&data->field->id); \ + if (ntree_id_cow != NULL) { \ + DEG_COW_PRINT(" Remapping datablock for %s: id_orig=%p id_cow=%p\n", \ + data->field->id.name, \ + data->field, \ + ntree_id_cow); \ + data->field = (field_type *)ntree_id_cow; \ + } \ + } \ + break; \ + } + + SPECIAL_CASE(ID_LS, FreestyleLineStyle, nodetree, bNodeTree) + SPECIAL_CASE(ID_LA, Lamp, nodetree, bNodeTree) + SPECIAL_CASE(ID_MA, Material, nodetree, bNodeTree) + SPECIAL_CASE(ID_SCE, Scene, nodetree, bNodeTree) + SPECIAL_CASE(ID_TE, Tex, nodetree, bNodeTree) + SPECIAL_CASE(ID_WO, World, nodetree, bNodeTree) + + SPECIAL_CASE(ID_CU, Curve, key, Key) + SPECIAL_CASE(ID_LT, Lattice, key, Key) + SPECIAL_CASE(ID_ME, Mesh, key, Key) + +#undef SPECIAL_CASE + default: + break; + } +} +#endif /* NODETREE_NASTY_WORKAROUND */ + +struct ValidateData { + bool is_valid; +}; + +/* Similar to generic id_copy() but does not require main and assumes pointer + * is already allocated, + */ +bool id_copy_inplace_no_main(const ID *id, ID *newid) +{ + const ID *id_for_copy = id; + +#ifdef NESTED_ID_NASTY_WORKAROUND + NestedIDHackTempStorage id_hack_storage; + id_for_copy = nested_id_hack_get_discarded_pointers(&id_hack_storage, id); +#endif + + bool result = BKE_id_copy_ex(NULL, + (ID *)id_for_copy, + &newid, + (LIB_ID_CREATE_NO_MAIN | + LIB_ID_CREATE_NO_USER_REFCOUNT | + LIB_ID_CREATE_NO_ALLOCATE | + LIB_ID_CREATE_NO_DEG_TAG | + LIB_ID_COPY_CACHES), + false); + +#ifdef NESTED_ID_NASTY_WORKAROUND + if (result) { + nested_id_hack_restore_pointers(id, newid); + } +#endif + + return result; +} + +/* Similar to BKE_scene_copy() but does not require main and assumes pointer + * is already allocated. + */ +bool scene_copy_inplace_no_main(const Scene *scene, Scene *new_scene) +{ + const ID *id_for_copy = &scene->id; + +#ifdef NESTED_ID_NASTY_WORKAROUND + NestedIDHackTempStorage id_hack_storage; + id_for_copy = nested_id_hack_get_discarded_pointers(&id_hack_storage, + &scene->id); +#endif + + bool result = BKE_id_copy_ex(NULL, + id_for_copy, + (ID **)&new_scene, + LIB_ID_CREATE_NO_MAIN | + LIB_ID_CREATE_NO_USER_REFCOUNT | + LIB_ID_CREATE_NO_ALLOCATE | + LIB_ID_CREATE_NO_DEG_TAG, + false); + +#ifdef NESTED_ID_NASTY_WORKAROUND + if (result) { + nested_id_hack_restore_pointers(&scene->id, &new_scene->id); + } +#endif + + return result; +} + +/* Check whether given ID is expanded or still a shallow copy. */ +BLI_INLINE bool check_datablock_expanded(const ID *id_cow) +{ + return (id_cow->name[0] != '\0'); +} + +/* Those are datablocks which are not covered by dependency graph and hence + * does not need any remapping or anything. + * + * TODO(sergey): How to make it more robust for the future, so we don't have + * to maintain exception lists all over the code? + */ +static bool check_datablocks_copy_on_writable(const ID *id_orig) +{ + const ID_Type id_type = GS(id_orig->name); + /* We shouldn't bother if copied ID is same as original one. */ + if (!deg_copy_on_write_is_needed(id_orig)) { + return false; + } + return !ELEM(id_type, ID_BR, + ID_LS, + ID_PAL); +} + +/* Callback for BKE_library_foreach_ID_link which remaps original ID pointer + * with the one created by CoW system. + */ + +struct RemapCallbackUserData { + /* Dependency graph for which remapping is happening. */ + const Depsgraph *depsgraph; + /* Create placeholder for ID nodes for cases when we need to remap original + * ID to it[s CoW version but we don't have required ID node yet. + * + * This happens when expansion happens a ta construction time. + */ + DepsgraphNodeBuilder *node_builder; + bool create_placeholders; +}; + +int foreach_libblock_remap_callback(void *user_data_v, + ID *id_self, + ID **id_p, + int /*cb_flag*/) +{ + if (*id_p == NULL) { + return IDWALK_RET_NOP; + } + RemapCallbackUserData *user_data = (RemapCallbackUserData *)user_data_v; + const Depsgraph *depsgraph = user_data->depsgraph; + ID *id_orig = *id_p; + if (check_datablocks_copy_on_writable(id_orig)) { + ID *id_cow; + if (user_data->create_placeholders) { + /* Special workaround to stop creating temp datablocks for + * objects which are coming from scene's collection and which + * are never linked to any of layers. + * + * TODO(sergey): Ideally we need to tell ID looper to ignore + * those or at least make it more reliable check where the + * pointer is coming from. + */ + const ID_Type id_type = GS(id_orig->name); + const ID_Type id_type_self = GS(id_self->name); + if (id_type == ID_OB && id_type_self == ID_SCE) { + IDDepsNode *id_node = depsgraph->find_id_node(id_orig); + if (id_node == NULL) { + id_cow = id_orig; + } + else { + id_cow = id_node->id_cow; + } + } + else { + id_cow = user_data->node_builder->ensure_cow_id(id_orig); + } + } + else { + id_cow = depsgraph->get_cow_id(id_orig); + } + BLI_assert(id_cow != NULL); + DEG_COW_PRINT(" Remapping datablock for %s: id_orig=%p id_cow=%p\n", + id_orig->name, id_orig, id_cow); + *id_p = id_cow; + } + return IDWALK_RET_NOP; +} + +void update_armature_edit_mode_pointers(const Depsgraph * /*depsgraph*/, + const ID *id_orig, ID *id_cow) +{ + const bArmature *armature_orig = (const bArmature *)id_orig; + bArmature *armature_cow = (bArmature *)id_cow; + armature_cow->edbo = armature_orig->edbo; +} + +void update_curve_edit_mode_pointers(const Depsgraph * /*depsgraph*/, + const ID *id_orig, ID *id_cow) +{ + const Curve *curve_orig = (const Curve *)id_orig; + Curve *curve_cow = (Curve *)id_cow; + curve_cow->editnurb = curve_orig->editnurb; + curve_cow->editfont = curve_orig->editfont; +} + +void update_mball_edit_mode_pointers(const Depsgraph * /*depsgraph*/, + const ID *id_orig, ID *id_cow) +{ + const MetaBall *mball_orig = (const MetaBall *)id_orig; + MetaBall *mball_cow = (MetaBall *)id_cow; + mball_cow->editelems = mball_orig->editelems; +} + +void update_lattice_edit_mode_pointers(const Depsgraph * /*depsgraph*/, + const ID *id_orig, ID *id_cow) +{ + const Lattice *lt_orig = (const Lattice *)id_orig; + Lattice *lt_cow = (Lattice *)id_cow; + lt_cow->editlatt = lt_orig->editlatt; +} + +void update_mesh_edit_mode_pointers(const Depsgraph *depsgraph, + const ID *id_orig, ID *id_cow) +{ + /* For meshes we need to update edit_btmesh to make it to point + * to the CoW version of object. + * + * This is kind of confusing, because actual bmesh is not owned by + * the CoW object, so need to be accurate about using link from + * edit_btmesh to object. + */ + const Mesh *mesh_orig = (const Mesh *)id_orig; + Mesh *mesh_cow = (Mesh *)id_cow; + if (mesh_orig->edit_btmesh == NULL) { + return; + } + mesh_cow->edit_btmesh = (BMEditMesh *)MEM_dupallocN(mesh_orig->edit_btmesh); + mesh_cow->edit_btmesh->ob = + (Object *)depsgraph->get_cow_id(&mesh_orig->edit_btmesh->ob->id); + mesh_cow->edit_btmesh->derivedFinal = NULL; + mesh_cow->edit_btmesh->derivedCage = NULL; +} + +/* Edit data is stored and owned by original datablocks, copied ones + * are simply referencing to them. + */ +void update_edit_mode_pointers(const Depsgraph *depsgraph, + const ID *id_orig, ID *id_cow) +{ + const ID_Type type = GS(id_orig->name); + switch (type) { + case ID_AR: + update_armature_edit_mode_pointers(depsgraph, id_orig, id_cow); + break; + case ID_ME: + update_mesh_edit_mode_pointers(depsgraph, id_orig, id_cow); + break; + case ID_CU: + update_curve_edit_mode_pointers(depsgraph, id_orig, id_cow); + break; + case ID_MB: + update_mball_edit_mode_pointers(depsgraph, id_orig, id_cow); + break; + case ID_LT: + update_lattice_edit_mode_pointers(depsgraph, id_orig, id_cow); + break; + default: + break; + } +} + +void update_particle_system_orig_pointers(const Object *object_orig, + Object *object_cow) +{ + ParticleSystem *psys_cow = + (ParticleSystem *) object_cow->particlesystem.first; + ParticleSystem *psys_orig = + (ParticleSystem *) object_orig->particlesystem.first; + while (psys_orig != NULL) { + psys_cow->orig_psys = psys_orig; + psys_cow = psys_cow->next; + psys_orig = psys_orig->next; + } +} + +void update_pose_orig_pointers(const bPose *pose_orig, bPose *pose_cow) +{ + bPoseChannel *pchan_cow = (bPoseChannel *) pose_cow->chanbase.first; + bPoseChannel *pchan_orig = (bPoseChannel *) pose_orig->chanbase.first; + while (pchan_orig != NULL) { + pchan_cow->orig_pchan = pchan_orig; + pchan_cow = pchan_cow->next; + pchan_orig = pchan_orig->next; + } +} + +/* Do some special treatment of data transfer from original ID to it's + * CoW complementary part. + * + * Only use for the newly created CoW datablocks. + */ +void update_special_pointers(const Depsgraph *depsgraph, + const ID *id_orig, ID *id_cow) +{ + const ID_Type type = GS(id_orig->name); + switch (type) { + case ID_OB: + { + /* Ensure we don't drag someone's else derived mesh to the + * new copy of the object. + */ + Object *object_cow = (Object *)id_cow; + const Object *object_orig = (const Object *)id_orig; + BLI_assert(object_cow->derivedFinal == NULL); + BLI_assert(object_cow->derivedDeform == NULL); + object_cow->mode = object_orig->mode; + object_cow->sculpt = object_orig->sculpt; + if (object_cow->type == OB_ARMATURE) { + BKE_pose_remap_bone_pointers((bArmature *)object_cow->data, + object_cow->pose); + update_pose_orig_pointers(object_orig->pose, object_cow->pose); + } + update_particle_system_orig_pointers(object_orig, object_cow); + break; + } + default: + break; + } + update_edit_mode_pointers(depsgraph, id_orig, id_cow); + BKE_animsys_update_driver_array(id_cow); +} + +/* This callback is used to validate that all nested ID datablocks are + * properly expanded. + */ +int foreach_libblock_validate_callback(void *user_data, + ID * /*id_self*/, + ID **id_p, + int /*cb_flag*/) +{ + ValidateData *data = (ValidateData *)user_data; + if (*id_p != NULL) { + if (!check_datablock_expanded(*id_p)) { + data->is_valid = false; + /* TODO(sergey): Store which is is not valid? */ + } + } + return IDWALK_RET_NOP; +} + +} // namespace + +/* Actual implementation of logic which "expands" all the data which was not + * yet copied-on-write. + * + * NOTE: Expects that CoW datablock is empty. + */ +ID *deg_expand_copy_on_write_datablock(const Depsgraph *depsgraph, + const IDDepsNode *id_node, + DepsgraphNodeBuilder *node_builder, + bool create_placeholders) +{ + const ID *id_orig = id_node->id_orig; + ID *id_cow = id_node->id_cow; + const int id_cow_recalc = id_cow->recalc; + /* No need to expand such datablocks, their copied ID is same as original + * one already. + */ + if (!deg_copy_on_write_is_needed(id_orig)) { + return id_cow; + } + DEG_COW_PRINT("Expanding datablock for %s: id_orig=%p id_cow=%p\n", + id_orig->name, id_orig, id_cow); + /* Sanity checks. */ + /* NOTE: Disabled for now, conflicts when re-using evaluated datablock when + * rebuilding dependencies. + */ + if (check_datablock_expanded(id_cow) && create_placeholders) { + deg_free_copy_on_write_datablock(id_cow); + } + // BLI_assert(check_datablock_expanded(id_cow) == false); + /* Copy data from original ID to a copied version. */ + /* TODO(sergey): Avoid doing full ID copy somehow, make Mesh to reference + * original geometry arrays for until those are modified. + */ + /* TODO(sergey): We do some trickery with temp bmain and extra ID pointer + * just to be able to use existing API. Ideally we need to replace this with + * in-place copy from existing datablock to a prepared memory. + * + * NOTE: We don't use BKE_main_{new,free} because: + * - We don't want heap-allocations here. + * - We don't want bmain's content to be freed when main is freed. + */ + bool done = false; + /* First we handle special cases which are not covered by id_copy() yet. + * or cases where we want to do something smarter than simple datablock + * copy. + */ + const ID_Type id_type = GS(id_orig->name); + switch (id_type) { + case ID_SCE: + { + done = scene_copy_inplace_no_main((Scene *)id_orig, (Scene *)id_cow); + break; + } + case ID_ME: + { + /* TODO(sergey): Ideally we want to handle meshes in a special + * manner here to avoid initial copy of all the geometry arrays. + */ + break; + } + default: + break; + } + if (!done) { + done = id_copy_inplace_no_main(id_orig, id_cow); + } + if (!done) { + BLI_assert(!"No idea how to perform CoW on datablock"); + } + /* Update pointers to nested ID datablocks. */ + DEG_COW_PRINT(" Remapping ID links for %s: id_orig=%p id_cow=%p\n", + id_orig->name, id_orig, id_cow); + +#ifdef NESTED_ID_NASTY_WORKAROUND + ntree_hack_remap_pointers(depsgraph, id_cow); +#endif + /* Do it now, so remapping will understand that possibly remapped self ID + * is not to be remapped again. + */ + deg_tag_copy_on_write_id(id_cow, id_orig); + /* Perform remapping of the nodes. */ + RemapCallbackUserData user_data = {NULL}; + user_data.depsgraph = depsgraph; + user_data.node_builder = node_builder; + user_data.create_placeholders = create_placeholders; + BKE_library_foreach_ID_link(NULL, + id_cow, + foreach_libblock_remap_callback, + (void *)&user_data, + IDWALK_NOP); + /* Correct or tweak some pointers which are not taken care by foreach + * from above. + */ + update_special_pointers(depsgraph, id_orig, id_cow); + id_cow->recalc = id_orig->recalc | id_cow_recalc; + return id_cow; +} + +/* NOTE: Depsgraph is supposed to have ID node already. */ +ID *deg_expand_copy_on_write_datablock(const Depsgraph *depsgraph, + ID *id_orig, + DepsgraphNodeBuilder *node_builder, + bool create_placeholders) +{ + DEG::IDDepsNode *id_node = depsgraph->find_id_node(id_orig); + BLI_assert(id_node != NULL); + return deg_expand_copy_on_write_datablock(depsgraph, + id_node, + node_builder, + create_placeholders); +} + +static void deg_update_copy_on_write_animation(const Depsgraph *depsgraph, + const IDDepsNode *id_node) +{ + DEG_debug_print_eval((::Depsgraph *)depsgraph, + __func__, + id_node->id_orig->name, + id_node->id_cow); + BKE_animdata_copy_id(NULL, id_node->id_cow, id_node->id_orig, false, false); + RemapCallbackUserData user_data = {NULL}; + user_data.depsgraph = depsgraph; + BKE_library_foreach_ID_link(NULL, + id_node->id_cow, + foreach_libblock_remap_callback, + (void *)&user_data, + IDWALK_NOP); +} + +typedef struct ObjectRuntimeBackup { + CurveCache *curve_cache; + Object_Runtime runtime; + short base_flag; +} ObjectRuntimeBackup; + +/* Make a backup of object's evaluation runtime data, additionally + * male object to be safe for free without invalidating backed up + * pointers. + */ +static void deg_backup_object_runtime( + Object *object, + ObjectRuntimeBackup *object_runtime_backup) +{ + /* Store evaluated mesh, and make sure we don't free it. */ + Mesh *mesh_eval = object->runtime.mesh_eval; + object_runtime_backup->runtime = object->runtime; + BKE_object_runtime_reset(object); + /* Currently object update will override actual object->data + * to an evaluated version. Need to make sure we don't have + * data set to evaluated one before free anything. + */ + if (mesh_eval != NULL && object->data == mesh_eval) { + object->data = mesh_eval->id.orig_id; + } + /* Store curve cache and make sure we don't free it. */ + object_runtime_backup->curve_cache = object->curve_cache; + object->curve_cache = NULL; + /* Make a backup of base flags. */ + object_runtime_backup->base_flag = object->base_flag; +} + +static void deg_restore_object_runtime( + Object *object, + const ObjectRuntimeBackup *object_runtime_backup) +{ + object->runtime = object_runtime_backup->runtime; + if (object->runtime.mesh_eval != NULL) { + Mesh *mesh_eval = object->runtime.mesh_eval; + /* Do same thing as object update: override actual object data + * pointer with evaluated datablock. + */ + if (object->type == OB_MESH) { + object->data = mesh_eval; + /* Evaluated mesh simply copied edit_btmesh pointer from + * original mesh during update, need to make sure no dead + * pointers are left behind. + */ + Mesh *mesh = ((Mesh *)mesh_eval->id.orig_id); + mesh_eval->edit_btmesh = mesh->edit_btmesh; + } + } + if (object_runtime_backup->curve_cache != NULL) { + object->curve_cache = object_runtime_backup->curve_cache; + } + object->base_flag = object_runtime_backup->base_flag; +} + +ID *deg_update_copy_on_write_datablock(const Depsgraph *depsgraph, + const IDDepsNode *id_node) +{ + const ID *id_orig = id_node->id_orig; + const ID_Type id_type = GS(id_orig->name); + ID *id_cow = id_node->id_cow; + /* Similar to expansion, no need to do anything here. */ + if (!deg_copy_on_write_is_needed(id_orig)) { + return id_cow; + } + /* For the rest if datablock types we use simple logic: + * - Free previously expanded data, if any. + * - Perform full datablock copy. + * + * Note that we never free GPU materials from here since that's not + * safe for threading and GPU materials are likely to be re-used. + */ + /* TODO(sergey): Either move this to an utility function or redesign + * Copy-on-Write components in a way that only needed parts are being + * copied over. + */ + /* TODO(sergey): Wrap GPU material backup and object runtime backup to a + * generic backup structure. + */ + ListBase gpumaterial_backup; + ListBase *gpumaterial_ptr = NULL; + ObjectRuntimeBackup object_runtime_backup = {NULL}; + if (check_datablock_expanded(id_cow)) { + switch (id_type) { + case ID_MA: + { + Material *material = (Material *)id_cow; + gpumaterial_ptr = &material->gpumaterial; + break; + } + case ID_WO: + { + World *world = (World *)id_cow; + gpumaterial_ptr = &world->gpumaterial; + break; + } + case ID_NT: + { + /* Node trees should try to preserve their socket pointers + * as much as possible. This is due to UBOs code in GPU, + * which references sockets from trees. + * + * These flags CURRENTLY don't need full datablock update, + * everything is done by node tree update function which + * only copies socket values. + */ + const int ignore_flag = (ID_RECALC_DRAW | + ID_RECALC_ANIMATION | + ID_RECALC_COPY_ON_WRITE); + if ((id_cow->recalc & ~ignore_flag) == 0) { + deg_update_copy_on_write_animation(depsgraph, id_node); + return id_cow; + } + break; + } + case ID_OB: + deg_backup_object_runtime((Object *)id_cow, + &object_runtime_backup); + break; + default: + break; + } + if (gpumaterial_ptr != NULL) { + gpumaterial_backup = *gpumaterial_ptr; + gpumaterial_ptr->first = gpumaterial_ptr->last = NULL; + } + } + deg_free_copy_on_write_datablock(id_cow); + deg_expand_copy_on_write_datablock(depsgraph, id_node); + /* Restore GPU materials. */ + if (gpumaterial_ptr != NULL) { + *gpumaterial_ptr = gpumaterial_backup; + } + if (id_type == ID_OB) { + deg_restore_object_runtime((Object *)id_cow, &object_runtime_backup); + } + return id_cow; +} + +/* NOTE: Depsgraph is supposed to have ID node already. */ +ID *deg_update_copy_on_write_datablock(const Depsgraph *depsgraph, + ID *id_orig) +{ + DEG::IDDepsNode *id_node = depsgraph->find_id_node(id_orig); + BLI_assert(id_node != NULL); + return deg_update_copy_on_write_datablock(depsgraph, id_node); +} + +namespace { + +void discard_armature_edit_mode_pointers(ID *id_cow) +{ + bArmature *armature_cow = (bArmature *)id_cow; + armature_cow->edbo = NULL; +} + +void discard_curve_edit_mode_pointers(ID *id_cow) +{ + Curve *curve_cow = (Curve *)id_cow; + curve_cow->editnurb = NULL; + curve_cow->editfont = NULL; +} + +void discard_mball_edit_mode_pointers(ID *id_cow) +{ + MetaBall *mball_cow = (MetaBall *)id_cow; + mball_cow->editelems = NULL; +} + +void discard_lattice_edit_mode_pointers(ID *id_cow) +{ + Lattice *lt_cow = (Lattice *)id_cow; + lt_cow->editlatt = NULL; +} + +void discard_mesh_edit_mode_pointers(ID *id_cow) +{ + Mesh *mesh_cow = (Mesh *)id_cow; + if (mesh_cow->edit_btmesh == NULL) { + return; + } + BKE_editmesh_free_derivedmesh(mesh_cow->edit_btmesh); + MEM_freeN(mesh_cow->edit_btmesh); + mesh_cow->edit_btmesh = NULL; +} + +/* NULL-ify all edit mode pointers which points to data from + * original object. + */ +void discard_edit_mode_pointers(ID *id_cow) +{ + const ID_Type type = GS(id_cow->name); + switch (type) { + case ID_AR: + discard_armature_edit_mode_pointers(id_cow); + break; + case ID_ME: + discard_mesh_edit_mode_pointers(id_cow); + break; + case ID_CU: + discard_curve_edit_mode_pointers(id_cow); + break; + case ID_MB: + discard_mball_edit_mode_pointers(id_cow); + break; + case ID_LT: + discard_lattice_edit_mode_pointers(id_cow); + break; + default: + break; + } +} + +} // namespace + +/* Free content of the CoW datablock + * Notes: + * - Does not recurs into nested ID datablocks. + * - Does not free datablock itself. + */ +void deg_free_copy_on_write_datablock(ID *id_cow) +{ + if (!check_datablock_expanded(id_cow)) { + /* Actual content was never copied on top of CoW block, we have + * nothing to free. + */ + return; + } + const ID_Type type = GS(id_cow->name); +#ifdef NESTED_ID_NASTY_WORKAROUND + nested_id_hack_discard_pointers(id_cow); +#endif + switch (type) { + case ID_OB: + { + /* TODO(sergey): This workaround is only to prevent free derived + * caches from modifying object->data. This is currently happening + * due to mesh/curve datablock boundbox tagging dirty. + */ + Object *ob_cow = (Object *)id_cow; + ob_cow->data = NULL; + ob_cow->sculpt = NULL; + break; + } + default: + break; + } + discard_edit_mode_pointers(id_cow); + BKE_libblock_free_datablock(id_cow, 0); + BKE_libblock_free_data(id_cow, false); + /* Signal datablock as not being expanded. */ + id_cow->name[0] = '\0'; +} + +void deg_evaluate_copy_on_write(struct ::Depsgraph *graph, + const IDDepsNode *id_node) +{ + const DEG::Depsgraph *depsgraph = reinterpret_cast<const DEG::Depsgraph *>(graph); + DEG_debug_print_eval(graph, __func__, id_node->id_orig->name, id_node->id_cow); + if (id_node->id_orig == &depsgraph->scene->id) { + /* NOTE: This is handled by eval_ctx setup routines, which + * ensures scene and view layer pointers are valid. + */ + return; + } + deg_update_copy_on_write_datablock(depsgraph, id_node); +} + +bool deg_validate_copy_on_write_datablock(ID *id_cow) +{ + if (id_cow == NULL) { + return false; + } + ValidateData data; + data.is_valid = true; + BKE_library_foreach_ID_link(NULL, + id_cow, + foreach_libblock_validate_callback, + &data, + IDWALK_NOP); + return data.is_valid; +} + +void deg_tag_copy_on_write_id(ID *id_cow, const ID *id_orig) +{ + BLI_assert(id_cow != id_orig); + BLI_assert((id_orig->tag & LIB_TAG_COPY_ON_WRITE) == 0); + id_cow->tag |= LIB_TAG_COPY_ON_WRITE; + id_cow->orig_id = (ID *)id_orig; +} + +bool deg_copy_on_write_is_expanded(const ID *id_cow) +{ + return check_datablock_expanded(id_cow); +} + +bool deg_copy_on_write_is_needed(const ID *id_orig) +{ + const ID_Type id_type = GS(id_orig->name); + return !ELEM(id_type, ID_IM); +} + +} // namespace DEG |