Depsgraph: Make Copy-on-Write a command line option

Before it was a compile time option which was not very easy to use or test. Now
the project is getting more mature, so very soon we will be able to call for a
public tests of limited features.

The copy-on-write (which includes animation, modifiers) is enabled using
--enable-copy-on-write command line argument.
This commit is contained in:
Sergey Sharybin 2017-10-18 14:35:34 +02:00
parent 46f0b97d20
commit 3e8abc0535
17 changed files with 173 additions and 153 deletions

View File

@ -517,10 +517,6 @@ if(CMAKE_COMPILER_IS_GNUCC)
mark_as_advanced(WITH_LINKER_GOLD)
endif()
# Dependency graph
option(WITH_DEPSGRAPH_COPY_ON_WRITE "Build Blender with copy-on-write support for dependency graph" OFF)
mark_as_advanced(WITH_DEPSGRAPH_COPY_ON_WRITE)
if(WIN32)
# Use hardcoded paths or find_package to find externals
option(WITH_WINDOWS_FIND_MODULES "Use find_package to locate libraries" OFF)

View File

@ -541,8 +541,4 @@ 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}")

View File

@ -349,55 +349,55 @@ void BKE_object_eval_uber_data(const 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, CD_MASK_MESH, 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..
*/
/* Store original ID as a pointer in evaluated ID.
* This way we can restore original object data when we are freeing
* evaluated mesh.
*/
new_mesh->id.newid = &mesh->id;
}
if (DEG_depsgraph_use_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, CD_MASK_MESH, 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..
*/
/* Store original ID as a pointer in evaluated ID.
* This way we can restore original object data when we are freeing
* evaluated mesh.
*/
new_mesh->id.newid = &mesh->id;
}
#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;
}
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);
}

View File

@ -128,8 +128,4 @@ 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}")

View File

@ -105,6 +105,9 @@ enum {
extern "C" {
#endif
bool DEG_depsgraph_use_copy_on_write(void);
void DEG_depsgraph_enable_copy_on_write(void);
/* ************************************************ */
/* Depsgraph API */

View File

@ -47,6 +47,7 @@ namespace DEG {
void deg_graph_build_finalize(Depsgraph *graph)
{
const bool use_copy_on_write = DEG_depsgraph_use_copy_on_write();
/* Re-tag IDs for update if it was tagged before the relations
* update tag.
*/
@ -63,9 +64,12 @@ void deg_graph_build_finalize(Depsgraph *graph)
id_node->tag_update(graph);
}
}
#ifdef WITH_COPY_ON_WRITE
DEG_id_tag_update_ex(graph->bmain, id_node->id_orig, DEG_TAG_COPY_ON_WRITE);
#endif
/* TODO(sergey): This is not ideal at all, since this forces
* re-evaluaiton of the whole tree.
*/
if (use_copy_on_write) {
DEG_id_tag_update_ex(graph->bmain, id_node->id_orig, DEG_TAG_COPY_ON_WRITE);
}
}
GHASH_FOREACH_END();
}

View File

@ -174,7 +174,9 @@ DepsgraphNodeBuilder::~DepsgraphNodeBuilder()
IDDepsNode *DepsgraphNodeBuilder::add_id_node(ID *id, bool do_tag)
{
#ifdef WITH_COPY_ON_WRITE
if (!DEG_depsgraph_use_copy_on_write()) {
return m_graph->add_id_node(id);
}
IDDepsNode *id_node = NULL;
ID *id_cow = (ID *)BLI_ghash_lookup(m_cow_id_hash, id);
if (id_cow != NULL) {
@ -197,10 +199,6 @@ IDDepsNode *DepsgraphNodeBuilder::add_id_node(ID *id, bool do_tag)
"", -1);
m_graph->operations.push_back(op_cow);
}
#else
IDDepsNode *id_node = m_graph->add_id_node(id);
UNUSED_VARS(do_tag);
#endif
return id_node;
}
@ -358,23 +356,25 @@ void DepsgraphNodeBuilder::begin_build(Main *bmain) {
}
FOREACH_NODETREE_END;
#ifdef WITH_COPY_ON_WRITE
/* Store existing copy-on-write versions of datablock, so we can re-use
* them for new ID nodes.
*/
m_cow_id_hash = BLI_ghash_ptr_new("Depsgraph id hash");
GHASH_FOREACH_BEGIN(IDDepsNode *, id_node, m_graph->id_hash)
{
if (GS(id_node->id_orig->name) != ID_SCE) {
continue;
}
if (deg_copy_on_write_is_expanded(id_node->id_cow)) {
BLI_ghash_insert(m_cow_id_hash, id_node->id_orig, id_node->id_cow);
id_node->id_cow = NULL;
if (DEG_depsgraph_use_copy_on_write()) {
/* Store existing copy-on-write versions of datablock, so we can re-use
* them for new ID nodes.
*/
m_cow_id_hash = BLI_ghash_ptr_new("Depsgraph id hash");
GHASH_FOREACH_BEGIN(IDDepsNode *, id_node, m_graph->id_hash)
{
if (GS(id_node->id_orig->name) != ID_SCE) {
continue;
}
if (deg_copy_on_write_is_expanded(id_node->id_cow)) {
BLI_ghash_insert(m_cow_id_hash,
id_node->id_orig,
id_node->id_cow);
id_node->id_cow = NULL;
}
}
GHASH_FOREACH_END();
}
GHASH_FOREACH_END();
#endif
/* Make sure graph has no nodes left from previous state. */
m_graph->clear_all_nodes();

View File

@ -32,6 +32,8 @@
#include "intern/depsgraph_types.h"
#include "DEG_depsgraph.h" /* used for DEG_depsgraph_use_copy_on_write() */
struct CacheFile;
struct bGPdata;
struct ListBase;
@ -98,11 +100,12 @@ struct DepsgraphNodeBuilder {
/* For a given COW datablock get corresponding original one. */
template<typename T>
T *get_orig_datablock(const T *cow) const {
#ifdef WITH_COPY_ON_WRITE
return (T *)cow->id.newid;
#else
return (T *)cow;
#endif
if (DEG_depsgraph_use_copy_on_write()) {
return (T *)cow->id.newid;
}
else {
return (T *)cow;
}
}
void begin_build(Main *bmain);

View File

@ -96,12 +96,14 @@ 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. */
Scene *scene_cow = expand_cow_datablock(scene);
#else
Scene *scene_cow = scene;
#endif
Scene *scene_cow;
if (DEG_depsgraph_use_copy_on_write()) {
/* Make sure we've got ID node, so we can get pointer to CoW datablock. */
scene_cow = expand_cow_datablock(scene);
}
else {
scene_cow = scene;
}
LayerCollectionState state;
state.index = 0;

View File

@ -138,18 +138,22 @@ void DepsgraphNodeBuilder::build_rig(Scene *scene, Object *object)
{
bArmature *armature = (bArmature *)object->data;
const short armature_tag = armature->id.tag;
#ifdef WITH_COPY_ON_WRITE
/* NOTE: We need to expand both object and armature, so this way we can
* safely create object level pose.
*/
Scene *scene_cow = get_cow_datablock(scene);
Object *object_cow = expand_cow_datablock(object);
bArmature *armature_cow = expand_cow_datablock(armature);
#else
Scene *scene_cow = scene;
Object *object_cow = object;
bArmature *armature_cow = armature;
#endif
Scene *scene_cow;
Object *object_cow;
bArmature *armature_cow;
if (DEG_depsgraph_use_copy_on_write()) {
/* NOTE: We need to expand both object and armature, so this way we can
* safely create object level pose.
*/
scene_cow = get_cow_datablock(scene);
object_cow = expand_cow_datablock(object);
armature_cow = expand_cow_datablock(armature);
}
else {
scene_cow = scene;
object_cow = object;
armature_cow = armature;
}
OperationDepsNode *op_node;
/* Animation and/or drivers linking posebones to base-armature used to

View File

@ -1803,18 +1803,18 @@ void DepsgraphRelationBuilder::build_lamp(Object *ob)
/* textures */
build_texture_stack(la->mtex);
#ifdef WITH_COPY_ON_WRITE
/* Make sure copy on write of lamp data is always properly updated for
* visible lamps.
*/
OperationKey ob_copy_on_write_key(&ob->id,
DEG_NODE_TYPE_COPY_ON_WRITE,
DEG_OPCODE_COPY_ON_WRITE);
OperationKey lamp_copy_on_write_key(lamp_id,
DEG_NODE_TYPE_COPY_ON_WRITE,
DEG_OPCODE_COPY_ON_WRITE);
add_relation(lamp_copy_on_write_key, ob_copy_on_write_key, "Eval Order");
#endif
if (DEG_depsgraph_use_copy_on_write()) {
/* Make sure copy on write of lamp data is always properly updated for
* visible lamps.
*/
OperationKey ob_copy_on_write_key(&ob->id,
DEG_NODE_TYPE_COPY_ON_WRITE,
DEG_OPCODE_COPY_ON_WRITE);
OperationKey lamp_copy_on_write_key(lamp_id,
DEG_NODE_TYPE_COPY_ON_WRITE,
DEG_OPCODE_COPY_ON_WRITE);
add_relation(lamp_copy_on_write_key, ob_copy_on_write_key, "Eval Order");
}
}
void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree)

View File

@ -62,6 +62,18 @@ extern "C" {
#include "intern/depsgraph_intern.h"
#include "util/deg_util_foreach.h"
static bool use_copy_on_write = true;
bool DEG_depsgraph_use_copy_on_write(void)
{
return use_copy_on_write;
}
void DEG_depsgraph_enable_copy_on_write(void)
{
use_copy_on_write = true;
}
namespace DEG {
static DEG_EditorUpdateIDCb deg_editor_update_id_cb = NULL;
@ -252,14 +264,6 @@ DepsNode *Depsgraph::find_node_from_pointer(const PointerRNA *ptr,
/* Node Management ---------------------------- */
#ifndef WITH_COPY_ON_WRITE
static void id_node_deleter(void *value)
{
IDDepsNode *id_node = reinterpret_cast<IDDepsNode *>(value);
OBJECT_GUARDED_DELETE(id_node, IDDepsNode);
}
#endif
TimeSourceDepsNode *Depsgraph::add_time_source()
{
if (time_source == NULL) {
@ -305,9 +309,6 @@ IDDepsNode *Depsgraph::add_id_node(ID *id, bool do_tag, ID *id_cow_hint)
void Depsgraph::clear_id_nodes()
{
#ifndef WITH_COPY_ON_WRITE
BLI_ghash_clear(id_hash, NULL, id_node_deleter);
#else
/* Stupid workaround to ensure we free IDs in a proper order. */
GHASH_FOREACH_BEGIN(IDDepsNode *, id_node, id_hash)
{
@ -331,7 +332,6 @@ void Depsgraph::clear_id_nodes()
OBJECT_GUARDED_DELETE(id_node, IDDepsNode);
}
GHASH_FOREACH_END();
#endif
}
/* Add new relationship between two nodes. */

View File

@ -216,9 +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
if (DEG_depsgraph_use_copy_on_write()) {
relation_builder.build_copy_on_write_relations();
}
/* Detect and solve cycles. */
DEG::deg_graph_detect_cycles(deg_graph);

View File

@ -269,16 +269,17 @@ void id_tag_update_shading(Depsgraph *graph, IDDepsNode *id_node)
shading_comp->tag_update(graph);
}
#ifdef WITH_COPY_ON_WRITE
/* Tag corresponding to DEG_TAG_COPY_ON_WRITE. */
void id_tag_update_copy_on_write(Depsgraph *graph, IDDepsNode *id_node)
{
if (!DEG_depsgraph_use_copy_on_write()) {
return;
}
ComponentDepsNode *cow_comp =
id_node->find_component(DEG_NODE_TYPE_COPY_ON_WRITE);
OperationDepsNode *cow_node = cow_comp->get_entry_operation();
cow_node->tag_update(graph);
}
#endif
void id_tag_update_ntree_special(Main *bmain, Depsgraph *graph, ID *id, int flag)
{
@ -320,16 +321,16 @@ void deg_graph_id_tag_update(Main *bmain, Depsgraph *graph, ID *id, int flag)
}
if (flag & OB_RECALC_DATA) {
id_tag_update_object_data(graph, id_node);
#ifdef WITH_COPY_ON_WRITE
if (flag & DEG_TAG_COPY_ON_WRITE) {
const ID_Type id_type = GS(id_node->id_orig->name);
if (id_type == ID_OB) {
Object *object = (Object *)id_node->id_orig;
ID *ob_data = (ID *)object->data;
DEG_id_tag_update_ex(bmain, ob_data, flag);
if (DEG_depsgraph_use_copy_on_write()) {
if (flag & DEG_TAG_COPY_ON_WRITE) {
const ID_Type id_type = GS(id_node->id_orig->name);
if (id_type == ID_OB) {
Object *object = (Object *)id_node->id_orig;
ID *ob_data = (ID *)object->data;
DEG_id_tag_update_ex(bmain, ob_data, flag);
}
}
}
#endif
}
if (flag & OB_RECALC_TIME) {
id_tag_update_object_time(graph, id_node);
@ -340,11 +341,9 @@ void deg_graph_id_tag_update(Main *bmain, Depsgraph *graph, ID *id, int flag)
if (flag & DEG_TAG_SHADING_UPDATE) {
id_tag_update_shading(graph, id_node);
}
#ifdef WITH_COPY_ON_WRITE
if (flag & DEG_TAG_COPY_ON_WRITE) {
id_tag_update_copy_on_write(graph, id_node);
}
#endif
id_tag_update_ntree_special(bmain, graph, id, flag);
}

View File

@ -82,6 +82,7 @@ static void flush_init_func(void *data_v, int i)
*/
void deg_graph_flush_updates(Main *bmain, Depsgraph *graph)
{
const bool use_copy_on_write = DEG_depsgraph_use_copy_on_write();
/* Sanity check. */
if (graph == NULL) {
return;
@ -146,18 +147,17 @@ void deg_graph_flush_updates(Main *bmain, Depsgraph *graph)
}
if (comp_node->done != COMPONENT_STATE_DONE) {
#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.
*/
if (comp_node->depends_on_cow()) {
if (use_copy_on_write && comp_node->depends_on_cow()) {
ComponentDepsNode *cow_comp =
id_node->find_component(DEG_NODE_TYPE_COPY_ON_WRITE);
cow_comp->tag_update(graph);
}
#endif
Object *object = NULL;
if (GS(id_orig->name) == ID_OB) {
object = (Object *)id_orig;

View File

@ -177,7 +177,14 @@ void IDDepsNode::init(const ID *id, const char *UNUSED(subdata))
void IDDepsNode::init_copy_on_write(ID *id_cow_hint)
{
#ifdef WITH_COPY_ON_WRITE
/* Early output for non-copy-on-write case: we keep CoW pointer same as
* an original one.
*/
if (!DEG_depsgraph_use_copy_on_write()) {
UNUSED_VARS(id_cow_hint);
id_cow = id_orig;
return;
}
/* 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.
@ -200,10 +207,6 @@ void IDDepsNode::init_copy_on_write(ID *id_cow_hint)
else {
id_cow = id_orig;
}
#else
UNUSED_VARS(id_cow_hint);
id_cow = id_orig;
#endif
}
/* Free 'id' node. */
@ -222,7 +225,6 @@ void IDDepsNode::destroy()
id_deps_node_hash_key_free,
id_deps_node_hash_value_free);
#ifdef WITH_COPY_ON_WRITE
/* Free memory used by this CoW ID. */
if (id_cow != id_orig && id_cow != NULL) {
deg_free_copy_on_write_datablock(id_cow);
@ -230,7 +232,7 @@ void IDDepsNode::destroy()
DEG_COW_PRINT("Destroy CoW for %s: id_orig=%p id_cow=%p\n",
id_orig->name, id_orig, id_cow);
}
#endif
/* Tag that the node is freed. */
id_orig = NULL;
}

View File

@ -87,6 +87,8 @@
# include "CCL_api.h"
#endif
#include "DEG_depsgraph.h"
#include "creator_intern.h" /* own include */
@ -583,6 +585,7 @@ static int arg_handle_print_help(int UNUSED(argc), const char **UNUSED(argv), vo
printf("\n");
printf("Experimental Features:\n");
BLI_argsPrintArgDoc(ba, "--enable-copy-on-write");
/* Other options _must_ be last (anything not handled will show here) */
printf("\n");
@ -1174,6 +1177,16 @@ static int arg_handle_threads_set(int argc, const char **argv, void *UNUSED(data
}
}
static const char arg_handle_use_copy_on_write_doc[] =
"\n\tUse new dependency graph"
;
static int arg_handle_use_copy_on_write(int UNUSED(argc), const char **UNUSED(argv), void *UNUSED(data))
{
printf("Using copy on write. This is highly EXPERIMENTAL!\n");
DEG_depsgraph_enable_copy_on_write();
return 0;
}
static const char arg_handle_verbosity_set_doc[] =
"<verbose>\n"
"\tSet logging verbosity level."
@ -1818,6 +1831,8 @@ void main_args_setup(bContext *C, bArgs *ba, SYS_SystemHandle *syshandle)
BLI_argsAdd(ba, 1, NULL, "--debug-gpu-shaders",
CB_EX(arg_handle_debug_mode_generic_set, gpumem), (void *)G_DEBUG_GPU_SHADERS);
BLI_argsAdd(ba, 1, NULL, "--enable-copy-on-write", CB(arg_handle_use_copy_on_write), NULL);
BLI_argsAdd(ba, 1, NULL, "--verbose", CB(arg_handle_verbosity_set), NULL);
BLI_argsAdd(ba, 1, NULL, "--factory-startup", CB(arg_handle_factory_startup_set), NULL);