Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorSergey Sharybin <sergey.vfx@gmail.com>2017-06-14 11:26:24 +0300
committerSergey Sharybin <sergey.vfx@gmail.com>2017-06-19 14:21:44 +0300
commit802027f3f8f9a83a77134a2b104a25ff3a4ac013 (patch)
treec07f31493dbc4f0149993588c606ab4d54a9df4c /source
parentb4d053efc75424fca4b413ac1bc7a7e826fac629 (diff)
Depsgraph: Initial groundwork for copy-on-write support
< Dependency graph Copy-on-Write > -------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || This is an initial commit of Copy-on-write support added to dependency graph. Main priority for now: get playback (Alt-A) and all operators (selection, transform etc) to work with the new concept of clear separation between evaluated data coming from dependency graph and original data coming from .blend file (and stored in bmain). = How does this work? = The idea is to support Copy-on-Write on the ID level. This means, we duplicate the whole ID before we cann it's evaluaiton function. This is currently done in the following way: - At the depsgraph construction time we create "shallow" copy of the ID datablock, just so we know it's pointer in memory and can use for function bindings. - At the evaluaiton time, the copy of ID get's "expanded" (needs a better name internally, so it does not conflict with expanding datablocks during library linking), which means the content of the datablock is being copied over and all IDs are getting remapped to the copied ones. Currently we do the whole copy, in the future we will support some tricks here to prevent duplicating geometry arrays (verts, edges, loops, faces and polys) when we don't need that. - Evaluation functions are operating on copied datablocks and never touching original datablock. - There are some cases when we need to know non-ID pointers for function bindings. This mainly applies to scene collections and armatures. The idea of dealing with this is to "expand" copy-on-write datablock at the dependency graph build time. This might introduce some slowdown to the dependency graph construction time, but allows us to have minimal changes in the code and avoid any hash look-up from evaluation function (one of the ideas to avoid using pointers as function bindings is to pass name of layer or a bone to the evaluation function and look up actual data based on that name). Currently there is a special function in depsgraph which does such a synchronization, in the future we might want to make it more generic. At some point we need to synchronize copy-on-write version of datablock with the original version. This happens, i.e., when we change active object or change selection. We don't want any actual evaluation of update flush happening for such thins, so now we have a special update tag: DEG_id_tag_update((id, DEG_TAG_COPY_ON_WRITE) - For the render engines we now have special call for the dependency graph to give evaluated datablock for the given original one. This isn't fully ideal but allows to have Cycles viewport render. This is definitely a subject for further investigation / improvement. This call will tag copy-on-write component tagged for update without causing updates to be flushed to any other objects, causing chain reaction of updates. This tag is handy when selection in the scene changes. This basically summarizes ideas underneath this commit. The code should be reasonably documented. Here is a demo of dependency graph with all copy-on-write stuff in it: https://developer.blender.org/F635468 = What to expect to (not) work? = - Only meshes are properly-ish aware of copy-on-write currently, Non-mesh geometry will probably crash or will not work at all. - Armatures will need similar depsgraph built-time expansion of the copied datablock. - There are some extra tags / relations added, to keep things demo-able but which are slowing things down for evaluation. - Edit mode works for until click selection is used (due to the selection code using EditDerivedMesh created ad-hoc). - Lots of tools will lack tagging synchronization of copied datablock for sync with original ID. = How to move forward? = There is some tedious work related on going over all the tools, checking whether they need to work with original or final evaluated object and make the required changes. Additionally, there need synchronization tag done in fair amount of tools and operators as well. For example, currently it's not possible to change render engine without re-opening the file or forcing dependency graph for re-build via python console. There is also now some thoughts required about copying evaluated properties between objects or from collection to a new object. Perhaps easiest way would be to move base flag flush to Object ID node and tag new objects for update instead of doing manual copy. here is some WIP patch which moves such evaluaiton / flush: https://developer.blender.org/F635479 Lots of TODOs in the code, with possible optimization. = How to test? = This is a feature under heavy development, so obviously it is disabled by default. The only reason it goes to 2.8 branch is to avoid possible merge hell. In order to enable this feature use WITH_DEPSGRAPH_COPY_ON_WRITE CMake configuration option.
Diffstat (limited to 'source')
-rw-r--r--source/blender/blenkernel/CMakeLists.txt4
-rw-r--r--source/blender/blenkernel/intern/object.c11
-rw-r--r--source/blender/blenkernel/intern/object_update.c48
-rw-r--r--source/blender/depsgraph/CMakeLists.txt6
-rw-r--r--source/blender/depsgraph/DEG_depsgraph.h25
-rw-r--r--source/blender/depsgraph/DEG_depsgraph_query.h3
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder.cc9
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_nodes.cc29
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_nodes.h2
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_nodes_layer.cc14
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_nodes_scene.cc7
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_relations.cc80
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_relations.h12
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_relations_scene.cc3
-rw-r--r--source/blender/depsgraph/intern/debug/deg_debug_graphviz.cc2
-rw-r--r--source/blender/depsgraph/intern/depsgraph.cc17
-rw-r--r--source/blender/depsgraph/intern/depsgraph.h5
-rw-r--r--source/blender/depsgraph/intern/depsgraph_build.cc3
-rw-r--r--source/blender/depsgraph/intern/depsgraph_query.cc27
-rw-r--r--source/blender/depsgraph/intern/depsgraph_tag.cc25
-rw-r--r--source/blender/depsgraph/intern/depsgraph_type_defines.cc9
-rw-r--r--source/blender/depsgraph/intern/depsgraph_types.h55
-rw-r--r--source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc650
-rw-r--r--source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.h84
-rw-r--r--source/blender/depsgraph/intern/eval/deg_eval_debug.cc4
-rw-r--r--source/blender/depsgraph/intern/eval/deg_eval_flush.cc23
-rw-r--r--source/blender/depsgraph/intern/nodes/deg_node.cc38
-rw-r--r--source/blender/depsgraph/intern/nodes/deg_node.h5
-rw-r--r--source/blender/depsgraph/intern/nodes/deg_node_component.cc11
-rw-r--r--source/blender/depsgraph/intern/nodes/deg_node_component.h6
-rw-r--r--source/blender/depsgraph/intern/nodes/deg_node_operation.cc3
-rw-r--r--source/blender/depsgraph/intern/nodes/deg_node_operation.h7
-rw-r--r--source/blender/editors/object/object_edit.c5
-rw-r--r--source/blender/makesdna/DNA_object_types.h6
-rw-r--r--source/blender/makesrna/intern/rna_depsgraph.c11
35 files changed, 1163 insertions, 86 deletions
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index 66c71dcfab1..1d4565f1ec6 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -547,4 +547,8 @@ endif()
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /WX")
#endif()
+if(WITH_DEPSGRAPH_COPY_ON_WRITE)
+ add_definitions(-DWITH_COPY_ON_WRITE)
+endif()
+
blender_add_lib(bf_blenkernel "${SRC}" "${INC}" "${INC_SYS}")
diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c
index 14d93d75440..68e5ddf8583 100644
--- a/source/blender/blenkernel/intern/object.c
+++ b/source/blender/blenkernel/intern/object.c
@@ -363,7 +363,16 @@ void BKE_object_free_derived_caches(Object *ob)
ob->derivedDeform->release(ob->derivedDeform);
ob->derivedDeform = NULL;
}
-
+
+ if (ob->mesh_evaluated != NULL) {
+ /* Evaluated mesh points to edit mesh, but does not own it. */
+ ob->mesh_evaluated->edit_btmesh = NULL;
+ BKE_mesh_free(ob->mesh_evaluated);
+ BKE_libblock_free_data(&ob->mesh_evaluated->id, false);
+ MEM_freeN(ob->mesh_evaluated);
+ ob->mesh_evaluated = NULL;
+ }
+
BKE_object_free_curve_cache(ob);
}
diff --git a/source/blender/blenkernel/intern/object_update.c b/source/blender/blenkernel/intern/object_update.c
index 0c53ee8818a..70e1f434388 100644
--- a/source/blender/blenkernel/intern/object_update.c
+++ b/source/blender/blenkernel/intern/object_update.c
@@ -32,6 +32,7 @@
#include "DNA_group_types.h"
#include "DNA_key_types.h"
#include "DNA_material_types.h"
+#include "DNA_mesh_types.h"
#include "DNA_scene_types.h"
#include "BLI_blenlib.h"
@@ -51,6 +52,7 @@
#include "BKE_key.h"
#include "BKE_lamp.h"
#include "BKE_lattice.h"
+#include "BKE_library.h"
#include "BKE_editmesh.h"
#include "BKE_object.h"
#include "BKE_particle.h"
@@ -60,6 +62,7 @@
#include "BKE_mesh.h"
#include "BKE_image.h"
+#include "MEM_guardedalloc.h"
#include "DEG_depsgraph.h"
#define DEBUG_PRINT if (G.debug & G_DEBUG_DEPSGRAPH) printf
@@ -345,6 +348,51 @@ void BKE_object_eval_uber_data(EvaluationContext *eval_ctx,
break;
}
+#ifdef WITH_COPY_ON_WRITE
+ if (ob->type == OB_MESH) {
+ /* Quick hack to convert evaluated derivedMesh to Mesh. */
+ DerivedMesh *dm = ob->derivedFinal;
+ if (dm != NULL) {
+ Mesh *mesh = (Mesh *)ob->data;
+ Mesh *new_mesh = BKE_libblock_alloc_notest(ID_ME);
+ BKE_mesh_init(new_mesh);
+ /* Copy ID name so GS(new_mesh->id) works correct later on. */
+ BLI_strncpy(new_mesh->id.name, mesh->id.name, sizeof(new_mesh->id.name));
+ /* Copy materials so render engines can access them. */
+ new_mesh->mat = MEM_dupallocN(mesh->mat);
+ new_mesh->totcol = mesh->totcol;
+ DM_to_mesh(dm, new_mesh, ob, ob->lastDataMask, true);
+ new_mesh->edit_btmesh = mesh->edit_btmesh;
+ /* Store result mesh as derived_mesh of object. This way we have
+ * explicit way to query final object evaluated data and know for sure
+ * who owns the newly created mesh datablock.
+ */
+ ob->mesh_evaluated = new_mesh;
+ /* TODO(sergey): This is kind of compatibility thing, so all render
+ * engines can use object->data for mesh data for display. This is
+ * something what we might want to change in the future.
+ */
+ ob->data = new_mesh;
+ /* Save some memory by throwing DerivedMesh away. */
+ /* NOTE: Watch out, some tools might need it!
+ * So keep around for now..
+ */
+ }
+#if 0
+ if (ob->derivedFinal != NULL) {
+ ob->derivedFinal->needsFree = 1;
+ ob->derivedFinal->release(ob->derivedFinal);
+ ob->derivedFinal = NULL;
+ }
+ if (ob->derivedDeform != NULL) {
+ ob->derivedDeform->needsFree = 1;
+ ob->derivedDeform->release(ob->derivedDeform);
+ ob->derivedDeform = NULL;
+ }
+#endif
+ }
+#endif
+
ob->recalc &= ~(OB_RECALC_DATA | OB_RECALC_TIME);
}
diff --git a/source/blender/depsgraph/CMakeLists.txt b/source/blender/depsgraph/CMakeLists.txt
index 5eb5d69d62e..17a13b66aaa 100644
--- a/source/blender/depsgraph/CMakeLists.txt
+++ b/source/blender/depsgraph/CMakeLists.txt
@@ -55,6 +55,7 @@ set(SRC
intern/builder/deg_builder_transitive.cc
intern/debug/deg_debug_graphviz.cc
intern/eval/deg_eval.cc
+ intern/eval/deg_eval_copy_on_write.cc
intern/eval/deg_eval_debug.cc
intern/eval/deg_eval_flush.cc
intern/nodes/deg_node.cc
@@ -80,6 +81,7 @@ set(SRC
intern/builder/deg_builder_relations.h
intern/builder/deg_builder_transitive.h
intern/eval/deg_eval.h
+ intern/eval/deg_eval_copy_on_write.h
intern/eval/deg_eval_debug.h
intern/eval/deg_eval_flush.h
intern/nodes/deg_node.h
@@ -126,4 +128,8 @@ if(WITH_OPENSUBDIV)
add_definitions(-DWITH_OPENSUBDIV)
endif()
+if(WITH_DEPSGRAPH_COPY_ON_WRITE)
+ add_definitions(-DWITH_COPY_ON_WRITE)
+endif()
+
blender_add_lib(bf_depsgraph "${SRC}" "${INC}" "${INC_SYS}")
diff --git a/source/blender/depsgraph/DEG_depsgraph.h b/source/blender/depsgraph/DEG_depsgraph.h
index 63c02d007dc..9caf7635ccf 100644
--- a/source/blender/depsgraph/DEG_depsgraph.h
+++ b/source/blender/depsgraph/DEG_depsgraph.h
@@ -144,10 +144,31 @@ void DEG_graph_data_tag_update(Depsgraph *graph, const struct PointerRNA *ptr);
void DEG_graph_property_tag_update(Depsgraph *graph, const struct PointerRNA *ptr, const struct PropertyRNA *prop);
/* Tag given ID for an update in all the dependency graphs. */
-void DEG_id_tag_update(struct ID *id, short flag);
+enum {
+ /* Object transformation changed, corresponds to OB_RECALC_OB. */
+ DEG_TAG_TRANSFORM = (1 << 0),
+
+ /* Object geoemtry changed, corresponds to OB_RECALC_DATA. */
+ DEG_TAG_GEOMETRY = (1 << 1),
+
+ /* Time changed and animation is to be re-evaluated, OB_RECALC_TIME. */
+ DEG_TAG_TIME = (1 << 2),
+
+ /* Particle system changed. */
+ DEG_TAG_PSYSC_REDO = (1 << 3),
+ DEG_TAG_PSYS_RESET = (1 << 4),
+ DEG_TAG_PSYS_TYPE = (1 << 5),
+ DEG_TAG_PSYS_CHILD = (1 << 6),
+ DEG_TAG_PSYS_PHYS = (1 << 7),
+ DEG_TAG_PSYS = ((1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7)),
+
+ /* Update copy on write component without flushing down the road. */
+ DEG_TAG_COPY_ON_WRITE = (1 << 8),
+};
+void DEG_id_tag_update(struct ID *id, int flag);
void DEG_id_tag_update_ex(struct Main *bmain,
struct ID *id,
- short flag);
+ int flag);
/* Tag given ID type for update.
*
diff --git a/source/blender/depsgraph/DEG_depsgraph_query.h b/source/blender/depsgraph/DEG_depsgraph_query.h
index 649e788a0ef..674389ddd6c 100644
--- a/source/blender/depsgraph/DEG_depsgraph_query.h
+++ b/source/blender/depsgraph/DEG_depsgraph_query.h
@@ -64,6 +64,9 @@ struct SceneLayer *DEG_get_scene_layer(struct Depsgraph *graph);
/* Get the object as properly evaluated by depsgraph. */
struct Object *DEG_get_object(struct Depsgraph *depsgraph, struct Object *ob);
+/* Get evaluated version of given ID datablock. */
+struct ID *DEG_get_evaluated_id(struct Depsgraph *depsgraph, struct ID *id);
+
/* ************************ DAG iterators ********************* */
enum {
diff --git a/source/blender/depsgraph/intern/builder/deg_builder.cc b/source/blender/depsgraph/intern/builder/deg_builder.cc
index e7f54f9b0cb..92c79388657 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder.cc
@@ -50,7 +50,8 @@ void deg_graph_build_finalize(Depsgraph *graph)
*/
GHASH_FOREACH_BEGIN(IDDepsNode *, id_node, graph->id_hash)
{
- ID *id = id_node->id;
+ ID *id = id_node->id_orig;
+ id_node->finalize_build(graph);
if ((id->tag & LIB_TAG_ID_RECALC_ALL)) {
id_node->tag_update(graph);
}
@@ -60,7 +61,11 @@ void deg_graph_build_finalize(Depsgraph *graph)
id_node->tag_update(graph);
}
}
- id_node->finalize_build();
+ /* XXX: This is only so we've got proper COW IDs after rebuild. */
+ /* TODO(sergey): Ideally we'll need to copy evaluated CoW from previous
+ * depsgraph, so we don't need to re-tag anything what we already have.
+ */
+ id_node->tag_update(graph);
}
GHASH_FOREACH_END();
}
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc
index 5ec541612f5..1fc107f0437 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc
@@ -102,6 +102,7 @@ extern "C" {
#include "DEG_depsgraph_build.h"
#include "intern/builder/deg_builder.h"
+#include "intern/eval/deg_eval_copy_on_write.h"
#include "intern/nodes/deg_node.h"
#include "intern/nodes/deg_node_component.h"
#include "intern/nodes/deg_node_operation.h"
@@ -162,7 +163,23 @@ DepsgraphNodeBuilder::~DepsgraphNodeBuilder()
IDDepsNode *DepsgraphNodeBuilder::add_id_node(ID *id)
{
- return m_graph->add_id_node(id, id->name);
+ IDDepsNode *id_node = m_graph->add_id_node(id, id->name);
+#ifdef WITH_COPY_ON_WRITE
+ /* Currently all ID nodes are supposed to have copy-on-write logic.
+ *
+ * NOTE: Zero number of components indicates that ID node was just created.
+ */
+ if (BLI_ghash_size(id_node->components) == 0) {
+ ComponentDepsNode *comp_cow =
+ id_node->add_component(DEG_NODE_TYPE_COPY_ON_WRITE);
+ OperationDepsNode *op_cow = comp_cow->add_operation(
+ function_bind(deg_evaluate_copy_on_write, _1, m_graph, id_node),
+ DEG_OPCODE_COPY_ON_WRITE,
+ "", -1);
+ m_graph->operations.push_back(op_cow);
+ }
+#endif
+ return id_node;
}
TimeSourceDepsNode *DepsgraphNodeBuilder::add_time_source()
@@ -273,6 +290,11 @@ OperationDepsNode *DepsgraphNodeBuilder::find_operation_node(
return find_operation_node(id, comp_type, "", opcode, name, name_tag);
}
+ID *DepsgraphNodeBuilder::get_cow_id(const ID *id_orig) const
+{
+ return m_graph->get_cow_id(id_orig);
+}
+
/* **** Build functions for entity nodes **** */
void DepsgraphNodeBuilder::begin_build(Main *bmain) {
@@ -735,7 +757,10 @@ void DepsgraphNodeBuilder::build_obdata_geom(Scene *scene, Object *ob)
*/
op_node = add_operation_node(&ob->id,
DEG_NODE_TYPE_GEOMETRY,
- function_bind(BKE_object_eval_uber_data, _1, scene, ob),
+ function_bind(BKE_object_eval_uber_data,
+ _1,
+ (Scene *)get_cow_id(&scene->id),
+ (Object *)get_cow_id(&ob->id)),
DEG_OPCODE_GEOMETRY_UBEREVAL);
op_node->set_as_exit();
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h
index 9aa5d68f1dd..54ad1bd5d8a 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h
+++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h
@@ -73,6 +73,8 @@ struct DepsgraphNodeBuilder {
void begin_build(Main *bmain);
+ ID *get_cow_id(const ID *id_orig) const;
+
IDDepsNode *add_id_node(ID *id);
TimeSourceDepsNode *add_time_source();
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes_layer.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes_layer.cc
index e419b72fee8..b6df176545e 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_nodes_layer.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes_layer.cc
@@ -49,6 +49,7 @@ extern "C" {
} /* extern "C" */
#include "intern/builder/deg_builder.h"
+#include "intern/eval/deg_eval_copy_on_write.h"
#include "intern/nodes/deg_node.h"
#include "intern/nodes/deg_node_component.h"
#include "intern/nodes/deg_node_operation.h"
@@ -95,13 +96,22 @@ void DepsgraphNodeBuilder::build_layer_collections(Scene *scene,
void DepsgraphNodeBuilder::build_scene_layer_collections(Scene *scene)
{
+#ifdef WITH_COPY_ON_WRITE
+ /* Make sure we've got ID node, so we can get pointer to CoW datablock. */
+ IDDepsNode *id_node = add_id_node(&scene->id);
+ Scene *scene_cow = (Scene *)deg_expand_copy_on_write_datablock(m_graph,
+ id_node);
+#else
+ Scene *scene_cow = scene;
+#endif
+
LayerCollectionState state;
state.index = 0;
- LINKLIST_FOREACH (SceneLayer *, scene_layer, &scene->render_layers) {
+ LINKLIST_FOREACH (SceneLayer *, scene_layer, &scene_cow->render_layers) {
ComponentDepsNode *comp = add_component_node(&scene->id, DEG_NODE_TYPE_LAYER_COLLECTIONS);
add_operation_node(comp,
- function_bind(BKE_layer_eval_layer_collection_pre, _1, scene, scene_layer),
+ function_bind(BKE_layer_eval_layer_collection_pre, _1, scene_cow, scene_layer),
DEG_OPCODE_SCENE_LAYER_INIT,
scene_layer->name);
add_operation_node(comp,
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes_scene.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes_scene.cc
index 71c74aad966..f24d4e8d3f2 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_nodes_scene.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes_scene.cc
@@ -135,6 +135,13 @@ void DepsgraphNodeBuilder::build_scene(Main *bmain, Scene *scene)
/* Collections. */
build_scene_layer_collections(scene);
+
+ /* Parameters evaluation for scene relations mainly. */
+ add_operation_node(&scene->id,
+ DEG_NODE_TYPE_PARAMETERS,
+ NULL,
+ DEG_OPCODE_PLACEHOLDER,
+ "Scene Eval");
}
} // namespace DEG
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
index 2c703441c8c..006358ea9e1 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
@@ -976,8 +976,8 @@ void DepsgraphRelationBuilder::build_driver(ID *id, FCurve *fcu)
IDDepsNode *to_node = (IDDepsNode *)rel->to;
/* we only care about objects with pose data which use this... */
- if (GS(to_node->id->name) == ID_OB) {
- Object *ob = (Object *)to_node->id;
+ if (GS(to_node->id_orig->name) == ID_OB) {
+ Object *ob = (Object *)to_node->id_orig;
bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name); // NOTE: ob->pose may be NULL
if (pchan) {
@@ -1391,12 +1391,22 @@ void DepsgraphRelationBuilder::build_obdata_geom(Main *bmain, Scene *scene, Obje
/* link components to each other */
add_relation(obdata_geom_key, geom_key, "Object Geometry Base Data");
+ OperationKey obdata_ubereval_key(&ob->id,
+ DEG_NODE_TYPE_GEOMETRY,
+ DEG_OPCODE_GEOMETRY_UBEREVAL);
+
+ /* Special case: modifiers and DerivedMesh creation queries scene for various
+ * things like data mask to be used. We add relation here to ensure object is
+ * never evaluated prior to Scene's CoW is ready.
+ */
+ OperationKey scene_key(&scene->id,
+ DEG_NODE_TYPE_PARAMETERS,
+ DEG_OPCODE_PLACEHOLDER,
+ "Scene Eval");
+ add_relation(scene_key, obdata_ubereval_key, "CoW Relation");
+
/* Modifiers */
if (ob->modifiers.first != NULL) {
- OperationKey obdata_ubereval_key(&ob->id,
- DEG_NODE_TYPE_GEOMETRY,
- DEG_OPCODE_GEOMETRY_UBEREVAL);
-
LINKLIST_FOREACH (ModifierData *, md, &ob->modifiers) {
const ModifierTypeInfo *mti = modifierType_getInfo((ModifierType)md->type);
@@ -1760,4 +1770,62 @@ void DepsgraphRelationBuilder::build_lightprobe(Object *object)
add_relation(probe_key, object_key, "LightProbe Update");
}
+void DepsgraphRelationBuilder::build_copy_on_write_relations()
+{
+ GHASH_FOREACH_BEGIN(IDDepsNode *, id_node, m_graph->id_hash)
+ {
+ build_copy_on_write_relations(id_node);
+ }
+ GHASH_FOREACH_END();
+}
+
+void DepsgraphRelationBuilder::build_copy_on_write_relations(IDDepsNode *id_node)
+{
+ ID *id_orig = id_node->id_orig;
+
+ TimeSourceKey time_source_key;
+ OperationKey copy_on_write_key(id_orig,
+ DEG_NODE_TYPE_COPY_ON_WRITE,
+ DEG_OPCODE_COPY_ON_WRITE);
+ /* XXX: This is a quick hack to make Alt-A to work. */
+ add_relation(time_source_key, copy_on_write_key, "Fluxgate capacitor hack");
+ /* Resat of code is using rather low level trickery, so need to get some
+ * explicit pointers.
+ */
+ DepsNode *node_cow = find_node(copy_on_write_key);
+ OperationDepsNode *op_cow = node_cow->get_exit_operation();
+ /* Plug any other components to this one. */
+ GHASH_FOREACH_BEGIN(ComponentDepsNode *, comp_node, id_node->components)
+ {
+ if (comp_node->type == DEG_NODE_TYPE_COPY_ON_WRITE) {
+ /* Copy-on-write component never depends on itself. */
+ continue;
+ }
+ /* All entry operations of each component should wait for a proper
+ * copy of ID.
+ */
+ OperationDepsNode *op_entry = comp_node->get_entry_operation();
+ if (op_entry != NULL) {
+ m_graph->add_new_relation(op_cow, op_entry, "CoW Dependency");
+ }
+ /* All dangling operations should also be executed after copy-on-write. */
+ GHASH_FOREACH_BEGIN(OperationDepsNode *, op_node, comp_node->operations_map)
+ {
+ if (op_node->inlinks.size() == 0) {
+ m_graph->add_new_relation(op_cow, op_node, "CoW Dependency");
+ }
+ }
+ GHASH_FOREACH_END();
+ /* NOTE: We currently ignore implicit relations to an external
+ * datablocks for copy-on-write operations. This means, for example,
+ * copy-on-write component of Object will not wait for copy-on-write
+ * component of it's Mesh. This is because pointers are all known
+ * already so remapping will happen all correct. And then If some object
+ * evaluation step needs geometry, it will have transitive dependency
+ * to Mesh copy-on-write already.
+ */
+ }
+ GHASH_FOREACH_END();
+}
+
} // namespace DEG
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.h b/source/blender/depsgraph/intern/builder/deg_builder_relations.h
index acab5da66b4..8a53bf4a6bf 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_relations.h
+++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.h
@@ -225,8 +225,13 @@ struct DepsgraphRelationBuilder
void build_movieclip(MovieClip *clip);
void build_lightprobe(Object *object);
- void add_collision_relations(const OperationKey &key, Scene *scene, Object *ob, Group *group, bool dupli, const char *name);
- void add_forcefield_relations(const OperationKey &key, Scene *scene, Object *ob, ParticleSystem *psys, EffectorWeights *eff, bool add_absorption, const char *name);
+ void add_collision_relations(const OperationKey &key,
+ Scene *scene, Object *ob, Group *group,
+ bool dupli, const char *name);
+ void add_forcefield_relations(const OperationKey &key,
+ Scene *scene, Object *ob, ParticleSystem *psys,
+ EffectorWeights *eff,
+ bool add_absorption, const char *name);
struct LayerCollectionState {
int index;
@@ -242,6 +247,9 @@ struct DepsgraphRelationBuilder
LayerCollectionState *state);
void build_scene_layer_collections(Scene *scene);
+ void build_copy_on_write_relations();
+ void build_copy_on_write_relations(IDDepsNode *id_node);
+
template <typename KeyType>
OperationDepsNode *find_operation_node(const KeyType &key);
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations_scene.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations_scene.cc
index f157a4b58d0..43204d5fe98 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_relations_scene.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_relations_scene.cc
@@ -122,13 +122,14 @@ void DepsgraphRelationBuilder::build_scene(Main *bmain, Scene *scene)
/* Collections. */
build_scene_layer_collections(scene);
+ /* TODO(sergey): Do this flush on CoW object? */
for (Depsgraph::OperationNodes::const_iterator it_op = m_graph->operations.begin();
it_op != m_graph->operations.end();
++it_op)
{
OperationDepsNode *node = *it_op;
IDDepsNode *id_node = node->owner->owner;
- ID *id = id_node->id;
+ ID *id = id_node->id_orig;
if (GS(id->name) == ID_OB) {
Object *object = (Object *)id;
object->customdata_mask |= node->customdata_mask;
diff --git a/source/blender/depsgraph/intern/debug/deg_debug_graphviz.cc b/source/blender/depsgraph/intern/debug/deg_debug_graphviz.cc
index 4a79f9c54b2..66b63f861ee 100644
--- a/source/blender/depsgraph/intern/debug/deg_debug_graphviz.cc
+++ b/source/blender/depsgraph/intern/debug/deg_debug_graphviz.cc
@@ -90,6 +90,7 @@ static const int deg_debug_node_type_color_map[][2] = {
{DEG_NODE_TYPE_SHADING, 8},
{DEG_NODE_TYPE_CACHE, 9},
{DEG_NODE_TYPE_LAYER_COLLECTIONS, 10},
+ {DEG_NODE_TYPE_COPY_ON_WRITE, 11},
{-1, 0}
};
#endif
@@ -379,6 +380,7 @@ static void deg_debug_graphviz_node(const DebugContext &ctx,
case DEG_NODE_TYPE_CACHE:
case DEG_NODE_TYPE_LAYER_COLLECTIONS:
case DEG_NODE_TYPE_EVAL_PARTICLES:
+ case DEG_NODE_TYPE_COPY_ON_WRITE:
{
ComponentDepsNode *comp_node = (ComponentDepsNode *)node;
if (!comp_node->operations.empty()) {
diff --git a/source/blender/depsgraph/intern/depsgraph.cc b/source/blender/depsgraph/intern/depsgraph.cc
index e5c5b197481..dc3174751bd 100644
--- a/source/blender/depsgraph/intern/depsgraph.cc
+++ b/source/blender/depsgraph/intern/depsgraph.cc
@@ -282,7 +282,11 @@ IDDepsNode *Depsgraph::add_id_node(ID *id, const char *name)
DepsNodeFactory *factory = deg_get_node_factory(DEG_NODE_TYPE_ID_REF);
id_node = (IDDepsNode *)factory->create_node(id, "", name);
id->tag |= LIB_TAG_DOIT;
- /* register */
+ /* Register node in ID hash.
+ *
+ * NOTE: We address ID nodes by the original ID pointer they are
+ * referencing to.
+ */
BLI_ghash_insert(id_hash, id, id_node);
}
return id_node;
@@ -306,7 +310,7 @@ DepsRelation *Depsgraph::add_new_relation(OperationDepsNode *from,
if (comp_node->type == DEG_NODE_TYPE_GEOMETRY) {
IDDepsNode *id_to = to->owner->owner;
IDDepsNode *id_from = from->owner->owner;
- if (id_to != id_from && (id_to->id->tag & LIB_TAG_ID_RECALC_ALL)) {
+ if (id_to != id_from && (id_to->id_orig->tag & LIB_TAG_ID_RECALC_ALL)) {
if ((id_from->eval_flags & DAG_EVAL_NEED_CPU) == 0) {
id_from->tag_update(this);
id_from->eval_flags |= DAG_EVAL_NEED_CPU;
@@ -404,6 +408,15 @@ void Depsgraph::clear_all_nodes()
}
}
+ID *Depsgraph::get_cow_id(const ID *id_orig) const
+{
+ IDDepsNode *id_node = find_id_node(id_orig);
+ if (id_node == NULL) {
+ return (ID *)id_orig;
+ }
+ return id_node->id_cow;
+}
+
void deg_editors_id_update(Main *bmain, ID *id)
{
if (deg_editor_update_id_cb != NULL) {
diff --git a/source/blender/depsgraph/intern/depsgraph.h b/source/blender/depsgraph/intern/depsgraph.h
index 86928f10d12..9785d2f90d0 100644
--- a/source/blender/depsgraph/intern/depsgraph.h
+++ b/source/blender/depsgraph/intern/depsgraph.h
@@ -130,6 +130,11 @@ struct Depsgraph {
/* Clear storage used by all nodes. */
void clear_all_nodes();
+ /* Copy-on-Write Functionality ........ */
+
+ /* For given original ID get ID which is created by CoW system. */
+ ID *get_cow_id(const ID *id_orig) const;
+
/* Core Graph Functionality ........... */
/* <ID : IDDepsNode> mapping from ID blocks to nodes representing these blocks
diff --git a/source/blender/depsgraph/intern/depsgraph_build.cc b/source/blender/depsgraph/intern/depsgraph_build.cc
index cd15882e306..88ff1ac0a9c 100644
--- a/source/blender/depsgraph/intern/depsgraph_build.cc
+++ b/source/blender/depsgraph/intern/depsgraph_build.cc
@@ -216,6 +216,9 @@ void DEG_graph_build_from_scene(Depsgraph *graph, Main *bmain, Scene *scene)
DEG::DepsgraphRelationBuilder relation_builder(deg_graph);
relation_builder.begin_build(bmain);
relation_builder.build_scene(bmain, scene);
+#ifdef WITH_COPY_ON_WRITE
+ relation_builder.build_copy_on_write_relations();
+#endif
/* Detect and solve cycles. */
DEG::deg_graph_detect_cycles(deg_graph);
diff --git a/source/blender/depsgraph/intern/depsgraph_query.cc b/source/blender/depsgraph/intern/depsgraph_query.cc
index c8d8e4ee37f..3dfa6bf5e9d 100644
--- a/source/blender/depsgraph/intern/depsgraph_query.cc
+++ b/source/blender/depsgraph/intern/depsgraph_query.cc
@@ -49,6 +49,10 @@ extern "C" {
#include "intern/depsgraph_intern.h"
#include "util/deg_util_foreach.h"
+#ifndef NDEBUG
+# include "intern/eval/deg_eval_copy_on_write.h"
+#endif
+
bool DEG_id_type_tagged(Main *bmain, short idtype)
{
return bmain->id_tag_update[BKE_idcode_to_index(idtype)] != 0;
@@ -80,22 +84,29 @@ short DEG_get_eval_flags_for_id(Depsgraph *graph, ID *id)
Scene *DEG_get_scene(Depsgraph *graph)
{
DEG::Depsgraph *deg_graph = reinterpret_cast<DEG::Depsgraph *>(graph);
- return deg_graph->scene;
+ Scene *scene_orig = deg_graph->scene;
+ return reinterpret_cast<Scene *>(deg_graph->get_cow_id(&scene_orig->id));
}
SceneLayer *DEG_get_scene_layer(Depsgraph *graph)
{
Scene *scene = DEG_get_scene(graph);
if (scene) {
- return BKE_scene_layer_context_active(scene);
+ return BKE_scene_layer_render_active(scene);
}
return NULL;
}
-Object *DEG_get_object(Depsgraph * /*depsgraph*/, Object *ob)
+Object *DEG_get_object(Depsgraph *depsgraph, Object *ob)
{
- /* XXX TODO */
- return ob;
+ DEG::Depsgraph *deg_graph = reinterpret_cast<DEG::Depsgraph *>(depsgraph);
+ return (Object *)deg_graph->get_cow_id(&ob->id);
+}
+
+ID *DEG_get_evaluated_id(struct Depsgraph *depsgraph, ID *id)
+{
+ DEG::Depsgraph *deg_graph = reinterpret_cast<DEG::Depsgraph *>(depsgraph);
+ return deg_graph->get_cow_id(id);
}
/* ************************ DAG ITERATORS ********************* */
@@ -167,6 +178,7 @@ static bool deg_objects_dupli_iterator_next(BLI_Iterator *iter)
data->base,
data->base_flag | BASE_FROMDUPLI);
iter->current = &data->temp_dupli_object;
+ BLI_assert(DEG::deg_validate_copy_on_write_datablock(&data->temp_dupli_object.id));
return true;
}
@@ -194,10 +206,13 @@ void DEG_objects_iterator_next(BLI_Iterator *iter)
base = data->base->next;
while (base != NULL) {
if ((base->flag & BASE_VISIBLED) != 0) {
- Object *ob = DEG_get_object(data->graph, base->object);
+ // Object *ob = DEG_get_object(data->graph, base->object);
+ Object *ob = base->object;
iter->current = ob;
data->base = base;
+ BLI_assert(DEG::deg_validate_copy_on_write_datablock(&ob->id));
+
/* Make sure we have the base collection settings is already populated.
* This will fail when BKE_layer_eval_layer_collection_pre hasn't run yet
* Which usually means a missing call to DAG_id_tag_update(). */
diff --git a/source/blender/depsgraph/intern/depsgraph_tag.cc b/source/blender/depsgraph/intern/depsgraph_tag.cc
index a2b8a93ecda..80fa89bbb97 100644
--- a/source/blender/depsgraph/intern/depsgraph_tag.cc
+++ b/source/blender/depsgraph/intern/depsgraph_tag.cc
@@ -127,7 +127,7 @@ void lib_id_recalc_tag_flag(Main *bmain, ID *id, int flag)
}
#ifdef DEPSGRAPH_USE_LEGACY_TAGGING
-void depsgraph_legacy_handle_update_tag(Main *bmain, ID *id, short flag)
+void depsgraph_legacy_handle_update_tag(Main *bmain, ID *id, int flag)
{
if (flag) {
Object *object;
@@ -153,6 +153,20 @@ void depsgraph_legacy_handle_update_tag(Main *bmain, ID *id, short flag)
}
#endif
+#ifdef WITH_COPY_ON_WRITE
+void id_tag_copy_on_write_update(Main *bmain, Depsgraph *graph, ID *id)
+{
+ lib_id_recalc_tag(bmain, id);
+ DEG::Depsgraph *deg_graph = reinterpret_cast<DEG::Depsgraph *>(graph);
+ DEG::IDDepsNode *id_node = deg_graph->find_id_node(id);
+ DEG::ComponentDepsNode *cow_comp =
+ id_node->find_component(DEG::DEG_NODE_TYPE_COPY_ON_WRITE);
+ DEG::OperationDepsNode *cow_node = cow_comp->get_entry_operation();
+ cow_node->tag_update(deg_graph);
+ cow_node->flag |= DEG::DEPSOP_FLAG_SKIP_FLUSH;
+}
+#endif
+
} /* namespace */
/* Tag all nodes in ID-block for update.
@@ -199,12 +213,12 @@ void DEG_graph_property_tag_update(Depsgraph *graph,
}
/* Tag given ID for an update in all the dependency graphs. */
-void DEG_id_tag_update(ID *id, short flag)
+void DEG_id_tag_update(ID *id, int flag)
{
DEG_id_tag_update_ex(G.main, id, flag);
}
-void DEG_id_tag_update_ex(Main *bmain, ID *id, short flag)
+void DEG_id_tag_update_ex(Main *bmain, ID *id, int flag)
{
if (id == NULL) {
/* Ideally should not happen, but old depsgraph allowed this. */
@@ -240,6 +254,11 @@ void DEG_id_tag_update_ex(Main *bmain, ID *id, short flag)
else if (flag & OB_RECALC_TIME) {
DEG_graph_id_tag_update(bmain, graph, id);
}
+ else if (flag & DEG_TAG_COPY_ON_WRITE) {
+#ifdef WITH_COPY_ON_WRITE
+ id_tag_copy_on_write_update(bmain, graph, id);
+#endif
+ }
}
}
diff --git a/source/blender/depsgraph/intern/depsgraph_type_defines.cc b/source/blender/depsgraph/intern/depsgraph_type_defines.cc
index 612944dab6e..bb75a85eea4 100644
--- a/source/blender/depsgraph/intern/depsgraph_type_defines.cc
+++ b/source/blender/depsgraph/intern/depsgraph_type_defines.cc
@@ -66,7 +66,9 @@ static GHash *_depsnode_typeinfo_registry = NULL;
void deg_register_node_typeinfo(DepsNodeFactory *factory)
{
BLI_assert(factory != NULL);
- BLI_ghash_insert(_depsnode_typeinfo_registry, SET_INT_IN_POINTER(factory->type()), factory);
+ BLI_ghash_insert(_depsnode_typeinfo_registry,
+ SET_INT_IN_POINTER(factory->type()),
+ factory);
}
/* Getters ------------------------------------------------- */
@@ -75,7 +77,8 @@ void deg_register_node_typeinfo(DepsNodeFactory *factory)
DepsNodeFactory *deg_get_node_factory(const eDepsNode_Type type)
{
/* look up type - at worst, it doesn't exist in table yet, and we fail */
- return (DepsNodeFactory *)BLI_ghash_lookup(_depsnode_typeinfo_registry, SET_INT_IN_POINTER(type));
+ return (DepsNodeFactory *)BLI_ghash_lookup(_depsnode_typeinfo_registry,
+ SET_INT_IN_POINTER(type));
}
/* Get typeinfo for provided node */
@@ -124,6 +127,8 @@ static const char *stringify_opcode(eDepsOperation_Code opcode)
STRINGIFY_OPCODE(SCENE_LAYER_EVAL);
STRINGIFY_OPCODE(SCENE_LAYER_DONE);
+ STRINGIFY_OPCODE(COPY_ON_WRITE);
+
case DEG_NUM_OPCODES: return "SpecialCase";
#undef STRINGIFY_OPCODE
}
diff --git a/source/blender/depsgraph/intern/depsgraph_types.h b/source/blender/depsgraph/intern/depsgraph_types.h
index 467aedcd9c7..737452549ec 100644
--- a/source/blender/depsgraph/intern/depsgraph_types.h
+++ b/source/blender/depsgraph/intern/depsgraph_types.h
@@ -103,10 +103,7 @@ typedef enum eDepsNode_Type {
DEG_NODE_TYPE_PARAMETERS,
/* Generic "Proxy-Inherit" Component. */
DEG_NODE_TYPE_PROXY,
- /* Animation Component
- *
- * XXX: merge in with parameters?
- */
+ /* Animation Component */
DEG_NODE_TYPE_ANIMATION,
/* Transform Component (Parenting/Constraints) */
DEG_NODE_TYPE_TRANSFORM,
@@ -114,6 +111,14 @@ typedef enum eDepsNode_Type {
DEG_NODE_TYPE_GEOMETRY,
/* Sequencer Component (Scene Only) */
DEG_NODE_TYPE_SEQUENCER,
+ /* Component which contains all operations needed for layer collections
+ * evaluation.
+ */
+ DEG_NODE_TYPE_LAYER_COLLECTIONS,
+ /* Entry component of majority of ID nodes: prepares CoW pointers for
+ * execution.
+ */
+ DEG_NODE_TYPE_COPY_ON_WRITE,
/* **** Evaluation-Related Outer Types (with Subdata) **** */
@@ -127,8 +132,6 @@ typedef enum eDepsNode_Type {
DEG_NODE_TYPE_SHADING,
/* Cache Component */
DEG_NODE_TYPE_CACHE,
- /* Component which contains all operations needed for layer collections evaluation. */
- DEG_NODE_TYPE_LAYER_COLLECTIONS,
} eDepsNode_Type;
/* Identifiers for common operations (as an enum). */
@@ -142,68 +145,52 @@ typedef enum eDepsOperation_Code {
DEG_OPCODE_PLACEHOLDER,
/* Animation, Drivers, etc. ------------------------ */
-
/* NLA + Action */
DEG_OPCODE_ANIMATION,
-
/* Driver */
DEG_OPCODE_DRIVER,
/* Transform --------------------------------------- */
-
/* Transform entry point - local transforms only */
DEG_OPCODE_TRANSFORM_LOCAL,
-
/* Parenting */
DEG_OPCODE_TRANSFORM_PARENT,
-
/* Constraints */
DEG_OPCODE_TRANSFORM_CONSTRAINTS,
+ /* Transform exit point */
+ DEG_OPCODE_TRANSFORM_FINAL,
+ /* Handle object-level updates, mainly proxies hacks and recalc flags. */
+ DEG_OPCODE_OBJECT_UBEREVAL,
- /* Rigidbody Sim - Perform Sim */
+ /* Rigid body -------------------------------------- */
+ /* Perform Simulation */
DEG_OPCODE_RIGIDBODY_REBUILD,
DEG_OPCODE_RIGIDBODY_SIM,
-
- /* Rigidbody Sim - Copy Results to Object */
+ /* Copy results to object */
DEG_OPCODE_TRANSFORM_RIGIDBODY,
- /* Transform exitpoint */
- DEG_OPCODE_TRANSFORM_FINAL,
-
- /* XXX: ubereval is for temporary porting purposes only */
- DEG_OPCODE_OBJECT_UBEREVAL,
-
/* Geometry ---------------------------------------- */
-
- /* XXX: Placeholder - UberEval */
+ /* Evaluate the whole geometry, including modifiers. */
DEG_OPCODE_GEOMETRY_UBEREVAL,
-
/* Curve Objects - Path Calculation (used for path-following tools, */
DEG_OPCODE_GEOMETRY_PATH,
/* Pose -------------------------------------------- */
-
/* Init IK Trees, etc. */
DEG_OPCODE_POSE_INIT,
-
/* Free IK Trees + Compute Deform Matrices */
DEG_OPCODE_POSE_DONE,
-
/* IK/Spline Solvers */
DEG_OPCODE_POSE_IK_SOLVER,
DEG_OPCODE_POSE_SPLINE_IK_SOLVER,
/* Bone -------------------------------------------- */
-
- /* Bone local transforms - Entrypoint */
+ /* Bone local transforms - entry point */
DEG_OPCODE_BONE_LOCAL,
-
/* Pose-space conversion (includes parent + restpose, */
DEG_OPCODE_BONE_POSE_PARENT,
-
/* Constraints */
DEG_OPCODE_BONE_CONSTRAINTS,
-
/* Bone transforms are ready
*
* - "READY" This (internal, noop is used to signal that all pre-IK
@@ -219,8 +206,7 @@ typedef enum eDepsOperation_Code {
DEG_OPCODE_BONE_DONE,
/* Particles --------------------------------------- */
-
- /* XXX: placeholder - Particle System eval */
+ /* Particle System evaluation. */
DEG_OPCODE_PSYS_EVAL,
/* Collections ------------------------------------- */
@@ -228,6 +214,9 @@ typedef enum eDepsOperation_Code {
DEG_OPCODE_SCENE_LAYER_EVAL,
DEG_OPCODE_SCENE_LAYER_DONE,
+ /* Copy on Write ------------------------- */
+ DEG_OPCODE_COPY_ON_WRITE,
+
DEG_NUM_OPCODES,
} eDepsOperation_Code;
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..ade822473e8
--- /dev/null
+++ b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc
@@ -0,0 +1,650 @@
+/*
+ * ***** 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.h
+ * \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
+
+#include "intern/eval/deg_eval_copy_on_write.h"
+
+#include <cstring>
+
+#include "BLI_utildefines.h"
+#include "BLI_threads.h"
+
+#include "BKE_global.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_mesh_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_object_types.h"
+
+#ifdef NESTED_ID_NASTY_WORKAROUND
+# include "DNA_key_types.h"
+# include "DNA_lamp_types.h"
+# include "DNA_linestyle_types.h"
+# include "DNA_material_types.h"
+# include "DNA_mesh_types.h"
+# include "DNA_node_types.h"
+# include "DNA_scene_types.h"
+# include "DNA_texture_types.h"
+# include "DNA_world_types.h"
+#endif
+
+#include "BKE_editmesh.h"
+#include "BKE_library_query.h"
+}
+
+#include "intern/depsgraph.h"
+#include "intern/nodes/deg_node.h"
+
+namespace DEG {
+
+#define DEBUG_PRINT if (G.debug & G_DEBUG_DEPSGRAPH) printf
+
+namespace {
+
+#ifdef NESTED_ID_NASTY_WORKAROUND
+union NestedIDHackTempStorage {
+ FreestyleLineStyle linestyle;
+ Lamp lamp;
+ 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_ME, Mesh, key)
+
+# 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_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_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_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.
+ *
+ * TODO(sergey): Get rid of this once T51804 is handled.
+ */
+bool id_copy_no_main(const ID *id, ID **newid)
+{
+ const ID *id_for_copy = id;
+ Main temp_bmain = {0};
+ SpinLock lock;
+ temp_bmain.lock = (MainLock *)&lock;
+ BLI_spin_init(&lock);
+
+#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 = id_copy(&temp_bmain, (ID *)id_for_copy, newid, false);
+
+#ifdef NESTED_ID_NASTY_WORKAROUND
+ if (result) {
+ nested_id_hack_restore_pointers(id, *newid);
+ }
+#endif
+
+ BLI_spin_end(&lock);
+ return result;
+}
+
+/* Similar to BKE_scene_copy() but does not require main.
+ *
+ * TODO(sergey): Get rid of this once T51804 is handled.
+ */
+Scene *scene_copy_no_main(Scene *scene)
+{
+ const ID *id_for_copy = &scene->id;
+ Main temp_bmain = {0};
+ SpinLock lock;
+ temp_bmain.lock = (MainLock *)&lock;
+ BLI_spin_init(&lock);
+
+#ifdef NESTED_ID_NASTY_WORKAROUND
+ NestedIDHackTempStorage id_hack_storage;
+ id_for_copy = nested_id_hack_get_discarded_pointers(&id_hack_storage,
+ &scene->id);
+#endif
+
+ Scene *new_scene = BKE_scene_copy(&temp_bmain,
+ (Scene *)id_for_copy,
+ SCE_COPY_LINK_OB);
+
+#ifdef NESTED_ID_NASTY_WORKAROUND
+ nested_id_hack_restore_pointers(&scene->id, &new_scene->id);
+#endif
+
+ BLI_spin_end(&lock);
+ return new_scene;
+}
+
+/* Callback for BKE_library_foreach_ID_link which remaps original ID pointer
+ * with the one created by CoW system.
+ */
+int foreach_libblock_remap_callback(void *user_data,
+ ID * /*id_self*/,
+ ID **id_p,
+ int /*cb_flag*/)
+{
+ Depsgraph *depsgraph = (Depsgraph *)user_data;
+ if (*id_p != NULL) {
+ const ID *id_orig = *id_p;
+ ID *id_cow = depsgraph->get_cow_id(id_orig);
+ if (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;
+}
+
+/* Check whether given ID is expanded or still a shallow copy. */
+BLI_INLINE bool check_datablock_expanded(ID *id_cow)
+{
+ return (id_cow->name[0] != '\0');
+}
+
+/* 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 short 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;
+ (void) object_cow; /* Ignored for release builds. */
+ BLI_assert(object_cow->derivedFinal == NULL);
+ BLI_assert(object_cow->derivedDeform == NULL);
+ break;
+ }
+ case ID_ME:
+ {
+ /* For meshes we need to update edit_brtmesh 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) {
+ 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);
+ }
+ break;
+ }
+ case ID_SCE:
+ {
+ const Scene *scene_orig = (const Scene *)id_orig;
+ Scene *scene_cow = (Scene *)id_cow;
+ if (scene_orig->obedit != NULL) {
+ scene_cow->obedit = (Object *)depsgraph->get_cow_id(&scene_orig->obedit->id);
+ }
+ else {
+ scene_cow->obedit = NULL;
+ }
+ break;
+ }
+ }
+}
+
+/* Update copy-on-write version of datablock from it's original ID without re-building
+ * the whole datablock from scratch.
+ *
+ * Used for such special cases as scene collections and armatures, which can not use full
+ * re-alloc due to pointers used as function bindings.
+ */
+void update_copy_on_write_datablock(const Depsgraph *depsgraph,
+ const ID *id_orig, ID *id_cow)
+{
+ if (GS(id_orig->name) == ID_SCE) {
+ const Scene *scene_orig = (const Scene *)id_orig;
+ Scene *scene_cow = (Scene *)id_cow;
+ // Some non-pointer data sync, current frame for now.
+ // TODO(sergey): Are we missing something here?
+ scene_cow->r.cfra = scene_orig->r.cfra;
+ scene_cow->r.subframe = scene_orig->r.subframe;
+ // Update bases.
+ const SceneLayer *sl_orig = (SceneLayer *)scene_orig->render_layers.first;
+ SceneLayer *sl_cow = (SceneLayer *)scene_cow->render_layers.first;
+ while (sl_orig != NULL) {
+ // Update pointers to active base.
+ if (sl_orig->basact == NULL) {
+ sl_cow->basact = NULL;
+ }
+ else {
+ const Object *obact_orig = sl_orig->basact->object;
+ Object *obact_cow = (Object *)depsgraph->get_cow_id(&obact_orig->id);
+ sl_cow->basact = BKE_scene_layer_base_find(sl_cow, obact_cow);
+ }
+ // Update base flags.
+ //
+ // TODO(sergey): We should probably check visibled/selectabled
+ // flag here?
+ const Base *base_orig = (Base *)sl_orig->object_bases.first;
+ Base *base_cow = (Base *)sl_cow->object_bases.first;;
+ while (base_orig != NULL) {
+ base_cow->flag = base_orig->flag;
+ base_orig = base_orig->next;
+ base_cow = base_cow->next;
+ }
+ sl_orig = sl_orig->next;
+ sl_cow = sl_cow->next;
+ }
+ // Update edit object pointer.
+ if (scene_orig->obedit != NULL) {
+ scene_cow->obedit = (Object *)depsgraph->get_cow_id(&scene_orig->obedit->id);
+ }
+ else {
+ scene_cow->obedit = NULL;
+ }
+ // TODO(sergey): Things which are still missing here:
+ // - Active render engine.
+ // - Something else?
+ }
+}
+
+/* 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)
+{
+ const ID *id_orig = id_node->id_orig;
+ ID *id_cow = id_node->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. */
+ 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 short type = GS(id_orig->name);
+ switch (type) {
+ case ID_SCE:
+ {
+ Scene *new_scene = scene_copy_no_main((Scene *)id_orig);
+ *(Scene *)id_cow = *new_scene;
+ MEM_freeN(new_scene);
+ done = true;
+ 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;
+ }
+ }
+ if (!done) {
+ ID *newid;
+ if (id_copy_no_main(id_orig, &newid)) {
+ /* We copy contents of new ID to our CoW placeholder and free ID memory
+ * returned by id_copy().
+ *
+ * TODO(sergey): We can avoid having extra ID allocation here if we'll
+ * have some smarter id_copy() which can use externally allocated memory.
+ */
+ const size_t size = BKE_libblock_get_alloc_info(GS(newid->name), NULL);
+ memcpy(id_cow, newid, size);
+ MEM_freeN(newid);
+ done = true;
+ }
+ }
+ 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
+
+ BKE_library_foreach_ID_link(NULL,
+ id_cow,
+ foreach_libblock_remap_callback,
+ (void *)depsgraph,
+ IDWALK_NOP);
+ /* Correct or tweak some pointers which are not taken care by foreach
+ * from above.
+ */
+ update_special_pointers(depsgraph, id_orig, id_cow);
+ 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)
+{
+ 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);
+}
+
+ID *deg_update_copy_on_write_datablock(const Depsgraph *depsgraph,
+ const IDDepsNode *id_node)
+{
+ const ID *id_orig = id_node->id_orig;
+ ID *id_cow = id_node->id_cow;
+ /* Special case for datablocks which are expanded at the dependency graph
+ * construction time. This datablocks must never change pointers of their
+ * nested data since it is used for function bindings.
+ */
+ if (GS(id_orig->name) == ID_SCE) {
+ BLI_assert(check_datablock_expanded(id_cow) == true);
+ update_copy_on_write_datablock(depsgraph, id_orig, id_cow);
+ return id_cow;
+ }
+ /* For the rest if datablock types we use simple logic:
+ * - Free previously expanded data, if any.
+ * - Perform full datablock copy.
+ */
+ deg_free_copy_on_write_datablock(id_cow);
+ deg_expand_copy_on_write_datablock(depsgraph, id_node);
+ 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);
+}
+
+/* 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 short type = GS(id_cow->name);
+ 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;
+ break;
+ }
+ case ID_ME:
+ {
+ Mesh *mesh_cow = (Mesh *)id_cow;
+ if (mesh_cow->edit_btmesh != NULL) {
+ BKE_editmesh_free_derivedmesh(mesh_cow->edit_btmesh);
+ MEM_freeN(mesh_cow->edit_btmesh);
+ mesh_cow->edit_btmesh = NULL;
+ }
+ break;
+ }
+ case ID_SCE:
+ {
+ /* Special case for scene: we use explicit function call which
+ * ensures no access to other datablocks is done.
+ */
+ BKE_scene_free_ex((Scene *)id_cow, false);
+ BKE_libblock_free_data(id_cow, false);
+ id_cow->name[0] = '\0';
+ return;
+ }
+ }
+#ifdef NESTED_ID_NASTY_WORKAROUND
+ nested_id_hack_discard_pointers(id_cow);
+#endif
+ BKE_libblock_free_datablock(id_cow);
+ BKE_libblock_free_data(id_cow, false);
+ /* Signal datablock as not being expanded. */
+ id_cow->name[0] = '\0';
+}
+
+void deg_evaluate_copy_on_write(EvaluationContext * /*eval_ctx*/,
+ const Depsgraph *depsgraph,
+ const IDDepsNode *id_node)
+{
+ DEBUG_PRINT("%s on %s\n", __func__, id_node->id_orig->name);
+ 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;
+}
+
+} // namespace DEG
diff --git a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.h b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.h
new file mode 100644
index 00000000000..0e1b6642002
--- /dev/null
+++ b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.h
@@ -0,0 +1,84 @@
+/*
+ * ***** 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.h
+ * \ingroup depsgraph
+ */
+
+#pragma once
+
+struct EvaluationContext;
+struct ID;
+
+/* Unkomment this to have verbose log about original and CoW pointers
+ * logged, with detailed information when they are allocated, expanded
+ * and remapped.
+ */
+// #define DEG_DEBUG_COW_POINTERS
+
+#ifdef DEG_DEBUG_COW_POINTERS
+# define DEG_COW_PRINT(format, ...) printf(format, __VA_ARGS__);
+#else
+# define DEG_COW_PRINT(format, ...)
+#endif
+
+namespace DEG {
+
+struct Depsgraph;
+struct IDDepsNode;
+
+/* Get fully expanded (ready for use) copy-on-write datablock for the given
+ * original datablock.
+ */
+ID *deg_expand_copy_on_write_datablock(const Depsgraph *depsgraph,
+ const IDDepsNode *id_node);
+ID *deg_expand_copy_on_write_datablock(const struct Depsgraph *depsgraph,
+ struct ID *id_orig);
+
+/* Makes sure given CoW datablock is brought back to state of the original
+ * datablock.
+ */
+ID *deg_update_copy_on_write_datablock(const Depsgraph *depsgraph,
+ const IDDepsNode *id_node);
+ID *deg_update_copy_on_write_datablock(const struct Depsgraph *depsgraph,
+ struct ID *id_orig);
+
+/* Helper function which frees memory used by copy-on-written databnlock. */
+void deg_free_copy_on_write_datablock(struct ID *id_cow);
+
+/* Callback function for depsgraph operation node which ensures copy-on-write
+ * datablock is ready for use by further evaluation routines.
+ */
+void deg_evaluate_copy_on_write(struct EvaluationContext *eval_ctx,
+ const struct Depsgraph *depsgraph,
+ const struct IDDepsNode *id_node);
+
+/* Check that given ID is propely expanded and does not have any shallow
+ * copies inside.
+ */
+bool deg_validate_copy_on_write_datablock(ID *id_cow);
+
+} // namespace DEG
diff --git a/source/blender/depsgraph/intern/eval/deg_eval_debug.cc b/source/blender/depsgraph/intern/eval/deg_eval_debug.cc
index 23f4adbaacd..e195fdd8ec8 100644
--- a/source/blender/depsgraph/intern/eval/deg_eval_debug.cc
+++ b/source/blender/depsgraph/intern/eval/deg_eval_debug.cc
@@ -104,7 +104,7 @@ void DepsgraphDebug::task_started(Depsgraph *graph,
BLI_spin_lock(&graph->lock);
ComponentDepsNode *comp = node->owner;
- ID *id = comp->owner->id;
+ ID *id = comp->owner->id_orig;
DepsgraphStatsID *id_stats = get_id_stats(id, true);
times_clear(id_stats->times);
@@ -133,7 +133,7 @@ void DepsgraphDebug::task_completed(Depsgraph *graph,
BLI_spin_lock(&graph->lock);
ComponentDepsNode *comp = node->owner;
- ID *id = comp->owner->id;
+ ID *id = comp->owner->id_orig;
DepsgraphStatsID *id_stats = get_id_stats(id, true);
times_add(id_stats->times, time);
diff --git a/source/blender/depsgraph/intern/eval/deg_eval_flush.cc b/source/blender/depsgraph/intern/eval/deg_eval_flush.cc
index 8894b2732a1..605ca990e07 100644
--- a/source/blender/depsgraph/intern/eval/deg_eval_flush.cc
+++ b/source/blender/depsgraph/intern/eval/deg_eval_flush.cc
@@ -120,10 +120,12 @@ void deg_graph_flush_updates(Main *bmain, Depsgraph *graph)
* NOTE: Count how many nodes we need to handle - entry nodes may be
* component nodes which don't count for this purpose!
*/
- GSET_FOREACH_BEGIN(OperationDepsNode *, node, graph->entry_tags)
+ GSET_FOREACH_BEGIN(OperationDepsNode *, op_node, graph->entry_tags)
{
- queue.push_back(node);
- node->scheduled = true;
+ if ((op_node->flag & DEPSOP_FLAG_SKIP_FLUSH) == 0) {
+ queue.push_back(op_node);
+ op_node->scheduled = true;
+ }
}
GSET_FOREACH_END();
@@ -138,12 +140,24 @@ void deg_graph_flush_updates(Main *bmain, Depsgraph *graph)
ComponentDepsNode *comp_node = node->owner;
IDDepsNode *id_node = comp_node->owner;
- ID *id = id_node->id;
+ /* TODO(sergey): Do we need to pass original or evaluated ID here? */
+ ID *id = id_node->id_orig;
if (id_node->done == 0) {
deg_editors_id_update(bmain, id);
lib_id_recalc_tag(bmain, id);
/* TODO(sergey): For until we've got proper data nodes in the graph. */
lib_id_recalc_data_tag(bmain, id);
+
+#ifdef WITH_COPY_ON_WRITE
+ /* Currently this is needed to get ob->mesh to be replaced with
+ * original mesh (rather than being evaluated_mesh).
+ *
+ * TODO(sergey): This is something we need to avoid.
+ */
+ ComponentDepsNode *cow_comp =
+ id_node->find_component(DEG_NODE_TYPE_COPY_ON_WRITE);
+ cow_comp->tag_update(graph);
+#endif
}
if (comp_node->done == 0) {
@@ -172,6 +186,7 @@ void deg_graph_flush_updates(Main *bmain, Depsgraph *graph)
case DEG_NODE_TYPE_PARAMETERS:
case DEG_NODE_TYPE_SEQUENCER:
case DEG_NODE_TYPE_LAYER_COLLECTIONS:
+ case DEG_NODE_TYPE_COPY_ON_WRITE:
/* Ignore, does not translate to object component. */
break;
case DEG_NODE_TYPE_ANIMATION:
diff --git a/source/blender/depsgraph/intern/nodes/deg_node.cc b/source/blender/depsgraph/intern/nodes/deg_node.cc
index 401de7fe048..548911dcfa9 100644
--- a/source/blender/depsgraph/intern/nodes/deg_node.cc
+++ b/source/blender/depsgraph/intern/nodes/deg_node.cc
@@ -39,16 +39,20 @@
extern "C" {
#include "DNA_ID.h"
#include "DNA_anim_types.h"
+#include "DNA_object_types.h"
#include "BKE_animsys.h"
+#include "BKE_library.h"
}
#include "DEG_depsgraph.h"
+#include "intern/eval/deg_eval_copy_on_write.h"
#include "intern/nodes/deg_node_component.h"
#include "intern/nodes/deg_node_operation.h"
#include "intern/depsgraph_intern.h"
#include "util/deg_util_foreach.h"
+#include "util/deg_util_function.h"
namespace DEG {
@@ -163,17 +167,24 @@ void IDDepsNode::init(const ID *id, const char *UNUSED(subdata))
{
/* Store ID-pointer. */
BLI_assert(id != NULL);
- this->id = (ID *)id;
+ this->id_orig = (ID *)id;
this->eval_flags = 0;
components = BLI_ghash_new(id_deps_node_hash_key,
id_deps_node_hash_key_cmp,
"Depsgraph id components hash");
- /* NOTE: components themselves are created if/when needed.
- * This prevents problems with components getting added
- * twice if an ID-Ref needs to be created to house it...
+#ifdef WITH_COPY_ON_WRITE
+ /* Create pointer as early as possible, so we can use it for function
+ * bindings. Rest of data we'll be copying to the new datablock when
+ * it is actually needed.
*/
+ id_cow = (ID *)BKE_libblock_alloc_notest(GS(id->name));
+ DEG_COW_PRINT("Create shallow copy for %s: id_orig=%p id_cow=%p\n",
+ id_orig->name, id_orig, id_cow);
+#else
+ id_cow = id_orig;
+#endif
}
/* Free 'id' node. */
@@ -182,6 +193,14 @@ IDDepsNode::~IDDepsNode()
BLI_ghash_free(components,
id_deps_node_hash_key_free,
id_deps_node_hash_value_free);
+
+#ifdef WITH_COPY_ON_WRITE
+ /* Free memory used by this CoW ID. */
+ deg_free_copy_on_write_datablock(id_cow);
+ MEM_freeN(id_cow);
+ DEG_COW_PRINT("Destroy CoW for %s: id_orig=%p id_cow=%p\n",
+ id_orig->name, id_orig, id_cow);
+#endif
}
ComponentDepsNode *IDDepsNode::find_component(eDepsNode_Type type,
@@ -197,7 +216,7 @@ ComponentDepsNode *IDDepsNode::add_component(eDepsNode_Type type,
ComponentDepsNode *comp_node = find_component(type, name);
if (!comp_node) {
DepsNodeFactory *factory = deg_get_node_factory(type);
- comp_node = (ComponentDepsNode *)factory->create_node(this->id, "", name);
+ comp_node = (ComponentDepsNode *)factory->create_node(this->id_orig, "", name);
/* Register. */
ComponentIDKey *key = OBJECT_GUARDED_NEW(ComponentIDKey, type, name);
@@ -211,10 +230,10 @@ void IDDepsNode::tag_update(Depsgraph *graph)
{
GHASH_FOREACH_BEGIN(ComponentDepsNode *, comp_node, components)
{
- /* TODO(sergey): What about drievrs? */
+ /* TODO(sergey): What about drivers? */
bool do_component_tag = comp_node->type != DEG_NODE_TYPE_ANIMATION;
if (comp_node->type == DEG_NODE_TYPE_ANIMATION) {
- AnimData *adt = BKE_animdata_from_id(id);
+ AnimData *adt = BKE_animdata_from_id(id_orig);
/* Animation data might be null if relations are tagged for update. */
if (adt != NULL && (adt->recalc & ADT_RECALC_ANIM)) {
do_component_tag = true;
@@ -227,11 +246,12 @@ void IDDepsNode::tag_update(Depsgraph *graph)
GHASH_FOREACH_END();
}
-void IDDepsNode::finalize_build()
+void IDDepsNode::finalize_build(Depsgraph *graph)
{
+ /* Finalize build of all components. */
GHASH_FOREACH_BEGIN(ComponentDepsNode *, comp_node, components)
{
- comp_node->finalize_build();
+ comp_node->finalize_build(graph);
}
GHASH_FOREACH_END();
}
diff --git a/source/blender/depsgraph/intern/nodes/deg_node.h b/source/blender/depsgraph/intern/nodes/deg_node.h
index 7b5bd0137a6..4e03072d486 100644
--- a/source/blender/depsgraph/intern/nodes/deg_node.h
+++ b/source/blender/depsgraph/intern/nodes/deg_node.h
@@ -147,10 +147,11 @@ struct IDDepsNode : public DepsNode {
void tag_update(Depsgraph *graph);
- void finalize_build();
+ void finalize_build(Depsgraph *graph);
/* ID Block referenced. */
- ID *id;
+ ID *id_orig;
+ ID *id_cow;
/* Hash to make it faster to look up components. */
GHash *components;
diff --git a/source/blender/depsgraph/intern/nodes/deg_node_component.cc b/source/blender/depsgraph/intern/nodes/deg_node_component.cc
index e1afb4d767c..bd9583a7b67 100644
--- a/source/blender/depsgraph/intern/nodes/deg_node_component.cc
+++ b/source/blender/depsgraph/intern/nodes/deg_node_component.cc
@@ -202,7 +202,7 @@ OperationDepsNode *ComponentDepsNode::add_operation(const DepsEvalOperationCb& o
OperationDepsNode *op_node = has_operation(opcode, name, name_tag);
if (!op_node) {
DepsNodeFactory *factory = deg_get_node_factory(DEG_NODE_TYPE_OPERATION);
- op_node = (OperationDepsNode *)factory->create_node(this->owner->id, "", name);
+ op_node = (OperationDepsNode *)factory->create_node(this->owner->id_orig, "", name);
/* register opnode in this component's operation set */
OperationIDKey *key = OBJECT_GUARDED_NEW(OperationIDKey, opcode, name, name_tag);
@@ -315,7 +315,7 @@ OperationDepsNode *ComponentDepsNode::get_exit_operation()
return NULL;
}
-void ComponentDepsNode::finalize_build()
+void ComponentDepsNode::finalize_build(Depsgraph * /*graph*/)
{
operations.reserve(BLI_ghash_size(operations_map));
GHASH_FOREACH_BEGIN(OperationDepsNode *, op_node, operations_map)
@@ -406,6 +406,11 @@ static DepsNodeFactoryImpl<CacheComponentDepsNode> DNTI_CACHE;
DEG_DEPSNODE_DEFINE(LayerCollectionsDepsNode, DEG_NODE_TYPE_LAYER_COLLECTIONS, "Layer Collections Component");
static DepsNodeFactoryImpl<LayerCollectionsDepsNode> DNTI_LAYER_COLLECTIONS;
+/* Copy-on-write Defines ============================ */
+
+DEG_DEPSNODE_DEFINE(CopyOnWriteDepsNode, DEG_NODE_TYPE_COPY_ON_WRITE, "Copy-on-Write Component");
+static DepsNodeFactoryImpl<CopyOnWriteDepsNode> DNTI_COPY_ON_WRITE;
+
/* Node Types Register =================================== */
void deg_register_component_depsnodes()
@@ -426,6 +431,8 @@ void deg_register_component_depsnodes()
deg_register_node_typeinfo(&DNTI_CACHE);
deg_register_node_typeinfo(&DNTI_LAYER_COLLECTIONS);
+
+ deg_register_node_typeinfo(&DNTI_COPY_ON_WRITE);
}
} // namespace DEG
diff --git a/source/blender/depsgraph/intern/nodes/deg_node_component.h b/source/blender/depsgraph/intern/nodes/deg_node_component.h
index 0d68f103c69..955d197b33a 100644
--- a/source/blender/depsgraph/intern/nodes/deg_node_component.h
+++ b/source/blender/depsgraph/intern/nodes/deg_node_component.h
@@ -131,7 +131,7 @@ struct ComponentDepsNode : public DepsNode {
OperationDepsNode *get_entry_operation();
OperationDepsNode *get_exit_operation();
- void finalize_build();
+ void finalize_build(Depsgraph *graph);
IDDepsNode *owner;
@@ -208,6 +208,10 @@ struct LayerCollectionsDepsNode : public ComponentDepsNode {
DEG_DEPSNODE_DECLARE;
};
+struct CopyOnWriteDepsNode : public ComponentDepsNode {
+ DEG_DEPSNODE_DECLARE;
+};
+
void deg_register_component_depsnodes();
diff --git a/source/blender/depsgraph/intern/nodes/deg_node_operation.cc b/source/blender/depsgraph/intern/nodes/deg_node_operation.cc
index 7467264f612..84b3d33f494 100644
--- a/source/blender/depsgraph/intern/nodes/deg_node_operation.cc
+++ b/source/blender/depsgraph/intern/nodes/deg_node_operation.cc
@@ -76,6 +76,9 @@ string OperationDepsNode::full_identifier() const
void OperationDepsNode::tag_update(Depsgraph *graph)
{
+ if (flag & DEPSOP_FLAG_SKIP_FLUSH) {
+ flag &= ~DEPSOP_FLAG_SKIP_FLUSH;
+ }
if (flag & DEPSOP_FLAG_NEEDS_UPDATE) {
return;
}
diff --git a/source/blender/depsgraph/intern/nodes/deg_node_operation.h b/source/blender/depsgraph/intern/nodes/deg_node_operation.h
index 1e5c3832d03..8a1fadd9c6c 100644
--- a/source/blender/depsgraph/intern/nodes/deg_node_operation.h
+++ b/source/blender/depsgraph/intern/nodes/deg_node_operation.h
@@ -49,7 +49,12 @@ typedef enum eDepsOperation_Flag {
/* Operation is evaluated using CPython; has GIL and security
* implications...
*/
- DEPSOP_FLAG_USES_PYTHON = (1 << 2),
+ DEPSOP_FLAG_USES_PYTHON = (1 << 2),
+
+ /* Special flag which indicates that update tag sohuld not be flushed
+ * up to the dependent nodes.
+ */
+ DEPSOP_FLAG_SKIP_FLUSH = (1 << 3),
} eDepsOperation_Flag;
/* Atomic Operation - Base type for all operations */
diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c
index b7012810fd1..2579b7db419 100644
--- a/source/blender/editors/object/object_edit.c
+++ b/source/blender/editors/object/object_edit.c
@@ -281,6 +281,9 @@ void ED_object_editmode_exit(bContext *C, int flag)
}
if (flag & EM_WAITCURSOR) waitcursor(0);
+
+ /* This way we ensure scene's obedit is copied into all CoW scenes. */
+ DEG_id_tag_update(&scene->id, 0);
}
@@ -396,6 +399,8 @@ void ED_object_editmode_enter(bContext *C, int flag)
if (ok) {
DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ /* This way we ensure scene's obedit is copied into all CoW scenes. */
+ DEG_id_tag_update(&scene->id, 0);
}
else {
scene->obedit = NULL; /* XXX for context */
diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h
index 8ec5e4d763d..160f7f7feff 100644
--- a/source/blender/makesdna/DNA_object_types.h
+++ b/source/blender/makesdna/DNA_object_types.h
@@ -326,7 +326,11 @@ typedef struct Object {
ListBase drawdata; /* runtime, ObjectEngineData */
int deg_update_flag; /* what has been updated in this object */
int select_color;
- int pad3[2];
+
+ /* Mesh structure createrd during object evaluaiton.
+ * It has all modifiers applied.
+ */
+ struct Mesh *mesh_evaluated;
} Object;
/* Warning, this is not used anymore because hooks are now modifiers */
diff --git a/source/blender/makesrna/intern/rna_depsgraph.c b/source/blender/makesrna/intern/rna_depsgraph.c
index 5047bee6b45..9ea12e3befa 100644
--- a/source/blender/makesrna/intern/rna_depsgraph.c
+++ b/source/blender/makesrna/intern/rna_depsgraph.c
@@ -224,6 +224,11 @@ static PointerRNA rna_Depsgraph_duplis_get(CollectionPropertyIterator *iter)
return rna_pointer_inherit_refine(&iter->parent, &RNA_DepsgraphIter, iterator);
}
+static ID *rna_Depsgraph_evaluated_id_get(Depsgraph *depsgraph, ID *id_orig)
+{
+ return DEG_get_evaluated_id(depsgraph, id_orig);
+}
+
#else
static void rna_def_depsgraph_iter(BlenderRNA *brna)
@@ -317,6 +322,12 @@ static void rna_def_depsgraph(BlenderRNA *brna)
"rna_Depsgraph_objects_get",
NULL, NULL, NULL, NULL);
+ func = RNA_def_function(srna, "evaluated_id_get", "rna_Depsgraph_evaluated_id_get");
+ parm = RNA_def_pointer(func, "id", "ID", "", "Original ID to get evaluated complementary part for");
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+ parm = RNA_def_pointer(func, "evaluated_id", "ID", "", "Evaluated ID for the given original one");
+ RNA_def_function_return(func, parm);
+
/* TODO(sergey): Find a better name. */
prop = RNA_def_property(srna, "duplis", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "DepsgraphIter");