Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
# SPDX-FileCopyrightText: 2014 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
PUBLIC .
../animrig
../makesrna
../modifiers
../nodes
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
intern/builder/deg_builder.cc
intern/builder/deg_builder_cache.cc
intern/builder/deg_builder_cycle.cc
intern/builder/deg_builder_key.cc
intern/builder/deg_builder_key.h
intern/builder/deg_builder_map.cc
intern/builder/deg_builder_nodes.cc
intern/builder/deg_builder_nodes_rig.cc
intern/builder/deg_builder_nodes_scene.cc
intern/builder/deg_builder_nodes_view_layer.cc
intern/builder/deg_builder_pchanmap.cc
intern/builder/deg_builder_relations.cc
intern/builder/deg_builder_relations_drivers.cc
intern/builder/deg_builder_relations_rig.cc
intern/builder/deg_builder_relations_scene.cc
intern/builder/deg_builder_relations_view_layer.cc
intern/builder/deg_builder_remove_noop.cc
intern/builder/deg_builder_rna.cc
intern/builder/deg_builder_stack.cc
intern/builder/deg_builder_transitive.cc
intern/builder/pipeline.cc
intern/builder/pipeline_all_objects.cc
intern/builder/pipeline_compositor.cc
intern/builder/pipeline_from_collection.cc
intern/builder/pipeline_from_ids.cc
intern/builder/pipeline_render.cc
intern/builder/pipeline_view_layer.cc
intern/debug/deg_debug.cc
intern/debug/deg_debug_relations_graphviz.cc
intern/debug/deg_debug_stats_gnuplot.cc
intern/eval/deg_eval.cc
intern/eval/deg_eval_copy_on_write.cc
intern/eval/deg_eval_flush.cc
intern/eval/deg_eval_runtime_backup.cc
intern/eval/deg_eval_runtime_backup_animation.cc
intern/eval/deg_eval_runtime_backup_modifier.cc
intern/eval/deg_eval_runtime_backup_movieclip.cc
intern/eval/deg_eval_runtime_backup_object.cc
intern/eval/deg_eval_runtime_backup_pose.cc
intern/eval/deg_eval_runtime_backup_scene.cc
intern/eval/deg_eval_runtime_backup_sequence.cc
intern/eval/deg_eval_runtime_backup_sequencer.cc
intern/eval/deg_eval_runtime_backup_sound.cc
intern/eval/deg_eval_runtime_backup_volume.cc
intern/eval/deg_eval_stats.cc
intern/eval/deg_eval_visibility.cc
intern/eval/deg_eval_visibility.h
intern/node/deg_node.cc
intern/node/deg_node_component.cc
intern/node/deg_node_factory.cc
intern/node/deg_node_id.cc
intern/node/deg_node_operation.cc
intern/node/deg_node_time.cc
intern/depsgraph.cc
intern/depsgraph_build.cc
intern/depsgraph_debug.cc
intern/depsgraph_eval.cc
intern/depsgraph_light_linking.cc
intern/depsgraph_light_linking.hh
intern/depsgraph_physics.cc
intern/depsgraph_query.cc
intern/depsgraph_query_foreach.cc
intern/depsgraph_query_iter.cc
intern/depsgraph_registry.cc
intern/depsgraph_relation.cc
intern/depsgraph_tag.cc
intern/depsgraph_type.cc
intern/depsgraph_update.cc
intern/depsgraph_writeback_sync.cc
DEG_depsgraph.hh
DEG_depsgraph_build.hh
DEG_depsgraph_debug.hh
DEG_depsgraph_light_linking.hh
DEG_depsgraph_physics.hh
DEG_depsgraph_query.hh
DEG_depsgraph_writeback_sync.hh
intern/builder/deg_builder.h
intern/builder/deg_builder_cache.h
intern/builder/deg_builder_cycle.h
intern/builder/deg_builder_map.h
intern/builder/deg_builder_nodes.h
intern/builder/deg_builder_pchanmap.h
intern/builder/deg_builder_relations.h
intern/builder/deg_builder_relations_drivers.h
intern/builder/deg_builder_relations_impl.h
intern/builder/deg_builder_remove_noop.h
intern/builder/deg_builder_rna.h
intern/builder/deg_builder_stack.h
intern/builder/deg_builder_transitive.h
intern/builder/pipeline.h
intern/builder/pipeline_all_objects.h
intern/builder/pipeline_compositor.h
intern/builder/pipeline_from_collection.h
intern/builder/pipeline_from_ids.h
intern/builder/pipeline_render.h
intern/builder/pipeline_view_layer.h
intern/debug/deg_debug.h
intern/eval/deg_eval.h
intern/eval/deg_eval_copy_on_write.h
intern/eval/deg_eval_flush.h
intern/eval/deg_eval_runtime_backup.h
intern/eval/deg_eval_runtime_backup_animation.h
intern/eval/deg_eval_runtime_backup_modifier.h
intern/eval/deg_eval_runtime_backup_movieclip.h
intern/eval/deg_eval_runtime_backup_object.h
intern/eval/deg_eval_runtime_backup_pose.h
intern/eval/deg_eval_runtime_backup_scene.h
intern/eval/deg_eval_runtime_backup_sequence.h
intern/eval/deg_eval_runtime_backup_sequencer.h
intern/eval/deg_eval_runtime_backup_sound.h
intern/eval/deg_eval_runtime_backup_volume.h
intern/eval/deg_eval_stats.h
intern/node/deg_node.hh
intern/node/deg_node_component.hh
intern/node/deg_node_factory.hh
intern/node/deg_node_factory_impl.hh
intern/node/deg_node_id.hh
intern/node/deg_node_operation.hh
intern/node/deg_node_time.hh
intern/depsgraph.hh
intern/depsgraph_physics.hh
intern/depsgraph_registry.hh
intern/depsgraph_relation.hh
intern/depsgraph_tag.hh
intern/depsgraph_type.hh
intern/depsgraph_update.hh
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::bmesh
PRIVATE bf::dna
PRIVATE bf::draw
PRIVATE bf::functions
PRIVATE bf::imbuf
PRIVATE bf::intern::atomic
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
PRIVATE bf::sequencer
PRIVATE bf::windowmanager
PRIVATE bf::dependencies::optional::audaspace
)
if(WITH_PYTHON)
add_definitions(-DWITH_PYTHON)
list(APPEND INC
../python
)
endif()
blender_add_lib(bf_depsgraph "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
add_library(bf::depsgraph ALIAS bf_depsgraph)
# RNA_prototypes.hh
add_dependencies(bf_depsgraph bf_rna)
if(WITH_GTESTS)
set(TEST_INC
)
set(TEST_SRC
intern/builder/deg_builder_rna_test.cc
)
set(TEST_LIB
${LIB}
bf_depsgraph
)
blender_add_test_suite_lib(depsgraph "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${TEST_LIB}")
endif()

View File

@@ -0,0 +1,296 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Public API for Depsgraph
*
* Dependency Graph
* ================
*
* The dependency graph tracks relations between various pieces of data in
* a Blender file, but mainly just those which make up scene data. It is used
* to determine the set of operations need to ensure that all data has been
* correctly evaluated in response to changes, based on dependencies and visibility
* of affected data.
* Evaluation Engine
* =================
*
* The evaluation takes the operation-nodes the Depsgraph has tagged for updating,
* and schedules them up for being evaluated/executed such that the all dependency
* relationship constraints are satisfied.
*/
/* ************************************************* */
/* Forward-defined typedefs for core types
* - These are used in all depsgraph code and by all callers of Depsgraph API...
*/
#pragma once
#include "DNA_ID.h"
namespace blender {
/* Dependency Graph */
struct Depsgraph;
/* ------------------------------------------------ */
struct Main;
struct Scene;
struct ViewLayer;
enum eEvaluationMode {
DAG_EVAL_VIEWPORT = 0, /* evaluate for OpenGL viewport */
DAG_EVAL_RENDER = 1, /* evaluate for render purposes */
};
/* DagNode->eval_flags */
enum {
/* Regardless to curve->path animation flag path is to be evaluated anyway,
* to meet dependencies with such a things as curve modifier and other guys
* who're using curve deform, where_on_path and so. */
DAG_EVAL_NEED_CURVE_PATH = (1 << 0),
/* A shrinkwrap modifier or constraint targeting this mesh needs information
* about non-manifold boundary edges for the Target Normal Project mode. */
DAG_EVAL_NEED_SHRINKWRAP_BOUNDARY = (1 << 1),
};
/* ************************************************ */
/* Depsgraph API */
/* -------------------------------------------------------------------- */
/** \name CRUD
* \{ */
/* Get main depsgraph instance from context! */
/**
* Create new Depsgraph instance.
*
* TODO: what arguments are needed here? What's the building-graph entry point?
*/
Depsgraph *DEG_graph_new(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluationMode mode);
/**
* Replace the "owner" pointers (currently Main/Scene/ViewLayer) of this depsgraph.
* Used for:
* - Undo steps when we do want to re-use the old depsgraph data as much as possible.
* - Rendering where we want to re-use objects between different view layers.
*/
void DEG_graph_replace_owners(Depsgraph *depsgraph,
Main *bmain,
Scene *scene,
ViewLayer *view_layer);
/** Free graph's contents and graph itself. */
void DEG_graph_free(Depsgraph *graph);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Node Types Registry
* \{ */
/** Register all node types. */
void DEG_register_node_types();
/** Free node type registry on exit. */
void DEG_free_node_types();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Update Tagging
* \{ */
/** Tag dependency graph for updates when visible scenes/layers changes. */
void DEG_graph_tag_on_visible_update(Depsgraph *depsgraph, bool do_time);
/** Tag all dependency graphs for update when visible scenes/layers changes. */
void DEG_tag_on_visible_update(Main *bmain, bool do_time);
/**
* \note Will return NULL if the flag is not known, allowing to gracefully handle situations
* when recalc flag has been removed.
*/
const char *DEG_update_tag_as_string(IDRecalcFlag flag);
/** Tag given ID for an update in all the dependency graphs. */
void DEG_id_tag_update(ID *id, unsigned int flags);
void DEG_id_tag_update_ex(Main *bmain, ID *id, unsigned int flags);
void DEG_graph_id_tag_update(Main *bmain, Depsgraph *depsgraph, ID *id, unsigned int flags);
/**
* Tag the given ID for an update in the given depsgraph even though it evaluated state might not
* have changed. This can be used when some data is required that is generated as a side effect of
* the evaluation. For example, baking an intermediate geometry in geometry nodes needs this,
* because the to-be-baked geometry has to be recomputed even though no input changed and the final
* geometry also did not change.
*/
void DEG_id_tag_update_for_side_effect_request(Depsgraph *depsgraph, ID *id, unsigned int flags);
/** Tag all dependency graphs when time has changed. */
void DEG_time_tag_update(Main *bmain);
/** Tag a dependency graph when time has changed. */
void DEG_graph_time_tag_update(Depsgraph *depsgraph);
/**
* Mark a particular data-block type as having changing.
* This does not cause any updates but is used by external
* render engines to detect if for example a data-block was removed.
*/
void DEG_graph_id_type_tag(Depsgraph *depsgraph, short id_type);
void DEG_id_type_tag(Main *bmain, short id_type);
/**
* Set a depsgraph to flush updates to editors. This would be done
* for viewport depsgraphs, but not render or export depsgraph for example.
*/
void DEG_enable_editors_update(Depsgraph *depsgraph);
/** Check if something was changed in the database and inform editors about this. */
void DEG_editors_update(Depsgraph *depsgraph, bool time);
/** Clear recalc flags after editors or renderers have handled updates. */
void DEG_ids_clear_recalc(Depsgraph *depsgraph, bool backup);
/**
* Restore recalc flags, backed up by a previous call to #DEG_ids_clear_recalc.
* This also clears the backup.
*/
void DEG_ids_restore_recalc(Depsgraph *depsgraph);
/** \} */
/* ************************************************ */
/* Evaluation Engine API */
/* -------------------------------------------------------------------- */
/** \name Graph Evaluation
* \{ */
enum DepsgraphEvaluateSyncWriteback {
DEG_EVALUATE_SYNC_WRITEBACK_NO,
/**
* Allow writing back to original data after depsgraph evaluation. The change to original data
* may add new ID relations and may tag the depsgraph as changed again.
*/
DEG_EVALUATE_SYNC_WRITEBACK_YES,
};
/**
* Frame changed recalculation entry point.
*
* \note The frame-change happened for root scene that graph belongs to.
*/
void DEG_evaluate_on_framechange(
Depsgraph *graph,
float frame,
DepsgraphEvaluateSyncWriteback sync_writeback = DEG_EVALUATE_SYNC_WRITEBACK_NO);
/**
* Data changed recalculation entry point.
* Evaluate all nodes tagged for updating.
*/
void DEG_evaluate_on_refresh(
Depsgraph *graph,
DepsgraphEvaluateSyncWriteback sync_writeback = DEG_EVALUATE_SYNC_WRITEBACK_NO);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Editors Integration
*
* Mechanism to allow editors to be informed of depsgraph updates,
* to do their own updates based on changes.
* \{ */
struct DEGEditorUpdateContext {
Main *bmain;
Depsgraph *depsgraph;
Scene *scene;
ViewLayer *view_layer;
};
using DEG_EditorUpdateIDCb = void (*)(const DEGEditorUpdateContext *update_ctx, ID *id);
using DEG_EditorUpdateSceneCb = void (*)(const DEGEditorUpdateContext *update_ctx, bool updated);
/** Set callbacks which are being called when depsgraph changes. */
void DEG_editors_set_update_cb(DEG_EditorUpdateIDCb id_func, DEG_EditorUpdateSceneCb scene_func);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Evaluation
* \{ */
bool DEG_is_evaluating(const Depsgraph *depsgraph);
bool DEG_is_active(const Depsgraph *depsgraph);
void DEG_make_active(Depsgraph *depsgraph);
void DEG_make_inactive(Depsgraph *depsgraph);
/* Returns the number of times the graph has been evaluated. */
uint64_t DEG_get_update_count(const Depsgraph *depsgraph);
/**
* Disable the visibility optimization making it so IDs which affect hidden objects or disabled
* modifiers are still evaluated.
*
* For example, this ensures that an object which is needed by a modifier is ignoring checks about
* whether the object is hidden or the modifier is disabled. */
void DEG_disable_visibility_optimization(Depsgraph *depsgraph);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Evaluation Debug
* \{ */
void DEG_debug_print_begin(Depsgraph *depsgraph);
void DEG_debug_print_eval(Depsgraph *depsgraph,
const char *function_name,
const char *object_name,
const void *object_address);
void DEG_debug_print_eval_subdata(Depsgraph *depsgraph,
const char *function_name,
const char *object_name,
const void *object_address,
const char *subdata_comment,
const char *subdata_name,
const void *subdata_address);
void DEG_debug_print_eval_subdata_index(Depsgraph *depsgraph,
const char *function_name,
const char *object_name,
const void *object_address,
const char *subdata_comment,
const char *subdata_name,
const void *subdata_address,
int subdata_index);
void DEG_debug_print_eval_parent_typed(Depsgraph *depsgraph,
const char *function_name,
const char *object_name,
const void *object_address,
const char *parent_comment,
const char *parent_name,
const void *parent_address);
void DEG_debug_print_eval_time(Depsgraph *depsgraph,
const char *function_name,
const char *object_name,
const void *object_address,
float time);
/** \} */
} // namespace blender

View File

@@ -0,0 +1,194 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Public API for Depsgraph
*/
#pragma once
#include "BLI_span.hh"
/* ************************************************* */
/* Dependency Graph */
namespace blender {
/* ------------------------------------------------ */
struct CacheFile;
struct Collection;
struct CustomData_MeshMasks;
struct Depsgraph;
struct DepsNodeHandle;
struct ID;
struct Main;
struct Object;
struct Scene;
struct bNodeTree;
struct VFont;
/* Graph Building -------------------------------- */
/** Build depsgraph for the given scene layer, and dump results in given graph container. */
void DEG_graph_build_from_view_layer(Depsgraph *graph);
/**
* Build depsgraph for all objects (so also invisible ones) in the given view layer.
*/
void DEG_graph_build_for_all_objects(Depsgraph *graph);
/**
* Special version of builder which produces dependency graph suitable for the render pipeline.
* It will contain sequencer and compositor (if needed) and all their dependencies.
*/
void DEG_graph_build_for_render_pipeline(Depsgraph *graph);
/**
* Builds minimal dependency graph for compositor preview.
*/
void DEG_graph_build_for_compositor_preview(Depsgraph *graph);
/**
* Builds the minimal dependency graph needed for evaluation of all IDs within the Collection.
*/
void DEG_graph_build_from_collection(Depsgraph *graph, Collection *collection);
/**
* Builds the minimal dependency graph needed for evaluation of the given IDs.
*/
void DEG_graph_build_from_ids(Depsgraph *graph, Span<ID *> ids);
/** Tag relations from the given graph for update. */
void DEG_graph_tag_relations_update(Depsgraph *graph);
/** Create or update relations in the specified graph. */
void DEG_graph_relations_update(Depsgraph *graph);
/** Tag all relations in the database for update. */
void DEG_relations_tag_update(Main *bmain);
/* Add Dependencies ----------------------------- */
/**
* Handle for components to define their dependencies from callbacks.
* This is generated by the depsgraph and passed to dependency callbacks
* as a symbolic reference to the current DepsNode.
* All relations will be defined in reference to that node.
*/
enum eDepsSceneComponentType {
/* Parameters Component - Default when nothing else fits
* (i.e. just SDNA property setting). */
DEG_SCENE_COMP_PARAMETERS,
/* Animation Component
* TODO(sergey): merge in with parameters? */
DEG_SCENE_COMP_ANIMATION,
/* Sequencer Component (Scene Only). */
DEG_SCENE_COMP_SEQUENCER,
/* Compositor Component. */
DEG_SCENE_COMP_COMPOSITOR,
};
enum eDepsObjectComponentType {
/* Used in query API, to denote which component caller is interested in. */
DEG_OB_COMP_ANY,
/* Parameters Component - Default when nothing else fits
* (i.e. just SDNA property setting). */
DEG_OB_COMP_PARAMETERS,
/* Animation Component.
*
* TODO(sergey): merge in with parameters? */
DEG_OB_COMP_ANIMATION,
/* Transform Component (Parenting/Constraints) */
DEG_OB_COMP_TRANSFORM,
/* Geometry Component (#Mesh / #Curves, etc.). */
DEG_OB_COMP_GEOMETRY,
/* Evaluation-Related Outer Types (with Sub-data) */
/* Pose Component - Owner/Container of Bones Eval */
DEG_OB_COMP_EVAL_POSE,
/* Bone Component - Child/Sub-component of Pose */
DEG_OB_COMP_BONE,
/* Material Shading Component */
DEG_OB_COMP_SHADING,
/* Cache Component */
DEG_OB_COMP_CACHE,
};
void DEG_add_scene_relation(DepsNodeHandle *node_handle,
Scene *scene,
eDepsSceneComponentType component,
const char *description);
void DEG_add_scene_camera_relation(DepsNodeHandle *node_handle,
Scene *scene,
eDepsObjectComponentType component,
const char *description);
void DEG_add_object_relation(DepsNodeHandle *node_handle,
Object *object,
eDepsObjectComponentType component,
const char *description);
void DEG_add_collection_geometry_relation(DepsNodeHandle *node_handle,
Collection *collection,
const char *description);
void DEG_add_collection_geometry_customdata_mask(DepsNodeHandle *node_handle,
Collection *collection,
const CustomData_MeshMasks *masks);
void DEG_add_node_tree_output_relation(DepsNodeHandle *node_handle,
bNodeTree *node_tree,
const char *description);
void DEG_add_bone_relation(DepsNodeHandle *handle,
Object *object,
const char *bone_name,
eDepsObjectComponentType component,
const char *description);
void DEG_add_object_cache_relation(DepsNodeHandle *handle,
CacheFile *cache_file,
eDepsObjectComponentType component,
const char *description);
void DEG_add_vfont_relation(DepsNodeHandle *handle, VFont *vfont, const char *description);
/**
* Adds relation from #DEG_OPCODE_GENERIC_DATABLOCK_UPDATE of a given ID.
* Is used for such entities as textures and images.
*/
void DEG_add_generic_id_relation(DepsNodeHandle *node_handle, ID *id, const char *description);
/**
* Special function which is used from modifiers' #update_depsgraph() callback
* to indicate that the modifier needs to know transformation of the object
* which that modifier belongs to.
* This function will take care of checking which operation is required to
* have transformation for the modifier, taking into account possible simulation solvers.
*/
void DEG_add_depends_on_transform_relation(DepsNodeHandle *node_handle, const char *description);
/**
* Adds relations from the given component of a given object to the given node
* handle AND the component to the point cache component of the node's ID.
*/
void DEG_add_object_pointcache_relation(DepsNodeHandle *node_handle,
Object *object,
eDepsObjectComponentType component,
const char *description);
void DEG_add_time_source_relation(DepsNodeHandle *node_handle, const char *description);
void DEG_add_special_eval_flag(DepsNodeHandle *handle, ID *id, uint32_t flag);
void DEG_add_customdata_mask(DepsNodeHandle *handle,
Object *object,
const CustomData_MeshMasks *masks);
ID *DEG_get_id_from_handle(DepsNodeHandle *node_handle);
Depsgraph *DEG_get_graph_from_handle(DepsNodeHandle *node_handle);
bool DEG_object_has_geometry_component(Object *object);
/* ************************************************ */
} // namespace blender

View File

@@ -0,0 +1,83 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Public API for Querying and Filtering Depsgraph
*/
#pragma once
#include <cstdio>
#include <string>
#include "BLI_string_ref.hh"
namespace blender {
struct Depsgraph;
struct Main;
struct Scene;
struct ViewLayer;
/* ------------------------------------------------ */
/* NOTE: Those flags are same bit-mask as #G.debug_flags */
void DEG_debug_flags_set(Depsgraph *depsgraph, int flags);
int DEG_debug_flags_get(const Depsgraph *depsgraph);
void DEG_debug_name_set(Depsgraph *depsgraph, const char *name);
const char *DEG_debug_name_get(Depsgraph *depsgraph);
/* ------------------------------------------------ */
/**
* Obtain simple statistics about the complexity of the depsgraph.
* \param[out] r_outer: The number of outer nodes in the graph.
* \param[out] r_operations: The number of operation nodes in the graph.
* \param[out] r_relations: The number of relations between (executable) nodes in the graph.
*/
void DEG_stats_simple(const Depsgraph *graph,
size_t *r_outer,
size_t *r_operations,
size_t *r_relations);
/* ************************************************ */
/* Diagram-Based Graph Debugging */
std::string DEG_debug_graph_to_dot(const Depsgraph &graph, StringRef label);
void DEG_debug_stats_gnuplot(const Depsgraph *graph,
FILE *fp,
const char *label,
const char *output_filename);
/* ************************************************ */
/** Compare two dependency graphs. */
bool DEG_debug_compare(const Depsgraph *graph1, const Depsgraph *graph2);
/** Check that dependencies in the graph are really up to date. */
bool DEG_debug_graph_relations_validate(Depsgraph *graph,
Main *bmain,
Scene *scene,
ViewLayer *view_layer);
/** Perform consistency check on the graph. */
bool DEG_debug_consistency_check(Depsgraph *graph);
/**
* Convert bit-flag representation of recalculation flags to string
* that consists of human-readable names of recalculation bits that are set in the given mask.
*
* If flags == 0 the result is "NONE".
*
* Example:
* DEG_stringify_recalc_flags(ID_RECALC_GEOMETRY | ID_RECALC_SHADING) -> "GEOMETRY, SHADING".
*/
std::string DEG_stringify_recalc_flags(unsigned int flags);
} // namespace blender

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
namespace blender {
struct Depsgraph;
struct Object;
namespace deg::light_linking {
/* Set runtime light linking data on evaluated object. */
void eval_runtime_data(const Depsgraph *depsgraph, Object &object_eval);
} // namespace deg::light_linking
} // namespace blender

View File

@@ -0,0 +1,60 @@
/* SPDX-FileCopyrightText: 2018 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Physics utilities for effectors and collision.
*/
#pragma once
#include "DNA_listBase.h"
#include "DEG_depsgraph.hh"
namespace blender {
struct Collection;
struct CollisionRelation;
struct DepsNodeHandle;
struct Depsgraph;
struct EffectorRelation;
struct EffectorWeights;
struct ModifierData;
struct Object;
enum ePhysicsCollisionType {
DEG_PHYSICS_COLLISION = 0,
DEG_PHYSICS_SMOKE_COLLISION = 1,
DEG_PHYSICS_DYNAMIC_BRUSH = 2,
DEG_PHYSICS_COLLISION_NUM = 3,
};
/* Get collision/effector relations from collection or entire scene. These
* created during depsgraph relations building and should only be accessed
* during evaluation. */
ListBaseT<EffectorRelation> *DEG_get_effector_relations(const Depsgraph *depsgraph,
Collection *collection);
ListBaseT<CollisionRelation> *DEG_get_collision_relations(const Depsgraph *depsgraph,
Collection *collection,
unsigned int modifier_type);
/* Build collision/effector relations for depsgraph. */
using DEG_CollobjFilterFunction = bool (*)(Object *obj, ModifierData *md);
void DEG_add_collision_relations(DepsNodeHandle *handle,
Object *object,
Collection *collection,
unsigned int modifier_type,
DEG_CollobjFilterFunction filter_function,
const char *name);
void DEG_add_forcefield_relations(DepsNodeHandle *handle,
Object *object,
EffectorWeights *eff,
bool add_absorption,
int skip_forcefield,
const char *name);
} // namespace blender

View File

@@ -0,0 +1,442 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Public API for Querying Depsgraph.
*/
#pragma once
#include "BLI_enum_flags.hh"
#include "BLI_function_ref.hh"
#include "BLI_iterator.h"
#include "BLI_set.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
/* Needed for the instance iterator. */
#include "DNA_object_types.h"
#include "BKE_duplilist.hh"
#include "BKE_object_types.hh"
namespace blender {
struct BLI_Iterator;
struct CustomData_MeshMasks;
struct Depsgraph;
struct DupliObject;
struct ID;
struct PointerRNA;
struct Scene;
struct ViewLayer;
struct ViewerPath;
/* -------------------------------------------------------------------- */
/** \name DEG input data
* \{ */
/** Get scene that depsgraph was built for. */
Scene *DEG_get_input_scene(const Depsgraph *graph);
/** Get view layer that depsgraph was built for. */
ViewLayer *DEG_get_input_view_layer(const Depsgraph *graph);
/** Get bmain that depsgraph was built for. */
Main *DEG_get_bmain(const Depsgraph *graph);
/** Get evaluation mode that depsgraph was built for. */
eEvaluationMode DEG_get_mode(const Depsgraph *graph);
/** Get time that depsgraph is being evaluated or was last evaluated at. */
float DEG_get_ctime(const Depsgraph *graph);
/** \} */
/* -------------------------------------------------------------------- */
/** \name DEG evaluated data
* \{ */
/** Check if given ID type was tagged for update. */
bool DEG_id_type_updated(const Depsgraph *depsgraph, short id_type);
bool DEG_id_type_any_updated(const Depsgraph *depsgraph);
/** Check if given ID type is present in the depsgraph */
bool DEG_id_type_any_exists(const Depsgraph *depsgraph, short id_type);
/** Get additional evaluation flags for the given ID. */
uint32_t DEG_get_eval_flags_for_id(const Depsgraph *graph, const ID *id);
/** Get additional mesh CustomData_MeshMasks flags for the given object. */
void DEG_get_customdata_mask_for_object(const Depsgraph *graph,
Object *object,
CustomData_MeshMasks *r_mask);
/**
* Get scene at its evaluated state.
*
* Technically, this is a copied-on-written and fully evaluated version of the input scene.
* This function will check that the data-block has been expanded (and copied) from the original
* one. Assert will happen if it's not.
*/
Scene *DEG_get_evaluated_scene(const Depsgraph *graph);
/**
* Get view layer at its evaluated state.
* This is a shortcut for accessing active view layer from evaluated scene.
*/
ViewLayer *DEG_get_evaluated_view_layer(const Depsgraph *graph);
/** Get evaluated version of given ID data-block. */
ID *DEG_get_evaluated_id(const Depsgraph *depsgraph, ID *id);
const ID *DEG_get_evaluated_id(const Depsgraph *depsgraph, const ID *id);
template<typename T> T *DEG_get_evaluated(const Depsgraph *depsgraph, T *id)
{
static_assert(dna::is_ID_v<T>);
return reinterpret_cast<T *>(DEG_get_evaluated_id(depsgraph, reinterpret_cast<ID *>(id)));
}
template<typename T> const T *DEG_get_evaluated(const Depsgraph *depsgraph, const T *id)
{
static_assert(dna::is_ID_v<T>);
return reinterpret_cast<const T *>(
DEG_get_evaluated_id(depsgraph, reinterpret_cast<const ID *>(id)));
}
/** Get evaluated version of data pointed to by RNA pointer */
void DEG_get_evaluated_rna_pointer(const Depsgraph *depsgraph,
PointerRNA *ptr,
PointerRNA *r_ptr_eval);
/** Get original version of given evaluated ID data-block. */
ID *DEG_get_original_id(ID *id);
const ID *DEG_get_original_id(const ID *id);
template<typename T> T *DEG_get_original(T *id)
{
static_assert(dna::is_ID_v<T>);
return reinterpret_cast<T *>(DEG_get_original_id(reinterpret_cast<ID *>(id)));
}
template<typename T> const T *DEG_get_original(const T *id)
{
static_assert(dna::is_ID_v<T>);
return reinterpret_cast<const T *>(DEG_get_original_id(reinterpret_cast<const ID *>(id)));
}
/**
* Get the depsgraph that owns the given ID. This is efficient because the depsgraph is cached on
* the ID.
*
* Only IDs that use the copy-on-eval mechanism of depsgraph evaluation have a corresponding
* depsgraph. Original IDs as well as temporary IDs e.g. generated by geometry nodes do not have a
* corresponding depsgraph, so null is returned here for these IDs.
*/
Depsgraph *DEG_get_depsgraph_by_id(const ID &id);
/**
* Check whether given ID is an original.
*
* Original IDs are considered all the IDs which are not covered by copy-on-evaluation system and
* are not out-of-main localized data-blocks.
*/
bool DEG_is_original_id(const ID *id);
template<typename T> bool DEG_is_original(const T *id)
{
static_assert(dna::is_ID_v<T>);
return DEG_is_original_id(reinterpret_cast<const ID *>(id));
}
/* Opposite of the above (`DEG_is_original_*`).
*
* If the data-block is not original it must be evaluated, and vice versa. */
bool DEG_is_evaluated_id(const ID *id);
template<typename T> bool DEG_is_evaluated(const T *id)
{
static_assert(dna::is_ID_v<T>);
return DEG_is_evaluated_id(reinterpret_cast<const ID *>(id));
}
/**
* Check whether depsgraph is fully evaluated. This includes the following checks:
* - Relations are up-to-date.
* - Nothing is tagged for update.
*/
bool DEG_is_fully_evaluated(const Depsgraph *depsgraph);
/**
* Check every component of the data-block is evaluated. For example, an object disabled in the
* viewport is not fully evaluated, even though the copy-on-eval data-block is created.
*/
bool DEG_id_is_fully_evaluated(const Depsgraph *depsgraph, const ID *id_eval);
/**
* Returns false when the objects geometry is not fully evaluated in its depsgraph yet. In this
* case, the geometry must not be accessed. Otherwise returns true when geometry is fully evaluated
* or the object does not belong to any specific depsgraph.
*
* The result of this function is non deterministic when multi-threading is used because the
* depsgraph nodes are not totally ordered. When the depsgraph contains all correct relations and
* there are no cycles, the result should always be `true` here though. So it does not break
* determinism when there are no dependency graph cycles.
*/
bool DEG_object_geometry_is_evaluated(const Object &object);
/**
* Same as #DEG_object_geometry_is_evaluated
* but for the transformation of the object.
*/
bool DEG_object_transform_is_evaluated(const Object &object);
/**
* Same as #DEG_object_geometry_is_evaluated
* but for the geometry of all objects in the collection.
*/
bool DEG_collection_geometry_is_evaluated(const Collection &collection);
/** \} */
/* -------------------------------------------------------------------- */
/** \name DEG object iterators
* \{ */
enum DegIterFlag {
DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY = (1 << 0),
DEG_ITER_OBJECT_FLAG_LINKED_INDIRECTLY = (1 << 1),
DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET = (1 << 2),
DEG_ITER_OBJECT_FLAG_VISIBLE = (1 << 3),
DEG_ITER_OBJECT_FLAG_DUPLI = (1 << 4),
};
ENUM_OPERATORS(DegIterFlag)
struct DEGObjectIterSettings {
Depsgraph *depsgraph;
/**
* Bit-field of the #DegIterFlag.
*
* NOTE: Be careful with #DEG_ITER_OBJECT_FLAG_LINKED_INDIRECTLY objects.
* Although they are available they have no overrides (collection_properties)
* and will crash if you try to access it.
*/
uint32_t flags;
/**
* When set, the final evaluated geometry of the corresponding object is omitted. Instead the
* geometry for the viewer path included in the iterator.
*/
const ViewerPath *viewer_path;
/**
* If not empty, the iterator should only return objects that are in this list (or their
* instances are in it). Pointers in this span should be the original data-block.
*/
Set<const Object *> *included_objects;
};
/**
* Flags to get objects for draw manager and final render.
*/
#define DEG_OBJECT_ITER_FOR_RENDER_ENGINE_FLAGS \
blender::DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY | blender::DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET | \
blender::DEG_ITER_OBJECT_FLAG_VISIBLE | blender::DEG_ITER_OBJECT_FLAG_DUPLI
struct DEGObjectIterData {
DEGObjectIterSettings *settings;
Depsgraph *graph;
int flag;
eEvaluationMode eval_mode;
/** Object whose preview instead of evaluated geometry should be part of the iterator. */
Object *object_orig_with_preview;
Object *next_object;
/* **** Iteration over dupli-list. *** */
/* Object which created the dupli-list. */
Object *dupli_parent;
/* List of duplicated objects. */
DupliList dupli_list;
/* Next duplicated object to step into. */
DupliObject *dupli_object_next;
/* The dupli_list index of dupli_object_next. */
int dupli_object_next_index;
/* Corresponds to current object: current iterator object is evaluated from
* this duplicated object. */
DupliObject *dupli_object_current;
/* Temporary storage to report fully populated DNA to the render engine or
* other users of the iterator. */
Object temp_dupli_object;
bke::ObjectRuntime temp_dupli_object_runtime;
/* **** Iteration over ID nodes **** */
size_t id_node_index;
size_t num_id_nodes;
/* Copy the current/next data and move the DupliList. */
void transfer_from(DEGObjectIterData &other);
};
void DEG_iterator_objects_begin(BLI_Iterator *iter, DEGObjectIterData *data);
void DEG_iterator_objects_next(BLI_Iterator *iter);
void DEG_iterator_objects_end(BLI_Iterator *iter);
#define DEG_OBJECT_ITER_BEGIN(settings_, instance_) \
{ \
DEGObjectIterData data_ = { \
(settings_), \
(settings_)->depsgraph, \
int((settings_)->flags), \
}; \
\
ITER_BEGIN (DEG_iterator_objects_begin, \
DEG_iterator_objects_next, \
DEG_iterator_objects_end, \
&data_, \
Object *, \
instance_)
#define DEG_OBJECT_ITER_END \
ITER_END; \
} \
((void)0)
/** \} */
/* -------------------------------------------------------------------- */
/** \name DEG object iterators - Manual dupli iteration
* Helper functions for handling dupli instances iteration manually (ie. without passing
* DEG_ITER_OBJECT_FLAG_DUPLI to DEGObjectIterSettings::flags)
* \{ */
/**
* Returns true if the object should be visible on the given context.
*/
bool DEG_iterator_object_is_visible(eEvaluationMode eval_mode, const Object *ob);
/**
* Returns true if the dupli instance should be visible on the given context.
*/
bool DEG_iterator_dupli_is_visible(const DupliObject *dupli, eEvaluationMode eval_mode);
namespace evil {
/**
* WARNING: DON'T USE!!!
*
* These functions are exposed publicly as a temporary measure while we figure out how to fully get
* rid of temporary objects in the Draw module (See #144811).
*
* DON'T ADD NEW USE CASES FOR THESE FUNCTIONS.
*/
/**
* WARNING: DON'T USE!!!
* Generates a temporary object for a given dupli instance.
*
* Returns true if the resulting object should be visible, otherwise the temp object should be
* considered invalid.
* NOTE: DEG_iterator_temp_object_free_properties should be called regardless.
*
* \param do_matrix_setup: If false, the temp_object won't have valid
* object_to_world/world_to_object matrices, and the OB_NEG_SCALE flag will never be set.
*/
[[nodiscard]] bool DEG_iterator_temp_object_from_dupli(const Object *dupli_parent,
const DupliObject *dupli,
eEvaluationMode eval_mode,
bool do_matrix_setup,
Object *r_temp_object,
bke::ObjectRuntime *r_temp_runtime);
/**
* WARNING: DON'T USE!!!
* Frees any property allocated when calling DEG_iterator_temp_object_from_dupli.
*/
void DEG_iterator_temp_object_free_properties(const DupliObject *dupli, Object *temp_object);
} // namespace evil
/** \} */
/* -------------------------------------------------------------------- */
/** \name DEG ID iterators
* \{ */
struct DEGIDIterData {
Depsgraph *graph;
bool only_updated;
size_t id_node_index;
size_t num_id_nodes;
};
void DEG_iterator_ids_begin(BLI_Iterator *iter, DEGIDIterData *data);
void DEG_iterator_ids_next(BLI_Iterator *iter);
void DEG_iterator_ids_end(BLI_Iterator *iter);
/** \} */
/* -------------------------------------------------------------------- */
/** \name DEG traversal
* \{ */
using DEGForeachIDCallback = FunctionRef<void(ID *id)>;
using DEGForeachIDComponentCallback =
FunctionRef<void(ID *id, eDepsObjectComponentType component)>;
/**
* \note Modifies runtime flags in depsgraph nodes,
* so can not be used in parallel. Keep an eye on that!
*/
void DEG_foreach_ancestor_ID(const Depsgraph *depsgraph,
const ID *id,
DEGForeachIDCallback callback);
void DEG_foreach_dependent_ID(const Depsgraph *depsgraph,
const ID *id,
DEGForeachIDCallback callback);
/**
* Starts traversal from given component of the given ID, invokes callback for every other
* component which is directly on indirectly dependent on the source one.
*/
enum {
/* Ignore transform solvers which depends on multiple inputs and affects final transform.
* Is used for cases like snapping objects which are part of a rigid body simulation:
* without this there will be "false-positive" dependencies between transform components of
* objects:
*
* object 1 transform before solver ---> solver ------> object 1 final transform
* object 2 transform before solver -----^ \------> object 2 final transform
*/
DEG_FOREACH_COMPONENT_IGNORE_TRANSFORM_SOLVERS = (1 << 0),
};
void DEG_foreach_dependent_ID_component(const Depsgraph *depsgraph,
const ID *id,
eDepsObjectComponentType source_component_type,
int flags,
DEGForeachIDComponentCallback callback);
void DEG_foreach_ID(const Depsgraph *depsgraph, DEGForeachIDCallback callback);
/** \} */
/* -------------------------------------------------------------------- */
/** \name DEG query evaluation timings
* \{ */
/**
* Return the last evaluation time of \a depsgraph in seconds or #std::nullopt if \a depsgraph
* hasn't been (fully) evaluated.
*/
std::optional<double> DEG_get_last_evaluation_time(const Depsgraph *depsgraph);
/** \} */
} // namespace blender

View File

@@ -0,0 +1,34 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bke
*
* This file provides an API that can be used to modify original (as opposed to evaluated)
* data-blocks after depsgraph evaluation. For some data (e.g. animated properties), this is done
* during depsgraph evaluation. However, this is not possible in all cases. For example, if the
* change to the original data adds a new relation between data-blocks, a user-count (#ID.us) has
* to be increased. This counter is not atomic and can therefore not be modified arbitrarily from
* different threads.
*/
#include <functional>
namespace blender {
struct Depsgraph;
namespace deg::sync_writeback {
/**
* Add a writeback task during depsgraph evaluation. The given function is called after depsgraph
* evaluation is done if the depsgraph is active. It is allowed to change original data blocks and
* even to add new relations.
*/
void add(Depsgraph &depsgraph, std::function<void()> fn);
} // namespace deg::sync_writeback
} // namespace blender

View File

@@ -0,0 +1,250 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/builder/deg_builder.h"
#include <cstring>
#include "DNA_ID.h"
#include "DNA_armature_types.h"
#include "DNA_layer_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "BLI_string.h"
#include "BKE_action.hh"
#include "BKE_collection.hh"
#include "BKE_lib_id.hh"
#include "RNA_prototypes.hh"
#include "intern/builder/deg_builder_cache.h"
#include "intern/builder/deg_builder_remove_noop.h"
#include "intern/depsgraph.hh"
#include "intern/depsgraph_tag.hh"
#include "intern/depsgraph_type.hh"
#include "intern/eval/deg_eval_copy_on_write.h"
#include "intern/eval/deg_eval_visibility.h"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_id.hh"
#include "DEG_depsgraph.hh"
namespace blender::deg {
bool deg_check_id_in_depsgraph(const Depsgraph *graph, ID *id_orig)
{
IDNode *id_node = graph->find_id_node(id_orig);
return id_node != nullptr;
}
bool deg_check_base_in_depsgraph(const Depsgraph *graph, Base *base)
{
Object *object_orig = base->base_orig->object;
IDNode *id_node = graph->find_id_node(&object_orig->id);
if (id_node == nullptr) {
return false;
}
return id_node->has_base;
}
/* -------------------------------------------------------------------- */
/** \name Base Class for Builders
* \{ */
DepsgraphBuilder::DepsgraphBuilder(Main *bmain, Depsgraph *graph, DepsgraphBuilderCache *cache)
: bmain_(bmain), graph_(graph), cache_(cache)
{
}
bool DepsgraphBuilder::need_pull_base_into_graph(const Base *base)
{
/* Simple check: enabled bases are always part of dependency graph. */
const int base_flag = (graph_->mode == DAG_EVAL_VIEWPORT) ? BASE_ENABLED_VIEWPORT :
BASE_ENABLED_RENDER;
if (!graph_->use_visibility_optimization || (base->flag & base_flag)) {
return true;
}
/* More involved check: since we don't support dynamic changes in dependency graph topology and
* all visible objects are to be part of dependency graph, we pull all objects which has animated
* visibility. */
return is_object_visibility_animated(base->object);
}
bool DepsgraphBuilder::is_object_visibility_animated(const Object *object)
{
AnimatedPropertyID property_id;
if (graph_->mode == DAG_EVAL_VIEWPORT) {
property_id = AnimatedPropertyID(&object->id, RNA_Object, "hide_viewport");
}
else if (graph_->mode == DAG_EVAL_RENDER) {
property_id = AnimatedPropertyID(&object->id, RNA_Object, "hide_render");
}
else {
BLI_assert_msg(0, "Unknown evaluation mode.");
return false;
}
return cache_->isPropertyAnimated(&object->id, property_id);
}
bool DepsgraphBuilder::is_modifier_visibility_animated(const Object *object,
const ModifierData *modifier)
{
AnimatedPropertyID property_id;
if (graph_->mode == DAG_EVAL_VIEWPORT) {
property_id = AnimatedPropertyID(&object->id, RNA_Modifier, (void *)modifier, "show_viewport");
}
else if (graph_->mode == DAG_EVAL_RENDER) {
property_id = AnimatedPropertyID(&object->id, RNA_Modifier, (void *)modifier, "show_render");
}
else {
BLI_assert_msg(0, "Unknown evaluation mode.");
return false;
}
return cache_->isPropertyAnimated(&object->id, property_id);
}
bool DepsgraphBuilder::check_pchan_has_bbone(const Object *object, const bPoseChannel *pchan)
{
BLI_assert(object->type == OB_ARMATURE);
bArmature *armature = id_cast<bArmature *>(object->data);
if (pchan == nullptr) {
return false;
}
const Bone *bone = pchan->bone_get(*armature);
if (bone == nullptr) {
return false;
}
/* We don't really care whether segments are higher than 1 due to static user input (as in,
* rigger entered value like 3 manually), or due to animation. In either way we need to create
* special evaluation. */
if (bone->segments > 1) {
return true;
}
AnimatedPropertyID property_id(
&armature->id, RNA_Bone, const_cast<Bone *>(bone), "bbone_segments");
/* Check both Object and Armature animation data, because drivers modifying Armature
* state could easily be created in the Object AnimData. */
return cache_->isPropertyAnimated(&object->id, property_id) ||
cache_->isPropertyAnimated(&armature->id, property_id);
}
bool DepsgraphBuilder::check_pchan_has_bbone_segments(const Object *object,
const bPoseChannel *pchan)
{
return check_pchan_has_bbone(object, pchan);
}
bool DepsgraphBuilder::check_pchan_has_bbone_segments(const Object *object, const char *bone_name)
{
const bPoseChannel *pchan = BKE_pose_channel_find_name(object->pose, bone_name);
return check_pchan_has_bbone_segments(object, pchan);
}
const char *DepsgraphBuilder::get_rna_path_relative_to_scene_camera(const Scene *scene,
const PointerRNA &target_prop,
const char *rna_path)
{
if (rna_path == nullptr || target_prop.data != scene || target_prop.type != RNA_Scene ||
!BLI_str_startswith(rna_path, "camera"))
{
return nullptr;
}
/* Return the part of the path relative to the camera. */
switch (rna_path[6]) {
case '.':
return rna_path + 7;
case '[':
return rna_path + 6;
default:
return nullptr;
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Builder Finalizer.
* \{ */
void deg_graph_build_finalize(Main *bmain, Depsgraph *graph)
{
deg_graph_flush_visibility_flags(graph);
deg_graph_remove_unused_noops(graph);
/* Re-tag IDs for update if it was tagged before the relations
* update tag. */
for (IDNode *id_node : graph->id_nodes) {
const ID_Type id_type = id_node->id_type;
ID *id_orig = id_node->id_orig;
id_node->finalize_build(graph);
int flag = 0;
/* Tag rebuild if special evaluation flags changed. */
if (id_node->eval_flags != id_node->previous_eval_flags) {
flag |= ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY;
}
/* Tag rebuild if the custom data mask changed. */
if (id_node->customdata_masks != id_node->previous_customdata_masks) {
flag |= ID_RECALC_GEOMETRY;
}
const bool is_expanded = deg_eval_copy_is_expanded(id_node->id_cow);
if (!is_expanded) {
flag |= ID_RECALC_SYNC_TO_EVAL;
/* This means ID is being added to the dependency graph first
* time, which is similar to "ob-visible-change" */
if (id_type == ID_OB) {
flag |= ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY;
}
if (id_type == ID_NT) {
flag |= ID_RECALC_NTREE_OUTPUT;
}
}
else {
if (id_type == ID_GR) {
/* Collection content might have changed (children collection might have been added or
* removed from the graph based on their inclusion and visibility flags). */
BKE_collection_object_cache_free(
nullptr, reinterpret_cast<Collection *>(id_node->id_cow), LIB_ID_CREATE_NO_DEG_TAG);
}
else if (id_type == ID_SCE) {
/* During undo the sequence strips might obtain a new session ID, which will disallow the
* audio handles to be re-used. Tag for the audio and sequence update to ensure the audio
* handles are open.
* NOTE: This is not something that should be required, and perhaps indicates a weakness in
* design somewhere else. For the cause of the problem check #117760. */
flag |= ID_RECALC_AUDIO | ID_RECALC_SEQUENCER_STRIPS;
}
}
/* Restore recalc flags from original ID, which could possibly contain recalc flags set by
* an operator and then were carried on by the undo system.
*
* Only do it for active dependency graph, because otherwise modifications to the original
* objects might keep affecting the render pipeline. For example, when a Python script is
* executed in headless mode it will tag original objects for recalculation, and the flag
* will never be reset to 0 because there is no active dependency graph (since the
* DEG_ids_clear_recalc() only clears original ID recalc flags for the active depsgraph).
*
* A bit of a safety is to also consider the accumulated recalc flags from the original
* data-block for the first evaluation of the data-block within an inactive graph. */
if (graph->is_active || !is_expanded) {
flag |= id_orig->recalc;
}
if (flag != 0) {
graph_id_tag_update(bmain, graph, id_node->id_orig, flag, DEG_UPDATE_SOURCE_RELATIONS);
}
}
}
/** \} */
} // namespace blender::deg

View File

@@ -0,0 +1,63 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
namespace blender {
struct Base;
struct ID;
struct Main;
struct ModifierData;
struct Object;
struct PointerRNA;
struct Scene;
struct bPoseChannel;
namespace deg {
struct Depsgraph;
class DepsgraphBuilderCache;
class DepsgraphBuilder {
public:
virtual ~DepsgraphBuilder() = default;
virtual bool need_pull_base_into_graph(const Base *base);
virtual bool is_object_visibility_animated(const Object *object);
virtual bool is_modifier_visibility_animated(const Object *object, const ModifierData *modifier);
virtual bool check_pchan_has_bbone(const Object *object, const bPoseChannel *pchan);
virtual bool check_pchan_has_bbone_segments(const Object *object, const bPoseChannel *pchan);
virtual bool check_pchan_has_bbone_segments(const Object *object, const char *bone_name);
/**
* If `target_prop` + `rna_path` uses indirection via the `scene.camera` pointer, returns
* the sub-string of `rna_path` relative to the camera; otherwise returns nullptr.
*/
static const char *get_rna_path_relative_to_scene_camera(const Scene *scene,
const PointerRNA &target_prop,
const char *rna_path);
protected:
/* NOTE: The builder does NOT take ownership over any of those resources. */
DepsgraphBuilder(Main *bmain, Depsgraph *graph, DepsgraphBuilderCache *cache);
/* State which never changes, same for the whole builder time. */
Main *bmain_;
Depsgraph *graph_;
DepsgraphBuilderCache *cache_;
};
bool deg_check_id_in_depsgraph(const Depsgraph *graph, ID *id_orig);
bool deg_check_base_in_depsgraph(const Depsgraph *graph, Base *base);
void deg_graph_build_finalize(Main *bmain, Depsgraph *graph);
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,148 @@
/* SPDX-FileCopyrightText: 2018 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/builder/deg_builder_cache.h"
#include "DNA_anim_types.h"
#include "BKE_anim_data.hh"
#include "RNA_access.hh"
#include "RNA_path.hh"
namespace blender::deg {
/* Animated property storage. */
AnimatedPropertyID::AnimatedPropertyID() : data(nullptr), property_rna(nullptr) {}
AnimatedPropertyID::AnimatedPropertyID(const PointerRNA *pointer_rna,
const PropertyRNA *property_rna)
: AnimatedPropertyID(*pointer_rna, property_rna)
{
}
AnimatedPropertyID::AnimatedPropertyID(const PointerRNA &pointer_rna,
const PropertyRNA *property_rna)
: data(pointer_rna.data), property_rna(property_rna)
{
}
AnimatedPropertyID::AnimatedPropertyID(const ID *id, StructRNA *type, const char *property_name)
: data(id)
{
property_rna = RNA_struct_type_find_property(type, property_name);
}
AnimatedPropertyID::AnimatedPropertyID(const ID * /*id*/,
StructRNA *type,
void *data,
const char *property_name)
: data(data)
{
property_rna = RNA_struct_type_find_property(type, property_name);
}
bool operator==(const AnimatedPropertyID &a, const AnimatedPropertyID &b)
{
return a.data == b.data && a.property_rna == b.property_rna;
}
uint64_t AnimatedPropertyID::hash() const
{
uintptr_t ptr1 = uintptr_t(data);
uintptr_t ptr2 = uintptr_t(property_rna);
return uint64_t(((ptr1 >> 4) * 33) ^ (ptr2 >> 4));
}
AnimatedPropertyStorage::AnimatedPropertyStorage() : is_fully_initialized(false) {}
void AnimatedPropertyStorage::initializeFromID(DepsgraphBuilderCache *builder_cache, const ID *id)
{
PointerRNA own_pointer_rna = RNA_id_pointer_create(const_cast<ID *>(id));
BKE_fcurves_id_cb(const_cast<ID *>(id), [&](ID * /*id*/, FCurve *fcurve) {
if (fcurve->rna_path == nullptr || fcurve->rna_path[0] == '\0') {
return;
}
/* Resolve property. */
PointerRNA pointer_rna;
PropertyRNA *property_rna = nullptr;
if (!RNA_path_resolve_property(
&own_pointer_rna, fcurve->rna_path, &pointer_rna, &property_rna))
{
return;
}
/* Get storage for the ID.
* This is needed to deal with cases when nested datablock is animated by its parent. */
AnimatedPropertyStorage *animated_property_storage = this;
if (pointer_rna.owner_id != own_pointer_rna.owner_id) {
animated_property_storage = builder_cache->ensureAnimatedPropertyStorage(
pointer_rna.owner_id);
}
/* Set the property as animated. */
animated_property_storage->tagPropertyAsAnimated(&pointer_rna, property_rna);
});
}
void AnimatedPropertyStorage::tagPropertyAsAnimated(const AnimatedPropertyID &property_id)
{
animated_objects_set.add(property_id.data);
animated_properties_set.add(property_id);
}
void AnimatedPropertyStorage::tagPropertyAsAnimated(const PointerRNA *pointer_rna,
const PropertyRNA *property_rna)
{
tagPropertyAsAnimated(AnimatedPropertyID(pointer_rna, property_rna));
}
bool AnimatedPropertyStorage::isPropertyAnimated(const AnimatedPropertyID &property_id)
{
return animated_properties_set.contains(property_id);
}
bool AnimatedPropertyStorage::isPropertyAnimated(const PointerRNA *pointer_rna,
const PropertyRNA *property_rna)
{
return isPropertyAnimated(AnimatedPropertyID(pointer_rna, property_rna));
}
bool AnimatedPropertyStorage::isAnyPropertyAnimated(const PointerRNA *pointer_rna)
{
return animated_objects_set.contains(pointer_rna->data);
}
/* Builder cache itself. */
DepsgraphBuilderCache::~DepsgraphBuilderCache()
{
for (AnimatedPropertyStorage *animated_property_storage :
animated_property_storage_map_.values())
{
delete animated_property_storage;
}
}
AnimatedPropertyStorage *DepsgraphBuilderCache::ensureAnimatedPropertyStorage(const ID *id)
{
return animated_property_storage_map_.lookup_or_add_cb(
id, []() { return new AnimatedPropertyStorage(); });
}
AnimatedPropertyStorage *DepsgraphBuilderCache::ensureInitializedAnimatedPropertyStorage(
const ID *id)
{
AnimatedPropertyStorage *animated_property_storage = ensureAnimatedPropertyStorage(id);
if (!animated_property_storage->is_fully_initialized) {
animated_property_storage->initializeFromID(this, id);
animated_property_storage->is_fully_initialized = true;
}
return animated_property_storage;
}
} // namespace blender::deg

View File

@@ -0,0 +1,109 @@
/* SPDX-FileCopyrightText: 2018 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "RNA_types.hh"
#include "BLI_map.hh"
#include "BLI_set.hh"
namespace blender {
struct ID;
struct PointerRNA;
struct PropertyRNA;
struct StructRNA;
namespace deg {
class DepsgraphBuilderCache;
/* Identifier for animated property. */
class AnimatedPropertyID {
public:
AnimatedPropertyID();
AnimatedPropertyID(const PointerRNA *pointer_rna, const PropertyRNA *property_rna);
AnimatedPropertyID(const PointerRNA &pointer_rna, const PropertyRNA *property_rna);
AnimatedPropertyID(const ID *id, StructRNA *type, const char *property_name);
AnimatedPropertyID(const ID *id, StructRNA *type, void *data, const char *property_name);
uint64_t hash() const;
friend bool operator==(const AnimatedPropertyID &a, const AnimatedPropertyID &b);
/* Corresponds to PointerRNA.data. */
const void *data;
const PropertyRNA *property_rna;
MEM_CXX_CLASS_ALLOC_FUNCS("AnimatedPropertyID");
};
class AnimatedPropertyStorage {
public:
AnimatedPropertyStorage();
void initializeFromID(DepsgraphBuilderCache *builder_cache, const ID *id);
void tagPropertyAsAnimated(const AnimatedPropertyID &property_id);
void tagPropertyAsAnimated(const PointerRNA *pointer_rna, const PropertyRNA *property_rna);
bool isPropertyAnimated(const AnimatedPropertyID &property_id);
bool isPropertyAnimated(const PointerRNA *pointer_rna, const PropertyRNA *property_rna);
bool isAnyPropertyAnimated(const PointerRNA *pointer_rna);
/* The storage is fully initialized from all F-Curves from corresponding ID. */
bool is_fully_initialized;
/* indexed by PointerRNA.data. */
Set<const void *> animated_objects_set;
Set<AnimatedPropertyID> animated_properties_set;
MEM_CXX_CLASS_ALLOC_FUNCS("AnimatedPropertyStorage");
};
/* Cached data which can be re-used by multiple builders. */
class DepsgraphBuilderCache {
public:
~DepsgraphBuilderCache();
/* Makes sure storage for animated properties exists and initialized for the given ID. */
AnimatedPropertyStorage *ensureAnimatedPropertyStorage(const ID *id);
AnimatedPropertyStorage *ensureInitializedAnimatedPropertyStorage(const ID *id);
/* Shortcuts to go through ensureInitializedAnimatedPropertyStorage and its
* isPropertyAnimated.
*
* NOTE: Avoid using for multiple subsequent lookups, query for the storage once, and then query
* the storage.
*
* TODO(sergey): Technically, this makes this class something else than just a cache, but what is
* the better name? */
template<typename... Args> bool isPropertyAnimated(const ID *id, Args... args)
{
AnimatedPropertyStorage *animated_property_storage = ensureInitializedAnimatedPropertyStorage(
id);
return animated_property_storage->isPropertyAnimated(args...);
}
bool isAnyPropertyAnimated(const PointerRNA *ptr)
{
AnimatedPropertyStorage *animated_property_storage = ensureInitializedAnimatedPropertyStorage(
ptr->owner_id);
return animated_property_storage->isAnyPropertyAnimated(ptr);
}
Map<const ID *, AnimatedPropertyStorage *> animated_property_storage_map_;
MEM_CXX_CLASS_ALLOC_FUNCS("DepsgraphBuilderCache");
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,221 @@
/* SPDX-FileCopyrightText: 2015 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/builder/deg_builder_cycle.h"
// TOO(sergey): Use some wrappers over those?
#include <cstdio>
#include <cstdlib>
#include "BLI_stack.hh"
#include "CLG_log.h"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_operation.hh"
#include "intern/depsgraph.hh"
#include "intern/depsgraph_relation.hh"
namespace blender {
static CLG_LogRef LOG = {"depsgraph"};
namespace deg {
namespace {
enum eCyclicCheckVisitedState {
/* Not is not visited at all during traversal. */
NODE_NOT_VISITED = 0,
/* Node has been visited during traversal and not in current stack. */
NODE_VISITED = 1,
/* Node has been visited during traversal and is in current stack. */
NODE_IN_STACK = 2,
};
struct StackEntry {
OperationNode *node;
StackEntry *from;
Relation *via_relation;
};
struct CyclesSolverState {
CyclesSolverState(Depsgraph *graph) : graph(graph) {}
~CyclesSolverState()
{
if (num_cycles != 0) {
CLOG_WARN(&LOG, "Detected %d dependency cycles", num_cycles);
}
}
Depsgraph *graph;
Stack<StackEntry> traversal_stack;
int num_cycles = 0;
};
inline void set_node_visited_state(Node *node, eCyclicCheckVisitedState state)
{
node->custom_flags = (node->custom_flags & ~0x3) | int(state);
}
inline eCyclicCheckVisitedState get_node_visited_state(Node *node)
{
return eCyclicCheckVisitedState(node->custom_flags & 0x3);
}
inline void set_node_num_visited_children(Node *node, int num_children)
{
node->custom_flags = (node->custom_flags & 0x3) | (num_children << 2);
}
inline int get_node_num_visited_children(Node *node)
{
return node->custom_flags >> 2;
}
void schedule_node_to_stack(CyclesSolverState *state, OperationNode *node)
{
StackEntry entry;
entry.node = node;
entry.from = nullptr;
entry.via_relation = nullptr;
state->traversal_stack.push(entry);
set_node_visited_state(node, NODE_IN_STACK);
}
/* Schedule leaf nodes (node without input links) for traversal. */
void schedule_leaf_nodes(CyclesSolverState *state)
{
for (OperationNode *node : state->graph->operations) {
bool has_inlinks = false;
for (Relation *rel : node->inlinks) {
if (rel->from->type == NodeType::OPERATION) {
has_inlinks = true;
}
}
node->custom_flags = 0;
if (has_inlinks == false) {
schedule_node_to_stack(state, node);
}
else {
set_node_visited_state(node, NODE_NOT_VISITED);
}
}
}
/* Schedule node which was not checked yet for being belong to
* any of dependency cycle.
*/
bool schedule_non_checked_node(CyclesSolverState *state)
{
for (OperationNode *node : state->graph->operations) {
if (get_node_visited_state(node) == NODE_NOT_VISITED) {
schedule_node_to_stack(state, node);
return true;
}
}
return false;
}
bool check_relation_can_murder(Relation *relation)
{
if (relation->flag & RELATION_FLAG_GODMODE) {
return false;
}
return true;
}
Relation *select_relation_to_murder(Relation *relation, StackEntry *cycle_start_entry)
{
/* More or less Russian roulette solver, which will make sure only
* specially marked relations are kept alive.
*
* TODO(sergey): There might be better strategies here. */
if (check_relation_can_murder(relation)) {
return relation;
}
StackEntry *current = cycle_start_entry;
OperationNode *to_node = static_cast<OperationNode *>(relation->to);
while (current->node != to_node) {
if (check_relation_can_murder(current->via_relation)) {
return current->via_relation;
}
current = current->from;
}
return relation;
}
/* Solve cycles with all nodes which are scheduled for traversal. */
void solve_cycles(CyclesSolverState *state)
{
Stack<StackEntry> &traversal_stack = state->traversal_stack;
while (!traversal_stack.is_empty()) {
StackEntry *entry = &traversal_stack.peek();
OperationNode *node = entry->node;
bool all_child_traversed = true;
const int num_visited = get_node_num_visited_children(node);
for (int i = num_visited; i < node->outlinks.size(); i++) {
Relation *rel = node->outlinks[i];
if (rel->to->type == NodeType::OPERATION) {
OperationNode *to = static_cast<OperationNode *>(rel->to);
eCyclicCheckVisitedState to_state = get_node_visited_state(to);
if (to_state == NODE_IN_STACK) {
std::string cycle_str = " " + to->full_identifier() + " depends on\n " +
node->full_identifier() + " via '" + rel->name + "'\n";
StackEntry *current = entry;
while (current->node != to) {
BLI_assert(current != nullptr);
cycle_str += " " + current->from->node->full_identifier() + " via '" +
current->via_relation->name + "'\n";
current = current->from;
}
CLOG_WARN(&LOG, "Dependency cycle detected:\n%s", cycle_str.c_str());
Relation *sacrificial_relation = select_relation_to_murder(rel, entry);
sacrificial_relation->flag |= RELATION_FLAG_CYCLIC;
++state->num_cycles;
}
else if (to_state == NODE_NOT_VISITED) {
StackEntry new_entry;
new_entry.node = to;
new_entry.from = entry;
new_entry.via_relation = rel;
traversal_stack.push(new_entry);
set_node_visited_state(node, NODE_IN_STACK);
all_child_traversed = false;
set_node_num_visited_children(node, i);
break;
}
}
}
if (all_child_traversed) {
set_node_visited_state(node, NODE_VISITED);
traversal_stack.pop();
}
}
}
} // namespace
void deg_graph_detect_cycles(Depsgraph *graph)
{
CyclesSolverState state(graph);
/* First we solve cycles which are reachable from leaf nodes. */
schedule_leaf_nodes(&state);
solve_cycles(&state);
/* We are not done yet. It is possible to have closed loop cycle,
* for example A -> B -> C -> A. These nodes were not scheduled
* yet (since they all have inlinks), and were not traversed since
* nobody else points to them. */
while (schedule_non_checked_node(&state)) {
solve_cycles(&state);
}
}
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2015 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
namespace blender::deg {
struct Depsgraph;
/* Detect and solve dependency cycles. */
void deg_graph_detect_cycles(Depsgraph *graph);
} // namespace blender::deg

View File

@@ -0,0 +1,110 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Methods for constructing depsgraph
*/
#include "intern/builder/deg_builder_key.h"
#include "RNA_access.hh"
#include "RNA_path.hh"
namespace blender::deg {
/* -------------------------------------------------------------------- */
/** \name Time source
* \{ */
std::string TimeSourceKey::identifier() const
{
return std::string("TimeSourceKey");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Component
* \{ */
std::string ComponentKey::identifier() const
{
const char *idname = (id) ? id->name : "<None>";
std::string result = std::string("ComponentKey(");
result += idname;
result += ", " + std::string(nodeTypeAsString(type));
if (name[0] != '\0') {
result += ", '" + std::string(name) + "'";
}
result += ')';
return result;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operation
* \{ */
std::string OperationKey::identifier() const
{
std::string result = std::string("OperationKey(");
result += "type: " + std::string(nodeTypeAsString(component_type));
result += ", component name: '" + std::string(component_name) + "'";
result += ", operation code: " + std::string(operationCodeAsString(opcode));
if (name[0] != '\0') {
result += ", '" + std::string(name) + "'";
}
result += ")";
return result;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name RNA path
* \{ */
RNAPathKey::RNAPathKey(ID *id, const char *path, RNAPointerSource source) : id(id), source(source)
{
/* Create ID pointer for root of path lookup. */
PointerRNA id_ptr = RNA_id_pointer_create(id);
/* Try to resolve path. */
int index;
if (!RNA_path_resolve_full(&id_ptr, path, &ptr, &prop, &index)) {
ptr = PointerRNA_NULL;
prop = nullptr;
}
}
RNAPathKey::RNAPathKey(ID *id, const PointerRNA &ptr, PropertyRNA *prop, RNAPointerSource source)
: id(id), ptr(ptr), prop(prop), source(source)
{
}
RNAPathKey::RNAPathKey(const PointerRNA &target_prop,
const char *rna_path_from_target_prop,
const RNAPointerSource source)
: id(target_prop.owner_id), source(source)
{
/* Try to resolve path. */
int index;
if (!RNA_path_resolve_full(&target_prop, rna_path_from_target_prop, &ptr, &prop, &index)) {
ptr = PointerRNA_NULL;
prop = nullptr;
}
}
std::string RNAPathKey::identifier() const
{
const char *id_name = (id) ? id->name : "<No ID>";
const char *prop_name = (prop) ? RNA_property_identifier(prop) : "<No Prop>";
return std::string("RnaPathKey(") + "id: " + id_name + ", prop: '" + prop_name + "')";
}
/** \} */
} // namespace blender::deg

View File

@@ -0,0 +1,183 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "intern/builder/deg_builder_rna.h"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_operation.hh"
#include "DNA_ID.h"
#include "RNA_types.hh"
namespace blender {
struct ID;
struct PropertyRNA;
namespace deg {
struct TimeSourceKey {
TimeSourceKey() = default;
std::string identifier() const;
};
struct ComponentKey {
ComponentKey() = default;
ComponentKey(const ID *id, NodeType type, const char *name = "") : id(id), type(type), name(name)
{
}
std::string identifier() const;
const ID *id = nullptr;
NodeType type = NodeType::UNDEFINED;
const char *name = "";
};
struct OperationKey {
OperationKey() = default;
OperationKey(const ID *id, NodeType component_type, const char *name, int name_tag = -1)
: id(id), component_type(component_type), name(name), name_tag(name_tag)
{
}
OperationKey(const ID *id,
NodeType component_type,
const char *component_name,
const char *name,
int name_tag)
: id(id),
component_type(component_type),
component_name(component_name),
name(name),
name_tag(name_tag)
{
}
OperationKey(const ID *id, NodeType component_type, OperationCode opcode)
: id(id), component_type(component_type), opcode(opcode)
{
}
OperationKey(const ID *id,
NodeType component_type,
const char *component_name,
OperationCode opcode)
: id(id), component_type(component_type), component_name(component_name), opcode(opcode)
{
}
OperationKey(const ID *id,
NodeType component_type,
OperationCode opcode,
const char *name,
int name_tag = -1)
: id(id), component_type(component_type), opcode(opcode), name(name), name_tag(name_tag)
{
}
OperationKey(const ID *id,
NodeType component_type,
const char *component_name,
OperationCode opcode,
const char *name,
int name_tag = -1)
: id(id),
component_type(component_type),
component_name(component_name),
opcode(opcode),
name(name),
name_tag(name_tag)
{
}
OperationKey(OperationKey &&other) noexcept = default;
OperationKey &operator=(OperationKey &&other) = default;
OperationKey(const OperationKey &other) = default;
OperationKey &operator=(const OperationKey &other) = default;
std::string identifier() const;
const ID *id = nullptr;
NodeType component_type = NodeType::UNDEFINED;
const char *component_name = "";
OperationCode opcode = OperationCode::OPERATION;
const char *name = "";
int name_tag = -1;
};
/* Similar to the #OperationKey but does not contain external references, which makes it
* suitable to identify operations even after the original database or graph was destroyed.
* The downside of this key over the #OperationKey is that it performs string allocation upon
* the key construction. */
struct PersistentOperationKey : public OperationKey {
/* Create the key which identifies the given operation node. */
PersistentOperationKey(const OperationNode *operation_node)
{
const ComponentNode *component_node = operation_node->owner;
const IDNode *id_node = component_node->owner;
/* Copy names over to our object, so that the key stays valid even after the `operation_node`
* is destroyed. */
component_name_storage_ = component_node->name;
name_storage_ = operation_node->name;
/* Assign fields used by the #OperationKey API. */
id = id_node->id_orig;
component_type = component_node->type;
component_name = component_name_storage_.c_str();
opcode = operation_node->opcode;
name = name_storage_.c_str();
name_tag = operation_node->name_tag;
}
PersistentOperationKey(PersistentOperationKey &&other) noexcept : OperationKey(other)
{
component_name_storage_ = std::move(other.component_name_storage_);
name_storage_ = std::move(other.name_storage_);
/* Re-assign pointers to the strings.
* This is needed because string content can actually change address if the string uses the
* small string optimization. */
component_name = component_name_storage_.c_str();
name = name_storage_.c_str();
}
PersistentOperationKey &operator=(PersistentOperationKey &&other) = delete;
PersistentOperationKey(const PersistentOperationKey &other) = delete;
PersistentOperationKey &operator=(const PersistentOperationKey &other) = delete;
private:
std::string component_name_storage_;
std::string name_storage_;
};
struct RNAPathKey {
RNAPathKey(ID *id, const char *path, RNAPointerSource source);
RNAPathKey(const PointerRNA &target_prop,
const char *rna_path_from_target_prop,
RNAPointerSource source);
RNAPathKey(ID *id, const PointerRNA &ptr, PropertyRNA *prop, RNAPointerSource source);
std::string identifier() const;
ID *id;
PointerRNA ptr;
PropertyRNA *prop;
RNAPointerSource source;
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,47 @@
/* SPDX-FileCopyrightText: 2018 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/builder/deg_builder_map.h"
#include "DNA_ID.h"
namespace blender::deg {
bool BuilderMap::check_is_built(ID *id, int tag) const
{
return (this->get_ID_tag(id) & tag) == tag;
}
void BuilderMap::tag_built(ID *id, int tag)
{
id_tags_.lookup_or_add(id, 0) |= tag;
}
bool BuilderMap::check_is_built_and_tag(ID *id, int tag)
{
int &id_tag = id_tags_.lookup_or_add(id, 0);
const bool result = (id_tag & tag) == tag;
id_tag |= tag;
return result;
}
int BuilderMap::get_ID_tag(ID *id) const
{
return id_tags_.lookup_default(id, 0);
}
Set<const ID *> BuilderMap::get_ids() const
{
Set<const ID *> result;
for (const ID *id : id_tags_.keys()) {
result.add(id);
}
return result;
}
} // namespace blender::deg

View File

@@ -0,0 +1,79 @@
/* SPDX-FileCopyrightText: 2018 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "BLI_map.hh"
#include "BLI_set.hh"
namespace blender {
struct ID;
namespace deg {
class BuilderMap {
public:
enum {
TAG_ANIMATION = (1 << 0),
TAG_PARAMETERS = (1 << 1),
TAG_TRANSFORM = (1 << 2),
TAG_GEOMETRY = (1 << 3),
TAG_SCENE_COMPOSITOR = (1 << 4),
TAG_SCENE_SEQUENCER = (1 << 5),
TAG_SCENE_AUDIO = (1 << 6),
/**
* Specific tag for whether the collection -> children object relations have been built.
* Purposefully not included in TAG_COMPLETE so it doesn't influence other decisions about
* whether the collection is considered complete.
*/
TAG_COLLECTION_CHILDREN_HIERARCHY = (1 << 7),
TAG_COLLECTION_PROPERTIES = (1 << 8),
/* All ID components has been built. */
TAG_COMPLETE = (TAG_ANIMATION | TAG_PARAMETERS | TAG_TRANSFORM | TAG_GEOMETRY |
TAG_SCENE_COMPOSITOR | TAG_SCENE_SEQUENCER | TAG_SCENE_AUDIO |
TAG_COLLECTION_PROPERTIES),
};
/* Check whether given ID is already handled by builder (or if it's being handled). */
bool check_is_built(ID *id, int tag = TAG_COMPLETE) const;
/* Tag given ID as handled/built. */
void tag_built(ID *id, int tag = TAG_COMPLETE);
/* Combination of previous two functions, returns truth if ID was already handled, or tags is
* handled otherwise and return false. */
bool check_is_built_and_tag(ID *id, int tag = TAG_COMPLETE);
template<typename T> bool check_is_built(T *datablock, int tag = TAG_COMPLETE) const
{
return this->check_is_built(&datablock->id, tag);
}
template<typename T> void tag_built(T *datablock, int tag = TAG_COMPLETE)
{
this->tag_built(&datablock->id, tag);
}
template<typename T> bool check_is_built_and_tag(T *datablock, int tag = TAG_COMPLETE)
{
return this->check_is_built_and_tag(&datablock->id, tag);
}
Set<const ID *> get_ids() const;
protected:
int get_ID_tag(ID *id) const;
Map<ID *, int> id_tags_;
};
} // namespace deg
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,360 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "BKE_lib_query.hh" /* For LibraryForeachIDCallbackFlag enum. */
#include "BLI_set.hh"
#include "DNA_armature_types.h"
#include "DNA_listBase.h"
#include "intern/builder/deg_builder.h"
#include "intern/builder/deg_builder_key.h"
#include "intern/builder/deg_builder_map.h"
#include "intern/depsgraph_type.hh"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_operation.hh"
#include "DEG_depsgraph.hh"
namespace blender {
struct BoneCollection;
struct CacheFile;
struct Camera;
struct Collection;
struct FCurve;
struct FreestyleLineSet;
struct FreestyleLineStyle;
struct ID;
struct IDProperty;
struct Image;
struct Key;
struct LayerCollection;
struct Light;
struct LightProbe;
struct Main;
struct Mask;
struct Material;
struct MovieClip;
struct NlaStrip;
struct Object;
struct ParticleSettings;
struct Scene;
struct Speaker;
struct Tex;
struct VFont;
struct World;
struct bAction;
struct bArmature;
struct bConstraint;
struct bNodeSocket;
struct bNodeTree;
struct bPoseChannel;
struct bSound;
struct PointerRNA;
namespace deg {
struct ComponentNode;
struct Depsgraph;
class DepsgraphBuilderCache;
struct IDNode;
struct OperationKey;
struct OperationNode;
struct TimeSourceNode;
class DepsgraphNodeBuilder : public DepsgraphBuilder {
public:
DepsgraphNodeBuilder(Main *bmain, Depsgraph *graph, DepsgraphBuilderCache *cache);
~DepsgraphNodeBuilder() override;
/* For given original ID get ID which is created by copy-on-evaluation system. */
ID *get_cow_id(const ID *id_orig) const;
/* Similar to above, but for the cases when there is no ID node we create
* one. */
ID *ensure_cow_id(ID *id_orig);
/* Helper wrapper function which wraps get_cow_id with a needed type cast. */
template<typename T> T *get_cow_datablock(const T *orig) const
{
return (T *)get_cow_id(&orig->id);
}
/* For a given evaluated datablock get corresponding original one. */
template<typename T> T *get_orig_datablock(const T *cow) const
{
return (T *)cow->id.orig_id;
}
virtual void begin_build();
virtual void end_build();
/**
* `id_cow_self` is the user of `id_pointer`,
* see also `LibraryIDLinkCallbackData` struct definition.
*/
int foreach_id_cow_detect_need_for_update_callback(ID *id_cow_self, ID *id_pointer);
IDNode *add_id_node(ID *id);
IDNode *find_id_node(const ID *id);
TimeSourceNode *add_time_source();
ComponentNode *add_component_node(ID *id, NodeType comp_type, const char *comp_name = "");
ComponentNode *find_component_node(const ID *id, NodeType comp_type, const char *comp_name = "");
OperationNode *add_operation_node(ComponentNode *comp_node,
OperationCode opcode,
const DepsEvalOperationCb &op = nullptr,
const char *name = "",
int name_tag = -1);
OperationNode *add_operation_node(ID *id,
NodeType comp_type,
const char *comp_name,
OperationCode opcode,
const DepsEvalOperationCb &op = nullptr,
const char *name = "",
int name_tag = -1);
OperationNode *add_operation_node(ID *id,
NodeType comp_type,
OperationCode opcode,
const DepsEvalOperationCb &op = nullptr,
const char *name = "",
int name_tag = -1);
OperationNode *ensure_operation_node(ID *id,
NodeType comp_type,
const char *comp_name,
OperationCode opcode,
const DepsEvalOperationCb &op = nullptr,
const char *name = "",
int name_tag = -1);
OperationNode *ensure_operation_node(ID *id,
NodeType comp_type,
OperationCode opcode,
const DepsEvalOperationCb &op = nullptr,
const char *name = "",
int name_tag = -1);
bool has_operation_node(ID *id,
NodeType comp_type,
const char *comp_name,
OperationCode opcode,
const char *name = "",
int name_tag = -1);
bool has_operation_node(ID *id, NodeType comp_type, OperationCode opcode);
OperationNode *find_operation_node(const ID *id,
NodeType comp_type,
const char *comp_name,
OperationCode opcode,
const char *name = "",
int name_tag = -1);
OperationNode *find_operation_node(const ID *id,
NodeType comp_type,
OperationCode opcode,
const char *name = "",
int name_tag = -1);
OperationNode *find_operation_node(const OperationKey &key);
virtual void build_id(ID *id, bool force_be_visible = false);
/* Build function for ID types that do not need their own build_xxx() function. */
virtual void build_generic_id(ID *id);
virtual void build_idproperties(IDProperty *id_property);
virtual void build_scene_render(Scene *scene, ViewLayer *view_layer);
virtual void build_scene_camera(Scene *scene);
virtual void build_scene_parameters(Scene *scene);
virtual void build_scene_compositor(Scene *scene);
virtual void build_empty_object(Object *object);
virtual void build_layer_collections(ListBaseT<LayerCollection> *lb);
virtual void build_view_layer(Scene *scene,
ViewLayer *view_layer,
eDepsNode_LinkedState_Type linked_state);
virtual void build_collection(LayerCollection *from_layer_collection, Collection *collection);
virtual void build_object(int base_index,
Object *object,
eDepsNode_LinkedState_Type linked_state,
bool is_visible);
virtual void build_object_instance_collection(Object *object, bool is_object_visible);
virtual void build_object_from_layer(int base_index,
Object *object,
eDepsNode_LinkedState_Type linked_state);
virtual void build_object_flags(int base_index,
Object *object,
eDepsNode_LinkedState_Type linked_state);
virtual void build_object_modifiers(Object *object);
virtual void build_object_data(Object *object);
virtual void build_object_data_camera(Object *object);
virtual void build_object_data_geometry(Object *object);
virtual void build_object_data_geometry_datablock(ID *obdata);
virtual void build_object_data_light(Object *object);
virtual void build_object_data_lightprobe(Object *object);
virtual void build_object_data_speaker(Object *object);
virtual void build_object_data_grease_pencil(Object *object);
virtual void build_object_transform(Object *object);
virtual void build_object_constraints(Object *object);
virtual void build_object_pointcache(Object *object);
virtual void build_object_shading(Object *object);
virtual void build_object_light_linking(Object *object);
virtual void build_light_linking_collection(Collection *collection);
virtual void build_pose_constraints(Object *object, bPoseChannel *pchan, int pchan_index);
virtual void build_rigidbody(Scene *scene);
virtual void build_particle_systems(Object *object, bool is_object_visible);
virtual void build_particle_settings(ParticleSettings *part);
/**
* Build graph nodes for #AnimData block and any animated images used.
* \param id: ID-Block which hosts the #AnimData
*/
virtual void build_animdata(ID *id);
virtual void build_animdata_nlastrip_targets(ListBaseT<NlaStrip> *strips);
/**
* Build graph nodes to update the current frame in image users.
*/
virtual void build_animation_images(ID *id);
virtual void build_action(bAction *action);
virtual void build_animdata_drivers(ID *id, AnimData *adt);
/**
* Build graph node(s) for Driver
* \param id: ID-Block that driver is attached to
* \param fcurve: Driver-FCurve
* \param driver_index: Index in animation data drivers list
*/
virtual void build_driver(ID *id, FCurve *fcurve, int driver_index);
virtual void build_driver_variables(ID *id, FCurve *fcurve);
virtual void build_driver_scene_camera_variable(Scene *scene, const char *camera_path);
/* Build operations of a property value from which is read by a driver target.
*
* The driver target points to a data-block (or a sub-data-block like View Layer).
* This data-block is presented in the interface as a "Prop" and its resolved RNA pointer is
* passed here as `target_prop`.
*
* The tricky part (and a bit confusing naming) is that the driver target accesses a property of
* the `target_prop` to get its value. The property which is read to give an actual target value
* is denoted by its RNA path relative to the `target_prop`. In the interface it is called "Path"
* and here it is called `rna_path_from_target_prop`. */
virtual void build_driver_id_property(const PointerRNA &target_prop,
const char *rna_path_from_target_prop);
virtual void build_parameters(ID *id);
virtual void build_dimensions(Object *object);
/** IK Solver Eval Steps. */
virtual void build_ik_pose(Object *object, bPoseChannel *pchan, bConstraint *con);
/** Spline IK Eval Steps. */
virtual void build_splineik_pose(Object *object, bPoseChannel *pchan, bConstraint *con);
/** Pose/Armature Bones Graph. */
virtual void build_rig(Object *object);
virtual void build_armature(bArmature *armature);
virtual void build_armature_bones(ListBaseT<Bone> *bones);
virtual void build_armature_bone_collections(Span<BoneCollection *> collections);
/** Shape-keys. */
virtual void build_shapekeys(Key *key);
virtual void build_camera(Camera *camera);
virtual void build_light(Light *lamp);
virtual void build_nodetree(bNodeTree *ntree);
virtual void build_nodetree_socket(bNodeSocket *socket);
/** Recursively build graph for material. */
virtual void build_material(Material *ma);
virtual void build_materials(Material **materials, int num_materials);
virtual void build_freestyle_lineset(FreestyleLineSet *fls);
virtual void build_freestyle_linestyle(FreestyleLineStyle *linestyle);
/** Recursively build graph for texture. */
virtual void build_texture(Tex *tex);
virtual void build_image(Image *image);
/** Recursively build graph for world. */
virtual void build_world(World *world);
virtual void build_cachefile(CacheFile *cache_file);
virtual void build_mask(Mask *mask);
virtual void build_movieclip(MovieClip *clip);
virtual void build_lightprobe(LightProbe *probe);
virtual void build_speaker(Speaker *speaker);
virtual void build_sound(bSound *sound);
virtual void build_scene_sequencer(Scene *scene);
virtual void build_scene_audio(Scene *scene);
virtual void build_scene_speakers(Scene *scene, ViewLayer *view_layer);
virtual void build_vfont(VFont *vfont);
virtual Set<const ID *> get_built_ids() const;
/* Per-ID information about what was already in the dependency graph.
* Allows to re-use certain values, to speed up following evaluation. */
struct IDInfo {
/* Copy-on-written pointer of the corresponding ID. */
ID *id_cow = nullptr;
/* Mask of visible components from previous state of the
* dependency graph. */
IDComponentsMask previously_visible_components_mask = 0;
/* Special evaluation flag mask from the previous depsgraph. */
uint32_t previous_eval_flags = 0;
/* Mesh CustomData mask from the previous depsgraph. */
DEGCustomDataMeshMasks previous_customdata_masks = {};
};
protected:
/* Entry tags and non-updated operations from the previous state of the dependency graph.
* The entry tags are operations which were directly tagged, the matching operations from the
* new dependency graph will be tagged. The needs-update operations are possibly indirectly
* modified operations, whose complementary part from the new dependency graph will only be
* marked as needs-update.
* Stored before the graph is re-created so that they can be transferred over. */
Vector<PersistentOperationKey> saved_entry_tags_;
Vector<PersistentOperationKey> needs_update_operations_;
struct BuilderWalkUserData {
DepsgraphNodeBuilder *builder;
};
static void modifier_walk(void *user_data,
struct Object *object,
struct ID **idpoin,
LibraryForeachIDCallbackFlag cb_flag);
static void constraint_walk(bConstraint *constraint,
ID **idpoin,
bool is_reference,
void *user_data);
void tag_previously_tagged_nodes();
/**
* Check for IDs that need to be flushed (copy-on-eval-updated)
* because the depsgraph itself created or removed some of their evaluated dependencies.
*/
void update_invalid_cow_pointers();
/* State which demotes currently built entities. */
Scene *scene_;
ViewLayer *view_layer_;
int view_layer_index_;
/* NOTE: Collection are possibly built recursively, so be careful when
* setting the current state. */
/* Accumulated flag over the hierarchy of currently building collections.
* Denotes whether all the hierarchy from parent of `collection_` to the
* very root is visible (aka not restricted.). */
bool is_parent_collection_visible_;
/* Indexed by original ID.session_uid, values are IDInfo. */
Map<uint, IDInfo> id_info_hash_;
/* Set of IDs which were already build. Makes it easier to keep track of
* what was already built and what was not. */
BuilderMap built_map_;
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,304 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Methods for constructing depsgraph's nodes
*/
#include "intern/builder/deg_builder_nodes.h"
#include <cstdlib>
#include "DNA_armature_types.h"
#include "DNA_constraint_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BLI_listbase.h"
#include "BKE_action.hh"
#include "BKE_armature.hh"
#include "BKE_constraint.h"
#include "BKE_lib_query.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "intern/eval/deg_eval_copy_on_write.h"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_operation.hh"
namespace blender::deg {
void DepsgraphNodeBuilder::build_pose_constraints(Object *object,
bPoseChannel *pchan,
int pchan_index)
{
/* Pull indirect dependencies via constraints. */
BuilderWalkUserData data;
data.builder = this;
BKE_constraints_id_loop(&pchan->constraints, constraint_walk, IDWALK_NOP, &data);
/* Create node for constraint stack. */
Scene *scene_cow = get_cow_datablock(scene_);
Object *object_cow = get_cow_datablock(object);
add_operation_node(&object->id,
NodeType::BONE,
pchan->name,
OperationCode::BONE_CONSTRAINTS,
[scene_cow, object_cow, pchan_index](blender::Depsgraph *depsgraph) {
BKE_pose_constraints_evaluate(
depsgraph, scene_cow, object_cow, pchan_index);
});
}
void DepsgraphNodeBuilder::build_ik_pose(Object *object, bPoseChannel *pchan, bConstraint *con)
{
bKinematicConstraint *data = static_cast<bKinematicConstraint *>(con->data);
/* Find the chain's root. */
bPoseChannel *rootchan = BKE_armature_ik_solver_find_root(pchan, data);
if (rootchan == nullptr) {
return;
}
if (has_operation_node(
&object->id, NodeType::EVAL_POSE, rootchan->name, OperationCode::POSE_IK_SOLVER))
{
return;
}
int rootchan_index = BLI_findindex(&object->pose->chanbase, rootchan);
BLI_assert(rootchan_index != -1);
/* Operation node for evaluating/running IK Solver. */
Scene *scene_cow = get_cow_datablock(scene_);
Object *object_cow = get_cow_datablock(object);
add_operation_node(&object->id,
NodeType::EVAL_POSE,
rootchan->name,
OperationCode::POSE_IK_SOLVER,
[scene_cow, object_cow, rootchan_index](blender::Depsgraph *depsgraph) {
BKE_pose_iktree_evaluate(depsgraph, scene_cow, object_cow, rootchan_index);
});
}
void DepsgraphNodeBuilder::build_splineik_pose(Object *object,
bPoseChannel *pchan,
bConstraint *con)
{
bSplineIKConstraint *data = static_cast<bSplineIKConstraint *>(con->data);
/* Find the chain's root. */
bPoseChannel *rootchan = BKE_armature_splineik_solver_find_root(pchan, data);
if (has_operation_node(
&object->id, NodeType::EVAL_POSE, rootchan->name, OperationCode::POSE_SPLINE_IK_SOLVER))
{
return;
}
/* Operation node for evaluating/running Spline IK Solver.
* Store the "root bone" of this chain in the solver, so it knows where to
* start. */
int rootchan_index = BLI_findindex(&object->pose->chanbase, rootchan);
BLI_assert(rootchan_index != -1);
Scene *scene_cow = get_cow_datablock(scene_);
Object *object_cow = get_cow_datablock(object);
add_operation_node(&object->id,
NodeType::EVAL_POSE,
rootchan->name,
OperationCode::POSE_SPLINE_IK_SOLVER,
[scene_cow, object_cow, rootchan_index](blender::Depsgraph *depsgraph) {
BKE_pose_splineik_evaluate(
depsgraph, scene_cow, object_cow, rootchan_index);
});
}
/* Pose/Armature Bones Graph */
void DepsgraphNodeBuilder::build_rig(Object *object)
{
bArmature *armature = id_cast<bArmature *>(object->data);
Scene *scene_cow = get_cow_datablock(scene_);
Object *object_cow = get_cow_datablock(object);
OperationNode *op_node;
/* Animation and/or drivers linking pose-bones to base-armature used to define them.
*
* NOTE: AnimData here is really used to control animated deform properties,
* which ideally should be able to be unique across different
* instances. Eventually, we need some type of proxy/isolation
* mechanism in-between here to ensure that we can use same rig
* multiple times in same scene. */
/* Armature. */
build_armature(armature);
/* Rebuild pose if not up to date. */
if (object->pose == nullptr || (object->pose->flag & POSE_RECALC)) {
/* By definition, no need to tag depsgraph as dirty from here, so we can pass nullptr bmain. */
BKE_pose_rebuild(nullptr, object, armature, true);
}
else {
/* Ensure the pose bone indices are up to date, so that the rest of the depsgraph building code
* can use `pchan->bone_get(armature)`, which is faster than passing the object. */
BKE_pose_ensure_bone_indices(*object);
}
/* Speed optimization for animation lookups. */
if (object->pose != nullptr) {
BKE_pose_channels_hash_ensure(object->pose);
if (object->pose->flag & POSE_CONSTRAINTS_NEED_UPDATE_FLAGS) {
BKE_pose_update_constraint_flags(*object);
}
}
/**
* Pose Rig Graph
* ==============
*
* Pose Component:
* - Mainly used for referencing Bone components.
* - This is where the evaluation operations for init/exec/cleanup
* (ik) solvers live, and are later hooked up (so that they can be
* interleaved during runtime) with bone-operations they depend on/affect.
* - init_pose_eval() and cleanup_pose_eval() are absolute first and last
* steps of pose eval process. ALL bone operations must be performed
* between these two...
*
* Bone Component:
* - Used for representing each bone within the rig
* - Acts to encapsulate the evaluation operations (base matrix + parenting,
* and constraint stack) so that they can be easily found.
* - Everything else which depends on bone-results hook up to the component
* only so that we can redirect those to point at either the
* post-IK/post-constraint/post-matrix steps, as needed. */
/* Pose eval context. */
op_node = add_operation_node(&object->id,
NodeType::EVAL_POSE,
OperationCode::POSE_INIT,
[scene_cow, object_cow](blender::Depsgraph *depsgraph) {
BKE_pose_eval_init(depsgraph, scene_cow, object_cow);
});
op_node->set_as_entry();
op_node = add_operation_node(&object->id,
NodeType::EVAL_POSE,
OperationCode::POSE_INIT_IK,
[scene_cow, object_cow](blender::Depsgraph *depsgraph) {
BKE_pose_eval_init_ik(depsgraph, scene_cow, object_cow);
});
add_operation_node(&object->id,
NodeType::EVAL_POSE,
OperationCode::POSE_CLEANUP,
[scene_cow, object_cow](blender::Depsgraph *depsgraph) {
BKE_pose_eval_cleanup(depsgraph, scene_cow, object_cow);
});
op_node = add_operation_node(
&object->id,
NodeType::EVAL_POSE,
OperationCode::POSE_DONE,
[object_cow](blender::Depsgraph *depsgraph) { BKE_pose_eval_done(depsgraph, object_cow); });
op_node->set_as_exit();
/* Bones. */
int pchan_index = 0;
for (bPoseChannel &pchan : object->pose->chanbase) {
/* Node for bone evaluation. */
op_node = add_operation_node(
&object->id, NodeType::BONE, pchan.name, OperationCode::BONE_LOCAL);
op_node->set_as_entry();
/* Add a separate node for bone visibility. Getting the visibility doesn't need the pose of the
* bone to be evaluated, so drivers that target the bone's "hide" RNA property can depend on
* this operation, rather than the BONE_LOCAL node. See #152121. */
add_operation_node(&object->id, NodeType::BONE, pchan.name, OperationCode::BONE_VISIBILITY);
add_operation_node(&object->id,
NodeType::BONE,
pchan.name,
OperationCode::BONE_POSE_PARENT,
[scene_cow, object_cow, pchan_index](blender::Depsgraph *depsgraph) {
BKE_pose_eval_bone(depsgraph, scene_cow, object_cow, pchan_index);
});
/* NOTE: Dedicated noop for easier relationship construction. */
add_operation_node(&object->id, NodeType::BONE, pchan.name, OperationCode::BONE_READY);
op_node = add_operation_node(&object->id,
NodeType::BONE,
pchan.name,
OperationCode::BONE_DONE,
[object_cow, pchan_index](blender::Depsgraph *depsgraph) {
BKE_pose_bone_done(depsgraph, object_cow, pchan_index);
});
/* B-Bone shape computation - the real last step if present. */
if (check_pchan_has_bbone(object, &pchan)) {
op_node = add_operation_node(&object->id,
NodeType::BONE,
pchan.name,
OperationCode::BONE_SEGMENTS,
[object_cow, pchan_index](blender::Depsgraph *depsgraph) {
BKE_pose_eval_bbone_segments(
depsgraph, object_cow, pchan_index);
});
}
op_node->set_as_exit();
/* Custom properties. */
bool add_idprops_operation = false;
if (pchan.prop != nullptr) {
build_idproperties(pchan.prop);
add_idprops_operation = true;
}
if (pchan.system_properties != nullptr) {
build_idproperties(pchan.system_properties);
add_idprops_operation = true;
}
if (add_idprops_operation) {
add_operation_node(
&object->id, NodeType::PARAMETERS, OperationCode::PARAMETERS_EVAL, nullptr, pchan.name);
}
/* Build constraints. */
if (pchan.constraints.first != nullptr) {
build_pose_constraints(object, &pchan, pchan_index);
}
/**
* IK Solvers.
*
* - These require separate processing steps are pose-level
* to be executed between chains of bones (i.e. once the
* base transforms of a bunch of bones is done)
*
* Unsolved Issues:
* - Care is needed to ensure that multi-headed trees work out the same
* as in ik-tree building
* - Animated chain-lengths are a problem. */
for (bConstraint &con : pchan.constraints) {
switch (con.type) {
case CONSTRAINT_TYPE_KINEMATIC:
build_ik_pose(object, &pchan, &con);
break;
case CONSTRAINT_TYPE_SPLINEIK:
build_splineik_pose(object, &pchan, &con);
break;
default:
break;
}
}
/* Custom shape. */
if (pchan.custom != nullptr) {
/* NOTE: The relation builder will ensure visibility of the custom shape object. */
build_object(-1, pchan.custom, DEG_ID_LINKED_INDIRECTLY, false);
}
pchan_index++;
}
}
} // namespace blender::deg

View File

@@ -0,0 +1,87 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/builder/deg_builder_nodes.h"
#include "DNA_scene_types.h"
#include "BLI_listbase.h"
namespace blender::deg {
void DepsgraphNodeBuilder::build_scene_render(Scene *scene, ViewLayer *view_layer)
{
scene_ = scene;
view_layer_ = view_layer;
const bool build_compositor = (scene->r.scemode & R_DOCOMP);
const bool build_sequencer = (scene->r.scemode & R_DOSEQ);
IDNode *id_node = add_id_node(&scene->id);
id_node->linked_state = DEG_ID_LINKED_DIRECTLY;
add_time_source();
build_animdata(&scene->id);
build_scene_parameters(scene);
build_scene_audio(scene);
if (build_compositor) {
build_scene_compositor(scene);
}
if (build_sequencer) {
build_scene_sequencer(scene);
build_scene_speakers(scene, view_layer);
}
build_scene_camera(scene);
}
void DepsgraphNodeBuilder::build_scene_camera(Scene *scene)
{
if (scene->camera != nullptr) {
build_object(-1, scene->camera, DEG_ID_LINKED_INDIRECTLY, true);
}
for (TimeMarker &marker : scene->markers) {
if (!ELEM(marker.camera, nullptr, scene->camera)) {
build_object(-1, marker.camera, DEG_ID_LINKED_INDIRECTLY, true);
}
}
}
void DepsgraphNodeBuilder::build_scene_parameters(Scene *scene)
{
if (built_map_.check_is_built_and_tag(scene, BuilderMap::TAG_PARAMETERS)) {
return;
}
build_parameters(&scene->id);
build_idproperties(scene->id.properties);
build_idproperties(scene->id.system_properties);
add_operation_node(&scene->id, NodeType::SCENE, OperationCode::SCENE_EVAL);
for (TimeMarker &marker : scene->markers) {
build_idproperties(marker.prop);
}
}
void DepsgraphNodeBuilder::build_scene_compositor(Scene *scene)
{
if (built_map_.check_is_built_and_tag(scene, BuilderMap::TAG_SCENE_COMPOSITOR)) {
return;
}
if (scene->compositing_node_group == nullptr) {
return;
}
add_operation_node(&scene->id,
NodeType::COMPOSITOR,
OperationCode::COMPOSITOR_EVAL,
[](blender::Depsgraph * /*depsgraph*/) {
/* Empty evaluate function, but needed to make sure the operation is not
* considered a no-op. */
});
build_nodetree(scene->compositing_node_group);
}
} // namespace blender::deg

View File

@@ -0,0 +1,170 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Methods for constructing depsgraph's nodes
*/
#include "intern/builder/deg_builder_nodes.h"
#include <cstdlib>
#include "DNA_collection_types.h"
#include "DNA_freestyle_types.h"
#include "DNA_layer_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BLI_listbase.h"
#include "BKE_layer.hh"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "intern/builder/deg_builder.h"
#include "intern/depsgraph.hh"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_operation.hh"
namespace blender::deg {
void DepsgraphNodeBuilder::build_layer_collections(ListBaseT<LayerCollection> *lb)
{
const int visibility_flag = (graph_->mode == DAG_EVAL_VIEWPORT) ? COLLECTION_HIDE_VIEWPORT :
COLLECTION_HIDE_RENDER;
for (LayerCollection &lc : *lb) {
if (lc.collection->flag & visibility_flag) {
continue;
}
if ((lc.flag & LAYER_COLLECTION_EXCLUDE) == 0) {
build_collection(&lc, lc.collection);
}
build_layer_collections(&lc.layer_collections);
}
}
void DepsgraphNodeBuilder::build_freestyle_lineset(FreestyleLineSet *fls)
{
if (fls->group != nullptr) {
build_collection(nullptr, fls->group);
}
if (fls->linestyle != nullptr) {
build_freestyle_linestyle(fls->linestyle);
}
}
void DepsgraphNodeBuilder::build_view_layer(Scene *scene,
ViewLayer *view_layer,
eDepsNode_LinkedState_Type linked_state)
{
/* NOTE: Pass view layer index of 0 since after scene evaluated copy there is
* only one view layer in there. */
view_layer_index_ = 0;
/* Scene ID block. */
IDNode *id_node = add_id_node(&scene->id);
id_node->linked_state = linked_state;
add_operation_node(&scene->id, NodeType::HIERARCHY, OperationCode::HIERARCHY);
/* Time source. */
add_time_source();
/* Setup currently building context. */
scene_ = scene;
view_layer_ = view_layer;
/* Get pointer to an evaluated version of scene ID. */
Scene *scene_cow = get_cow_datablock(scene);
/* Scene objects. */
/* NOTE: Base is used for function bindings as-is, so need to pass evaluated base,
* but object is expected to be an original one. Hence we go into some
* tricks here iterating over the view layer. */
int base_index = 0;
BKE_view_layer_synced_ensure(*bmain_, scene, view_layer);
for (Base &base : *BKE_view_layer_object_bases_get(view_layer)) {
/* object itself */
if (!need_pull_base_into_graph(&base)) {
continue;
}
/* NOTE: We consider object visible even if it's currently
* restricted by the base/restriction flags. Otherwise its drivers
* will never be evaluated.
*
* TODO(sergey): Need to go more granular on visibility checks. */
build_object(base_index, base.object, linked_state, true);
base_index++;
if (!graph_->has_animated_visibility) {
graph_->has_animated_visibility |= is_object_visibility_animated(base.object);
}
}
build_layer_collections(&view_layer->layer_collections);
build_scene_camera(scene);
/* Rigidbody. */
if (scene->rigidbody_world != nullptr) {
build_rigidbody(scene);
}
/* Scene's animation and drivers. */
if (scene->adt != nullptr) {
build_animdata(&scene->id);
}
/* World. */
if (scene->world != nullptr) {
build_world(scene->world);
}
/* Cache file. */
for (CacheFile &cachefile : bmain_->cachefiles) {
build_cachefile(&cachefile);
}
/* Masks. */
for (Mask &mask : bmain_->masks) {
build_mask(&mask);
}
/* Movie clips. */
for (MovieClip &clip : bmain_->movieclips) {
build_movieclip(&clip);
}
/* Material override. */
if (view_layer->mat_override != nullptr) {
build_material(view_layer->mat_override);
}
/* World override */
if (view_layer->world_override != nullptr) {
build_world(view_layer->world_override);
}
/* Freestyle linesets. */
for (FreestyleLineSet &fls : view_layer->freestyle_config.linesets) {
build_freestyle_lineset(&fls);
}
/* Sequencer. */
if (linked_state == DEG_ID_LINKED_DIRECTLY) {
build_scene_audio(scene);
build_scene_sequencer(scene);
}
/* Collections. */
add_operation_node(
&scene->id,
NodeType::LAYER_COLLECTIONS,
OperationCode::VIEW_LAYER_EVAL,
[view_layer_index = view_layer_index_, scene_cow](blender::Depsgraph *depsgraph) {
BKE_layer_eval_view_layer_indexed(depsgraph, scene_cow, view_layer_index);
});
/* Parameters evaluation for scene relations mainly. */
build_scene_compositor(scene);
build_scene_parameters(scene);
/* Build all set scenes. */
if (scene->set != nullptr) {
ViewLayer *set_view_layer = BKE_view_layer_default_render(scene->set);
build_view_layer(scene->set, set_view_layer, DEG_ID_LINKED_VIA_SET);
}
}
} // namespace blender::deg

View File

@@ -0,0 +1,52 @@
/* SPDX-FileCopyrightText: 2015 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/builder/deg_builder_pchanmap.h"
#include <cstdio>
#include <cstring>
namespace blender::deg {
void RootPChanMap::print_debug()
{
map_.foreach_item([](StringRefNull key, const Set<StringRefNull> &values) {
printf(" %s : { ", key.data());
for (StringRefNull val : values) {
printf("%s, ", val.data());
}
printf("}\n");
});
}
void RootPChanMap::add_bone(const char *bone, const char *root)
{
map_.lookup_or_add_default(bone).add(root);
}
bool RootPChanMap::has_common_root(const char *bone1, const char *bone2) const
{
const Set<StringRefNull> *bone1_roots = map_.lookup_ptr(bone1);
const Set<StringRefNull> *bone2_roots = map_.lookup_ptr(bone2);
if (bone1_roots == nullptr) {
// fprintf("RootPChanMap: bone1 '%s' not found (%s => %s)\n", bone1, bone1, bone2);
// print_debug();
return false;
}
if (bone2_roots == nullptr) {
// fprintf("RootPChanMap: bone2 '%s' not found (%s => %s)\n", bone2, bone1, bone2);
// print_debug();
return false;
}
return Set<StringRefNull>::Intersects(*bone1_roots, *bone2_roots);
}
} // namespace blender::deg

View File

@@ -0,0 +1,35 @@
/* SPDX-FileCopyrightText: 2015 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_string_ref.hh"
namespace blender::deg {
struct RootPChanMap {
/** Debug contents of map. */
void print_debug();
/** Add a mapping. */
void add_bone(const char *bone, const char *root);
/** Check if there's a common root bone between two bones. */
bool has_common_root(const char *bone1, const char *bone2) const;
protected:
/**
* The strings are only referenced by this map. Users of RootPChanMap have to make sure that the
* life-time of the strings is long enough.
*/
Map<StringRefNull, Set<StringRefNull>> map_;
};
} // namespace blender::deg

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,373 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include <cstring>
#include "intern/depsgraph_type.hh"
#include "DNA_ID.h"
#include "DNA_listBase.h"
#include "BLI_span.hh"
#include "BKE_lib_query.hh" /* For LibraryForeachIDCallbackFlag enum. */
#include "intern/builder/deg_builder.h"
#include "intern/builder/deg_builder_key.h"
#include "intern/builder/deg_builder_map.h"
#include "intern/builder/deg_builder_rna.h"
#include "intern/builder/deg_builder_stack.h"
#include "intern/depsgraph.hh"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_operation.hh"
namespace blender {
struct CacheFile;
struct Camera;
struct Collection;
struct EffectorWeights;
struct FCurve;
struct FreestyleLineSet;
struct FreestyleLineStyle;
struct ID;
struct IDProperty;
struct Image;
struct Key;
struct LayerCollection;
struct Light;
struct LightProbe;
struct Main;
struct Mask;
struct Material;
struct MovieClip;
struct NlaStrip;
struct Object;
struct ParticleSettings;
struct ParticleSystem;
struct Scene;
struct Speaker;
struct Tex;
struct VFont;
struct ViewLayer;
struct World;
struct bAction;
struct bArmature;
struct bConstraint;
struct bNodeSocket;
struct bNodeTree;
struct bPoseChannel;
struct bSound;
namespace deg {
struct ComponentNode;
struct DepsNodeHandle;
struct Depsgraph;
class DepsgraphBuilderCache;
struct IDNode;
struct Node;
struct OperationNode;
struct Relation;
struct RootPChanMap;
struct TimeSourceNode;
class DepsgraphRelationBuilder : public DepsgraphBuilder {
public:
DepsgraphRelationBuilder(Main *bmain, Depsgraph *graph, DepsgraphBuilderCache *cache);
void begin_build();
template<typename KeyFrom, typename KeyTo>
Relation *add_relation(const KeyFrom &key_from,
const KeyTo &key_to,
const char *description,
int flags = 0);
template<typename KeyTo>
Relation *add_relation(const TimeSourceKey &key_from,
const KeyTo &key_to,
const char *description,
int flags = 0);
template<typename KeyType>
requires(!std::is_same_v<KeyType, TimeSourceKey>)
Relation *add_node_handle_relation(const KeyType &key_from,
const DepsNodeHandle *handle,
const char *description,
int flags = 0);
Relation *add_node_handle_relation(const TimeSourceKey &key_from,
const DepsNodeHandle *handle,
const char *description,
int flags = 0);
template<typename KeyTo>
Relation *add_depends_on_transform_relation(ID *id,
const KeyTo &key_to,
const char *description,
int flags = 0);
/* Adds relation from proper transformation operation to the modifier.
* Takes care of checking for possible physics solvers modifying position
* of this object. */
void add_depends_on_transform_relation(const DepsNodeHandle *handle, const char *description);
void add_customdata_mask(Object *object, const DEGCustomDataMeshMasks &customdata_masks);
void add_special_eval_flag(ID *id, uint32_t flag);
virtual void build_id(ID *id);
/* Build function for ID types that do not need their own build_xxx() function. */
virtual void build_generic_id(ID *id);
virtual void build_idproperties(IDProperty *id_property);
virtual void build_scene_camera(Scene *scene);
virtual void build_scene_render(Scene *scene, ViewLayer *view_layer);
virtual void build_scene_parameters(Scene *scene);
virtual void build_scene_compositor(Scene *scene);
virtual bool build_layer_collection(LayerCollection *layer_collection);
virtual void build_view_layer_collections(ViewLayer *view_layer);
virtual void build_view_layer(Scene *scene,
ViewLayer *view_layer,
eDepsNode_LinkedState_Type linked_state);
virtual void build_collection(LayerCollection *from_layer_collection, Collection *collection);
virtual void build_object(Object *object);
virtual void build_object_from_view_layer_base(Object *object);
virtual void build_object_layer_component_relations(Object *object);
virtual void build_object_modifiers(Object *object);
virtual void build_object_data(Object *object);
virtual void build_object_data_camera(Object *object);
virtual void build_object_data_geometry(Object *object);
virtual void build_object_data_geometry_datablock(ID *obdata);
virtual void build_object_data_empty(Object *object);
virtual void build_object_data_light(Object *object);
virtual void build_object_data_lightprobe(Object *object);
virtual void build_object_data_speaker(Object *object);
virtual void build_object_parent(Object *object);
virtual void build_object_pointcache(Object *object);
virtual void build_object_instance_collection(Object *object);
virtual void build_object_shading(Object *object);
virtual void build_object_light_linking(Object *emitter);
virtual void build_light_linking_collection(Object *emitter, Collection *collection);
virtual void build_constraints(ID *id,
NodeType component_type,
const char *component_subdata,
ListBaseT<bConstraint> *constraints,
RootPChanMap *root_map);
virtual void build_animdata(ID *id);
virtual void build_animdata_curves(ID *id);
virtual void build_animdata_fcurve_target(ID *id,
PointerRNA id_ptr,
ComponentKey &adt_key,
OperationNode *operation_from,
FCurve *fcu);
virtual void build_animdata_action_targets(ID *id,
int32_t slot_handle,
ComponentKey &adt_key,
OperationNode *operation_from,
bAction *action);
virtual void build_animdata_nlastrip_targets(ID *id,
ComponentKey &adt_key,
OperationNode *operation_from,
ListBaseT<NlaStrip> *strips);
virtual void build_animdata_drivers(ID *id);
virtual void build_animdata_force(ID *id);
virtual void build_animation_images(ID *id);
virtual void build_action(bAction *action);
virtual void build_driver(ID *id, FCurve *fcurve);
virtual void build_driver_data(ID *id, FCurve *fcurve);
virtual void build_driver_variables(ID *id, FCurve *fcurve);
virtual void build_driver_scene_camera_variable(const OperationKey &driver_key,
const RNAPathKey &self_key,
Scene *scene,
const char *rna_path);
virtual void build_driver_rna_path_variable(const OperationKey &driver_key,
const RNAPathKey &self_key,
ID *target_id,
const PointerRNA &target_prop,
const char *rna_path);
/* Build operations of a property value from which is read by a driver target.
*
* The driver target points to a data-block (or a sub-data-block like View Layer).
* This data-block is presented in the interface as a "Prop" and its resolved RNA pointer is
* passed here as `target_prop`.
*
* The tricky part (and a bit confusing naming) is that the driver target accesses a property of
* the `target_prop` to get its value. The property which is read to give an actual target value
* is denoted by its RNA path relative to the `target_prop`. In the interface it is called "Path"
* and here it is called `rna_path_from_target_prop`. */
virtual void build_driver_id_property(const PointerRNA &target_prop,
const char *rna_path_from_target_prop);
virtual void build_parameters(ID *id);
virtual void build_dimensions(Object *object);
virtual void build_world(World *world);
virtual void build_rigidbody(Scene *scene);
virtual void build_particle_systems(Object *object);
virtual void build_particle_settings(ParticleSettings *part);
virtual void build_particle_system_visualization_object(Object *object,
ParticleSystem *psys,
Object *draw_object);
virtual void build_ik_pose(Object *object,
bPoseChannel *pchan,
bConstraint *con,
RootPChanMap *root_map);
virtual void build_splineik_pose(Object *object,
bPoseChannel *pchan,
bConstraint *con,
RootPChanMap *root_map);
virtual void build_inter_ik_chains(Object *object,
const OperationKey &solver_key,
const bPoseChannel *rootchan,
const RootPChanMap *root_map);
virtual void build_rig(Object *object);
virtual void build_shapekeys(Key *key);
virtual void build_armature(bArmature *armature);
virtual void build_armature_bones(ListBaseT<Bone> *bones);
virtual void build_armature_bone_collections(Span<BoneCollection *> collections);
virtual void build_camera(Camera *camera);
virtual void build_light(Light *lamp);
virtual void build_nodetree(bNodeTree *ntree);
virtual void build_nodetree_socket(bNodeSocket *socket);
virtual void build_material(Material *ma, ID *owner = nullptr);
virtual void build_materials(ID *owner, Material **materials, int num_materials);
virtual void build_freestyle_lineset(FreestyleLineSet *fls);
virtual void build_freestyle_linestyle(FreestyleLineStyle *linestyle);
virtual void build_texture(Tex *tex);
virtual void build_image(Image *image);
virtual void build_cachefile(CacheFile *cache_file);
virtual void build_mask(Mask *mask);
virtual void build_movieclip(MovieClip *clip);
virtual void build_lightprobe(LightProbe *probe);
virtual void build_speaker(Speaker *speaker);
virtual void build_sound(bSound *sound);
virtual void build_scene_sequencer(Scene *scene);
virtual void build_scene_audio(Scene *scene);
virtual void build_scene_speakers(Scene *scene, ViewLayer *view_layer);
virtual void build_vfont(VFont *vfont);
virtual void build_nested_datablock(ID *owner, ID *id, bool flush_cow_changes);
virtual void build_nested_nodetree(ID *owner, bNodeTree *ntree);
virtual void build_nested_shapekey(ID *owner, Key *key);
void add_particle_collision_relations(const OperationKey &key,
Object *object,
Collection *collection,
const char *name);
void add_particle_forcefield_relations(const OperationKey &key,
Object *object,
ParticleSystem *psys,
EffectorWeights *eff,
bool add_absorption,
const char *name);
virtual void build_copy_on_write_relations();
virtual void build_copy_on_write_relations(IDNode *id_node);
virtual void build_driver_relations();
virtual void build_driver_relations(IDNode *id_node);
template<typename KeyType> OperationNode *find_operation_node(const KeyType &key);
Depsgraph *getGraph();
virtual Set<const ID *> get_built_ids() const;
protected:
TimeSourceNode *get_node(const TimeSourceKey &key) const;
ComponentNode *get_node(const ComponentKey &key) const;
OperationNode *get_node(const OperationKey &key) const;
Node *get_node(const RNAPathKey &key);
OperationNode *find_node(const OperationKey &key) const;
ComponentNode *find_node(const ComponentKey &key) const;
bool has_node(const ComponentKey &key) const;
bool has_node(const OperationKey &key) const;
Relation *add_time_relation(TimeSourceNode *timesrc,
Node *node_to,
const char *description,
int flags = 0);
/* Add relation which ensures visibility of `id_from` when `id_to` is visible.
* For the more detailed explanation see comment for `NodeType::VISIBILITY`. */
void add_visibility_relation(ID *id_from, ID *id_to);
Relation *add_operation_relation(OperationNode *node_from,
OperationNode *node_to,
const char *description,
int flags = 0);
template<typename KeyType>
DepsNodeHandle create_node_handle(const KeyType &key, const char *default_name = "");
/* TODO(sergey): All those is_same* functions are to be generalized. */
/* Check whether two keys corresponds to the same bone from same armature.
*
* This is used by drivers relations builder to avoid possible fake
* dependency cycle when one bone property drives another property of the
* same bone. */
template<typename KeyFrom, typename KeyTo>
bool is_same_bone_dependency(const KeyFrom &key_from, const KeyTo &key_to);
/* Similar to above, but used to check whether driver is using node from
* the same node tree as a driver variable. */
template<typename KeyFrom, typename KeyTo>
bool is_same_nodetree_node_dependency(const KeyFrom &key_from, const KeyTo &key_to);
private:
struct BuilderWalkUserData {
DepsgraphRelationBuilder *builder;
};
static void modifier_walk(void *user_data,
struct Object *object,
struct ID **idpoin,
LibraryForeachIDCallbackFlag cb_flag);
static void constraint_walk(bConstraint *con, ID **idpoin, bool is_reference, void *user_data);
/* State which demotes currently built entities. */
Scene *scene_;
BuilderMap built_map_;
RNANodeQuery rna_node_query_;
BuilderStack stack_;
};
struct DepsNodeHandle {
DepsNodeHandle(DepsgraphRelationBuilder *builder,
OperationNode *node,
const char *default_name = "")
: builder(builder), node(node), default_name(default_name)
{
BLI_assert(node != nullptr);
}
DepsgraphRelationBuilder *builder;
OperationNode *node;
const char *default_name;
};
} // namespace deg
} // namespace blender
#include "intern/builder/deg_builder_relations_impl.h" // IWYU pragma: export

View File

@@ -0,0 +1,271 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Methods for constructing depsgraph relations for drivers.
*/
#include "intern/builder/deg_builder_relations_drivers.h"
#include <cstring>
#include <deque>
#include "BLI_listbase.h"
#include "DNA_anim_types.h"
#include "RNA_access.hh"
#include "RNA_path.hh"
#include "BKE_anim_data.hh"
#include "intern/builder/deg_builder_relations.h"
#include "intern/depsgraph_relation.hh"
#include "intern/node/deg_node.hh"
namespace blender::deg {
DriverDescriptor::DriverDescriptor(PointerRNA *id_ptr, FCurve *fcu)
: id_ptr_(id_ptr),
fcu_(fcu),
driver_relations_needed_(false),
pointer_rna_(),
property_rna_(nullptr),
is_array_(false)
{
driver_relations_needed_ = determine_relations_needed();
split_rna_path();
}
bool DriverDescriptor::determine_relations_needed()
{
if (fcu_->array_index > 0) {
/* Drivers on array elements always need relations. */
is_array_ = true;
return true;
}
if (!resolve_rna()) {
/* Properties that don't exist can't cause threading issues either. */
return false;
}
if (RNA_property_array_check(property_rna_)) {
/* Drivers on array elements always need relations. */
is_array_ = true;
return true;
}
/* Drivers on Booleans and Enums (when used as bit-flags) can write to the same memory location,
* so they need relations between each other. */
return ELEM(RNA_property_type(property_rna_), PROP_BOOLEAN, PROP_ENUM);
}
bool DriverDescriptor::driver_relations_needed() const
{
return driver_relations_needed_;
}
bool DriverDescriptor::is_array() const
{
return is_array_;
}
bool DriverDescriptor::is_same_array_as(const DriverDescriptor &other) const
{
if (!is_array_ || !other.is_array_) {
return false;
}
return rna_suffix == other.rna_suffix;
}
OperationKey DriverDescriptor::depsgraph_key() const
{
return OperationKey(id_ptr_->owner_id,
NodeType::PARAMETERS,
OperationCode::DRIVER,
fcu_->rna_path,
fcu_->array_index);
}
void DriverDescriptor::split_rna_path()
{
const char *last_dot = strrchr(fcu_->rna_path, '.');
if (last_dot == nullptr || last_dot[1] == '\0') {
rna_prefix = StringRef();
rna_suffix = StringRef(fcu_->rna_path);
return;
}
rna_prefix = StringRef(fcu_->rna_path, last_dot);
rna_suffix = StringRef(last_dot + 1);
}
bool DriverDescriptor::resolve_rna()
{
return RNA_path_resolve_property(id_ptr_, fcu_->rna_path, &pointer_rna_, &property_rna_);
}
static bool is_reachable(const Node *const from, const Node *const to)
{
if (from == to) {
return true;
}
/* Perform a graph walk from 'to' towards its incoming connections.
* Walking from 'from' towards its outgoing connections is 10x slower on the Spring rig. */
std::deque<const Node *> queue;
Set<const Node *> seen;
queue.push_back(to);
while (!queue.empty()) {
/* Visit the next node to inspect. */
const Node *visit = queue.back();
queue.pop_back();
if (visit == from) {
return true;
}
/* Queue all incoming relations that we haven't seen before. */
for (Relation *relation : visit->inlinks) {
const Node *prev_node = relation->from;
if (seen.add(prev_node)) {
queue.push_back(prev_node);
}
}
}
return false;
}
/* **** DepsgraphRelationBuilder functions **** */
void DepsgraphRelationBuilder::build_driver_relations()
{
for (IDNode *id_node : graph_->id_nodes) {
build_driver_relations(id_node);
}
}
void DepsgraphRelationBuilder::build_driver_relations(IDNode *id_node)
{
/* Add relations between drivers that write to the same datablock.
*
* This prevents threading issues when two separate RNA properties write to
* the same memory address. For example:
* - Drivers on individual array elements, as the animation system will write
* the whole array back to RNA even when changing individual array value.
* - Drivers on RNA properties that map to a single bit flag. Changing the RNA
* value will write the entire int containing the bit, in a non-thread-safe
* way.
*/
ID *id_orig = id_node->id_orig;
AnimData *adt = BKE_animdata_from_id(id_orig);
if (adt == nullptr) {
return;
}
/* Mapping from RNA prefix -> set of driver descriptors: */
Map<std::string, Vector<DriverDescriptor>> driver_groups;
PointerRNA id_ptr = RNA_id_pointer_create(id_orig);
for (FCurve &fcu : adt->drivers) {
if (fcu.rna_path == nullptr) {
continue;
}
DriverDescriptor driver_desc(&id_ptr, &fcu);
if (!driver_desc.driver_relations_needed()) {
continue;
}
driver_groups.lookup_or_add_default_as(driver_desc.rna_prefix).append(driver_desc);
}
for (Span<DriverDescriptor> prefix_group : driver_groups.values()) {
/* For each node in the driver group, try to connect it to another node
* in the same group without creating any cycles. */
int num_drivers = prefix_group.size();
if (num_drivers < 2) {
/* A relation requires two drivers. */
continue;
}
for (int from_index = 0; from_index < num_drivers; ++from_index) {
const DriverDescriptor &driver_from = prefix_group[from_index];
Node *op_from = get_node(driver_from.depsgraph_key());
/* Start by trying the next node in the group. */
for (int to_offset = 1; to_offset < num_drivers; ++to_offset) {
const int to_index = (from_index + to_offset) % num_drivers;
const DriverDescriptor &driver_to = prefix_group[to_index];
Node *op_to = get_node(driver_to.depsgraph_key());
/* Duplicate drivers can exist (see #78615), but cannot be distinguished by OperationKey
* and thus have the same depsgraph node. Relations between those drivers should not be
* created. This not something that is expected to happen (both the UI and the Python API
* prevent duplicate drivers), it did happen in a file and it is easy to deal with here. */
if (op_from == op_to) {
continue;
}
if (from_index < to_index && driver_from.is_same_array_as(driver_to)) {
/* This is for adding a relation like `color[0]` -> `color[1]`.
* When the search for another driver wraps around,
* we cannot blindly add relations any more. */
}
else {
/* Investigate whether this relation would create a dependency cycle.
* Example graph:
* A -> B -> C
* and investigating a potential connection C->A. Because A->C is an
* existing transitive connection, adding C->A would create a cycle. */
if (is_reachable(op_to, op_from)) {
continue;
}
/* No need to directly connect this node if there is already a transitive connection. */
if (is_reachable(op_from, op_to)) {
break;
}
}
add_operation_relation(
op_from->get_exit_operation(), op_to->get_entry_operation(), "Driver Serialization");
break;
}
}
}
}
bool data_path_maybe_shared(const ID &id, const StringRef data_path)
{
/* As it is hard to generally detect implicit sharing, this is implemented as
* a 'known to not share' list. */
/* Allow concurrent writes to custom properties. #140706 shows that this
* shouldn't be a problem in practice. */
if (data_path.startswith("[\"") && data_path.endswith("\"]")) {
return false;
}
if (GS(id.name) == ID_OB) {
const Object &ob = *reinterpret_cast<const Object *>(&id);
const bool is_thread_safe = (ob.type == OB_ARMATURE && data_path.startswith("pose.bones["));
return !is_thread_safe;
}
/* Allow concurrent writes to shape-key values. #140706 shows that this
* shouldn't be a problem in practice. */
if (GS(id.name) == ID_KE) {
const bool is_thread_safe = data_path.startswith("key_blocks[") &&
data_path.endswith("].value");
return !is_thread_safe;
}
return true;
}
} // namespace blender::deg

View File

@@ -0,0 +1,76 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "BLI_string_ref.hh"
#include "RNA_types.hh"
#include "intern/builder/deg_builder_relations.h"
namespace blender {
struct FCurve;
namespace deg {
/* Helper class for determining which relations are needed between driver evaluation nodes. */
class DriverDescriptor {
public:
/**
* Drivers are grouped by their RNA prefix. The prefix is the part of the RNA
* path up to the last dot, the suffix is the remainder of the RNA path:
*
* \code{.unparsed}
* fcu->rna_path rna_prefix rna_suffix
* ------------------------------- ---------------------- ----------
* 'color' '' 'color'
* 'rigidbody_world.time_scale' 'rigidbody_world' 'time_scale'
* 'pose.bones["master"].location' 'pose.bones["master"]' 'location'
* \endcode
*/
StringRef rna_prefix;
StringRef rna_suffix;
DriverDescriptor(PointerRNA *id_ptr, FCurve *fcu);
bool driver_relations_needed() const;
bool is_array() const;
/** Assumes that 'other' comes from the same RNA group, that is, has the same RNA path prefix. */
bool is_same_array_as(const DriverDescriptor &other) const;
OperationKey depsgraph_key() const;
private:
PointerRNA *id_ptr_;
FCurve *fcu_;
bool driver_relations_needed_;
PointerRNA pointer_rna_;
PropertyRNA *property_rna_;
bool is_array_;
bool determine_relations_needed();
void split_rna_path();
bool resolve_rna();
};
/**
* Returns whether the data at the given path may be implicitly shared (also see
* #ImplicitSharingInfo). If it is shared, writing to it through RNA will make a
* local copy that can be edited without affecting the other users.
*
* If multi-threaded writing to the path is required, one should trigger making
* the mutable copy before multi-threaded writing starts. Otherwise there is a
* race condition where each thread tries to make its own copy. The "unsharing"
* can be triggered by doing a dummy-write to it.
*/
bool data_path_maybe_shared(const ID &id, StringRef data_path);
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,214 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "intern/builder/deg_builder_relations.h"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_time.hh"
#include <iostream>
#include "DNA_ID.h"
#include "DNA_rigidbody_types.h"
namespace blender::deg {
template<typename KeyType>
OperationNode *DepsgraphRelationBuilder::find_operation_node(const KeyType &key)
{
Node *node = get_node(key);
return node != nullptr ? node->get_exit_operation() : nullptr;
}
template<typename KeyFrom, typename KeyTo>
Relation *DepsgraphRelationBuilder::add_relation(const KeyFrom &key_from,
const KeyTo &key_to,
const char *description,
int flags)
{
Node *node_from = get_node(key_from);
Node *node_to = get_node(key_to);
OperationNode *op_from = node_from ? node_from->get_exit_operation() : nullptr;
OperationNode *op_to = node_to ? node_to->get_entry_operation() : nullptr;
if (op_from && op_to) {
return add_operation_relation(op_from, op_to, description, flags);
}
/* TODO(sergey): Report error in the interface. */
std::cerr << "--------------------------------------------------------------------\n";
std::cerr << "Failed to add relation \"" << description << "\"\n";
if (!op_from) {
std::cerr << "Could not find op_from: " << key_from.identifier() << "\n";
}
if (!op_to) {
std::cerr << "Could not find op_to: " << key_to.identifier() << "\n";
}
if (!stack_.is_empty()) {
std::cerr << "\nTrace:\n\n";
stack_.print_backtrace(std::cerr);
std::cerr << "\n";
}
return nullptr;
}
template<typename KeyTo>
Relation *DepsgraphRelationBuilder::add_relation(const TimeSourceKey &key_from,
const KeyTo &key_to,
const char *description,
int flags)
{
TimeSourceNode *time_from = get_node(key_from);
Node *node_to = get_node(key_to);
OperationNode *op_to = node_to ? node_to->get_entry_operation() : nullptr;
if (time_from != nullptr && op_to != nullptr) {
return add_time_relation(time_from, op_to, description, flags);
}
return nullptr;
}
template<typename KeyType>
requires(!std::is_same_v<KeyType, TimeSourceKey>)
Relation *DepsgraphRelationBuilder::add_node_handle_relation(const KeyType &key_from,
const DepsNodeHandle *handle,
const char *description,
int flags)
{
Node *node_from = get_node(key_from);
OperationNode *op_from = node_from ? node_from->get_exit_operation() : nullptr;
OperationNode *op_to = handle->node->get_entry_operation();
if (op_from != nullptr && op_to != nullptr) {
return add_operation_relation(op_from, op_to, description, flags);
}
if (!op_from) {
fprintf(stderr,
"add_node_handle_relation(%s) - Could not find op_from (%s)\n",
description,
key_from.identifier().c_str());
}
if (!op_to) {
fprintf(stderr,
"add_node_handle_relation(%s) - Could not find op_to (%s)\n",
description,
key_from.identifier().c_str());
}
return nullptr;
}
static inline bool rigidbody_object_depends_on_evaluated_geometry(const RigidBodyOb *rbo)
{
if (rbo == nullptr) {
return false;
}
if (ELEM(rbo->shape, RB_SHAPE_CONVEXH, RB_SHAPE_TRIMESH)) {
if (rbo->mesh_source != RBO_MESH_BASE) {
return true;
}
}
return false;
}
template<typename KeyTo>
Relation *DepsgraphRelationBuilder::add_depends_on_transform_relation(ID *id,
const KeyTo &key_to,
const char *description,
int flags)
{
if (GS(id->name) == ID_OB) {
Object *object = reinterpret_cast<Object *>(id);
if (rigidbody_object_depends_on_evaluated_geometry(object->rigidbody_object)) {
OperationKey transform_key(&object->id, NodeType::TRANSFORM, OperationCode::TRANSFORM_EVAL);
return add_relation(transform_key, key_to, description, flags);
}
}
ComponentKey transform_key(id, NodeType::TRANSFORM);
return add_relation(transform_key, key_to, description, flags);
}
template<typename KeyType>
DepsNodeHandle DepsgraphRelationBuilder::create_node_handle(const KeyType &key,
const char *default_name)
{
return DepsNodeHandle(this, get_node(key), default_name);
}
/* Rig compatibility: we check if bone is using local transform as a variable
* for driver on itself and ignore those relations to avoid "false-positive"
* dependency cycles.
*/
template<typename KeyFrom, typename KeyTo>
bool DepsgraphRelationBuilder::is_same_bone_dependency(const KeyFrom &key_from,
const KeyTo &key_to)
{
/* Get operations for requested keys. */
Node *node_from = get_node(key_from);
Node *node_to = get_node(key_to);
if (node_from == nullptr || node_to == nullptr) {
return false;
}
OperationNode *op_from = node_from->get_exit_operation();
OperationNode *op_to = node_to->get_entry_operation();
if (op_from == nullptr || op_to == nullptr) {
return false;
}
/* Different armatures, bone can't be the same. */
if (op_from->owner->owner != op_to->owner->owner) {
return false;
}
/* We are only interested in relations like BONE_DONE -> BONE_LOCAL... */
if (!(op_from->opcode == OperationCode::BONE_DONE && op_to->opcode == OperationCode::BONE_LOCAL))
{
return false;
}
/* ... BUT, we also need to check if it's same bone. */
if (op_from->owner->name != op_to->owner->name) {
return false;
}
return true;
}
template<typename KeyFrom, typename KeyTo>
bool DepsgraphRelationBuilder::is_same_nodetree_node_dependency(const KeyFrom &key_from,
const KeyTo &key_to)
{
/* Get operations for requested keys. */
Node *node_from = get_node(key_from);
Node *node_to = get_node(key_to);
if (node_from == nullptr || node_to == nullptr) {
return false;
}
OperationNode *op_from = node_from->get_exit_operation();
OperationNode *op_to = node_to->get_entry_operation();
if (op_from == nullptr || op_to == nullptr) {
return false;
}
/* Check if this is actually a node tree. */
if (GS(op_from->owner->owner->id_orig->name) != ID_NT) {
return false;
}
/* Different node trees. */
if (op_from->owner->owner != op_to->owner->owner) {
return false;
}
/* We are only interested in relations like BONE_DONE -> BONE_LOCAL... */
if (!(op_from->opcode == OperationCode::PARAMETERS_EVAL &&
op_to->opcode == OperationCode::PARAMETERS_EVAL))
{
return false;
}
return true;
}
} // namespace blender::deg

View File

@@ -0,0 +1,481 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Methods for constructing depsgraph
*/
#include "DEG_depsgraph_debug.hh"
#include "intern/builder/deg_builder_relations.h"
#include <cstdlib>
#include <cstring> /* required for STREQ later on. */
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "DNA_action_types.h"
#include "DNA_armature_types.h"
#include "DNA_constraint_types.h"
#include "DNA_customdata_types.h"
#include "DNA_object_types.h"
#include "BKE_action.hh"
#include "BKE_armature.hh"
#include "BKE_constraint.h"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "intern/builder/deg_builder.h"
#include "intern/builder/deg_builder_cache.h"
#include "intern/builder/deg_builder_pchanmap.h"
#include "intern/debug/deg_debug.h"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_operation.hh"
#include "intern/depsgraph_relation.hh"
#include "intern/depsgraph_type.hh"
namespace blender::deg {
/* IK Solver Eval Steps */
void DepsgraphRelationBuilder::build_ik_pose(Object *object,
bPoseChannel *pchan,
bConstraint *con,
RootPChanMap *root_map)
{
if ((con->flag & CONSTRAINT_DISABLE) != 0) {
/* Do not add disabled IK constraints to the relations. If these needs to be temporarily
* enabled, they will be added as temporary constraints during transform. */
return;
}
bKinematicConstraint *data = static_cast<bKinematicConstraint *>(con->data);
/* Attach owner to IK Solver to. */
bPoseChannel *rootchan = BKE_armature_ik_solver_find_root(pchan, data);
if (rootchan == nullptr) {
return;
}
OperationKey pchan_local_key(
&object->id, NodeType::BONE, pchan->name, OperationCode::BONE_LOCAL);
OperationKey init_ik_key(&object->id, NodeType::EVAL_POSE, OperationCode::POSE_INIT_IK);
OperationKey solver_key(
&object->id, NodeType::EVAL_POSE, rootchan->name, OperationCode::POSE_IK_SOLVER);
OperationKey pose_cleanup_key(&object->id, NodeType::EVAL_POSE, OperationCode::POSE_CLEANUP);
/* If any of the constraint parameters are animated, connect the relation. Since there is only
* one Init IK node per armature, this link has quite high risk of spurious dependency cycles.
*/
const bool is_itasc = (object->pose->iksolver == IKSOLVER_ITASC);
PointerRNA con_ptr = RNA_pointer_create_discrete(&object->id, RNA_Constraint, con);
if (is_itasc || cache_->isAnyPropertyAnimated(&con_ptr)) {
add_relation(pchan_local_key, init_ik_key, "IK Constraint -> Init IK Tree");
}
add_relation(init_ik_key, solver_key, "Init IK -> IK Solver");
/* Never cleanup before solver is run. */
add_relation(solver_key, pose_cleanup_key, "IK Solver -> Cleanup", RELATION_FLAG_GODMODE);
/* The ITASC solver currently accesses the target transforms in init tree :(
* TODO: Fix ITASC and remove this.
*/
OperationKey target_dependent_key = is_itasc ? init_ik_key : solver_key;
/* IK target */
/* TODO(sergey): This should get handled as part of the constraint code. */
if (data->tar != nullptr) {
/* Different object - requires its transform. */
if (data->tar != object) {
ComponentKey target_key(&data->tar->id, NodeType::TRANSFORM);
add_relation(target_key, target_dependent_key, con->name);
/* Ensure target evaluated copy is ready by the time IK tree is built just in case. */
ComponentKey target_cow_key(&data->tar->id, NodeType::COPY_ON_EVAL);
add_relation(target_cow_key,
init_ik_key,
"IK Target Copy-on-Eval -> Init IK Tree",
RELATION_CHECK_BEFORE_ADD);
}
/* Subtarget references: */
if ((data->tar->type == OB_ARMATURE) && (data->subtarget[0])) {
/* Bone - use the final transformation. */
OperationKey target_key(
&data->tar->id, NodeType::BONE, data->subtarget, OperationCode::BONE_DONE);
add_relation(target_key, target_dependent_key, con->name);
}
else if (data->subtarget[0] && ELEM(data->tar->type, OB_MESH, OB_LATTICE)) {
/* Vertex group target. */
/* NOTE: for now, we don't need to represent vertex groups
* separately. */
ComponentKey target_key(&data->tar->id, NodeType::GEOMETRY);
add_relation(target_key, target_dependent_key, con->name);
add_customdata_mask(data->tar, DEGCustomDataMeshMasks::MaskVert(CD_MASK_MDEFORMVERT));
}
if (data->tar == object && data->subtarget[0]) {
/* Prevent target's constraints from linking to anything from same
* chain that it controls. */
root_map->add_bone(data->subtarget, rootchan->name);
}
}
/* Pole Target. */
/* TODO(sergey): This should get handled as part of the constraint code. */
if (data->poletar != nullptr) {
/* Different object - requires its transform. */
if (data->poletar != object) {
ComponentKey target_key(&data->poletar->id, NodeType::TRANSFORM);
add_relation(target_key, target_dependent_key, con->name);
/* Ensure target evaluated copy is ready by the time IK tree is built just in case. */
ComponentKey target_cow_key(&data->poletar->id, NodeType::COPY_ON_EVAL);
add_relation(target_cow_key,
init_ik_key,
"IK Target Copy-on-Eval -> Init IK Tree",
RELATION_CHECK_BEFORE_ADD);
}
/* Subtarget references: */
if ((data->poletar->type == OB_ARMATURE) && (data->polesubtarget[0])) {
/* Bone - use the final transformation. */
OperationKey target_key(
&data->poletar->id, NodeType::BONE, data->polesubtarget, OperationCode::BONE_DONE);
add_relation(target_key, target_dependent_key, con->name);
}
else if (data->polesubtarget[0] && ELEM(data->poletar->type, OB_MESH, OB_LATTICE)) {
/* Vertex group target. */
/* NOTE: for now, we don't need to represent vertex groups
* separately. */
ComponentKey target_key(&data->poletar->id, NodeType::GEOMETRY);
add_relation(target_key, target_dependent_key, con->name);
add_customdata_mask(data->poletar, DEGCustomDataMeshMasks::MaskVert(CD_MASK_MDEFORMVERT));
}
}
DEG_DEBUG_PRINTF((blender::Depsgraph *)graph_,
BUILD,
"\nStarting IK Build: pchan = %s, target = (%s, %s), "
"segcount = %d\n",
pchan->name,
data->tar ? data->tar->id.name : "nullptr",
data->subtarget,
data->rootbone);
bPoseChannel *parchan = pchan;
/* Exclude tip from chain if needed. */
if (!(data->flag & CONSTRAINT_IK_TIP)) {
parchan = pchan->parent;
}
root_map->add_bone(parchan->name, rootchan->name);
OperationKey parchan_transforms_key(
&object->id, NodeType::BONE, parchan->name, OperationCode::BONE_READY);
add_relation(parchan_transforms_key, solver_key, "IK Solver Owner");
/* Walk to the chain's root. */
int segcount = 0;
while (parchan != nullptr) {
/* Make IK-solver dependent on this bone's result, since it can only run
* after the standard results of the bone are know. Validate links step
* on the bone will ensure that users of this bone only grab the result
* with IK solver results. */
if (parchan != pchan) {
OperationKey parent_key(
&object->id, NodeType::BONE, parchan->name, OperationCode::BONE_READY);
add_relation(parent_key, solver_key, "IK Chain Parent");
OperationKey bone_done_key(
&object->id, NodeType::BONE, parchan->name, OperationCode::BONE_DONE);
add_relation(solver_key, bone_done_key, "IK Chain Result");
}
else {
OperationKey final_transforms_key(
&object->id, NodeType::BONE, parchan->name, OperationCode::BONE_DONE);
add_relation(solver_key, final_transforms_key, "IK Solver Result");
}
parchan->flag |= POSE_DONE;
root_map->add_bone(parchan->name, rootchan->name);
/* continue up chain, until we reach target number of items. */
DEG_DEBUG_PRINTF((blender::Depsgraph *)graph_, BUILD, " %d = %s\n", segcount, parchan->name);
/* TODO(sergey): This is an arbitrary value, which was just following
* old code convention. */
segcount++;
if ((segcount == data->rootbone) || (segcount > 255)) {
break;
}
parchan = parchan->parent;
}
OperationKey pose_done_key(&object->id, NodeType::EVAL_POSE, OperationCode::POSE_DONE);
add_relation(solver_key, pose_done_key, "PoseEval Result-Bone Link");
/* Add relation when the root of this IK chain is influenced by another IK chain. */
build_inter_ik_chains(object, solver_key, rootchan, root_map);
}
/* Spline IK Eval Steps */
void DepsgraphRelationBuilder::build_splineik_pose(Object *object,
bPoseChannel *pchan,
bConstraint *con,
RootPChanMap *root_map)
{
bSplineIKConstraint *data = static_cast<bSplineIKConstraint *>(con->data);
bPoseChannel *rootchan = BKE_armature_splineik_solver_find_root(pchan, data);
OperationKey transforms_key(&object->id, NodeType::BONE, pchan->name, OperationCode::BONE_READY);
OperationKey init_ik_key(&object->id, NodeType::EVAL_POSE, OperationCode::POSE_INIT_IK);
OperationKey solver_key(
&object->id, NodeType::EVAL_POSE, rootchan->name, OperationCode::POSE_SPLINE_IK_SOLVER);
OperationKey pose_cleanup_key(&object->id, NodeType::EVAL_POSE, OperationCode::POSE_CLEANUP);
/* Solver depends on initialization. */
add_relation(init_ik_key, solver_key, "Init IK -> IK Solver");
/* Never cleanup before solver is run. */
add_relation(solver_key, pose_cleanup_key, "IK Solver -> Cleanup");
/* Attach owner to IK Solver. */
add_relation(transforms_key, solver_key, "Spline IK Solver Owner", RELATION_FLAG_GODMODE);
/* Attach path dependency to solver. */
if (data->tar != nullptr) {
ComponentKey target_geometry_key(&data->tar->id, NodeType::GEOMETRY);
add_relation(target_geometry_key, solver_key, "Curve.Path -> Spline IK");
ComponentKey target_transform_key(&data->tar->id, NodeType::TRANSFORM);
add_relation(target_transform_key, solver_key, "Curve.Transform -> Spline IK");
add_special_eval_flag(&data->tar->id, DAG_EVAL_NEED_CURVE_PATH);
}
pchan->flag |= POSE_DONE;
OperationKey final_transforms_key(
&object->id, NodeType::BONE, pchan->name, OperationCode::BONE_DONE);
add_relation(solver_key, final_transforms_key, "Spline IK Result");
root_map->add_bone(pchan->name, rootchan->name);
/* Walk to the chain's root/ */
int segcount = 1;
for (bPoseChannel *parchan = pchan->parent; parchan != nullptr && segcount < data->chainlen;
parchan = parchan->parent, segcount++)
{
/* Make Spline IK solver dependent on this bone's result, since it can
* only run after the standard results of the bone are know. Validate
* links step on the bone will ensure that users of this bone only grab
* the result with IK solver results. */
OperationKey parent_key(&object->id, NodeType::BONE, parchan->name, OperationCode::BONE_READY);
add_relation(parent_key, solver_key, "Spline IK Solver Update");
OperationKey bone_done_key(
&object->id, NodeType::BONE, parchan->name, OperationCode::BONE_DONE);
add_relation(solver_key, bone_done_key, "Spline IK Solver Result");
parchan->flag |= POSE_DONE;
root_map->add_bone(parchan->name, rootchan->name);
}
OperationKey pose_done_key(&object->id, NodeType::EVAL_POSE, OperationCode::POSE_DONE);
add_relation(solver_key, pose_done_key, "PoseEval Result-Bone Link");
/* Add relation when the root of this IK chain is influenced by another IK chain. */
build_inter_ik_chains(object, solver_key, rootchan, root_map);
}
void DepsgraphRelationBuilder::build_inter_ik_chains(Object *object,
const OperationKey &solver_key,
const bPoseChannel *rootchan,
const RootPChanMap *root_map)
{
bPoseChannel *deepest_root = nullptr;
const char *root_name = rootchan->name;
/* Find shared IK chain root. */
for (bPoseChannel *parchan = rootchan->parent; parchan; parchan = parchan->parent) {
if (!root_map->has_common_root(root_name, parchan->name)) {
break;
}
deepest_root = parchan;
}
if (deepest_root == nullptr) {
return;
}
OperationKey other_bone_key(
&object->id, NodeType::BONE, deepest_root->name, OperationCode::BONE_DONE);
add_relation(other_bone_key, solver_key, "IK Chain Overlap");
}
/* Pose/Armature Bones Graph */
void DepsgraphRelationBuilder::build_rig(Object *object)
{
/* Armature-Data */
bArmature *armature = id_cast<bArmature *>(object->data);
/* TODO: selection status? */
/* Attach links between pose operations. */
ComponentKey local_transform(&object->id, NodeType::TRANSFORM);
OperationKey pose_init_key(&object->id, NodeType::EVAL_POSE, OperationCode::POSE_INIT);
OperationKey pose_init_ik_key(&object->id, NodeType::EVAL_POSE, OperationCode::POSE_INIT_IK);
OperationKey pose_cleanup_key(&object->id, NodeType::EVAL_POSE, OperationCode::POSE_CLEANUP);
OperationKey pose_done_key(&object->id, NodeType::EVAL_POSE, OperationCode::POSE_DONE);
add_relation(local_transform, pose_init_key, "Local Transform -> Pose Init");
add_relation(pose_init_key, pose_init_ik_key, "Pose Init -> Pose Init IK");
add_relation(pose_init_ik_key, pose_done_key, "Pose Init IK -> Pose Cleanup");
/* Make sure pose is up-to-date with armature updates. */
build_armature(armature);
OperationKey armature_key(&armature->id, NodeType::ARMATURE, OperationCode::ARMATURE_EVAL);
add_relation(armature_key, pose_init_key, "Data dependency");
/* Run cleanup even when there are no bones. */
add_relation(pose_init_ik_key, pose_cleanup_key, "Init -> Cleanup");
/* Relation to the instance, so that instancer can use pose of this object. */
add_relation(ComponentKey(&object->id, NodeType::EVAL_POSE),
OperationKey{&object->id, NodeType::INSTANCING, OperationCode::INSTANCE},
"Transform -> Instance");
/* IK Solvers.
*
* - These require separate processing steps are pose-level to be executed
* between chains of bones (i.e. once the base transforms of a bunch of
* bones is done).
*
* - We build relations for these before the dependencies between operations
* in the same component as it is necessary to check whether such bones
* are in the same IK chain (or else we get weird issues with either
* in-chain references, or with bones being parented to IK'd bones).
*
* Unsolved Issues:
* - Care is needed to ensure that multi-headed trees work out the same as
* in ik-tree building
* - Animated chain-lengths are a problem. */
RootPChanMap root_map;
bool pose_depends_on_local_transform = false;
for (bPoseChannel &pchan : object->pose->chanbase) {
const BuilderStack::ScopedEntry stack_entry = stack_.trace(pchan);
for (bConstraint &con : pchan.constraints) {
const BuilderStack::ScopedEntry stack_entry = stack_.trace(con);
switch (con.type) {
case CONSTRAINT_TYPE_KINEMATIC:
build_ik_pose(object, &pchan, &con, &root_map);
pose_depends_on_local_transform = true;
break;
case CONSTRAINT_TYPE_SPLINEIK:
build_splineik_pose(object, &pchan, &con, &root_map);
pose_depends_on_local_transform = true;
break;
/* Constraints which needs world's matrix for transform.
* TODO(sergey): More constraints here? */
case CONSTRAINT_TYPE_ROTLIKE:
case CONSTRAINT_TYPE_SIZELIKE:
case CONSTRAINT_TYPE_LOCLIKE:
case CONSTRAINT_TYPE_TRANSLIKE:
/* TODO(sergey): Add used space check. */
pose_depends_on_local_transform = true;
break;
default:
break;
}
}
}
// root_map.print_debug();
if (pose_depends_on_local_transform) {
/* TODO(sergey): Once partial updates are possible use relation between
* object transform and solver itself in its build function. */
ComponentKey pose_key(&object->id, NodeType::EVAL_POSE);
ComponentKey local_transform_key(&object->id, NodeType::TRANSFORM);
add_relation(local_transform_key, pose_key, "Local Transforms");
}
/* Links between operations for each bone. */
for (bPoseChannel &pchan : object->pose->chanbase) {
const BuilderStack::ScopedEntry stack_entry = stack_.trace(pchan);
build_idproperties(pchan.prop);
build_idproperties(pchan.system_properties);
OperationKey bone_local_key(
&object->id, NodeType::BONE, pchan.name, OperationCode::BONE_LOCAL);
OperationKey bone_pose_key(
&object->id, NodeType::BONE, pchan.name, OperationCode::BONE_POSE_PARENT);
OperationKey bone_ready_key(
&object->id, NodeType::BONE, pchan.name, OperationCode::BONE_READY);
OperationKey bone_done_key(&object->id, NodeType::BONE, pchan.name, OperationCode::BONE_DONE);
pchan.flag &= ~POSE_DONE;
/* Pose init to bone local. */
add_relation(pose_init_key, bone_local_key, "Pose Init - Bone Local", RELATION_FLAG_GODMODE);
/* Bone visibility (BONE_VISIBILITY) has no relationships. The node is a no-op, and it's just
* there to ensure the pose data itself is there. It does have the implicit dependency on the
* COPY_ON_EVAL node. */
/* Local to pose parenting operation. */
add_relation(bone_local_key, bone_pose_key, "Bone Local - Bone Pose");
/* Parent relation. */
if (pchan.parent != nullptr) {
OperationCode parent_key_opcode;
/* NOTE: this difference in handling allows us to prevent lockups
* while ensuring correct poses for separate chains. */
if (root_map.has_common_root(pchan.name, pchan.parent->name)) {
parent_key_opcode = OperationCode::BONE_READY;
}
else {
parent_key_opcode = OperationCode::BONE_DONE;
}
OperationKey parent_key(&object->id, NodeType::BONE, pchan.parent->name, parent_key_opcode);
add_relation(parent_key, bone_pose_key, "Parent Bone -> Child Bone");
}
/* Build constraints. */
if (pchan.constraints.first != nullptr) {
/* Build relations for indirectly linked objects. */
BuilderWalkUserData data;
data.builder = this;
BKE_constraints_id_loop(&pchan.constraints, constraint_walk, IDWALK_NOP, &data);
/* Constraints stack and constraint dependencies. */
build_constraints(&object->id, NodeType::BONE, pchan.name, &pchan.constraints, &root_map);
/* Pose -> constraints. */
OperationKey constraints_key(
&object->id, NodeType::BONE, pchan.name, OperationCode::BONE_CONSTRAINTS);
add_relation(bone_pose_key, constraints_key, "Pose -> Constraints Stack");
add_relation(bone_local_key, constraints_key, "Local -> Constraints Stack");
/* Constraints -> ready/ */
/* TODO(sergey): When constraint stack is exploded, this step should
* occur before the first IK solver. */
add_relation(constraints_key, bone_ready_key, "Constraints -> Ready");
}
else {
/* Pose -> Ready */
add_relation(bone_pose_key, bone_ready_key, "Pose -> Ready");
}
/* Bone ready -> Bone done.
* NOTE: For bones without IK, this is all that's needed.
* For IK chains however, an additional rel is created from IK
* to done, with transitive reduction removing this one. */
add_relation(bone_ready_key, bone_done_key, "Ready -> Done");
/* B-Bone shape is the real final step after Done if present. */
if (check_pchan_has_bbone(object, &pchan)) {
OperationKey bone_segments_key(
&object->id, NodeType::BONE, pchan.name, OperationCode::BONE_SEGMENTS);
/* B-Bone shape depends on the final position of the bone. */
add_relation(bone_done_key, bone_segments_key, "Done -> B-Bone Segments");
/* B-Bone shape depends on final position of handle bones. */
bPoseChannel *prev, *next;
Bone *pchan_bone = pchan.bone_get(*object);
BKE_pchan_bbone_handles_get({&pchan, pchan_bone}, &prev, &next);
if (prev) {
OperationCode opcode = OperationCode::BONE_DONE;
/* Inheriting parent roll requires access to prev handle's B-Bone properties. */
if ((pchan_bone->bbone_flag & BBONE_ADD_PARENT_END_ROLL) != 0 &&
check_pchan_has_bbone_segments(object, prev))
{
opcode = OperationCode::BONE_SEGMENTS;
}
OperationKey prev_key(&object->id, NodeType::BONE, prev->name, opcode);
add_relation(prev_key, bone_segments_key, "Prev Handle -> B-Bone Segments");
}
if (next) {
OperationKey next_key(&object->id, NodeType::BONE, next->name, OperationCode::BONE_DONE);
add_relation(next_key, bone_segments_key, "Next Handle -> B-Bone Segments");
}
/* Pose requires the B-Bone shape. */
add_relation(
bone_segments_key, pose_done_key, "PoseEval Result-Bone Link", RELATION_FLAG_GODMODE);
add_relation(bone_segments_key, pose_cleanup_key, "Cleanup dependency");
}
else {
/* Assume that all bones must be done for the pose to be ready
* (for deformers). */
add_relation(bone_done_key, pose_done_key, "PoseEval Result-Bone Link");
/* Bones must be traversed before cleanup. */
add_relation(bone_done_key, pose_cleanup_key, "Done -> Cleanup");
add_relation(bone_ready_key, pose_cleanup_key, "Ready -> Cleanup");
}
/* Custom shape. */
if (pchan.custom != nullptr) {
build_object(pchan.custom);
add_visibility_relation(&pchan.custom->id, &armature->id);
}
}
}
} // namespace blender::deg

View File

@@ -0,0 +1,93 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/builder/deg_builder_relations.h"
#include "DNA_node_types.h"
#include "DNA_scene_types.h"
#include "BKE_compositor.hh"
#include "BLI_listbase.h"
namespace blender::deg {
void DepsgraphRelationBuilder::build_scene_render(Scene *scene, ViewLayer *view_layer)
{
scene_ = scene;
const bool build_compositor = (scene->r.scemode & R_DOCOMP);
const bool build_sequencer = (scene->r.scemode & R_DOSEQ);
build_scene_parameters(scene);
build_animdata(&scene->id);
build_scene_audio(scene);
if (build_compositor) {
build_scene_compositor(scene);
}
if (build_sequencer) {
build_scene_sequencer(scene);
build_scene_speakers(scene, view_layer);
}
build_scene_camera(scene);
}
void DepsgraphRelationBuilder::build_scene_camera(Scene *scene)
{
if (scene->camera != nullptr) {
build_object(scene->camera);
}
for (TimeMarker &marker : scene->markers) {
if (!ELEM(marker.camera, nullptr, scene->camera)) {
build_object(marker.camera);
}
}
}
void DepsgraphRelationBuilder::build_scene_parameters(Scene *scene)
{
if (built_map_.check_is_built_and_tag(scene, BuilderMap::TAG_PARAMETERS)) {
return;
}
/* TODO(sergey): Trace as a scene parameters. */
build_idproperties(scene->id.properties);
build_idproperties(scene->id.system_properties);
build_parameters(&scene->id);
OperationKey parameters_eval_key(
&scene->id, NodeType::PARAMETERS, OperationCode::PARAMETERS_EXIT);
ComponentKey scene_eval_key(&scene->id, NodeType::SCENE);
add_relation(parameters_eval_key, scene_eval_key, "Parameters -> Scene Eval");
for (TimeMarker &marker : scene->markers) {
build_idproperties(marker.prop);
}
}
void DepsgraphRelationBuilder::build_scene_compositor(Scene *scene)
{
if (built_map_.check_is_built_and_tag(scene, BuilderMap::TAG_SCENE_COMPOSITOR)) {
return;
}
if (scene->compositing_node_group == nullptr) {
return;
}
ComponentKey compositor_key(&scene->id, NodeType::COMPOSITOR);
const OperationKey node_output_key(
&scene->compositing_node_group->id, NodeType::NTREE_OUTPUT, OperationCode::NTREE_OUTPUT);
this->add_relation(node_output_key, compositor_key, "NTree Output -> Compositor");
/* TODO(sergey): Trace as a scene compositor. */
build_nodetree(scene->compositing_node_group);
DepsNodeHandle handle = this->create_node_handle(node_output_key);
bke::compositor::add_depsgraph_relations(*scene,
reinterpret_cast<blender::DepsNodeHandle *>(&handle));
}
} // namespace blender::deg

View File

@@ -0,0 +1,167 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Methods for constructing depsgraph
*/
#include "intern/builder/deg_builder_relations.h"
#include <cstdlib>
#include <cstring> /* required for STREQ later on. */
#include "DNA_collection_types.h"
#include "DNA_scene_types.h"
#include "BLI_listbase.h"
#include "BKE_layer.hh"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "intern/builder/deg_builder.h"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_operation.hh"
namespace blender::deg {
bool DepsgraphRelationBuilder::build_layer_collection(LayerCollection *layer_collection)
{
const int hide_flag = (graph_->mode == DAG_EVAL_VIEWPORT) ? COLLECTION_HIDE_VIEWPORT :
COLLECTION_HIDE_RENDER;
Collection *collection = layer_collection->collection;
const bool is_collection_hidden = collection->flag & hide_flag;
const bool is_layer_collection_excluded = layer_collection->flag & LAYER_COLLECTION_EXCLUDE;
if (is_collection_hidden || is_layer_collection_excluded) {
return false;
}
build_collection(layer_collection, collection);
const ComponentKey collection_hierarchy_key{&collection->id, NodeType::HIERARCHY};
for (LayerCollection &child_layer_collection : layer_collection->layer_collections) {
if (build_layer_collection(&child_layer_collection)) {
Collection *child_collection = child_layer_collection.collection;
const ComponentKey child_collection_hierarchy_key{&child_collection->id,
NodeType::HIERARCHY};
add_relation(
collection_hierarchy_key, child_collection_hierarchy_key, "Collection hierarchy");
}
}
return true;
}
void DepsgraphRelationBuilder::build_view_layer_collections(ViewLayer *view_layer)
{
const ComponentKey scene_hierarchy_key{&scene_->id, NodeType::HIERARCHY};
for (LayerCollection &layer_collection : view_layer->layer_collections) {
if (build_layer_collection(&layer_collection)) {
Collection *collection = layer_collection.collection;
const ComponentKey collection_hierarchy_key{&collection->id, NodeType::HIERARCHY};
add_relation(scene_hierarchy_key, collection_hierarchy_key, "Scene -> Collection hierarchy");
}
}
}
void DepsgraphRelationBuilder::build_freestyle_lineset(FreestyleLineSet *fls)
{
if (fls->group != nullptr) {
build_collection(nullptr, fls->group);
}
if (fls->linestyle != nullptr) {
build_freestyle_linestyle(fls->linestyle);
}
}
void DepsgraphRelationBuilder::build_view_layer(Scene *scene,
ViewLayer *view_layer,
eDepsNode_LinkedState_Type linked_state)
{
/* Setup currently building context. */
scene_ = scene;
BKE_view_layer_synced_ensure(*bmain_, scene, view_layer);
/* Scene objects. */
/* NOTE: Nodes builder requires us to pass evaluated base because it's being
* passed to the evaluation functions. During relations builder we only
* do nullptr-pointer check of the base, so it's fine to pass original one. */
for (Base &base : *BKE_view_layer_object_bases_get(view_layer)) {
if (need_pull_base_into_graph(&base)) {
build_object_from_view_layer_base(base.object);
}
}
build_view_layer_collections(view_layer);
build_scene_camera(scene);
/* Rigidbody. */
if (scene->rigidbody_world != nullptr) {
build_rigidbody(scene);
}
/* Scene's animation and drivers. */
if (scene->adt != nullptr) {
build_animdata(&scene->id);
}
/* World. */
if (scene->world != nullptr) {
build_world(scene->world);
}
/* Cache file. */
for (CacheFile &cachefile : bmain_->cachefiles) {
build_cachefile(&cachefile);
}
/* Masks. */
for (Mask &mask : bmain_->masks) {
build_mask(&mask);
}
/* Movie clips. */
for (MovieClip &clip : bmain_->movieclips) {
build_movieclip(&clip);
}
/* Material override. */
if (view_layer->mat_override != nullptr) {
build_material(view_layer->mat_override);
}
/* World override */
if (view_layer->world_override != nullptr) {
build_world(view_layer->world_override);
}
/* Freestyle linesets. */
for (FreestyleLineSet &fls : view_layer->freestyle_config.linesets) {
build_freestyle_lineset(&fls);
}
/* Scene parameters, compositor and such. */
build_scene_compositor(scene);
build_scene_parameters(scene);
/* Make final scene evaluation dependent on view layer evaluation. */
OperationKey scene_view_layer_key(
&scene->id, NodeType::LAYER_COLLECTIONS, OperationCode::VIEW_LAYER_EVAL);
ComponentKey scene_eval_key(&scene->id, NodeType::SCENE);
add_relation(scene_view_layer_key, scene_eval_key, "View Layer -> Scene Eval");
/* Sequencer. */
if (linked_state == DEG_ID_LINKED_DIRECTLY) {
build_scene_audio(scene);
build_scene_sequencer(scene);
}
/* Build all set scenes. */
if (scene->set != nullptr) {
ViewLayer *set_view_layer = BKE_view_layer_default_render(scene->set);
build_view_layer(scene->set, set_view_layer, DEG_ID_LINKED_VIA_SET);
}
}
} // namespace blender::deg

View File

@@ -0,0 +1,100 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include <deque>
#include "intern/builder/deg_builder_remove_noop.h"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_operation.hh"
#include "intern/debug/deg_debug.h"
#include "intern/depsgraph.hh"
#include "intern/depsgraph_relation.hh"
#include "DEG_depsgraph_debug.hh"
namespace blender::deg {
static inline bool is_unused_noop(OperationNode *op_node)
{
if (op_node == nullptr) {
return false;
}
if (op_node->flag & OperationFlag::DEPSOP_FLAG_PINNED) {
return false;
}
return op_node->is_noop() && op_node->outlinks.is_empty();
}
static inline bool is_removable_relation(const Relation *relation)
{
if (relation->from->type != NodeType::OPERATION || relation->to->type != NodeType::OPERATION) {
return true;
}
const OperationNode *operation_from = static_cast<OperationNode *>(relation->from);
const OperationNode *operation_to = static_cast<OperationNode *>(relation->to);
/* If the relation connects two different IDs there is a high risk that the removal of the
* relation will make it so visibility flushing is not possible at runtime. This happens with
* relations like the DoF on camera of custom shape on bones: such relation do not lead to an
* actual depsgraph evaluation operation as they are handled on render engine level.
*
* The indirectly linked objects could have some of their components invisible as well, so
* also keep relations which connect different components of the same object so that visibility
* tracking happens correct in those cases as well. */
return operation_from->owner == operation_to->owner;
}
void deg_graph_remove_unused_noops(Depsgraph *graph)
{
std::deque<OperationNode *> queue;
for (OperationNode *node : graph->operations) {
if (is_unused_noop(node)) {
queue.push_back(node);
}
}
Vector<Relation *> relations_to_remove;
while (!queue.empty()) {
OperationNode *to_remove = queue.front();
queue.pop_front();
for (Relation *rel_in : to_remove->inlinks) {
if (!is_removable_relation(rel_in)) {
continue;
}
Node *dependency = rel_in->from;
relations_to_remove.append(rel_in);
/* Queue parent no-op node that has now become unused. */
OperationNode *operation = dependency->get_exit_operation();
if (is_unused_noop(operation)) {
queue.push_back(operation);
}
}
/* TODO(Sybren): Remove the node itself. */
}
/* Remove the relations. */
for (Relation *relation : relations_to_remove) {
relation->unlink();
}
DEG_DEBUG_PRINTF((blender::Depsgraph *)graph,
BUILD,
"Removed %d relations to no-op nodes\n",
int(relations_to_remove.size()));
}
} // namespace blender::deg

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
namespace blender::deg {
struct Depsgraph;
/* Remove all no-op nodes that have zero outgoing relations. */
void deg_graph_remove_unused_noops(Depsgraph *graph);
} // namespace blender::deg

View File

@@ -0,0 +1,416 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/builder/deg_builder_rna.h"
#include <cstring>
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "DNA_action_types.h"
#include "DNA_constraint_types.h"
#include "DNA_key_types.h"
#include "DNA_object_types.h"
#include "BKE_constraint.h"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "intern/builder/deg_builder.h"
#include "intern/depsgraph.hh"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_operation.hh"
namespace blender::deg {
/* ********************************* ID Data ******************************** */
class RNANodeQueryIDData {
public:
explicit RNANodeQueryIDData(const ID *id) : id_(id) {}
~RNANodeQueryIDData()
{
delete constraint_to_pchan_map_;
}
const bPoseChannel *get_pchan_for_constraint(const bConstraint *constraint)
{
ensure_constraint_to_pchan_map();
return constraint_to_pchan_map_->lookup_default(constraint, nullptr);
}
void ensure_constraint_to_pchan_map()
{
if (constraint_to_pchan_map_ != nullptr) {
return;
}
BLI_assert(GS(id_->name) == ID_OB);
const Object *object = reinterpret_cast<const Object *>(id_);
constraint_to_pchan_map_ = new Map<const bConstraint *, const bPoseChannel *>();
if (object->pose != nullptr) {
for (const bPoseChannel &pchan : object->pose->chanbase) {
for (const bConstraint &constraint : pchan.constraints) {
constraint_to_pchan_map_->add_new(&constraint, &pchan);
}
}
}
}
protected:
/* ID this data corresponds to. */
const ID *id_;
/* indexed by bConstraint*, returns pose channel which contains that
* constraint. */
Map<const bConstraint *, const bPoseChannel *> *constraint_to_pchan_map_ = nullptr;
};
/* ***************************** Node Identifier **************************** */
RNANodeIdentifier::RNANodeIdentifier()
: id(nullptr),
type(NodeType::UNDEFINED),
component_name(""),
operation_code(OperationCode::OPERATION),
operation_name(),
operation_name_tag(-1)
{
}
bool RNANodeIdentifier::is_valid() const
{
return id != nullptr && type != NodeType::UNDEFINED;
}
/* ********************************** Query ********************************* */
RNANodeQuery::RNANodeQuery(Depsgraph *depsgraph, DepsgraphBuilder *builder)
: depsgraph_(depsgraph), builder_(builder)
{
}
RNANodeQuery::~RNANodeQuery() = default;
Node *RNANodeQuery::find_node(const PointerRNA *ptr,
const PropertyRNA *prop,
RNAPointerSource source)
{
const RNANodeIdentifier node_identifier = construct_node_identifier(ptr, prop, source);
if (!node_identifier.is_valid()) {
return nullptr;
}
IDNode *id_node = depsgraph_->find_id_node(node_identifier.id);
if (id_node == nullptr) {
return nullptr;
}
ComponentNode *comp_node = id_node->find_component(node_identifier.type,
node_identifier.component_name);
if (comp_node == nullptr) {
return nullptr;
}
if (node_identifier.operation_code == OperationCode::OPERATION) {
return comp_node;
}
return comp_node->find_operation(node_identifier.operation_code,
node_identifier.operation_name,
node_identifier.operation_name_tag);
}
bool RNANodeQuery::contains(const char *prop_identifier, const char *rna_path_component)
{
const char *substr = strstr(prop_identifier, rna_path_component);
if (substr == nullptr) {
return false;
}
/* If `substr != prop_identifier`, it means that the sub-string is found further in
* `prop_identifier`, and that thus index -1 is a valid memory location. */
const bool start_ok = substr == prop_identifier || substr[-1] == '.';
if (!start_ok) {
return false;
}
const size_t component_len = strlen(rna_path_component);
const bool end_ok = ELEM(substr[component_len], '\0', '.', '[');
return end_ok;
}
RNANodeIdentifier RNANodeQuery::construct_node_identifier(const PointerRNA *ptr,
const PropertyRNA *prop,
RNAPointerSource source)
{
RNANodeIdentifier node_identifier;
if (ptr->type == nullptr) {
return node_identifier;
}
/* Set default values for returns. */
node_identifier.id = ptr->owner_id;
node_identifier.component_name = "";
node_identifier.operation_code = OperationCode::OPERATION;
node_identifier.operation_name = "";
node_identifier.operation_name_tag = -1;
/* Handling of commonly known scenarios. */
if (rna_prop_affects_parameters_node(ptr, prop)) {
/* Custom properties of bones are placed in their components to improve granularity. */
if (RNA_struct_is_a(ptr->type, RNA_PoseBone)) {
const bPoseChannel *pchan = static_cast<const bPoseChannel *>(ptr->data);
node_identifier.type = NodeType::BONE;
node_identifier.component_name = pchan->name;
}
else {
node_identifier.type = NodeType::PARAMETERS;
}
node_identifier.operation_code = OperationCode::ID_PROPERTY;
node_identifier.operation_name = RNA_property_identifier(prop);
return node_identifier;
}
if (ptr->type == RNA_PoseBone) {
const bPoseChannel *pchan = static_cast<const bPoseChannel *>(ptr->data);
/* Bone - generally, we just want the bone component. */
node_identifier.type = NodeType::BONE;
node_identifier.component_name = pchan->name;
/* However check property name for special handling. */
if (prop != nullptr) {
Object *object = reinterpret_cast<Object *>(node_identifier.id);
const char *prop_name = RNA_property_identifier(prop);
/* B-Bone properties should connect to the final operation. */
if (STRPREFIX(prop_name, "bbone_")) {
if (builder_->check_pchan_has_bbone_segments(object, pchan)) {
node_identifier.operation_code = OperationCode::BONE_SEGMENTS;
}
else {
node_identifier.operation_code = OperationCode::BONE_DONE;
}
}
/* Final transform properties go to the Done node for the exit. */
else if (STR_ELEM(prop_name, "head", "tail", "length") || STRPREFIX(prop_name, "matrix")) {
if (source == RNAPointerSource::EXIT) {
node_identifier.operation_code = OperationCode::BONE_DONE;
}
}
/* Bone visibility has its own depsgraph node. */
else if (STREQ(prop_name, "hide")) {
node_identifier.operation_code = OperationCode::BONE_VISIBILITY;
}
/* And other properties can always go to the entry operation. */
else {
node_identifier.operation_code = OperationCode::BONE_LOCAL;
}
}
return node_identifier;
}
if (ptr->type == RNA_Bone) {
/* Armature-level bone mapped to Armature Eval, and thus Pose Init.
* Drivers have special code elsewhere that links them to the pose
* bone components, instead of using this generic code. */
node_identifier.type = NodeType::ARMATURE;
node_identifier.operation_code = OperationCode::ARMATURE_EVAL;
/* If trying to look up via an Object, e.g. due to lookup via
* obj.pose.bones[].bone in a driver attached to the Object,
* redirect to its data. */
if (GS(node_identifier.id->name) == ID_OB) {
node_identifier.id = id_cast<Object *>(node_identifier.id)->data;
}
return node_identifier;
}
const char *prop_identifier = prop != nullptr ?
RNA_property_identifier(const_cast<PropertyRNA *>(prop)) :
"";
if (RNA_struct_is_a(ptr->type, RNA_Constraint)) {
const Object *object = reinterpret_cast<const Object *>(ptr->owner_id);
const bConstraint *constraint = static_cast<const bConstraint *>(ptr->data);
RNANodeQueryIDData *id_data = ensure_id_data(&object->id);
/* Check whether is object or bone constraint. */
/* NOTE: Currently none of the area can address transform of an object
* at a given constraint, but for rigging one might use constraint
* influence to be used to drive some corrective shape keys or so. */
const bPoseChannel *pchan = id_data->get_pchan_for_constraint(constraint);
if (pchan == nullptr) {
node_identifier.type = NodeType::TRANSFORM;
node_identifier.operation_code = OperationCode::TRANSFORM_LOCAL;
}
else {
node_identifier.type = NodeType::BONE;
node_identifier.operation_code = OperationCode::BONE_LOCAL;
node_identifier.component_name = pchan->name;
}
return node_identifier;
}
if (ELEM(ptr->type, RNA_ConstraintTarget, RNA_ConstraintTargetBone)) {
Object *object = reinterpret_cast<Object *>(ptr->owner_id);
bConstraintTarget *tgt = static_cast<bConstraintTarget *>(ptr->data);
/* Check whether is object or bone constraint. */
bPoseChannel *pchan = nullptr;
bConstraint *con = BKE_constraint_find_from_target(object, tgt, &pchan);
if (con != nullptr) {
if (pchan != nullptr) {
node_identifier.type = NodeType::BONE;
node_identifier.operation_code = OperationCode::BONE_LOCAL;
node_identifier.component_name = pchan->name;
}
else {
node_identifier.type = NodeType::TRANSFORM;
node_identifier.operation_code = OperationCode::TRANSFORM_LOCAL;
}
return node_identifier;
}
}
else if (RNA_struct_is_a(ptr->type, RNA_Modifier) &&
(contains(prop_identifier, "show_viewport") ||
contains(prop_identifier, "show_render")))
{
node_identifier.type = NodeType::GEOMETRY;
node_identifier.operation_code = OperationCode::VISIBILITY;
return node_identifier;
}
else if (RNA_struct_is_a(ptr->type, RNA_Mesh) || RNA_struct_is_a(ptr->type, RNA_Modifier) ||
RNA_struct_is_a(ptr->type, RNA_Spline) || RNA_struct_is_a(ptr->type, RNA_TextBox) ||
RNA_struct_is_a(ptr->type, RNA_AnnotationLayer) ||
RNA_struct_is_a(ptr->type, RNA_LatticePoint) ||
RNA_struct_is_a(ptr->type, RNA_MeshUVLoop) ||
RNA_struct_is_a(ptr->type, RNA_MeshLoopColor) ||
RNA_struct_is_a(ptr->type, RNA_VertexGroupElement) ||
RNA_struct_is_a(ptr->type, RNA_ShaderFx) ||
(prop &&
RNA_property_flag(const_cast<PropertyRNA *>(prop)) & PROP_FORCE_GEOMETRY_EVAL) != 0)
{
/* When modifier is used as FROM operation this is likely referencing to
* the property (for example, modifier's influence).
* But when it's used as TO operation, this is geometry component. */
switch (source) {
case RNAPointerSource::ENTRY:
node_identifier.type = NodeType::GEOMETRY;
break;
case RNAPointerSource::EXIT:
node_identifier.type = NodeType::PARAMETERS;
node_identifier.operation_code = OperationCode::PARAMETERS_EVAL;
break;
}
return node_identifier;
}
else if (ptr->type == RNA_Object) {
/* Transforms props? */
if (prop != nullptr) {
/* TODO(sergey): How to optimize this? */
if (contains(prop_identifier, "location") || contains(prop_identifier, "matrix_basis") ||
contains(prop_identifier, "matrix_channel") ||
contains(prop_identifier, "matrix_inverse") ||
contains(prop_identifier, "matrix_local") ||
contains(prop_identifier, "matrix_parent_inverse") ||
contains(prop_identifier, "matrix_world") ||
contains(prop_identifier, "rotation_axis_angle") ||
contains(prop_identifier, "rotation_euler") ||
contains(prop_identifier, "rotation_mode") ||
contains(prop_identifier, "rotation_quaternion") || contains(prop_identifier, "scale") ||
contains(prop_identifier, "delta_location") ||
contains(prop_identifier, "delta_rotation_euler") ||
contains(prop_identifier, "delta_rotation_quaternion") ||
contains(prop_identifier, "delta_scale"))
{
node_identifier.type = NodeType::TRANSFORM;
return node_identifier;
}
if (contains(prop_identifier, "data")) {
/* We access object.data, most likely a geometry.
* Might be a bone tho. */
node_identifier.type = NodeType::GEOMETRY;
return node_identifier;
}
if (STR_ELEM(prop_identifier, "hide_viewport", "hide_render")) {
node_identifier.type = NodeType::OBJECT_FROM_LAYER;
return node_identifier;
}
if (STREQ(prop_identifier, "dimensions")) {
node_identifier.type = NodeType::PARAMETERS;
node_identifier.operation_code = OperationCode::DIMENSIONS;
return node_identifier;
}
}
}
else if (ptr->type == RNA_ShapeKey) {
KeyBlock *key_block = static_cast<KeyBlock *>(ptr->data);
node_identifier.id = ptr->owner_id;
node_identifier.type = NodeType::PARAMETERS;
node_identifier.operation_code = OperationCode::PARAMETERS_EVAL;
node_identifier.operation_name = key_block->name;
return node_identifier;
}
else if (ptr->type == RNA_Key) {
node_identifier.id = ptr->owner_id;
node_identifier.type = NodeType::GEOMETRY;
return node_identifier;
}
else if (RNA_struct_is_a(ptr->type, RNA_Strip)) {
/* Sequencer strip */
node_identifier.type = NodeType::SEQUENCER;
return node_identifier;
}
else if (RNA_struct_is_a(ptr->type, RNA_NodeSocket)) {
node_identifier.type = NodeType::NTREE_OUTPUT;
return node_identifier;
}
else if (RNA_struct_is_a(ptr->type, RNA_ShaderNode)) {
node_identifier.type = NodeType::SHADING;
return node_identifier;
}
else if (ELEM(ptr->type, RNA_Curve, RNA_TextCurve)) {
node_identifier.id = ptr->owner_id;
node_identifier.type = NodeType::GEOMETRY;
return node_identifier;
}
else if (ELEM(ptr->type, RNA_BezierSplinePoint, RNA_SplinePoint)) {
node_identifier.id = ptr->owner_id;
node_identifier.type = NodeType::GEOMETRY;
return node_identifier;
}
else if (RNA_struct_is_a(ptr->type, RNA_ImageUser)) {
if (GS(node_identifier.id->name) == ID_NT) {
node_identifier.type = NodeType::IMAGE_ANIMATION;
node_identifier.operation_code = OperationCode::IMAGE_ANIMATION;
return node_identifier;
}
}
else if (ELEM(ptr->type, RNA_MeshVertex, RNA_MeshEdge, RNA_MeshLoop, RNA_MeshPolygon)) {
node_identifier.type = NodeType::GEOMETRY;
return node_identifier;
}
if (prop != nullptr) {
/* All unknown data effectively falls under "parameter evaluation". */
node_identifier.type = NodeType::PARAMETERS;
node_identifier.operation_code = OperationCode::PARAMETERS_EVAL;
node_identifier.operation_name = "";
node_identifier.operation_name_tag = -1;
return node_identifier;
}
return node_identifier;
}
RNANodeQueryIDData *RNANodeQuery::ensure_id_data(const ID *id)
{
std::unique_ptr<RNANodeQueryIDData> &id_data = id_data_map_.lookup_or_add_cb(
id, [&]() { return std::make_unique<RNANodeQueryIDData>(id); });
return id_data.get();
}
bool rna_prop_affects_parameters_node(const PointerRNA *ptr, const PropertyRNA *prop)
{
return prop != nullptr && RNA_property_is_idprop(prop) &&
/* ID properties in the geometry nodes modifier don't affect that parameters node.
* Instead they affect the modifier and therefore the geometry node directly. */
!RNA_struct_is_a(ptr->type, RNA_NodesModifier);
}
} // namespace blender::deg

View File

@@ -0,0 +1,104 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_operation.hh"
#include "BLI_map.hh"
#include <memory>
namespace blender {
struct ID;
struct PointerRNA;
struct PropertyRNA;
namespace deg {
struct Depsgraph;
struct Node;
class RNANodeQueryIDData;
class DepsgraphBuilder;
/* For queries which gives operation node or key defines whether we are
* interested in a result of the given property or whether we are linking some
* dependency to that property. */
enum class RNAPointerSource {
/* Query will return pointer to an entry operation of component which is
* responsible for evaluation of the given property. */
ENTRY,
/* Query will return pointer to an exit operation of component which is
* responsible for evaluation of the given property.
* More precisely, it will return operation at which the property is known
* to be evaluated. */
EXIT,
};
/* A helper structure which wraps all fields needed to find a node inside of
* the dependency graph. */
class RNANodeIdentifier {
public:
RNANodeIdentifier();
/* Check whether this identifier is valid and usable. */
bool is_valid() const;
ID *id;
NodeType type;
const char *component_name;
OperationCode operation_code;
const char *operation_name;
int operation_name_tag;
};
/* Helper class which performs optimized lookups of a node within a given
* dependency graph which satisfies given RNA pointer or RNA path. */
class RNANodeQuery {
public:
RNANodeQuery(Depsgraph *depsgraph, DepsgraphBuilder *builder);
~RNANodeQuery();
Node *find_node(const PointerRNA *ptr, const PropertyRNA *prop, RNAPointerSource source);
protected:
Depsgraph *depsgraph_;
DepsgraphBuilder *builder_;
/* Indexed by an ID, returns RNANodeQueryIDData associated with that ID. */
Map<const ID *, std::unique_ptr<RNANodeQueryIDData>> id_data_map_;
/* Construct identifier of the node which corresponds given configuration
* of RNA property. */
RNANodeIdentifier construct_node_identifier(const PointerRNA *ptr,
const PropertyRNA *prop,
RNAPointerSource source);
/* Make sure ID data exists for the given ID, and returns it. */
RNANodeQueryIDData *ensure_id_data(const ID *id);
/* Check whether prop_identifier contains rna_path_component.
*
* This checks more than a sub-string:
*
* prop_identifier contains(prop_identifier, "location")
* ------------------------ -------------------------------------
* location true
* ["test_location"] false
* pose["bone"].location true
* pose["bone"].location.x true
*/
static bool contains(const char *prop_identifier, const char *rna_path_component);
};
bool rna_prop_affects_parameters_node(const PointerRNA *ptr, const PropertyRNA *prop);
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "BKE_gtest_base.hh"
#include "intern/builder/deg_builder_rna.h"
#include "testing/testing.h"
namespace blender::deg::tests {
class TestableRNANodeQuery : public RNANodeQuery {
public:
static bool contains(const char *prop_identifier, const char *rna_path_component)
{
return RNANodeQuery::contains(prop_identifier, rna_path_component);
}
};
class DegBuilderRNATest : public bke::BlenderGTestBase {};
TEST_F(DegBuilderRNATest, contains)
{
EXPECT_TRUE(TestableRNANodeQuery::contains("location", "location"));
EXPECT_TRUE(TestableRNANodeQuery::contains("location.x", "location"));
EXPECT_TRUE(TestableRNANodeQuery::contains("pose.bone[\"blork\"].location", "location"));
EXPECT_TRUE(TestableRNANodeQuery::contains("pose.bone[\"blork\"].location.x", "location"));
EXPECT_TRUE(TestableRNANodeQuery::contains("pose.bone[\"blork\"].location[0]", "location"));
EXPECT_FALSE(TestableRNANodeQuery::contains("", "location"));
EXPECT_FALSE(TestableRNANodeQuery::contains("locatio", "location"));
EXPECT_FALSE(TestableRNANodeQuery::contains("locationnn", "location"));
EXPECT_FALSE(TestableRNANodeQuery::contains("test_location", "location"));
EXPECT_FALSE(TestableRNANodeQuery::contains("location_test", "location"));
EXPECT_FALSE(TestableRNANodeQuery::contains("test_location_test", "location"));
EXPECT_FALSE(TestableRNANodeQuery::contains("pose.bone[\"location\"].scale", "location"));
EXPECT_FALSE(TestableRNANodeQuery::contains("pose.bone[\"location\"].scale[0]", "location"));
}
} // namespace blender::deg::tests

View File

@@ -0,0 +1,95 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/builder/deg_builder_stack.h"
#include <iomanip>
#include <ios>
#include <iostream>
#include "BKE_idtype.hh"
#include "DNA_ID.h"
#include "DNA_action_types.h"
#include "DNA_constraint_types.h"
#include "DNA_modifier_types.h"
namespace blender::deg {
/* Spacing between adjacent columns, in number of spaces. */
constexpr int kColumnSpacing = 4;
/* Width of table columns including column padding.
* The type column width is a guesstimate based on "Particle Settings" with some extra padding. */
constexpr int kPrintDepthWidth = 5 + kColumnSpacing;
constexpr int kPrintTypeWidth = 21 + kColumnSpacing;
namespace {
/* NOTE: Depth column printing is already taken care of. */
void print(std::ostream &stream, const ID *id)
{
const IDTypeInfo *id_type_info = BKE_idtype_get_info_from_id(id);
stream << std::setw(kPrintTypeWidth) << id_type_info->name << (id->name + 2) << "\n";
}
void print(std::ostream &stream, const bConstraint *constraint)
{
stream << std::setw(kPrintTypeWidth) << ("Constraint") << constraint->name << "\n";
}
void print(std::ostream &stream, const ModifierData *modifier_data)
{
stream << std::setw(kPrintTypeWidth) << ("Modifier") << modifier_data->name << "\n";
}
void print(std::ostream &stream, const bPoseChannel *pchan)
{
stream << std::setw(kPrintTypeWidth) << ("Pose Channel") << pchan->name << "\n";
}
} // namespace
void BuilderStack::print_backtrace(std::ostream &stream)
{
const std::ios_base::fmtflags old_flags(stream.flags());
stream << std::left;
stream << std::setw(kPrintDepthWidth) << "Depth" << std::setw(kPrintTypeWidth) << "Type"
<< "Name"
<< "\n";
stream << std::setw(kPrintDepthWidth) << "-----" << std::setw(kPrintTypeWidth) << "----"
<< "----"
<< "\n";
int depth = 1;
for (const Entry &entry : stack_) {
stream << std::setw(kPrintDepthWidth) << depth;
++depth;
if (entry.id_ != nullptr) {
print(stream, entry.id_);
}
else if (entry.constraint_ != nullptr) {
print(stream, entry.constraint_);
}
else if (entry.modifier_data_ != nullptr) {
print(stream, entry.modifier_data_);
}
else if (entry.pchan_ != nullptr) {
print(stream, entry.pchan_);
}
}
stream.flags(old_flags);
}
} // namespace blender::deg

View File

@@ -0,0 +1,133 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "BLI_vector.hh"
namespace blender {
struct ID;
struct bConstraint;
struct bPoseChannel;
struct ModifierData;
namespace deg {
/* This class keeps track of the builder calls nesting, allowing to unroll them back and provide a
* clue about how the builder made it to its current state.
*
* The tracing is based on the builder giving a trace clues to the stack. Typical usage is:
*
* void DepsgraphRelationBuilder::my_id_builder(ID *id)
* {
* if (built_map_.check_is_built_and_tag(id)) {
* return;
* }
*
* const BuilderStack::ScopedEntry stack_entry = stack_.trace(*id);
*
* ...
* }
*/
class BuilderStack {
public:
/* Entry of the backtrace.
* A cheap-to-construct wrapper which allows to gather a proper string representation whenever
* the stack is printed. */
class Entry {
public:
explicit Entry(const ID &id) : id_(&id) {}
explicit Entry(const bConstraint &constraint) : constraint_(&constraint) {}
explicit Entry(const bPoseChannel &pchan) : pchan_(&pchan) {}
explicit Entry(const ModifierData &modifier_data) : modifier_data_(&modifier_data) {}
private:
friend class BuilderStack;
const ID *id_ = nullptr;
const bConstraint *constraint_ = nullptr;
const ModifierData *modifier_data_ = nullptr;
const bPoseChannel *pchan_ = nullptr;
};
using Stack = Vector<Entry>;
/* A helper class to provide a RAII style of tracing. It is constructed by the
* `BuilderStack::trace` (which pushes entry to the stack), and upon destruction of this object
* the corresponding entry is popped from the stack.
*
* The goal of this `ScopedEntry` is to free developers from worrying about removing entries from
* the stack whenever leaving a builder step scope. */
class ScopedEntry {
public:
/* Delete copy constructor and operator: scoped entries are only supposed to be constructed
* once and never copied. */
ScopedEntry(const ScopedEntry &other) = delete;
ScopedEntry &operator=(const ScopedEntry &other) = delete;
/* Move semantic. */
ScopedEntry(ScopedEntry &&other) noexcept : stack_(other.stack_)
{
other.stack_ = nullptr;
}
ScopedEntry &operator=(ScopedEntry &&other)
{
if (this == &other) {
return *this;
}
stack_ = other.stack_;
other.stack_ = nullptr;
return *this;
}
~ScopedEntry()
{
/* Stack will become nullptr when the entry was moved somewhere else. */
if (stack_ != nullptr) {
BLI_assert(!stack_->is_empty());
stack_->pop_last();
}
}
private:
friend BuilderStack;
explicit ScopedEntry(Stack &stack) : stack_(&stack) {}
Stack *stack_;
};
BuilderStack() = default;
~BuilderStack() = default;
bool is_empty() const
{
return stack_.is_empty();
}
void print_backtrace(std::ostream &stream);
template<class... Args> ScopedEntry trace(const Args &...args)
{
stack_.append_as(args...);
return ScopedEntry(stack_);
}
private:
Stack stack_;
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2015 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "DEG_depsgraph_debug.hh"
#include "intern/builder/deg_builder_transitive.h"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_operation.hh"
#include "intern/debug/deg_debug.h"
#include "intern/depsgraph.hh"
#include "intern/depsgraph_relation.hh"
namespace blender::deg {
/* -------------------------------------------------- */
/* Performs a transitive reduction to remove redundant relations.
* https://en.wikipedia.org/wiki/Transitive_reduction
*
* XXX The current implementation is somewhat naive and has O(V*E) worst case
* runtime.
* A more optimized algorithm can be implemented later, e.g.
*
* http://www.sciencedirect.com/science/article/pii/0304397588900321/pdf?md5=3391e309b708b6f9cdedcd08f84f4afc&pid=1-s2.0-0304397588900321-main.pdf
*
* Care has to be taken to make sure the algorithm can handle the cyclic case
* too! (unless we can to prevent this case early on).
*/
enum {
OP_VISITED = 1,
OP_REACHABLE = 2,
};
static void deg_graph_tag_paths_recursive(Node *node)
{
if (node->custom_flags & OP_VISITED) {
return;
}
node->custom_flags |= OP_VISITED;
for (Relation *rel : node->inlinks) {
deg_graph_tag_paths_recursive(rel->from);
/* Do this only in inlinks loop, so the target node does not get
* flagged. */
rel->from->custom_flags |= OP_REACHABLE;
}
}
void deg_graph_transitive_reduction(Depsgraph *graph)
{
int num_removed_relations = 0;
Vector<Relation *> relations_to_remove;
for (OperationNode *target : graph->operations) {
/* Clear tags. */
for (OperationNode *node : graph->operations) {
node->custom_flags = 0;
}
/* Mark nodes from which we can reach the target
* start with children, so the target node and direct children are not
* flagged. */
target->custom_flags |= OP_VISITED;
for (Relation *rel : target->inlinks) {
deg_graph_tag_paths_recursive(rel->from);
}
/* Remove redundant paths to the target. */
for (Relation *rel : target->inlinks) {
if (rel->from->type == NodeType::TIMESOURCE) {
/* HACK: time source nodes don't get "custom_flags" flag
* set/cleared. */
/* TODO: there will be other types in future, so iterators above
* need modifying. */
continue;
}
if (rel->from->custom_flags & OP_REACHABLE) {
relations_to_remove.append(rel);
}
}
for (Relation *rel : relations_to_remove) {
rel->unlink();
}
num_removed_relations += relations_to_remove.size();
relations_to_remove.clear();
}
DEG_DEBUG_PRINTF(
(blender::Depsgraph *)graph, BUILD, "Removed %d relations\n", num_removed_relations);
}
} // namespace blender::deg

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2015 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
namespace blender::deg {
struct Depsgraph;
/* Performs a transitive reduction to remove redundant relations. */
void deg_graph_transitive_reduction(Depsgraph *graph);
} // namespace blender::deg

View File

@@ -0,0 +1,181 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "pipeline.h"
#include "BLI_listbase.h"
#include "BLI_time.h"
#include "CLG_log.h"
#include "BKE_global.hh"
#include "DNA_scene_types.h"
#include "deg_builder_cycle.h"
#include "deg_builder_nodes.h"
#include "deg_builder_relations.h"
#include "deg_builder_transitive.h"
namespace blender {
static CLG_LogRef LOG = {"depsgraph"};
namespace deg {
static bool need_sanity_checks()
{
#if !defined(NDEBUG)
return true;
#endif
return G.debug & G_DEBUG_DEPSGRAPH_BUILD;
}
static void do_sanity_checks(const Depsgraph *graph,
const Set<const ID *> &ids_build_by_node_builder,
const Set<const ID *> &ids_build_by_relations_builder)
{
if (ids_build_by_node_builder != ids_build_by_relations_builder) {
CLOG_ERROR(&LOG, "Some IDs missed nodes or relations building.");
for (const ID *id_iter : ids_build_by_node_builder) {
if (!ids_build_by_relations_builder.contains(id_iter)) {
CLOG_ERROR(&LOG, "\t- ID '%s' was not built for relations.", id_iter->name);
}
}
for (const ID *id_iter : ids_build_by_relations_builder) {
if (!ids_build_by_node_builder.contains(id_iter)) {
CLOG_ERROR(&LOG, "\t- ID '%s' was not built for nodes.", id_iter->name);
}
}
}
for (const IDNode *id_node : graph->id_nodes) {
if (!ids_build_by_node_builder.contains(id_node->id_orig)) {
CLOG_ERROR(&LOG,
"\t+ ID '%s' is in depsgraph but was not built for nodes.",
id_node->id_orig->name);
}
if (!ids_build_by_relations_builder.contains(id_node->id_orig)) {
CLOG_ERROR(&LOG,
"\t+ ID '%s' is in depsgraph but was not built for relations.",
id_node->id_orig->name);
}
}
for (const ID *id : ids_build_by_node_builder) {
if (graph->find_id_node(id) == nullptr) {
CLOG_ERROR(&LOG, "\t* ID '%s' was built for nodes but is not in Depsgraph.", id->name);
}
}
for (const ID *id : ids_build_by_relations_builder) {
if (graph->find_id_node(id) == nullptr) {
CLOG_ERROR(&LOG, "\t* ID '%s' was built for relations but is not in Depsgraph.", id->name);
}
}
}
AbstractBuilderPipeline::AbstractBuilderPipeline(blender::Depsgraph *graph)
: deg_graph_(reinterpret_cast<Depsgraph *>(graph)),
bmain_(deg_graph_->bmain),
scene_(deg_graph_->scene),
view_layer_(deg_graph_->view_layer)
{
}
void AbstractBuilderPipeline::build()
{
double start_time = 0.0;
if (G.debug & (G_DEBUG_DEPSGRAPH_BUILD | G_DEBUG_DEPSGRAPH_TIME)) {
start_time = BLI_time_now_seconds();
}
build_step_sanity_check();
build_step_nodes();
build_step_relations();
build_step_finalize();
if (need_sanity_checks()) {
do_sanity_checks(deg_graph_, ids_build_by_node_builder_, ids_build_by_relations_builder_);
}
if (G.debug & (G_DEBUG_DEPSGRAPH_BUILD | G_DEBUG_DEPSGRAPH_TIME)) {
printf("Depsgraph built in %f seconds.\n", BLI_time_now_seconds() - start_time);
}
}
void AbstractBuilderPipeline::build_step_sanity_check()
{
BLI_assert(BLI_findindex(&scene_->view_layers, view_layer_) != -1);
BLI_assert(deg_graph_->scene == scene_);
BLI_assert(deg_graph_->view_layer == view_layer_);
}
void AbstractBuilderPipeline::build_step_nodes()
{
/* Generate all the nodes in the graph first */
std::unique_ptr<DepsgraphNodeBuilder> node_builder = construct_node_builder();
node_builder->begin_build();
build_nodes(*node_builder);
node_builder->end_build();
if (need_sanity_checks()) {
ids_build_by_node_builder_ = node_builder->get_built_ids();
}
}
void AbstractBuilderPipeline::build_step_relations()
{
/* Hook up relationships between operations - to determine evaluation order. */
std::unique_ptr<DepsgraphRelationBuilder> relation_builder = construct_relation_builder();
relation_builder->begin_build();
build_relations(*relation_builder);
relation_builder->build_copy_on_write_relations();
relation_builder->build_driver_relations();
if (need_sanity_checks()) {
ids_build_by_relations_builder_ = relation_builder->get_built_ids();
}
}
void AbstractBuilderPipeline::build_step_finalize()
{
/* Detect and solve cycles. */
deg_graph_detect_cycles(deg_graph_);
/* Simplify the graph by removing redundant relations (to optimize
* traversal later). */
/* TODO: it would be useful to have an option to disable this in cases where
* it is causing trouble. */
if (G.debug_value == 799) {
deg_graph_transitive_reduction(deg_graph_);
}
/* Store pointers to commonly used evaluated datablocks. */
deg_graph_->scene_cow = reinterpret_cast<Scene *>(
deg_graph_->get_cow_id(&deg_graph_->scene->id));
/* Flush visibility layer and re-schedule nodes for update. */
deg_graph_build_finalize(bmain_, deg_graph_);
DEG_graph_tag_on_visible_update(reinterpret_cast<blender::Depsgraph *>(deg_graph_), false);
#if 0
if (!DEG_debug_consistency_check(deg_graph_)) {
printf("Consistency validation failed, ABORTING!\n");
abort();
}
#endif
/* Relations are up to date. */
deg_graph_->need_update_relations = false;
}
std::unique_ptr<DepsgraphNodeBuilder> AbstractBuilderPipeline::construct_node_builder()
{
return std::make_unique<DepsgraphNodeBuilder>(bmain_, deg_graph_, &builder_cache_);
}
std::unique_ptr<DepsgraphRelationBuilder> AbstractBuilderPipeline::construct_relation_builder()
{
return std::make_unique<DepsgraphRelationBuilder>(bmain_, deg_graph_, &builder_cache_);
}
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,66 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "deg_builder_cache.h"
#include "BLI_set.hh"
namespace blender {
struct Depsgraph;
struct Main;
struct Scene;
struct ViewLayer;
namespace deg {
struct Depsgraph;
class DepsgraphNodeBuilder;
class DepsgraphRelationBuilder;
/* Base class for Depsgraph Builder pipelines.
*
* Basically it runs through the following steps:
* - sanity check
* - build nodes
* - build relations
* - finalize
*/
class AbstractBuilderPipeline {
public:
AbstractBuilderPipeline(blender::Depsgraph *graph);
virtual ~AbstractBuilderPipeline() = default;
void build();
protected:
Depsgraph *deg_graph_;
Main *bmain_;
Scene *scene_;
ViewLayer *view_layer_;
DepsgraphBuilderCache builder_cache_;
virtual std::unique_ptr<DepsgraphNodeBuilder> construct_node_builder();
virtual std::unique_ptr<DepsgraphRelationBuilder> construct_relation_builder();
virtual void build_step_sanity_check();
void build_step_nodes();
void build_step_relations();
void build_step_finalize();
virtual void build_nodes(DepsgraphNodeBuilder &node_builder) = 0;
virtual void build_relations(DepsgraphRelationBuilder &relation_builder) = 0;
Set<const ID *> ids_build_by_node_builder_;
Set<const ID *> ids_build_by_relations_builder_;
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,60 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "pipeline_all_objects.h"
#include "intern/builder/deg_builder_nodes.h"
#include "intern/builder/deg_builder_relations.h"
#include "intern/depsgraph.hh"
#include "DNA_layer_types.h"
namespace blender::deg {
namespace {
class AllObjectsNodeBuilder : public DepsgraphNodeBuilder {
public:
AllObjectsNodeBuilder(Main *bmain, Depsgraph *graph, DepsgraphBuilderCache *cache)
: DepsgraphNodeBuilder(bmain, graph, cache)
{
}
bool need_pull_base_into_graph(const Base * /*base*/) override
{
return true;
}
};
class AllObjectsRelationBuilder : public DepsgraphRelationBuilder {
public:
AllObjectsRelationBuilder(Main *bmain, Depsgraph *graph, DepsgraphBuilderCache *cache)
: DepsgraphRelationBuilder(bmain, graph, cache)
{
}
bool need_pull_base_into_graph(const Base * /*base*/) override
{
return true;
}
};
} // namespace
AllObjectsBuilderPipeline::AllObjectsBuilderPipeline(blender::Depsgraph *graph)
: ViewLayerBuilderPipeline(graph)
{
}
std::unique_ptr<DepsgraphNodeBuilder> AllObjectsBuilderPipeline::construct_node_builder()
{
return std::make_unique<AllObjectsNodeBuilder>(bmain_, deg_graph_, &builder_cache_);
}
std::unique_ptr<DepsgraphRelationBuilder> AllObjectsBuilderPipeline::construct_relation_builder()
{
return std::make_unique<AllObjectsRelationBuilder>(bmain_, deg_graph_, &builder_cache_);
}
} // namespace blender::deg

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "pipeline_view_layer.h"
namespace blender::deg {
/* Builds a dependency graph that contains all objects in the view layer.
* This is contrary to the regular ViewLayerBuilderPipeline, which is limited to visible objects
* (and their dependencies). */
class AllObjectsBuilderPipeline : public ViewLayerBuilderPipeline {
public:
AllObjectsBuilderPipeline(blender::Depsgraph *graph);
protected:
std::unique_ptr<DepsgraphNodeBuilder> construct_node_builder() override;
std::unique_ptr<DepsgraphRelationBuilder> construct_relation_builder() override;
};
} // namespace blender::deg

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "pipeline_compositor.h"
#include "intern/builder/deg_builder_nodes.h"
#include "intern/builder/deg_builder_relations.h"
#include "intern/depsgraph.hh"
namespace blender::deg {
namespace {
class CompositorDepsgraphNodeBuilder : public DepsgraphNodeBuilder {
public:
using DepsgraphNodeBuilder::DepsgraphNodeBuilder;
void build_idproperties(IDProperty * /*id_property*/) override {}
};
class CompositorDepsgraphRelationBuilder : public DepsgraphRelationBuilder {
public:
using DepsgraphRelationBuilder::DepsgraphRelationBuilder;
void build_idproperties(IDProperty * /*id_property*/) override {}
};
} // namespace
CompositorBuilderPipeline::CompositorBuilderPipeline(blender::Depsgraph *graph)
: AbstractBuilderPipeline(graph)
{
deg_graph_->is_render_pipeline_depsgraph = true;
}
std::unique_ptr<DepsgraphNodeBuilder> CompositorBuilderPipeline::construct_node_builder()
{
return std::make_unique<CompositorDepsgraphNodeBuilder>(bmain_, deg_graph_, &builder_cache_);
}
std::unique_ptr<DepsgraphRelationBuilder> CompositorBuilderPipeline::construct_relation_builder()
{
return std::make_unique<CompositorDepsgraphRelationBuilder>(bmain_, deg_graph_, &builder_cache_);
}
void CompositorBuilderPipeline::build_nodes(DepsgraphNodeBuilder &node_builder)
{
node_builder.build_scene_render(scene_, view_layer_);
node_builder.build_scene_compositor(scene_);
}
void CompositorBuilderPipeline::build_relations(DepsgraphRelationBuilder &relation_builder)
{
relation_builder.build_scene_render(scene_, view_layer_);
relation_builder.build_scene_compositor(scene_);
}
} // namespace blender::deg

View File

@@ -0,0 +1,32 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "pipeline.h"
namespace blender {
struct bNodeTree;
namespace deg {
class CompositorBuilderPipeline : public AbstractBuilderPipeline {
public:
CompositorBuilderPipeline(blender::Depsgraph *graph);
protected:
std::unique_ptr<DepsgraphNodeBuilder> construct_node_builder() override;
std::unique_ptr<DepsgraphRelationBuilder> construct_relation_builder() override;
void build_nodes(DepsgraphNodeBuilder &node_builder) override;
void build_relations(DepsgraphRelationBuilder &relation_builder) override;
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,125 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "pipeline_from_collection.h"
#include "BKE_collection.hh"
#include "DNA_layer_types.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "intern/builder/deg_builder_nodes.h"
#include "intern/builder/deg_builder_relations.h"
#include "intern/depsgraph.hh"
namespace blender::deg {
namespace {
class DepsgraphFromCollectionIDsFilter {
public:
DepsgraphFromCollectionIDsFilter(const Set<ID *> &ids) : ids_(ids) {}
bool contains(ID *id)
{
return ids_.contains(id);
}
protected:
const Set<ID *> &ids_;
};
class DepsgraphFromCollectionIDsNodeBuilder : public DepsgraphNodeBuilder {
public:
DepsgraphFromCollectionIDsNodeBuilder(Main *bmain,
Depsgraph *graph,
DepsgraphBuilderCache *cache,
const Set<ID *> &ids)
: DepsgraphNodeBuilder(bmain, graph, cache), filter_(ids)
{
}
bool need_pull_base_into_graph(const Base *base) override
{
if (!filter_.contains(&base->object->id)) {
return false;
}
return DepsgraphNodeBuilder::need_pull_base_into_graph(base);
}
protected:
DepsgraphFromCollectionIDsFilter filter_;
};
class DepsgraphFromCollectionIDsRelationBuilder : public DepsgraphRelationBuilder {
public:
DepsgraphFromCollectionIDsRelationBuilder(Main *bmain,
Depsgraph *graph,
DepsgraphBuilderCache *cache,
const Set<ID *> &ids)
: DepsgraphRelationBuilder(bmain, graph, cache), filter_(ids)
{
}
bool need_pull_base_into_graph(const Base *base) override
{
if (!filter_.contains(&base->object->id)) {
return false;
}
return DepsgraphRelationBuilder::need_pull_base_into_graph(base);
}
protected:
DepsgraphFromCollectionIDsFilter filter_;
};
} // namespace
FromCollectionBuilderPipeline::FromCollectionBuilderPipeline(blender::Depsgraph *graph,
Collection *collection)
: AbstractBuilderPipeline(graph)
{
Base *base = BKE_collection_or_layer_objects(
*DEG_get_bmain(graph), scene_, view_layer_, collection);
const int base_flag = (deg_graph_->mode == DAG_EVAL_RENDER) ? BASE_ENABLED_RENDER :
BASE_ENABLED_VIEWPORT;
for (; base; base = base->next) {
if (!deg_graph_->use_visibility_optimization || (base->flag & base_flag)) {
ids_.add(&base->object->id);
}
}
}
std::unique_ptr<DepsgraphNodeBuilder> FromCollectionBuilderPipeline::construct_node_builder()
{
return std::make_unique<DepsgraphFromCollectionIDsNodeBuilder>(
bmain_, deg_graph_, &builder_cache_, ids_);
}
std::unique_ptr<DepsgraphRelationBuilder> FromCollectionBuilderPipeline::
construct_relation_builder()
{
return std::make_unique<DepsgraphFromCollectionIDsRelationBuilder>(
bmain_, deg_graph_, &builder_cache_, ids_);
}
void FromCollectionBuilderPipeline::build_nodes(DepsgraphNodeBuilder &node_builder)
{
node_builder.build_view_layer(scene_, view_layer_, DEG_ID_LINKED_DIRECTLY);
for (ID *id : ids_) {
node_builder.build_id(id, true);
}
}
void FromCollectionBuilderPipeline::build_relations(DepsgraphRelationBuilder &relation_builder)
{
relation_builder.build_view_layer(scene_, view_layer_, DEG_ID_LINKED_DIRECTLY);
for (ID *id : ids_) {
relation_builder.build_id(id);
}
}
} // namespace blender::deg

View File

@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "pipeline.h"
namespace blender {
struct Collection;
namespace deg {
/* Optimized builders for dependency graph built from a given Collection.
*
* General notes:
*
* - We pull in all bases if their objects are in the set of IDs. This allows to have proper
* visibility and other flags assigned to the objects.
* All other bases (the ones which points to object which is outside of the set of IDs) are
* completely ignored.
*/
class FromCollectionBuilderPipeline : public AbstractBuilderPipeline {
public:
FromCollectionBuilderPipeline(blender::Depsgraph *graph, Collection *collection);
protected:
std::unique_ptr<DepsgraphNodeBuilder> construct_node_builder() override;
std::unique_ptr<DepsgraphRelationBuilder> construct_relation_builder() override;
void build_nodes(DepsgraphNodeBuilder &node_builder) override;
void build_relations(DepsgraphRelationBuilder &relation_builder) override;
private:
Set<ID *> ids_;
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,111 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "pipeline_from_ids.h"
#include "DNA_layer_types.h"
#include "intern/builder/deg_builder_nodes.h"
#include "intern/builder/deg_builder_relations.h"
#include "intern/depsgraph.hh"
namespace blender::deg {
namespace {
class DepsgraphFromIDsFilter {
public:
DepsgraphFromIDsFilter(Span<ID *> ids)
{
ids_.add_multiple(ids);
}
bool contains(ID *id)
{
return ids_.contains(id);
}
protected:
Set<ID *> ids_;
};
class DepsgraphFromIDsNodeBuilder : public DepsgraphNodeBuilder {
public:
DepsgraphFromIDsNodeBuilder(Main *bmain,
Depsgraph *graph,
DepsgraphBuilderCache *cache,
Span<ID *> ids)
: DepsgraphNodeBuilder(bmain, graph, cache), filter_(ids)
{
}
bool need_pull_base_into_graph(const Base *base) override
{
if (!filter_.contains(&base->object->id)) {
return false;
}
return DepsgraphNodeBuilder::need_pull_base_into_graph(base);
}
protected:
DepsgraphFromIDsFilter filter_;
};
class DepsgraphFromIDsRelationBuilder : public DepsgraphRelationBuilder {
public:
DepsgraphFromIDsRelationBuilder(Main *bmain,
Depsgraph *graph,
DepsgraphBuilderCache *cache,
Span<ID *> ids)
: DepsgraphRelationBuilder(bmain, graph, cache), filter_(ids)
{
}
bool need_pull_base_into_graph(const Base *base) override
{
if (!filter_.contains(&base->object->id)) {
return false;
}
return DepsgraphRelationBuilder::need_pull_base_into_graph(base);
}
protected:
DepsgraphFromIDsFilter filter_;
};
} // namespace
FromIDsBuilderPipeline::FromIDsBuilderPipeline(blender::Depsgraph *graph, Span<ID *> ids)
: AbstractBuilderPipeline(graph), ids_(ids)
{
}
std::unique_ptr<DepsgraphNodeBuilder> FromIDsBuilderPipeline::construct_node_builder()
{
return std::make_unique<DepsgraphFromIDsNodeBuilder>(bmain_, deg_graph_, &builder_cache_, ids_);
}
std::unique_ptr<DepsgraphRelationBuilder> FromIDsBuilderPipeline::construct_relation_builder()
{
return std::make_unique<DepsgraphFromIDsRelationBuilder>(
bmain_, deg_graph_, &builder_cache_, ids_);
}
void FromIDsBuilderPipeline::build_nodes(DepsgraphNodeBuilder &node_builder)
{
node_builder.build_view_layer(scene_, view_layer_, DEG_ID_LINKED_DIRECTLY);
for (ID *id : ids_) {
node_builder.build_id(id, true);
}
}
void FromIDsBuilderPipeline::build_relations(DepsgraphRelationBuilder &relation_builder)
{
relation_builder.build_view_layer(scene_, view_layer_, DEG_ID_LINKED_DIRECTLY);
for (ID *id : ids_) {
relation_builder.build_id(id);
}
}
} // namespace blender::deg

View File

@@ -0,0 +1,39 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "pipeline.h"
namespace blender::deg {
/* Optimized builders for dependency graph built from a given set of IDs.
*
* General notes:
*
* - We pull in all bases if their objects are in the set of IDs. This allows to have proper
* visibility and other flags assigned to the objects.
* All other bases (the ones which points to object which is outside of the set of IDs) are
* completely ignored.
*/
class FromIDsBuilderPipeline : public AbstractBuilderPipeline {
Span<ID *> ids_;
public:
FromIDsBuilderPipeline(blender::Depsgraph *graph, Span<ID *> ids);
protected:
std::unique_ptr<DepsgraphNodeBuilder> construct_node_builder() override;
std::unique_ptr<DepsgraphRelationBuilder> construct_relation_builder() override;
void build_nodes(DepsgraphNodeBuilder &node_builder) override;
void build_relations(DepsgraphRelationBuilder &relation_builder) override;
};
} // namespace blender::deg

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "pipeline_render.h"
#include "intern/builder/deg_builder_nodes.h"
#include "intern/builder/deg_builder_relations.h"
#include "intern/depsgraph.hh"
namespace blender::deg {
namespace {
class RenderDepsgraphNodeBuilder : public DepsgraphNodeBuilder {
public:
using DepsgraphNodeBuilder::DepsgraphNodeBuilder;
void build_idproperties(IDProperty * /*id_property*/) override {}
};
class RenderDepsgraphRelationBuilder : public DepsgraphRelationBuilder {
public:
using DepsgraphRelationBuilder::DepsgraphRelationBuilder;
void build_idproperties(IDProperty * /*id_property*/) override {}
};
} // namespace
RenderBuilderPipeline::RenderBuilderPipeline(blender::Depsgraph *graph)
: AbstractBuilderPipeline(graph)
{
deg_graph_->is_render_pipeline_depsgraph = true;
}
std::unique_ptr<DepsgraphNodeBuilder> RenderBuilderPipeline::construct_node_builder()
{
return std::make_unique<RenderDepsgraphNodeBuilder>(bmain_, deg_graph_, &builder_cache_);
}
std::unique_ptr<DepsgraphRelationBuilder> RenderBuilderPipeline::construct_relation_builder()
{
return std::make_unique<RenderDepsgraphRelationBuilder>(bmain_, deg_graph_, &builder_cache_);
}
void RenderBuilderPipeline::build_nodes(DepsgraphNodeBuilder &node_builder)
{
node_builder.build_scene_render(scene_, view_layer_);
}
void RenderBuilderPipeline::build_relations(DepsgraphRelationBuilder &relation_builder)
{
relation_builder.build_scene_render(scene_, view_layer_);
}
} // namespace blender::deg

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "pipeline.h"
namespace blender::deg {
class RenderBuilderPipeline : public AbstractBuilderPipeline {
public:
RenderBuilderPipeline(blender::Depsgraph *graph);
protected:
std::unique_ptr<DepsgraphNodeBuilder> construct_node_builder() override;
std::unique_ptr<DepsgraphRelationBuilder> construct_relation_builder() override;
void build_nodes(DepsgraphNodeBuilder &node_builder) override;
void build_relations(DepsgraphRelationBuilder &relation_builder) override;
};
} // namespace blender::deg

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "pipeline_view_layer.h"
#include "intern/builder/deg_builder_nodes.h"
#include "intern/builder/deg_builder_relations.h"
namespace blender::deg {
ViewLayerBuilderPipeline::ViewLayerBuilderPipeline(blender::Depsgraph *graph)
: AbstractBuilderPipeline(graph)
{
}
void ViewLayerBuilderPipeline::build_nodes(DepsgraphNodeBuilder &node_builder)
{
node_builder.build_view_layer(scene_, view_layer_, DEG_ID_LINKED_DIRECTLY);
}
void ViewLayerBuilderPipeline::build_relations(DepsgraphRelationBuilder &relation_builder)
{
relation_builder.build_view_layer(scene_, view_layer_, DEG_ID_LINKED_DIRECTLY);
}
} // namespace blender::deg

View File

@@ -0,0 +1,24 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "pipeline.h"
namespace blender::deg {
class ViewLayerBuilderPipeline : public AbstractBuilderPipeline {
public:
ViewLayerBuilderPipeline(blender::Depsgraph *graph);
protected:
void build_nodes(DepsgraphNodeBuilder &node_builder) override;
void build_relations(DepsgraphRelationBuilder &relation_builder) override;
};
} // namespace blender::deg

View File

@@ -0,0 +1,81 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/debug/deg_debug.h"
#include "BLI_console.h"
#include "BLI_hash.h"
#include "BLI_string.h"
#include "BLI_time.h"
#include "BKE_global.hh"
namespace blender::deg {
DepsgraphDebug::DepsgraphDebug() : flags(G.debug), graph_evaluation_start_time_(0) {}
bool DepsgraphDebug::do_time_debug() const
{
return ((G.debug & G_DEBUG_DEPSGRAPH_TIME) != 0);
}
void DepsgraphDebug::begin_graph_evaluation()
{
const double current_time = BLI_time_now_seconds();
graph_evaluation_start_time_ = current_time;
}
void DepsgraphDebug::end_graph_evaluation()
{
const double graph_eval_end_time = BLI_time_now_seconds();
graph_evaluation_total_time_ = graph_eval_end_time - graph_evaluation_start_time_;
if (!do_time_debug()) {
return;
}
if (name.empty()) {
printf("Depsgraph updated in %f seconds.\n", graph_evaluation_total_time_);
}
else {
printf("Depsgraph [%s] updated in %f seconds.\n", name.c_str(), graph_evaluation_total_time_);
}
}
double DepsgraphDebug::total_evaluation_time() const
{
return graph_evaluation_total_time_;
}
bool terminal_do_color()
{
return (G.debug & G_DEBUG_DEPSGRAPH_PRETTY) != 0;
}
std::string color_for_pointer(const void *pointer)
{
if (!terminal_do_color()) {
return "";
}
int r, g, b;
BLI_hash_pointer_to_color(pointer, &r, &g, &b);
char buffer[64];
SNPRINTF(buffer, TRUECOLOR_ANSI_COLOR_FORMAT, r, g, b);
return std::string(buffer);
}
std::string color_end()
{
if (!terminal_do_color()) {
return "";
}
return std::string(TRUECOLOR_ANSI_COLOR_FINISH);
}
} // namespace blender::deg

View File

@@ -0,0 +1,72 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include <string>
#include "BKE_global.hh" // IWYU pragma: keep
namespace blender::deg {
class DepsgraphDebug {
public:
DepsgraphDebug();
bool do_time_debug() const;
void begin_graph_evaluation();
void end_graph_evaluation();
double total_evaluation_time() const;
/* NOTE: Corresponds to G_DEBUG_DEPSGRAPH_* flags. */
int flags;
/* Name of this dependency graph (is used for debug prints, helping to distinguish graphs
* created for different view layer). */
std::string name;
protected:
/* Maximum number of counters used to calculate frame rate of depsgraph update. */
static const constexpr int MAX_FPS_COUNTERS = 64;
/* Point in time when last graph evaluation began.
* Is initialized from begin_graph_evaluation().
*/
double graph_evaluation_start_time_;
/* Total time of the last evaluation. */
double graph_evaluation_total_time_;
};
#define DEG_DEBUG_PRINTF(depsgraph, type, ...) \
do { \
if (DEG_debug_flags_get(depsgraph) & G_DEBUG_DEPSGRAPH_##type) { \
DEG_debug_print_begin(depsgraph); \
fprintf(stdout, __VA_ARGS__); \
} \
} while (0)
#define DEG_GLOBAL_DEBUG_PRINTF(type, ...) \
do { \
if (G.debug & G_DEBUG_DEPSGRAPH_##type) { \
fprintf(stdout, __VA_ARGS__); \
} \
} while (0)
#define DEG_ERROR_PRINTF(...) \
do { \
fprintf(stderr, __VA_ARGS__); \
fflush(stderr); \
} while (0)
bool terminal_do_color();
std::string color_for_pointer(const void *pointer);
std::string color_end();
} // namespace blender::deg

View File

@@ -0,0 +1,529 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Implementation of tools for debugging the depsgraph
*/
#include <cstdarg>
#include "BLI_dot_export.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_debug.hh"
#include "intern/depsgraph.hh"
#include "intern/depsgraph_relation.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_operation.hh"
#include "intern/node/deg_node_time.hh"
#include <sstream>
namespace blender {
/* ****************** */
/* Graphviz Debugging */
namespace deg {
/* Only one should be enabled, defines whether graphviz nodes
* get colored by individual types or classes.
*/
#define COLOR_SCHEME_NODE_CLASS 1
// #define COLOR_SCHEME_NODE_TYPE 2
static const char *deg_debug_graphviz_fontname = "helvetica";
static float deg_debug_graphviz_graph_label_size = 20.0f;
static float deg_debug_graphviz_node_label_size = 14.0f;
static const int deg_debug_max_colors = 12;
#ifdef COLOR_SCHEME_NODE_TYPE
static const char *deg_debug_colors[] = {
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c",
"#fb9a99",
"#e31a1c",
"#fdbf6f",
"#ff7f00",
"#cab2d6",
"#6a3d9a",
"#ffff99",
"#b15928",
"#ff00ff",
};
#endif
static const char *deg_debug_colors_light[] = {
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5",
"#ffed6f",
"#ff00ff",
};
#ifdef COLOR_SCHEME_NODE_TYPE
static const int deg_debug_node_type_color_map[][2] = {
{NodeType::TIMESOURCE, 0},
{NodeType::ID_REF, 1},
/* Outer Types */
{NodeType::PARAMETERS, 2},
{NodeType::ANIMATION, 4},
{NodeType::TRANSFORM, 5},
{NodeType::GEOMETRY, 6},
{NodeType::SEQUENCER, 7},
{NodeType::SHADING, 8},
{NodeType::CACHE, 9},
{NodeType::POINT_CACHE, 10},
{NodeType::LAYER_COLLECTIONS, 11},
{NodeType::COPY_ON_EVAL, 12},
{-1, 0},
};
#endif
static int deg_debug_node_color_index(const Node *node)
{
#ifdef COLOR_SCHEME_NODE_CLASS
/* Some special types. */
switch (node->type) {
case NodeType::ID_REF:
return 5;
case NodeType::OPERATION: {
OperationNode *op_node = static_cast<OperationNode *>(const_cast<Node *>(node));
if (op_node->is_noop()) {
if (op_node->flag & OperationFlag::DEPSOP_FLAG_PINNED) {
return 7;
}
return 8;
}
break;
}
default:
break;
}
/* Do others based on class. */
switch (node->get_class()) {
case NodeClass::OPERATION:
return 4;
case NodeClass::COMPONENT:
return 1;
default:
return 9;
}
#endif
#ifdef COLOR_SCHEME_NODE_TYPE
const int (*pair)[2];
for (pair = deg_debug_node_type_color_map; (*pair)[0] >= 0; pair++) {
if ((*pair)[0] == node->type) {
return (*pair)[1];
}
}
return -1;
#endif
}
struct DotExportContext {
bool show_tags;
dot_export::DirectedGraph &digraph;
Map<const Node *, dot_export::Node *> nodes_map;
Map<const Node *, dot_export::Cluster *> clusters_map;
};
static void deg_debug_graphviz_legend_color(const char *name,
const char *color,
std::stringstream &ss)
{
ss << "<TR>";
ss << "<TD>" << name << "</TD>";
ss << "<TD BGCOLOR=\"" << color << "\"></TD>";
ss << "</TR>";
}
static void deg_debug_graphviz_legend(DotExportContext &ctx)
{
dot_export::Node &legend_node = ctx.digraph.new_node("");
legend_node.attributes.set("rank", "sink");
legend_node.attributes.set("shape", "none");
legend_node.attributes.set("margin", 0);
std::stringstream ss;
ss << "<";
ss << R"(<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">)";
ss << "<TR><TD COLSPAN=\"2\"><B>Legend</B></TD></TR>";
#ifdef COLOR_SCHEME_NODE_CLASS
const char **colors = deg_debug_colors_light;
deg_debug_graphviz_legend_color("Operation", colors[4], ss);
deg_debug_graphviz_legend_color("Component", colors[1], ss);
deg_debug_graphviz_legend_color("ID Node", colors[5], ss);
deg_debug_graphviz_legend_color("NOOP", colors[8], ss);
deg_debug_graphviz_legend_color("Pinned OP", colors[7], ss);
#endif
#ifdef COLOR_SCHEME_NODE_TYPE
const int (*pair)[2];
for (pair = deg_debug_node_type_color_map; (*pair)[0] >= 0; pair++) {
DepsNodeFactory *nti = type_get_factory((NodeType)(*pair)[0]);
deg_debug_graphviz_legend_color(
ctx, nti->tname().c_str(), deg_debug_colors_light[(*pair)[1] % deg_debug_max_colors], ss);
}
#endif
ss << "</TABLE>";
ss << ">";
legend_node.attributes.set("label", ss.str());
legend_node.attributes.set("fontname", deg_debug_graphviz_fontname);
}
static void deg_debug_graphviz_node_color(DotExportContext &ctx,
const Node *node,
dot_export::Attributes &dot_attributes)
{
const char *color_default = "black";
const char *color_modified = "orangered4";
const char *color_update = "dodgerblue3";
const char *color = color_default;
if (ctx.show_tags) {
if (node->get_class() == NodeClass::OPERATION) {
OperationNode *op_node = static_cast<OperationNode *>(const_cast<Node *>(node));
if (op_node->flag & DEPSOP_FLAG_DIRECTLY_MODIFIED) {
color = color_modified;
}
else if (op_node->flag & DEPSOP_FLAG_NEEDS_UPDATE) {
color = color_update;
}
}
}
dot_attributes.set("color", color);
}
static void deg_debug_graphviz_node_penwidth(DotExportContext &ctx,
const Node *node,
dot_export::Attributes &dot_attributes)
{
float penwidth_default = 1.0f;
float penwidth_modified = 4.0f;
float penwidth_update = 4.0f;
float penwidth = penwidth_default;
if (ctx.show_tags) {
if (node->get_class() == NodeClass::OPERATION) {
OperationNode *op_node = static_cast<OperationNode *>(const_cast<Node *>(node));
if (op_node->flag & DEPSOP_FLAG_DIRECTLY_MODIFIED) {
penwidth = penwidth_modified;
}
else if (op_node->flag & DEPSOP_FLAG_NEEDS_UPDATE) {
penwidth = penwidth_update;
}
}
}
dot_attributes.set("penwidth", penwidth);
}
static void deg_debug_graphviz_node_fillcolor(const Node *node,
dot_export::Attributes &dot_attributes)
{
const char *defaultcolor = "gainsboro";
int color_index = deg_debug_node_color_index(node);
const char *fillcolor = color_index < 0 ?
defaultcolor :
deg_debug_colors_light[color_index % deg_debug_max_colors];
dot_attributes.set("fillcolor", fillcolor);
}
static void deg_debug_graphviz_relation_color(const Relation *rel, dot_export::DirectedEdge &edge)
{
const char *color_default = "black";
const char *color_cyclic = "red4"; /* The color of crime scene. */
const char *color_godmode = "blue4"; /* The color of beautiful sky. */
const char *color = color_default;
if (rel->flag & RELATION_FLAG_CYCLIC) {
color = color_cyclic;
}
else if (rel->flag & RELATION_FLAG_GODMODE) {
color = color_godmode;
}
edge.attributes.set("color", color);
}
static void deg_debug_graphviz_relation_style(const Relation *rel, dot_export::DirectedEdge &edge)
{
const char *style_default = "solid";
const char *style_no_flush = "dashed";
const char *style_flush_user_only = "dotted";
const char *style = style_default;
if (rel->flag & RELATION_FLAG_NO_FLUSH) {
style = style_no_flush;
}
if (rel->flag & RELATION_FLAG_FLUSH_USER_EDIT_ONLY) {
style = style_flush_user_only;
}
edge.attributes.set("style", style);
}
static void deg_debug_graphviz_relation_arrowhead(const Relation *rel,
dot_export::DirectedEdge &edge)
{
const char *shape_default = "normal";
const char *shape_no_cow = "box";
const char *shape = shape_default;
if (rel->from->get_class() == NodeClass::OPERATION &&
rel->to->get_class() == NodeClass::OPERATION)
{
OperationNode *op_from = static_cast<OperationNode *>(rel->from);
OperationNode *op_to = static_cast<OperationNode *>(rel->to);
if (op_from->owner->type == NodeType::COPY_ON_EVAL &&
/* The #ID::recalc flag depends on run-time state which is not valid at this point in time.
* Pass in all flags although there may be a better way to represent this. */
!op_to->owner->need_tag_cow_before_update(ID_RECALC_ALL))
{
shape = shape_no_cow;
}
}
edge.attributes.set("arrowhead", shape);
}
static void deg_debug_graphviz_node_style(DotExportContext &ctx,
const Node *node,
dot_export::Attributes &dot_attributes)
{
StringRef base_style = "filled"; /* default style */
if (ctx.show_tags) {
if (node->get_class() == NodeClass::OPERATION) {
OperationNode *op_node = static_cast<OperationNode *>(const_cast<Node *>(node));
if (op_node->flag & (DEPSOP_FLAG_DIRECTLY_MODIFIED | DEPSOP_FLAG_NEEDS_UPDATE)) {
base_style = "striped";
}
}
}
switch (node->get_class()) {
case NodeClass::GENERIC:
dot_attributes.set("style", base_style);
break;
case NodeClass::COMPONENT:
dot_attributes.set("style", base_style);
break;
case NodeClass::OPERATION:
dot_attributes.set("style", base_style + ",rounded");
break;
}
}
static void deg_debug_graphviz_node_single(DotExportContext &ctx,
const Node *node,
dot_export::Cluster *parent_cluster)
{
std::string name = node->identifier();
dot_export::Node &dot_node = ctx.digraph.new_node(name);
ctx.nodes_map.add_new(node, &dot_node);
dot_node.set_parent_cluster(parent_cluster);
dot_node.attributes.set("fontname", deg_debug_graphviz_fontname);
dot_node.attributes.set("frontsize", deg_debug_graphviz_node_label_size);
dot_node.attributes.set("shape", "box");
deg_debug_graphviz_node_style(ctx, node, dot_node.attributes);
deg_debug_graphviz_node_color(ctx, node, dot_node.attributes);
deg_debug_graphviz_node_fillcolor(node, dot_node.attributes);
deg_debug_graphviz_node_penwidth(ctx, node, dot_node.attributes);
}
static dot_export::Cluster &deg_debug_graphviz_node_cluster_create(
DotExportContext &ctx, const Node *node, dot_export::Cluster *parent_cluster)
{
std::string name = node->identifier();
dot_export::Cluster &cluster = ctx.digraph.new_cluster(name);
cluster.set_parent_cluster(parent_cluster);
cluster.attributes.set("fontname", deg_debug_graphviz_fontname);
cluster.attributes.set("fontsize", deg_debug_graphviz_node_label_size);
cluster.attributes.set("margin", 16);
deg_debug_graphviz_node_style(ctx, node, cluster.attributes);
deg_debug_graphviz_node_color(ctx, node, cluster.attributes);
deg_debug_graphviz_node_fillcolor(node, cluster.attributes);
deg_debug_graphviz_node_penwidth(ctx, node, cluster.attributes);
/* dummy node, so we can add edges between clusters */
dot_export::Node &dot_node = ctx.digraph.new_node("");
dot_node.attributes.set("shape", "point");
dot_node.attributes.set("style", "invis");
dot_node.set_parent_cluster(&cluster);
ctx.nodes_map.add_new(node, &dot_node);
ctx.clusters_map.add_new(node, &cluster);
return cluster;
}
static void deg_debug_graphviz_graph_nodes(DotExportContext &ctx, const Depsgraph *graph);
static void deg_debug_graphviz_graph_relations(DotExportContext &ctx, const Depsgraph *graph);
static void deg_debug_graphviz_node(DotExportContext &ctx,
const Node *node,
dot_export::Cluster *parent_cluster)
{
switch (node->type) {
case NodeType::ID_REF: {
const IDNode *id_node = static_cast<const IDNode *>(node);
if (id_node->components.is_empty()) {
deg_debug_graphviz_node_single(ctx, node, parent_cluster);
}
else {
dot_export::Cluster &cluster = deg_debug_graphviz_node_cluster_create(
ctx, node, parent_cluster);
for (const ComponentNode *comp : id_node->components.values()) {
deg_debug_graphviz_node(ctx, comp, &cluster);
}
}
break;
}
case NodeType::PARAMETERS:
case NodeType::ANIMATION:
case NodeType::TRANSFORM:
case NodeType::GEOMETRY:
case NodeType::SEQUENCER:
case NodeType::COMPOSITOR:
case NodeType::EVAL_POSE:
case NodeType::BONE:
case NodeType::SHADING:
case NodeType::CACHE:
case NodeType::POINT_CACHE:
case NodeType::IMAGE_ANIMATION:
case NodeType::LAYER_COLLECTIONS:
case NodeType::PARTICLE_SYSTEM:
case NodeType::PARTICLE_SETTINGS:
case NodeType::COPY_ON_EVAL:
case NodeType::OBJECT_FROM_LAYER:
case NodeType::HIERARCHY:
case NodeType::BATCH_CACHE:
case NodeType::INSTANCING:
case NodeType::SYNCHRONIZATION:
case NodeType::AUDIO:
case NodeType::ARMATURE:
case NodeType::GENERIC_DATABLOCK:
case NodeType::SCENE:
case NodeType::VISIBILITY:
case NodeType::NTREE_OUTPUT:
case NodeType::NTREE_GEOMETRY_PREPROCESS: {
ComponentNode *comp_node = static_cast<ComponentNode *>(const_cast<Node *>(node));
if (comp_node->operations.is_empty()) {
deg_debug_graphviz_node_single(ctx, node, parent_cluster);
}
else {
dot_export::Cluster &cluster = deg_debug_graphviz_node_cluster_create(
ctx, node, parent_cluster);
for (Node *op_node : comp_node->operations) {
deg_debug_graphviz_node(ctx, op_node, &cluster);
}
}
break;
}
case NodeType::UNDEFINED:
case NodeType::TIMESOURCE:
case NodeType::OPERATION:
deg_debug_graphviz_node_single(ctx, node, parent_cluster);
break;
case NodeType::NUM_TYPES:
break;
}
}
static void deg_debug_graphviz_node_relations(DotExportContext &ctx, const Node *node)
{
for (Relation *rel : node->inlinks) {
float penwidth = 2.0f;
const Node *head = rel->to; /* same as node */
const Node *tail = rel->from;
dot_export::Node &dot_tail = *ctx.nodes_map.lookup(tail);
dot_export::Node &dot_head = *ctx.nodes_map.lookup(head);
dot_export::DirectedEdge &edge = ctx.digraph.new_edge(dot_tail, dot_head);
/* NOTE: without label an id seem necessary to avoid bugs in graphviz/dot. */
edge.attributes.set("id", rel->name);
deg_debug_graphviz_relation_color(rel, edge);
deg_debug_graphviz_relation_style(rel, edge);
deg_debug_graphviz_relation_arrowhead(rel, edge);
edge.attributes.set("penwidth", penwidth);
/* NOTE: edge from node to our own cluster is not possible and gives graphviz
* warning, avoid this here by just linking directly to the invisible
* placeholder node. */
dot_export::Cluster *tail_cluster = ctx.clusters_map.lookup_default(tail, nullptr);
if (tail_cluster != nullptr && tail_cluster->contains(dot_head)) {
edge.attributes.set("ltail", tail_cluster->name());
}
dot_export::Cluster *head_cluster = ctx.clusters_map.lookup_default(head, nullptr);
if (head_cluster != nullptr && head_cluster->contains(dot_tail)) {
edge.attributes.set("lhead", head_cluster->name());
}
}
}
static void deg_debug_graphviz_graph_nodes(DotExportContext &ctx, const Depsgraph *graph)
{
for (Node *node : graph->id_nodes) {
deg_debug_graphviz_node(ctx, node, nullptr);
}
TimeSourceNode *time_source = graph->find_time_source();
if (time_source != nullptr) {
deg_debug_graphviz_node(ctx, time_source, nullptr);
}
}
static void deg_debug_graphviz_graph_relations(DotExportContext &ctx, const Depsgraph *graph)
{
for (IDNode *id_node : graph->id_nodes) {
for (ComponentNode *comp_node : id_node->components.values()) {
for (OperationNode *op_node : comp_node->operations) {
deg_debug_graphviz_node_relations(ctx, op_node);
}
}
}
TimeSourceNode *time_source = graph->find_time_source();
if (time_source != nullptr) {
deg_debug_graphviz_node_relations(ctx, time_source);
}
}
} // namespace deg
std::string DEG_debug_graph_to_dot(const Depsgraph &graph, const StringRef label)
{
const deg::Depsgraph &deg_graph = reinterpret_cast<const deg::Depsgraph &>(graph);
dot_export::DirectedGraph digraph;
deg::DotExportContext ctx{false, digraph};
digraph.set_rankdir(dot_export::Attr_rankdir::LeftToRight);
digraph.attributes.set("compound", "true");
digraph.attributes.set("labelloc", "t");
digraph.attributes.set("fontsize", deg::deg_debug_graphviz_graph_label_size);
digraph.attributes.set("fontname", deg::deg_debug_graphviz_fontname);
digraph.attributes.set("label", label);
digraph.attributes.set("splines", "ortho");
digraph.attributes.set("overlap", "scalexy");
deg::deg_debug_graphviz_graph_nodes(ctx, &deg_graph);
deg::deg_debug_graphviz_graph_relations(ctx, &deg_graph);
deg::deg_debug_graphviz_legend(ctx);
return digraph.to_dot_string();
}
} // namespace blender

View File

@@ -0,0 +1,154 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "DEG_depsgraph_debug.hh"
#include <algorithm>
#include <cstdarg>
#include "BLI_compiler_attrs.h"
#include "BLI_math_base.h"
#include "intern/depsgraph.hh"
#include "intern/node/deg_node_id.hh"
#include "DNA_ID.h"
namespace blender {
#define NL "\r\n"
namespace deg {
namespace {
struct DebugContext {
FILE *file;
const Depsgraph *graph;
const char *label;
const char *output_filename;
};
struct StatsEntry {
const IDNode *id_node;
double time;
};
/* TODO(sergey): De-duplicate with graphviz relation debugger. */
void deg_debug_fprintf(const DebugContext &ctx, const char *fmt, ...) ATTR_PRINTF_FORMAT(2, 3);
void deg_debug_fprintf(const DebugContext &ctx, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(ctx.file, fmt, args);
va_end(args);
}
inline double get_node_time(const DebugContext & /*ctx*/, const Node *node)
{
/* TODO(sergey): Figure out a nice way to define which exact time
* we want to show. */
return node->stats.current_time;
}
bool stat_entry_comparator(const StatsEntry &a, const StatsEntry &b)
{
return a.time > b.time;
}
std::string gnuplotify_id_code(const std::string &name)
{
return std::string("") + name[0] + name[1];
}
std::string gnuplotify_name(const std::string &name)
{
std::string result;
const int length = name.length();
for (int i = 0; i < length; i++) {
const char ch = name[i];
if (ch == '_') {
result += R"(\\\)";
}
result += ch;
}
return result;
}
void write_stats_data(const DebugContext &ctx)
{
/* Fill in array of all stats which are to be displayed. */
Vector<StatsEntry> stats;
stats.reserve(ctx.graph->id_nodes.size());
for (const IDNode *id_node : ctx.graph->id_nodes) {
const double time = get_node_time(ctx, id_node);
if (time == 0.0) {
continue;
}
StatsEntry entry;
entry.id_node = id_node;
entry.time = time;
stats.append(entry);
}
/* Sort the data. */
std::ranges::sort(stats, stat_entry_comparator);
/* We limit number of entries, otherwise things become unreadable. */
stats.resize(min_ii(stats.size(), 32));
std::reverse(stats.begin(), stats.end());
/* Print data to the file stream. */
deg_debug_fprintf(ctx, "$data << EOD" NL);
for (const StatsEntry &entry : stats) {
deg_debug_fprintf(ctx,
"\"[%s] %s\",%f" NL,
gnuplotify_id_code(entry.id_node->id_orig->name).c_str(),
gnuplotify_name(entry.id_node->id_orig->name + 2).c_str(),
entry.time);
}
deg_debug_fprintf(ctx, "EOD" NL);
}
void deg_debug_stats_gnuplot(const DebugContext &ctx)
{
/* Data itself. */
write_stats_data(ctx);
/* Optional label. */
if (ctx.label && ctx.label[0]) {
deg_debug_fprintf(ctx, "set title \"%s\"" NL, ctx.label);
}
/* Rest of the commands.
* TODO(sergey): Need to decide on the resolution somehow. */
deg_debug_fprintf(ctx, "set terminal pngcairo size 1920,1080" NL);
deg_debug_fprintf(ctx, "set output \"%s\"" NL, ctx.output_filename);
deg_debug_fprintf(ctx, "set grid" NL);
deg_debug_fprintf(ctx, "set datafile separator ','" NL);
deg_debug_fprintf(ctx, "set style fill solid" NL);
deg_debug_fprintf(ctx,
"plot \"$data\" using "
"($2*0.5):0:($2*0.5):(0.2):yticlabels(1) "
"with boxxyerrorbars t '' lt rgb \"#406090\"" NL);
}
} // namespace
} // namespace deg
void DEG_debug_stats_gnuplot(const Depsgraph *depsgraph,
FILE *fp,
const char *label,
const char *output_filename)
{
if (depsgraph == nullptr) {
return;
}
deg::DebugContext ctx;
ctx.file = fp;
ctx.graph = reinterpret_cast<deg::Depsgraph *>(const_cast<Depsgraph *>(depsgraph));
ctx.label = label;
ctx.output_filename = output_filename;
deg::deg_debug_stats_gnuplot(ctx);
}
} // namespace blender

View File

@@ -0,0 +1,362 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Core routines for how the Depsgraph works.
*/
#include "intern/depsgraph.hh" /* own include */
#include <cstring>
#include <type_traits>
#include "BLI_utildefines.h"
#include "BKE_global.hh"
#include "BKE_idtype.hh"
#include "BKE_scene.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_debug.hh"
#include "intern/depsgraph_physics.hh"
#include "intern/depsgraph_registry.hh"
#include "intern/depsgraph_relation.hh"
#include "intern/eval/deg_eval_copy_on_write.h"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_factory.hh"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_operation.hh"
#include "intern/node/deg_node_time.hh"
namespace blender {
namespace deg {
Depsgraph::Depsgraph(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluationMode mode)
: time_source(nullptr),
has_animated_visibility(false),
need_update_relations(true),
need_update_nodes_visibility(true),
need_tag_id_on_graph_visibility_update(true),
need_tag_id_on_graph_visibility_time_update(false),
bmain(bmain),
scene(scene),
view_layer(view_layer),
mode(mode),
frame(BKE_scene_frame_get(scene)),
ctime(BKE_scene_ctime_get(scene)),
scene_cow(nullptr),
is_active(false),
use_visibility_optimization(true),
is_evaluating(false),
is_render_pipeline_depsgraph(false),
use_editors_update(false),
physics_relations_effector(nullptr),
update_count(0),
sync_writeback(DEG_EVALUATE_SYNC_WRITEBACK_NO)
{
BLI_spin_init(&lock);
memset(id_type_updated, 0, sizeof(id_type_updated));
memset(id_type_updated_backup, 0, sizeof(id_type_updated_backup));
memset(id_type_exist, 0, sizeof(id_type_exist));
memset(physics_relations_collision, 0, sizeof(physics_relations_collision));
add_time_source();
}
Depsgraph::~Depsgraph()
{
clear_id_nodes();
delete time_source;
BLI_spin_end(&lock);
}
/* Node Management ---------------------------- */
TimeSourceNode *Depsgraph::add_time_source()
{
if (time_source == nullptr) {
DepsNodeFactory *factory = type_get_factory(NodeType::TIMESOURCE);
time_source = static_cast<TimeSourceNode *>(factory->create_node(nullptr, "", "Time Source"));
}
return time_source;
}
TimeSourceNode *Depsgraph::find_time_source() const
{
return time_source;
}
void Depsgraph::tag_time_source()
{
time_source->tag_update(this, DEG_UPDATE_SOURCE_TIME);
}
IDNode *Depsgraph::find_id_node(const ID *id) const
{
return id_hash.lookup_default(id, nullptr);
}
IDNode *Depsgraph::add_id_node(ID *id, ID *id_cow_hint)
{
BLI_assert((id->tag & ID_TAG_COPIED_ON_EVAL) == 0);
IDNode *id_node = find_id_node(id);
if (!id_node) {
DepsNodeFactory *factory = type_get_factory(NodeType::ID_REF);
id_node = static_cast<IDNode *>(factory->create_node(id, "", id->name));
id_node->init_copy_on_write(id_cow_hint);
/* Register node in ID hash.
*
* NOTE: We address ID nodes by the original ID pointer they are
* referencing to. */
id_hash.add_new(id, id_node);
id_nodes.append(id_node);
id_type_exist[BKE_idtype_idcode_to_index(GS(id->name))] = 1;
}
return id_node;
}
template<typename FilterFunc>
static void clear_id_nodes_conditional(Depsgraph::IDDepsNodes *id_nodes, const FilterFunc &filter)
{
for (IDNode *id_node : *id_nodes) {
if (id_node->id_cow == nullptr) {
/* This means builder "stole" ownership of the evaluated
* datablock for its own dirty needs. */
continue;
}
if (id_node->id_cow == id_node->id_orig) {
/* Evaluated copy is not needed for this ID type.
*
* NOTE: Is important to not de-reference the original datablock here because it might be
* freed already (happens during main database free when some IDs are freed prior to a
* scene). */
continue;
}
if (!deg_eval_copy_is_expanded(id_node->id_cow)) {
continue;
}
const ID_Type id_type = GS(id_node->id_cow->name);
if (filter(id_type)) {
id_node->destroy();
}
}
}
void Depsgraph::clear_id_nodes()
{
/* Free memory used by ID nodes. */
/* Stupid workaround to ensure we free IDs in a proper order. */
clear_id_nodes_conditional(&id_nodes, [](ID_Type id_type) { return id_type == ID_SCE; });
clear_id_nodes_conditional(&id_nodes, [](ID_Type id_type) { return id_type != ID_PA; });
for (IDNode *id_node : id_nodes) {
delete id_node;
}
/* Clear containers. */
id_hash.clear();
id_nodes.clear();
/* Clear physics relation caches. */
clear_physics_relations(this);
light_linking_cache.clear();
}
Relation *Depsgraph::add_new_relation(Node *from, Node *to, const char *description, int flags)
{
Relation *rel = nullptr;
if (flags & RELATION_CHECK_BEFORE_ADD) {
rel = check_nodes_connected(from, to, description);
}
if (rel != nullptr) {
rel->flag |= flags;
return rel;
}
#ifndef NDEBUG
if (from->type == NodeType::OPERATION && to->type == NodeType::OPERATION) {
OperationNode *operation_from = static_cast<OperationNode *>(from);
OperationNode *operation_to = static_cast<OperationNode *>(to);
BLI_assert(operation_to->owner->type != NodeType::COPY_ON_EVAL ||
operation_from->owner->type == NodeType::COPY_ON_EVAL);
}
#endif
/* Create new relation, and add it to the graph. The type must be trivially destructible for
* `.release()` to be okay. If it weren't, we could store the relations with #destruct_ptr on
* either the `inlinks` or `outlinks`. But since so many #Relation structs are allocated, it's
* probably better for it be a simple type anyway. */
static_assert(std::is_trivially_destructible_v<Relation>);
rel = this->build_allocator.construct<Relation>(from, to, description).release();
from->outlinks.append(rel);
to->inlinks.append(rel);
rel->flag |= flags;
return rel;
}
Relation *Depsgraph::check_nodes_connected(const Node *from,
const Node *to,
const char *description)
{
for (Relation *rel : from->outlinks) {
BLI_assert(rel->from == from);
if (rel->to != to) {
continue;
}
if (description != nullptr && !STREQ(rel->name, description)) {
continue;
}
return rel;
}
return nullptr;
}
/* Low level tagging -------------------------------------- */
void Depsgraph::add_entry_tag(OperationNode *node)
{
/* Sanity check. */
if (node == nullptr) {
return;
}
/* Add to graph-level set of directly modified nodes to start searching
* from.
* NOTE: this is necessary since we have several thousand nodes to play
* with. */
entry_tags.add(node);
}
void Depsgraph::clear_all_nodes()
{
clear_id_nodes();
delete time_source;
time_source = nullptr;
/* Memory used by the build allocator is now unused. Rebuild it from scratch. */
std::destroy_at(&this->build_allocator);
new (&this->build_allocator) LinearAllocator<>();
}
ID *Depsgraph::get_cow_id(const ID *id_orig) const
{
IDNode *id_node = find_id_node(id_orig);
if (id_node == nullptr) {
/* This function is used from places where we expect ID to be either
* already a copy-on-evaluation version or have a corresponding copy-on-evaluation
* version.
*
* We try to enforce that in debug builds, for release we play a bit
* safer game here. */
if ((id_orig->tag & ID_TAG_COPIED_ON_EVAL) == 0) {
/* TODO(sergey): This is nice sanity check to have, but it fails
* in following situations:
*
* - Material has link to texture, which is not needed by new
* shading system and hence can be ignored at construction.
* - Object or mesh has material at a slot which is not used (for
* example, object has material slot by materials are set to
* object data). */
// BLI_assert_msg(0, "Request for non-existing copy-on-evaluation ID");
}
return const_cast<ID *>(id_orig);
}
return id_node->id_cow;
}
} // namespace deg
/* **************** */
/* Public Graph API */
Depsgraph *DEG_graph_new(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluationMode mode)
{
deg::Depsgraph *deg_depsgraph = new deg::Depsgraph(bmain, scene, view_layer, mode);
deg::register_graph(deg_depsgraph);
return reinterpret_cast<Depsgraph *>(deg_depsgraph);
}
void DEG_graph_replace_owners(Depsgraph *depsgraph,
Main *bmain,
Scene *scene,
ViewLayer *view_layer)
{
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(depsgraph);
const bool do_update_register = deg_graph->bmain != bmain;
if (do_update_register && deg_graph->bmain != nullptr) {
deg::unregister_graph(deg_graph);
}
deg_graph->bmain = bmain;
deg_graph->scene = scene;
deg_graph->view_layer = view_layer;
if (do_update_register) {
deg::register_graph(deg_graph);
}
}
void DEG_graph_free(Depsgraph *graph)
{
if (graph == nullptr) {
return;
}
using deg::Depsgraph;
deg::Depsgraph *deg_depsgraph = reinterpret_cast<deg::Depsgraph *>(graph);
deg::unregister_graph(deg_depsgraph);
delete deg_depsgraph;
}
bool DEG_is_evaluating(const Depsgraph *depsgraph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(depsgraph);
return deg_graph->is_evaluating;
}
bool DEG_is_active(const Depsgraph *depsgraph)
{
if (depsgraph == nullptr) {
/* Happens for such cases as work object in what_does_obaction(),
* and sine render pipeline parts. Shouldn't really be accepting
* nullptr depsgraph, but is quite hard to get proper one in those
* cases. */
return false;
}
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(depsgraph);
return deg_graph->is_active;
}
void DEG_make_active(Depsgraph *depsgraph)
{
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(depsgraph);
deg_graph->is_active = true;
/* TODO(sergey): Copy data from evaluated state to original. */
}
void DEG_make_inactive(Depsgraph *depsgraph)
{
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(depsgraph);
deg_graph->is_active = false;
}
void DEG_disable_visibility_optimization(Depsgraph *depsgraph)
{
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(depsgraph);
deg_graph->use_visibility_optimization = false;
}
uint64_t DEG_get_update_count(const Depsgraph *depsgraph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(depsgraph);
return deg_graph->update_count;
}
} // namespace blender

View File

@@ -0,0 +1,206 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Data-types for internal use in the Depsgraph
*
* All of these data-types are only really used within the "core" depsgraph.
* In particular, node types declared here form the structure of operations
* in the graph.
*/
#pragma once
#include <cstdlib>
#include <functional>
#include "MEM_guardedalloc.h"
#include "DNA_ID.h" /* for ID_Type and INDEX_ID_MAX */
#include "BLI_linear_allocator.hh"
#include "BLI_mutex.hh"
#include "BLI_set.hh"
#include "BLI_threads.h" /* for SpinLock */
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_physics.hh"
#include "intern/debug/deg_debug.h"
#include "intern/depsgraph_light_linking.hh"
namespace blender {
struct ID;
struct Scene;
struct ViewLayer;
namespace deg {
struct IDNode;
struct Node;
struct OperationNode;
struct Relation;
struct TimeSourceNode;
/* Dependency Graph object */
struct Depsgraph {
using OperationNodes = Vector<OperationNode *>;
using IDDepsNodes = Vector<IDNode *>;
Depsgraph(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluationMode mode);
~Depsgraph();
TimeSourceNode *add_time_source();
TimeSourceNode *find_time_source() const;
void tag_time_source();
IDNode *find_id_node(const ID *id) const;
IDNode *add_id_node(ID *id, ID *id_cow_hint = nullptr);
void clear_id_nodes();
/** Add new relationship between two nodes. */
Relation *add_new_relation(Node *from, Node *to, const char *description, int flags = 0);
/* Check whether two nodes are connected by relation with given
* description. Description might be nullptr to check ANY relation between
* given nodes. */
Relation *check_nodes_connected(const Node *from, const Node *to, const char *description);
/* Tag a specific node as needing updates. */
void add_entry_tag(OperationNode *node);
/* Clear storage used by all nodes. */
void clear_all_nodes();
/* Copy-on-Write Functionality ........ */
/* For given original ID get ID which is created by copy-on-evaluation system. */
ID *get_cow_id(const ID *id_orig) const;
/* Core Graph Functionality ........... */
/**
* Used to decrease the cost of allocating many small structs when building the graph. This is a
* viable strategy because the graph is rebuilt from scratch rather than changed in-place.
*/
LinearAllocator<> build_allocator;
/* <ID : IDNode> mapping from ID blocks to nodes representing these
* blocks, used for quick lookups. */
Map<const ID *, IDNode *> id_hash;
/* Ordered list of ID nodes, order matches ID allocation order.
* Used for faster iteration, especially for areas which are critical to
* keep exact order of iteration. */
IDDepsNodes id_nodes;
/* Top-level time source node. */
TimeSourceNode *time_source;
/* The graph contains data-blocks whose visibility depends on evaluation (driven or animated). */
bool has_animated_visibility;
/* Indicates whether relations needs to be updated. */
bool need_update_relations;
/* Indicates whether indirect effect of nodes on a directly visible ones needs to be updated. */
bool need_update_nodes_visibility;
/* Indicated whether IDs in this graph are to be tagged as if they first appear visible, with
* an optional tag for their animation (time) update. */
bool need_tag_id_on_graph_visibility_update;
bool need_tag_id_on_graph_visibility_time_update;
/* Indicates which ID types were updated. */
char id_type_updated[INDEX_ID_MAX];
/* Accumulate id type updates from multiple update passes. */
char id_type_updated_backup[INDEX_ID_MAX];
/* Indicates type of IDs present in the depsgraph. */
char id_type_exist[INDEX_ID_MAX];
/* Quick-Access Temp Data ............. */
/* Nodes which have been tagged as "directly modified". */
Set<OperationNode *> entry_tags;
/* Convenience Data ................... */
/* XXX: should be collected after building (if actually needed?) */
/* All operation nodes, sorted in order of single-thread traversal order. */
OperationNodes operations;
/* Spin lock for threading-critical operations.
* Mainly used by graph evaluation. */
SpinLock lock;
/* Main, scene, layer, mode this dependency graph is built for. */
Main *bmain;
Scene *scene;
ViewLayer *view_layer;
eEvaluationMode mode;
/* Time at which dependency graph is being or was last evaluated.
* frame is the value before, and ctime the value after time remapping. */
float frame;
float ctime;
/* Evaluated version of datablocks we access a lot.
* Stored here to save us form doing hash lookup. */
Scene *scene_cow;
/* Active dependency graph is a dependency graph which is used by the
* currently active window. When dependency graph is active, it is allowed
* for evaluation functions to write animation f-curve result, drivers
* result and other selective things (object matrix?) to original object.
*
* This way we simplify operators, which don't need to worry about where
* to read stuff from. */
bool is_active;
/* Optimize out evaluation of operations which affect hidden objects or disabled modifiers. */
bool use_visibility_optimization;
DepsgraphDebug debug;
bool is_evaluating;
/* Is set to truth for dependency graph which are used for post-processing (compositor and
* sequencer).
* Such dependency graph needs all view layers (so render pipeline can access names), but it
* does not need any bases. */
bool is_render_pipeline_depsgraph;
/* Notify editors about changes to IDs in this depsgraph. */
bool use_editors_update;
/* Cached list of colliders/effectors for collections and the scene
* created along with relations, for fast lookup during evaluation. */
Map<const ID *, ListBaseT<EffectorRelation> *> *physics_relations_effector;
Map<const ID *, ListBaseT<CollisionRelation> *>
*physics_relations_collision[DEG_PHYSICS_COLLISION_NUM];
light_linking::Cache light_linking_cache;
/* The number of times this graph has been evaluated. */
uint64_t update_count;
/* If this mode does not allow writing back to original data any callbacks will be discarded. */
DepsgraphEvaluateSyncWriteback sync_writeback;
/**
* Stores functions that can be called after depsgraph evaluation to writeback some changes to
* original data. Also see `DEG_depsgraph_writeback_sync.hh`.
*/
Vector<std::function<void()>> sync_writeback_callbacks;
/** Needs to be locked when adding a writeback callback during evaluation. */
Mutex sync_writeback_callbacks_mutex;
MEM_CXX_CLASS_ALLOC_FUNCS("Depsgraph");
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,358 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Methods for constructing depsgraph.
*/
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "DNA_cachefile_types.h"
#include "DNA_camera_types.h"
#include "DNA_collection_types.h"
#include "DNA_layer_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_vfont_types.h"
#include "BKE_collection.hh"
#include "BKE_global.hh"
#include "BKE_main.hh"
#include "BKE_scene.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_debug.hh"
#include "builder/deg_builder_relations.h"
#include "builder/pipeline_all_objects.h"
#include "builder/pipeline_compositor.h"
#include "builder/pipeline_from_collection.h"
#include "builder/pipeline_from_ids.h"
#include "builder/pipeline_render.h"
#include "builder/pipeline_view_layer.h"
#include "intern/debug/deg_debug.h"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_operation.hh"
#include "intern/depsgraph_registry.hh"
#include "intern/depsgraph_relation.hh"
#include "intern/depsgraph_tag.hh"
#include "intern/depsgraph_type.hh"
namespace blender {
/* ****************** */
/* External Build API */
static deg::NodeType deg_build_scene_component_type(eDepsSceneComponentType component)
{
switch (component) {
case DEG_SCENE_COMP_PARAMETERS:
return deg::NodeType::PARAMETERS;
case DEG_SCENE_COMP_ANIMATION:
return deg::NodeType::ANIMATION;
case DEG_SCENE_COMP_SEQUENCER:
return deg::NodeType::SEQUENCER;
case DEG_SCENE_COMP_COMPOSITOR:
return deg::NodeType::COMPOSITOR;
}
return deg::NodeType::UNDEFINED;
}
static deg::DepsNodeHandle *get_node_handle(DepsNodeHandle *node_handle)
{
return reinterpret_cast<deg::DepsNodeHandle *>(node_handle);
}
void DEG_add_scene_relation(DepsNodeHandle *node_handle,
Scene *scene,
eDepsSceneComponentType component,
const char *description)
{
deg::NodeType type = deg_build_scene_component_type(component);
deg::ComponentKey comp_key(&scene->id, type);
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg_node_handle->builder->add_node_handle_relation(comp_key, deg_node_handle, description);
}
static void add_camera_parameters_relation(DepsNodeHandle *node_handle,
Camera *camera,
const char *description)
{
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg::ComponentKey parameters_key(&camera->id, deg::NodeType::PARAMETERS);
deg_node_handle->builder->add_node_handle_relation(parameters_key, deg_node_handle, description);
}
void DEG_add_scene_camera_relation(DepsNodeHandle *node_handle,
Scene *scene,
eDepsObjectComponentType component,
const char *description)
{
if (scene->camera != nullptr) {
DEG_add_object_relation(node_handle, scene->camera, component, description);
if (scene->camera->type == OB_CAMERA) {
add_camera_parameters_relation(
node_handle, reinterpret_cast<Camera *>(scene->camera->data), description);
}
}
/* Like DepsgraphNodeBuilder::build_scene_camera(), we also need to account for other cameras
* referenced by markers. */
for (TimeMarker &marker : scene->markers) {
if (!ELEM(marker.camera, nullptr, scene->camera)) {
DEG_add_object_relation(node_handle, marker.camera, component, description);
if (marker.camera->type == OB_CAMERA) {
add_camera_parameters_relation(
node_handle, reinterpret_cast<Camera *>(marker.camera->data), description);
}
}
}
}
void DEG_add_object_relation(DepsNodeHandle *node_handle,
Object *object,
eDepsObjectComponentType component,
const char *description)
{
deg::NodeType type = deg::nodeTypeFromObjectComponent(component);
deg::ComponentKey comp_key(&object->id, type);
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg_node_handle->builder->add_node_handle_relation(comp_key, deg_node_handle, description);
}
bool DEG_object_has_geometry_component(Object *object)
{
return deg::geometry_tag_to_component(&object->id) == deg::NodeType::GEOMETRY;
}
void DEG_add_collection_geometry_relation(DepsNodeHandle *node_handle,
Collection *collection,
const char *description)
{
deg::OperationKey operation_key{
&collection->id, deg::NodeType::GEOMETRY, deg::OperationCode::GEOMETRY_EVAL_DONE};
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg_node_handle->builder->add_node_handle_relation(operation_key, deg_node_handle, description);
}
void DEG_add_collection_geometry_customdata_mask(DepsNodeHandle *node_handle,
Collection *collection,
const CustomData_MeshMasks *masks)
{
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection, ob) {
DEG_add_customdata_mask(node_handle, ob, masks);
if (ob->type == OB_EMPTY && ob->instance_collection != nullptr) {
DEG_add_collection_geometry_customdata_mask(node_handle, ob->instance_collection, masks);
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
void DEG_add_node_tree_output_relation(DepsNodeHandle *node_handle,
bNodeTree *node_tree,
const char *description)
{
deg::OperationKey ntree_output_key(
&node_tree->id, deg::NodeType::NTREE_OUTPUT, deg::OperationCode::NTREE_OUTPUT);
deg::OperationKey ntree_preprocess_key(&node_tree->id,
deg::NodeType::NTREE_GEOMETRY_PREPROCESS,
deg::OperationCode::NTREE_GEOMETRY_PREPROCESS);
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg_node_handle->builder->add_node_handle_relation(
ntree_output_key, deg_node_handle, description);
deg_node_handle->builder->add_node_handle_relation(
ntree_preprocess_key, deg_node_handle, description, deg::RELATION_FLAG_NO_FLUSH);
}
void DEG_add_object_cache_relation(DepsNodeHandle *node_handle,
CacheFile *cache_file,
eDepsObjectComponentType component,
const char *description)
{
deg::NodeType type = deg::nodeTypeFromObjectComponent(component);
deg::ComponentKey comp_key(&cache_file->id, type);
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg_node_handle->builder->add_node_handle_relation(comp_key, deg_node_handle, description);
}
void DEG_add_bone_relation(DepsNodeHandle *node_handle,
Object *object,
const char *bone_name,
eDepsObjectComponentType component,
const char *description)
{
deg::NodeType type = deg::nodeTypeFromObjectComponent(component);
deg::ComponentKey comp_key(&object->id, type, bone_name);
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg_node_handle->builder->add_node_handle_relation(comp_key, deg_node_handle, description);
}
void DEG_add_vfont_relation(DepsNodeHandle *handle, VFont *vfont, const char *description)
{
deg::OperationKey operation_key(
&vfont->id, deg::NodeType::PARAMETERS, deg::OperationCode::PARAMETERS_EXIT);
deg::DepsNodeHandle *deg_node_handle = get_node_handle(handle);
deg_node_handle->builder->add_node_handle_relation(operation_key, deg_node_handle, description);
}
void DEG_add_object_pointcache_relation(DepsNodeHandle *node_handle,
Object *object,
eDepsObjectComponentType component,
const char *description)
{
deg::NodeType type = deg::nodeTypeFromObjectComponent(component);
deg::ComponentKey comp_key(&object->id, type);
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg::DepsgraphRelationBuilder *relation_builder = deg_node_handle->builder;
/* Add relation from source to the node handle. */
relation_builder->add_node_handle_relation(comp_key, deg_node_handle, description);
/* Node deduct point cache component and connect source to it. */
ID *id = DEG_get_id_from_handle(node_handle);
deg::ComponentKey point_cache_key(id, deg::NodeType::POINT_CACHE);
deg::Relation *rel = relation_builder->add_relation(comp_key, point_cache_key, "Point Cache");
if (rel != nullptr) {
rel->flag |= deg::RELATION_FLAG_FLUSH_USER_EDIT_ONLY;
}
else {
fprintf(stderr, "Error in point cache relation from %s to ^%s.\n", object->id.name, id->name);
}
}
void DEG_add_generic_id_relation(DepsNodeHandle *node_handle, ID *id, const char *description)
{
deg::OperationKey operation_key(
id, deg::NodeType::GENERIC_DATABLOCK, deg::OperationCode::GENERIC_DATABLOCK_UPDATE);
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg_node_handle->builder->add_node_handle_relation(operation_key, deg_node_handle, description);
}
void DEG_add_depends_on_transform_relation(DepsNodeHandle *node_handle, const char *description)
{
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg_node_handle->builder->add_depends_on_transform_relation(deg_node_handle, description);
}
void DEG_add_time_source_relation(DepsNodeHandle *node_handle, const char *description)
{
const deg::TimeSourceKey time_src_key;
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg_node_handle->builder->add_node_handle_relation(time_src_key, deg_node_handle, description);
}
void DEG_add_special_eval_flag(DepsNodeHandle *node_handle, ID *id, uint32_t flag)
{
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg_node_handle->builder->add_special_eval_flag(id, flag);
}
void DEG_add_customdata_mask(DepsNodeHandle *node_handle,
Object *object,
const CustomData_MeshMasks *masks)
{
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg_node_handle->builder->add_customdata_mask(object, deg::DEGCustomDataMeshMasks(masks));
}
ID *DEG_get_id_from_handle(DepsNodeHandle *node_handle)
{
deg::DepsNodeHandle *deg_handle = get_node_handle(node_handle);
return deg_handle->node->owner->owner->id_orig;
}
Depsgraph *DEG_get_graph_from_handle(DepsNodeHandle *node_handle)
{
deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle);
deg::DepsgraphRelationBuilder *relation_builder = deg_node_handle->builder;
return reinterpret_cast<Depsgraph *>(relation_builder->getGraph());
}
/* ******************** */
/* Graph Building API's */
void DEG_graph_build_from_view_layer(Depsgraph *graph)
{
deg::ViewLayerBuilderPipeline builder(graph);
builder.build();
}
void DEG_graph_build_for_all_objects(Depsgraph *graph)
{
deg::AllObjectsBuilderPipeline builder(graph);
builder.build();
}
void DEG_graph_build_for_render_pipeline(Depsgraph *graph)
{
deg::RenderBuilderPipeline builder(graph);
builder.build();
}
void DEG_graph_build_for_compositor_preview(Depsgraph *graph)
{
deg::CompositorBuilderPipeline builder(graph);
builder.build();
}
void DEG_graph_build_from_ids(Depsgraph *graph, Span<ID *> ids)
{
deg::FromIDsBuilderPipeline builder(graph, ids);
builder.build();
}
void DEG_graph_build_from_collection(Depsgraph *graph, Collection *collection)
{
deg::FromCollectionBuilderPipeline builder(graph, collection);
builder.build();
}
void DEG_graph_tag_relations_update(Depsgraph *graph)
{
DEG_DEBUG_PRINTF(graph, TAG, "%s: Tagging relations for update.\n", __func__);
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(graph);
deg_graph->need_update_relations = true;
/* NOTE: When relations are updated, it's quite possible that we've got new bases in the scene.
* This means, we need to re-create flat array of bases in view layer. */
/* TODO(sergey): It is expected that bases manipulation tags scene for update to tag bases array
* for re-creation. Once it is ensured to happen from all places this implicit tag can be
* removed. */
deg::IDNode *id_node = deg_graph->find_id_node(&deg_graph->scene->id);
if (id_node != nullptr) {
graph_id_tag_update(deg_graph->bmain,
deg_graph,
&deg_graph->scene->id,
ID_RECALC_BASE_FLAGS | ID_RECALC_HIERARCHY,
deg::DEG_UPDATE_SOURCE_RELATIONS);
}
}
void DEG_graph_relations_update(Depsgraph *graph)
{
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(graph);
if (!deg_graph->need_update_relations) {
/* Graph is up to date, nothing to do. */
return;
}
DEG_graph_build_from_view_layer(graph);
}
void DEG_relations_tag_update(Main *bmain)
{
DEG_GLOBAL_DEBUG_PRINTF(TAG, "%s: Tagging relations for update.\n", __func__);
for (deg::Depsgraph *depsgraph : deg::get_all_registered_graphs(bmain)) {
DEG_graph_tag_relations_update(reinterpret_cast<Depsgraph *>(depsgraph));
}
}
} // namespace blender

View File

@@ -0,0 +1,396 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Implementation of tools for debugging the depsgraph
*/
#include "DNA_scene_types.h"
#include "BKE_global.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_debug.hh"
#include "DEG_depsgraph_query.hh"
#include "intern/debug/deg_debug.h"
#include "intern/depsgraph.hh"
#include "intern/depsgraph_relation.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_time.hh"
#include "BLI_math_bits.h"
namespace blender {
void DEG_debug_flags_set(Depsgraph *depsgraph, int flags)
{
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(depsgraph);
deg_graph->debug.flags = flags;
}
int DEG_debug_flags_get(const Depsgraph *depsgraph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(depsgraph);
return deg_graph->debug.flags;
}
void DEG_debug_name_set(Depsgraph *depsgraph, const char *name)
{
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(depsgraph);
deg_graph->debug.name = name;
}
const char *DEG_debug_name_get(Depsgraph *depsgraph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(depsgraph);
return deg_graph->debug.name.c_str();
}
bool DEG_debug_compare(const Depsgraph *graph1, const Depsgraph *graph2)
{
BLI_assert(graph1 != nullptr);
BLI_assert(graph2 != nullptr);
const deg::Depsgraph *deg_graph1 = reinterpret_cast<const deg::Depsgraph *>(graph1);
const deg::Depsgraph *deg_graph2 = reinterpret_cast<const deg::Depsgraph *>(graph2);
if (deg_graph1->operations.size() != deg_graph2->operations.size()) {
return false;
}
/* TODO(sergey): Currently we only do real stupid check,
* which is fast but which isn't 100% reliable.
*
* Would be cool to make it more robust, but it's good enough
* for now. Also, proper graph check is actually NP-complex
* problem. */
return true;
}
bool DEG_debug_graph_relations_validate(Depsgraph *graph,
Main *bmain,
Scene *scene,
ViewLayer *view_layer)
{
Depsgraph *temp_depsgraph = DEG_graph_new(bmain, scene, view_layer, DEG_get_mode(graph));
bool valid = true;
DEG_graph_build_from_view_layer(temp_depsgraph);
if (!DEG_debug_compare(temp_depsgraph, graph)) {
fprintf(stderr, "ERROR! Depsgraph wasn't tagged for update when it should have!\n");
BLI_assert_msg(0, "This should not happen!");
valid = false;
}
DEG_graph_free(temp_depsgraph);
return valid;
}
bool DEG_debug_consistency_check(Depsgraph *graph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
/* Validate links exists in both directions. */
for (deg::OperationNode *node : deg_graph->operations) {
for (deg::Relation *rel : node->outlinks) {
int counter1 = 0;
for (deg::Relation *tmp_rel : node->outlinks) {
if (tmp_rel == rel) {
counter1++;
}
}
int counter2 = 0;
for (deg::Relation *tmp_rel : rel->to->inlinks) {
if (tmp_rel == rel) {
counter2++;
}
}
if (counter1 != counter2) {
printf(
"Relation exists in outgoing direction but not in "
"incoming (%d vs. %d).\n",
counter1,
counter2);
return false;
}
}
}
for (deg::OperationNode *node : deg_graph->operations) {
for (deg::Relation *rel : node->inlinks) {
int counter1 = 0;
for (deg::Relation *tmp_rel : node->inlinks) {
if (tmp_rel == rel) {
counter1++;
}
}
int counter2 = 0;
for (deg::Relation *tmp_rel : rel->from->outlinks) {
if (tmp_rel == rel) {
counter2++;
}
}
if (counter1 != counter2) {
printf("Relation exists in incoming direction but not in outcoming (%d vs. %d).\n",
counter1,
counter2);
}
}
}
/* Validate node valency calculated in both directions. */
for (deg::OperationNode *node : deg_graph->operations) {
node->num_links_pending = 0;
node->custom_flags = 0;
}
for (deg::OperationNode *node : deg_graph->operations) {
if (node->custom_flags) {
printf("Node %s is twice in the operations!\n", node->identifier().c_str());
return false;
}
for (deg::Relation *rel : node->outlinks) {
if (rel->to->type == deg::NodeType::OPERATION) {
deg::OperationNode *to = static_cast<deg::OperationNode *>(rel->to);
BLI_assert(to->num_links_pending < to->inlinks.size());
++to->num_links_pending;
}
}
node->custom_flags = 1;
}
for (deg::OperationNode *node : deg_graph->operations) {
int num_links_pending = 0;
for (deg::Relation *rel : node->inlinks) {
if (rel->from->type == deg::NodeType::OPERATION) {
num_links_pending++;
}
}
if (node->num_links_pending != num_links_pending) {
printf("Valency mismatch: %s, %u != %d\n",
node->identifier().c_str(),
node->num_links_pending,
num_links_pending);
printf("Number of inlinks: %d\n", int(node->inlinks.size()));
return false;
}
}
return true;
}
/* ------------------------------------------------ */
void DEG_stats_simple(const Depsgraph *graph,
size_t *r_outer,
size_t *r_operations,
size_t *r_relations)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
/* number of operations */
if (r_operations) {
/* All operations should be in this list, allowing us to count the total
* number of nodes. */
*r_operations = deg_graph->operations.size();
}
/* Count number of outer nodes and/or relations between these. */
if (r_outer || r_relations) {
size_t tot_outer = 0;
size_t tot_rels = 0;
for (deg::IDNode *id_node : deg_graph->id_nodes) {
tot_outer++;
for (deg::ComponentNode *comp_node : id_node->components.values()) {
tot_outer++;
for (deg::OperationNode *op_node : comp_node->operations) {
tot_rels += op_node->inlinks.size();
}
}
}
deg::TimeSourceNode *time_source = deg_graph->find_time_source();
if (time_source != nullptr) {
tot_rels += time_source->inlinks.size();
}
if (r_relations) {
*r_relations = tot_rels;
}
if (r_outer) {
*r_outer = tot_outer;
}
}
}
static std::string depsgraph_name_for_logging(Depsgraph *depsgraph)
{
const char *name = DEG_debug_name_get(depsgraph);
if (name[0] == '\0') {
return "";
}
return "[" + std::string(name) + "]: ";
}
void DEG_debug_print_begin(Depsgraph *depsgraph)
{
fprintf(stdout, "%s", depsgraph_name_for_logging(depsgraph).c_str());
}
void DEG_debug_print_eval(Depsgraph *depsgraph,
const char *function_name,
const char *object_name,
const void *object_address)
{
if ((DEG_debug_flags_get(depsgraph) & G_DEBUG_DEPSGRAPH_EVAL) == 0) {
return;
}
fprintf(stdout,
"%s%s on %s %s(%p)%s\n",
depsgraph_name_for_logging(depsgraph).c_str(),
function_name,
object_name,
deg::color_for_pointer(object_address).c_str(),
object_address,
deg::color_end().c_str());
fflush(stdout);
}
void DEG_debug_print_eval_subdata(Depsgraph *depsgraph,
const char *function_name,
const char *object_name,
const void *object_address,
const char *subdata_comment,
const char *subdata_name,
const void *subdata_address)
{
if ((DEG_debug_flags_get(depsgraph) & G_DEBUG_DEPSGRAPH_EVAL) == 0) {
return;
}
fprintf(stdout,
"%s%s on %s %s(%p)%s %s %s %s(%p)%s\n",
depsgraph_name_for_logging(depsgraph).c_str(),
function_name,
object_name,
deg::color_for_pointer(object_address).c_str(),
object_address,
deg::color_end().c_str(),
subdata_comment,
subdata_name,
deg::color_for_pointer(subdata_address).c_str(),
subdata_address,
deg::color_end().c_str());
fflush(stdout);
}
void DEG_debug_print_eval_subdata_index(Depsgraph *depsgraph,
const char *function_name,
const char *object_name,
const void *object_address,
const char *subdata_comment,
const char *subdata_name,
const void *subdata_address,
const int subdata_index)
{
if ((DEG_debug_flags_get(depsgraph) & G_DEBUG_DEPSGRAPH_EVAL) == 0) {
return;
}
fprintf(stdout,
"%s%s on %s %s(%p)%s %s %s[%d] %s(%p)%s\n",
depsgraph_name_for_logging(depsgraph).c_str(),
function_name,
object_name,
deg::color_for_pointer(object_address).c_str(),
object_address,
deg::color_end().c_str(),
subdata_comment,
subdata_name,
subdata_index,
deg::color_for_pointer(subdata_address).c_str(),
subdata_address,
deg::color_end().c_str());
fflush(stdout);
}
void DEG_debug_print_eval_parent_typed(Depsgraph *depsgraph,
const char *function_name,
const char *object_name,
const void *object_address,
const char *parent_comment,
const char *parent_name,
const void *parent_address)
{
if ((DEG_debug_flags_get(depsgraph) & G_DEBUG_DEPSGRAPH_EVAL) == 0) {
return;
}
fprintf(stdout,
"%s%s on %s %s(%p) [%s] %s %s %s(%p)%s\n",
depsgraph_name_for_logging(depsgraph).c_str(),
function_name,
object_name,
deg::color_for_pointer(object_address).c_str(),
object_address,
deg::color_end().c_str(),
parent_comment,
parent_name,
deg::color_for_pointer(parent_address).c_str(),
parent_address,
deg::color_end().c_str());
fflush(stdout);
}
void DEG_debug_print_eval_time(Depsgraph *depsgraph,
const char *function_name,
const char *object_name,
const void *object_address,
float time)
{
if ((DEG_debug_flags_get(depsgraph) & G_DEBUG_DEPSGRAPH_EVAL) == 0) {
return;
}
fprintf(stdout,
"%s%s on %s %s(%p)%s at time %f\n",
depsgraph_name_for_logging(depsgraph).c_str(),
function_name,
object_name,
deg::color_for_pointer(object_address).c_str(),
object_address,
deg::color_end().c_str(),
time);
fflush(stdout);
}
static std::string stringify_append_bit(const std::string &str, IDRecalcFlag tag)
{
const char *tag_name = DEG_update_tag_as_string(tag);
if (tag_name == nullptr) {
return str;
}
std::string result = str;
if (!result.empty()) {
result += ", ";
}
result += tag_name;
return result;
}
std::string DEG_stringify_recalc_flags(uint flags)
{
if (flags == 0) {
return "NONE";
}
std::string result;
uint current_flag = flags;
/* Special cases to avoid ALL flags from being split into individual bits. */
if ((current_flag & ID_RECALC_PSYS_ALL) == ID_RECALC_PSYS_ALL) {
result = stringify_append_bit(result, ID_RECALC_PSYS_ALL);
}
/* Handle all the rest of the flags. */
while (current_flag != 0) {
IDRecalcFlag tag = IDRecalcFlag(1 << bitscan_forward_clear_uint(&current_flag));
result = stringify_append_bit(result, tag);
}
return result;
}
} // namespace blender

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Evaluation engine entry-points for Depsgraph Engine.
*/
#include "BKE_scene.hh"
#include "DNA_scene_types.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "DEG_depsgraph_writeback_sync.hh"
#include "intern/eval/deg_eval.h"
#include "intern/eval/deg_eval_flush.h"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_operation.hh"
#include "intern/depsgraph.hh"
#include "intern/depsgraph_tag.hh"
namespace blender {
static void deg_flush_updates_and_refresh(deg::Depsgraph *deg_graph,
const DepsgraphEvaluateSyncWriteback sync_writeback)
{
BLI_assert(!deg_graph->need_update_relations);
/* Update the time on the cow scene. */
if (deg_graph->scene_cow) {
BKE_scene_frame_set(deg_graph->scene_cow, deg_graph->frame);
}
deg::graph_tag_ids_for_visible_update(deg_graph);
deg::deg_graph_flush_updates(deg_graph);
BLI_assert(deg_graph->sync_writeback_callbacks.is_empty());
deg_graph->sync_writeback = sync_writeback;
deg::deg_evaluate_on_refresh(deg_graph);
if ((deg_graph->sync_writeback == DEG_EVALUATE_SYNC_WRITEBACK_YES) && deg_graph->is_active) {
for (std::function<void()> &fn : deg_graph->sync_writeback_callbacks) {
fn();
}
}
deg_graph->sync_writeback = DEG_EVALUATE_SYNC_WRITEBACK_NO;
deg_graph->sync_writeback_callbacks.clear();
}
void DEG_evaluate_on_refresh(Depsgraph *graph, const DepsgraphEvaluateSyncWriteback sync_writeback)
{
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(graph);
const Scene *scene = DEG_get_input_scene(graph);
const float frame = BKE_scene_frame_get(scene);
const float ctime = BKE_scene_ctime_get(scene);
if (deg_graph->frame != frame || ctime != deg_graph->ctime) {
deg_graph->tag_time_source();
deg_graph->frame = frame;
deg_graph->ctime = ctime;
}
else if (scene->id.recalc & ID_RECALC_FRAME_CHANGE) {
/* Comparing depsgraph & scene frame fails in the case of undo,
* since the undo state is stored before updates from the frame change have been applied.
* In this case reading back the undo state will behave as if no updates on frame change
* is needed as the #Depsgraph.ctime & frame will match the values in the input scene.
* Use #ID_RECALC_FRAME_CHANGE to detect that recalculation is necessary. see: #66913. */
deg_graph->tag_time_source();
}
deg_flush_updates_and_refresh(deg_graph, sync_writeback);
}
void DEG_evaluate_on_framechange(Depsgraph *graph,
float frame,
const DepsgraphEvaluateSyncWriteback sync_writeback)
{
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(graph);
const Scene *scene = DEG_get_input_scene(graph);
deg_graph->tag_time_source();
deg_graph->frame = frame;
deg_graph->ctime = BKE_scene_frame_to_ctime(scene, frame);
deg_flush_updates_and_refresh(deg_graph, sync_writeback);
}
} // namespace blender

View File

@@ -0,0 +1,493 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Light linking utilities. */
#include "intern/depsgraph_light_linking.hh"
#include "MEM_guardedalloc.h"
#include "BLI_hash.hh"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "DNA_collection_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DEG_depsgraph_light_linking.hh"
#include "DEG_depsgraph_query.hh"
#include "intern/depsgraph.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Public C++ API
* \{ */
namespace deg::light_linking {
void eval_runtime_data(const ::blender::Depsgraph *depsgraph, Object &object_eval)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(depsgraph);
deg_graph->light_linking_cache.eval_runtime_data(object_eval);
}
} // namespace deg::light_linking
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal builder API
* \{ */
namespace {
/* Check whether the ID is suitable to be an input of the dependency graph. */
/* TODO(sergey): Move the function and check to a more generic place. */
#ifndef NDEBUG
bool is_valid_input_id(const ID &id)
{
return (id.tag & ID_TAG_LOCALIZED) || DEG_is_original(&id);
}
#endif
} // namespace
namespace deg::light_linking {
using LightSet = internal::LightSet;
using EmitterData = internal::EmitterData;
using EmitterDataMap = internal::EmitterDataMap;
using EmitterSetMembership = internal::EmitterSetMembership;
using LinkingData = internal::LinkingData;
namespace internal {
namespace {
/* Helper class which takes care of allocating an unique light set IDs, performing checks for
* overflows. */
class LightSetIDManager {
using LightSet = internal::LightSet;
public:
explicit LightSetIDManager(const Scene &scene) : scene_(scene) {}
bool get(const LightSet &light_set, uint64_t &id)
{
/* Performance note.
*
* Always ensure the light set data exists in the map, even when an overflow happens. This has
* a downside of potentially higher memory usage and when there are many emitters with light
* linking, but it avoids distinct lookup + add fore the normal cases. */
const uint64_t light_set_id = light_set_id_map_.lookup_or_add_cb(light_set, [&]() {
const uint64_t new_light_set_id = next_light_set_id_++;
if (new_light_set_id == LightSet::MAX_ID + 1) {
printf("Maximum number of light linking sets (%d) exceeded scene \"%s\".\n",
LightSet::MAX_ID + 1,
scene_.id.name + 2);
}
return new_light_set_id;
});
id = light_set_id;
return id <= LightSet::MAX_ID;
}
private:
const Scene &scene_;
/* Next unique ID of a light set. */
uint64_t next_light_set_id_ = LightSet::DEFAULT_ID + 1;
/* Map from a link set to its original id. */
Map<internal::LightSet, uint64_t> light_set_id_map_;
};
} // namespace
/* LightSet */
bool LightSet::operator==(const LightSet &other) const
{
return include_collection_mask == other.include_collection_mask &&
exclude_collection_mask == other.exclude_collection_mask;
}
uint64_t LightSet::hash() const
{
return get_default_hash(get_default_hash(include_collection_mask),
get_default_hash(exclude_collection_mask));
}
/* EmitterSetMembership */
uint64_t EmitterSetMembership::get_mask() const
{
const uint64_t effective_included_mask = included_sets_mask ? included_sets_mask :
SET_MEMBERSHIP_ALL;
return effective_included_mask & ~excluded_sets_mask;
}
/* EmitterDataMap. */
void EmitterDataMap::clear()
{
emitter_data_map_.clear();
next_collection_id_ = 0;
}
EmitterData *EmitterDataMap::ensure_data_if_possible(const Scene &scene, const Object &emitter)
{
BLI_assert(is_valid_input_id(emitter.id));
const Collection *collection = get_collection(emitter);
BLI_assert(collection);
/* Performance note.
*
* Always ensure the emitter data exists in the map, even when an overflow happens. This has a
* downside of potentially higher memory usage when there are many emitters with light linking,
* but it avoids distinct lookup + add fore the normal cases.
*
* On the API level the function always returns nullptr on overflow, so it is more of an internal
* behavior. */
EmitterData &emitter_data = emitter_data_map_.lookup_or_add_cb(collection, [&]() {
const uint64_t collection_id = next_collection_id_++;
EmitterData new_emitter_data;
if (collection_id > EmitterData::MAX_COLLECTION_ID) {
if (collection_id == EmitterData::MAX_COLLECTION_ID + 1) {
printf("Maximum number of light linking collections (%d) exceeded in scene \"%s\".\n",
EmitterData::MAX_COLLECTION_ID + 1,
scene.id.name + 2);
}
new_emitter_data.collection_mask = 0;
}
else {
new_emitter_data.collection_mask = uint64_t(1) << collection_id;
}
return new_emitter_data;
});
if (!emitter_data.collection_mask) {
return nullptr;
}
return &emitter_data;
}
const EmitterData *EmitterDataMap::get_data(const Object &emitter) const
{
const Collection *collection_eval = get_collection(emitter);
if (!collection_eval) {
return nullptr;
}
const Collection *collection_orig = DEG_get_original(collection_eval);
return emitter_data_map_.lookup_ptr(collection_orig);
}
bool EmitterDataMap::can_skip_emitter(const Object &emitter) const
{
BLI_assert(is_valid_input_id(emitter.id));
const Collection *collection = get_collection(emitter);
if (!collection) {
return true;
}
return emitter_data_map_.contains(collection);
}
/* LinkingData */
void LinkingData::clear()
{
light_linked_sets_.clear();
object_light_sets_.clear();
}
void LinkingData::link_object(const EmitterData &emitter_data,
const eCollectionLightLinkingState link_state,
const Object &object)
{
LightSet &light_set = ensure_light_set_for(object);
switch (link_state) {
case COLLECTION_LIGHT_LINKING_STATE_INCLUDE:
light_set.include_collection_mask |= emitter_data.collection_mask;
light_set.exclude_collection_mask &= ~emitter_data.collection_mask;
break;
case COLLECTION_LIGHT_LINKING_STATE_EXCLUDE:
light_set.exclude_collection_mask |= emitter_data.collection_mask;
light_set.include_collection_mask &= ~emitter_data.collection_mask;
break;
}
}
LightSet &LinkingData::ensure_light_set_for(const Object &object)
{
BLI_assert(is_valid_input_id(object.id));
return light_linked_sets_.lookup_or_add_as(&object);
}
void LinkingData::clear_after_build()
{
light_linked_sets_.clear();
}
void LinkingData::end_build(const Scene &scene, EmitterDataMap &emitter_data_map)
{
LightSetIDManager light_set_id_manager(scene);
for (const auto it : light_linked_sets_.items()) {
const Object *receiver = it.key;
LightSet &light_set = it.value;
uint64_t light_set_id;
if (!light_set_id_manager.get(light_set, light_set_id)) {
continue;
}
const uint64_t light_set_mask = uint64_t(1) << light_set_id;
object_light_sets_.add(receiver, light_set_id);
update_emitters_membership(emitter_data_map, light_set, light_set_mask);
}
clear_after_build();
}
void LinkingData::update_emitters_membership(EmitterDataMap &emitter_data_map,
const LightSet &light_set,
const uint64_t light_set_mask)
{
for (EmitterData &emitter_data : emitter_data_map.values()) {
EmitterSetMembership &set_membership = get_emitter_set_membership(emitter_data);
if (emitter_data.collection_mask & light_set.include_collection_mask) {
set_membership.included_sets_mask |= light_set_mask;
}
if (emitter_data.collection_mask & light_set.exclude_collection_mask) {
set_membership.excluded_sets_mask |= light_set_mask;
}
}
}
uint64_t LinkingData::get_light_set_for(const Object &object) const
{
const Object *object_orig = DEG_get_original(&object);
return object_light_sets_.lookup_default(object_orig, LightSet::DEFAULT_ID);
}
} // namespace internal
namespace {
/* Iterate over all objects of the collection and invoke the given callback with two arguments:
* the given collection light linking settings, and the object (passed as reference).
*
* Note that if an object is reachable from multiple children collection the callback is invoked
* for all of them. */
template<class Proc>
void foreach_light_collection_object_inner(const CollectionLightLinking &collection_light_linking,
const Collection &collection,
Proc &&callback)
{
for (const CollectionChild &collection_child : collection.children) {
foreach_light_collection_object_inner(
collection_light_linking, *collection_child.collection, callback);
}
for (const CollectionObject &collection_object : collection.gobject) {
callback(collection_light_linking, *collection_object.ob);
}
}
/* Iterate over all objects of the collection and invoke the given callback with two arguments:
* CollectionLightLinking and the actual Object (passed as reference).
*
* The CollectionLightLinking denotes the effective light linking settings of the object. It comes
* from the first level of hierarchy from the given collection.
*
* Note that if an object is reachable from multiple children collection the callback is invoked
* for all of them. */
template<class Proc>
void foreach_light_collection_object(const Collection &collection, Proc &&callback)
{
for (const CollectionChild &collection_child : collection.children) {
foreach_light_collection_object_inner(
collection_child.light_linking, *collection_child.collection, callback);
}
for (const CollectionObject &collection_object : collection.gobject) {
callback(collection_object.light_linking, *collection_object.ob);
}
}
} // namespace
void Cache::clear()
{
light_emitter_data_map_.clear();
shadow_emitter_data_map_.clear();
light_linking_.clear();
shadow_linking_.clear();
}
void Cache::add_emitter(const Scene &scene, const Object &emitter)
{
BLI_assert(is_valid_input_id(emitter.id));
add_light_linking_emitter(scene, emitter);
add_shadow_linking_emitter(scene, emitter);
}
void Cache::add_light_linking_emitter(const Scene &scene, const Object &emitter)
{
BLI_assert(is_valid_input_id(emitter.id));
if (light_emitter_data_map_.can_skip_emitter(emitter)) {
return;
}
const EmitterData *light_emitter_data = light_emitter_data_map_.ensure_data_if_possible(scene,
emitter);
if (light_emitter_data) {
foreach_light_collection_object(
*emitter.light_linking->receiver_collection,
[&](const CollectionLightLinking &collection_light_linking, const Object &receiver) {
add_receiver_object(*light_emitter_data, collection_light_linking, receiver);
});
}
}
void Cache::add_shadow_linking_emitter(const Scene &scene, const Object &emitter)
{
BLI_assert(is_valid_input_id(emitter.id));
if (shadow_emitter_data_map_.can_skip_emitter(emitter)) {
return;
}
const EmitterData *shadow_emitter_data = shadow_emitter_data_map_.ensure_data_if_possible(
scene, emitter);
if (shadow_emitter_data) {
foreach_light_collection_object(
*emitter.light_linking->blocker_collection,
[&](const CollectionLightLinking &collection_light_linking, const Object &receiver) {
add_blocker_object(*shadow_emitter_data, collection_light_linking, receiver);
});
}
}
void Cache::add_receiver_object(const EmitterData &emitter_data,
const CollectionLightLinking &collection_light_linking,
const Object &receiver)
{
BLI_assert(is_valid_input_id(receiver.id));
light_linking_.link_object(
emitter_data, eCollectionLightLinkingState(collection_light_linking.link_state), receiver);
}
void Cache::add_blocker_object(const EmitterData &emitter_data,
const CollectionLightLinking &collection_light_linking,
const Object &blocker)
{
BLI_assert(is_valid_input_id(blocker.id));
shadow_linking_.link_object(
emitter_data, eCollectionLightLinkingState(collection_light_linking.link_state), blocker);
}
void Cache::end_build(const Scene &scene)
{
if (!has_light_linking()) {
return;
}
light_linking_.end_build(scene, light_emitter_data_map_);
shadow_linking_.end_build(scene, shadow_emitter_data_map_);
}
void Cache::eval_runtime_data(Object &object_eval) const
{
static const LightLinkingRuntime runtime_no_links = {
EmitterSetMembership::SET_MEMBERSHIP_ALL, EmitterSetMembership::SET_MEMBERSHIP_ALL, 0, 0};
if (!has_light_linking()) {
/* No light linking used in the scene, still reset to default on objects that have
* allocated light linking data structures since we can't free them here. */
if (object_eval.light_linking) {
object_eval.light_linking->runtime = runtime_no_links;
}
return;
}
/* Receiver and blocker configuration. */
LightLinkingRuntime runtime = {};
runtime.receiver_light_set = light_linking_.get_light_set_for(object_eval);
runtime.blocker_shadow_set = shadow_linking_.get_light_set_for(object_eval);
/* Emitter configuration. */
const EmitterData *light_emitter_data = light_emitter_data_map_.get_data(object_eval);
if (light_emitter_data) {
runtime.light_set_membership = light_emitter_data->light_membership.get_mask();
}
else {
runtime.light_set_membership = EmitterSetMembership::SET_MEMBERSHIP_ALL;
}
const EmitterData *shadow_emitter_data = shadow_emitter_data_map_.get_data(object_eval);
if (shadow_emitter_data) {
runtime.shadow_set_membership = shadow_emitter_data->shadow_membership.get_mask();
}
else {
runtime.shadow_set_membership = EmitterSetMembership::SET_MEMBERSHIP_ALL;
}
const bool need_runtime = (memcmp(&runtime, &runtime_no_links, sizeof(runtime)) != 0);
/* Assign, allocating light linking on demand if needed. */
if (object_eval.light_linking) {
object_eval.light_linking->runtime = runtime;
if (!need_runtime) {
/* Note that this will only remove lazily allocated light_linking on the evaluated object,
* as an empty light_linking is not allowed on the original object. */
BKE_light_linking_free_if_empty(&object_eval);
}
}
else if (need_runtime) {
BKE_light_linking_ensure(&object_eval);
object_eval.light_linking->runtime = runtime;
}
}
} // namespace deg::light_linking
/** \} */
} // namespace blender

View File

@@ -0,0 +1,284 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include <cstdint>
#include "BLI_map.hh"
#include "BKE_light_linking.h" /* LightLinkingType */
#include "DNA_collection_types.h" /* eCollectionLightLinkingState */
#include "DNA_object_types.h"
namespace blender {
struct Collection;
struct CollectionLightLinking;
struct Object;
struct Scene;
namespace deg::light_linking {
namespace internal {
/* Set of light as seen from a receiver perspective. */
class LightSet {
public:
/* Maximum possible identifier of a light set. The identifier is 0-based.
* The limitation is imposed by the fact that its identifier is converted to a bitmask. */
static constexpr int MAX_ID = 63;
/* Identifier of a light set which is not explicitly linked to anything. */
static constexpr int DEFAULT_ID = 0;
bool operator==(const LightSet &other) const;
bool operator!=(const LightSet &other) const
{
return !(*this == other);
}
uint64_t hash() const;
/* Lights which are explicitly included/excluded into the light set.
*
* The light is denoted as a bit mask of a light linking collection. This mask is allocated for
* every unique light linking collection on an emitter. */
uint64_t include_collection_mask = 0;
uint64_t exclude_collection_mask = 0;
};
class EmitterSetMembership {
public:
/* Bitmask which indicates the emitter belongs to all light sets. */
static constexpr uint64_t SET_MEMBERSHIP_ALL = ~uint64_t(0);
/* Get final membership mask in the light sets, considering its inclusion and exclusion. */
uint64_t get_mask() const;
/* Bit masks of the emitter membership in the light sets. */
uint64_t included_sets_mask = 0;
uint64_t excluded_sets_mask = 0;
};
/* Packed information about emitter.
* Emitter is actually corresponding to a light linking collection on an object. */
class EmitterData {
public:
/* Maximum possible identifier of a light linking collection. The identifier is 0-based.
* The limitation is imposed by the fact that its identifier is converted to a bitmask. */
static constexpr int MAX_COLLECTION_ID = 63;
/* Mask of a light linking collection this emitter uses in its configuration.
* A single bit is set in this bit-field which corresponds to an identifier of a light linking
* collection in the scene. */
uint64_t collection_mask = 0;
/* Membership masks for the light and shadow linking. */
EmitterSetMembership light_membership;
EmitterSetMembership shadow_membership;
};
/* Helper class which deals with keeping per-emitter data. */
class EmitterDataMap {
using MapType = Map<const Collection *, EmitterData>;
public:
explicit EmitterDataMap(const LightLinkingType link_type) : link_type_(link_type) {}
/* Returns true if there is no information about emitters at all. */
bool is_empty() const
{
return emitter_data_map_.is_empty();
}
/* Entirely clear the state, become ready for a new light linking relations build. */
void clear();
/* Ensure that the data exists for the given emitter.
* The emitter must be original and have light linking collection.
*
* Note that there is limited number of emitters possible within a scene, When this number is
* exceeded an error is printed and a nullptr is returned. */
EmitterData *ensure_data_if_possible(const Scene &scene, const Object &emitter);
/* Get emitter data for the given original or evaluated object.
* If the light linking is not configured for this emitted nullptr is returned. */
const EmitterData *get_data(const Object &emitter) const;
/* Returns true if the underlying data of the light linking emitter has been handled, and there
* is no need to handle the emitter.
* The emitter must be original object. */
bool can_skip_emitter(const Object &emitter) const;
/* Returns an iterator over all emitter data in the map. */
MapType::MutableValueIterator values()
{
return emitter_data_map_.values();
}
private:
/* Get linked collection depending on whether this is emitter information for light or shadow
* linking. */
/* TODO(sergey): Check whether template specialization is preferred here. */
const Collection *get_collection(const Object &emitter) const
{
return BKE_light_linking_collection_get(&emitter, link_type_);
}
LightLinkingType link_type_ = LIGHT_LINKING_RECEIVER;
/* Emitter-centric information: indexed by an original emitter object, contains accumulated
* information about emitter. */
MapType emitter_data_map_;
/* Next unique light linking collection ID. */
uint64_t next_collection_id_ = 0;
};
/* Common part of receiver (for light linking) and blocker (for shadow lining) data. */
class LinkingData {
public:
explicit LinkingData(const LightLinkingType link_type) : link_type_(link_type) {}
/* Entirely clear the state, become ready for a new light linking relations build. */
void clear();
/* Link the given object with the given light linking state. */
void link_object(const EmitterData &emitter_data,
eCollectionLightLinkingState link_state,
const Object &object);
/* Compute unique sets of emitters used by receivers or blockers.
*
* This must be called at the end of depsgraph relations build after all emitters have been
* added, and before runtime data can be set as part of evaluation. */
void end_build(const Scene &scene, EmitterDataMap &emitter_data_map);
/* Get an unique index the given object is receiving light or casting shadow from.
* The object can either be original or evaluated.
*
* If the object is not linked to any emitter LightSet::DEFAULT_ID is returned. */
uint64_t get_light_set_for(const Object &object) const;
private:
/* Ensure that the light set exists for the given receiver/blocker object.
* The object must be original. */
LightSet &ensure_light_set_for(const Object &object);
/* Update the emitter light/shadow set membership after the final unique light set identifier
* is known.
* The light_set_mask consists of a single bit set corresponding to the light set index. */
void update_emitters_membership(EmitterDataMap &emitter_data_map,
const LightSet &light_set,
uint64_t light_set_mask);
/* Clear data which is only needed during the build. */
void clear_after_build();
/* Get light set membership information of the emitter data depending whether this linking
* data is a light or shadow linking. */
/* TODO(sergey): Check whether template specialization is preferred here. */
EmitterSetMembership &get_emitter_set_membership(EmitterData &emitter_data) const
{
if (link_type_ == LIGHT_LINKING_BLOCKER) {
return emitter_data.shadow_membership;
}
return emitter_data.light_membership;
}
LightLinkingType link_type_ = LIGHT_LINKING_RECEIVER;
/* Receiver/blocker-centric view of light sets: indexed by an original receiver object, contains
* light set which defines from which emitters it receives light from or casts shadow when is lit
* ny.
*
* NOTE: Only available during build. */
Map<const Object *, LightSet> light_linked_sets_;
/* Map from an original receiver/blocker object: map to index of light set for this
* receiver/blocker. */
/* TODO(sergey): What is the generic term for receiver and blocker which is less generic than
* object? */
Map<const Object *, uint64_t> object_light_sets_;
};
} // namespace internal
/* Cached light linking evaluation data.
*
* This cache is only valid within a specific dependency graph, hence the dependency graph is
* expected to own this cache.
*
* This cache takes care of making it efficient to lookup emitter masks, emitters which affect
* given receiver and so on. */
class Cache {
using EmitterData = internal::EmitterData;
using EmitterDataMap = internal::EmitterDataMap;
using LinkingData = internal::LinkingData;
public:
/* Entirely clear the cache.
* Should be called whenever the dependency graph is being re-built, in the beginning of the
* build process. */
void clear();
/* Add emitter to the cache.
*
* This call does nothing if the emitter does not have light configured linking (as in, if it
* has light linking collection set to nullptr).
*
* The emitter must be original. This is asserted, but in release builds passing evaluated
* object leads to an undefined behavior. */
void add_emitter(const Scene &scene, const Object &emitter);
/* Compute unique sets of emitters used by receivers and blockers.
*
* This must be called at the end of depsgraph relations build after all emitters have been
* added, and before runtime data can be set as part of evaluation. */
void end_build(const Scene &scene);
/* Set runtime light linking data on evaluated object. */
void eval_runtime_data(Object &object_eval) const;
private:
/* Add emitter information specific for light and shadow linking. */
void add_light_linking_emitter(const Scene &scene, const Object &emitter);
void add_shadow_linking_emitter(const Scene &scene, const Object &emitter);
/* Add receiver or blocker object with the given light linking configuration.
*
* The term receiver here is meant in a wider meaning of it. For the light linking it is a
* receiver of light, but for the shadow linking is it actually a shadow caster. */
void add_receiver_object(const EmitterData &emitter_data,
const CollectionLightLinking &collection_light_linking,
const Object &receiver);
void add_blocker_object(const EmitterData &emitter_data,
const CollectionLightLinking &collection_light_linking,
const Object &blocker);
/* Returns true if there is light linking configuration in the scene. */
bool has_light_linking() const
{
return !light_emitter_data_map_.is_empty() || !shadow_emitter_data_map_.is_empty();
}
/* Per-emitter light and shadow linking information. */
EmitterDataMap light_emitter_data_map_{LIGHT_LINKING_RECEIVER};
EmitterDataMap shadow_emitter_data_map_{LIGHT_LINKING_BLOCKER};
/* Light and shadow linking data. */
LinkingData light_linking_{LIGHT_LINKING_RECEIVER};
LinkingData shadow_linking_{LIGHT_LINKING_BLOCKER};
};
} // namespace deg::light_linking
} // namespace blender

View File

@@ -0,0 +1,317 @@
/* SPDX-FileCopyrightText: 2018 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Physics utilities for effectors and collision.
*/
#include "intern/depsgraph_physics.hh"
#include "BLI_enum_flags.hh"
#include "BLI_listbase.h"
#include "BKE_collision.h"
#include "BKE_dynamicpaint.h"
#include "BKE_effect.h"
#include "BKE_modifier.hh"
#include "BKE_object.hh"
#include "DNA_collection_types.h"
#include "DNA_object_force_types.h"
#include "DNA_object_types.h"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_physics.hh"
#include "DEG_depsgraph_query.hh"
#include "depsgraph.hh"
namespace blender {
/*************************** Evaluation Query API *****************************/
static ePhysicsCollisionType modifier_to_collision_type(uint modifier_type)
{
switch (modifier_type) {
case eModifierType_Collision:
return DEG_PHYSICS_COLLISION;
case eModifierType_Fluid:
return DEG_PHYSICS_SMOKE_COLLISION;
case eModifierType_DynamicPaint:
return DEG_PHYSICS_DYNAMIC_BRUSH;
}
BLI_assert_msg(0, "Unknown collision modifier type");
return DEG_PHYSICS_COLLISION_NUM;
}
/* Get ID from an ID type object, in a safe manner. This means that object can be nullptr,
* in which case the function returns nullptr.
*/
template<class T> static ID *object_id_safe(T *object)
{
if (object == nullptr) {
return nullptr;
}
return &object->id;
}
ListBaseT<EffectorRelation> *DEG_get_effector_relations(const Depsgraph *graph,
Collection *collection)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
Map<const ID *, ListBaseT<EffectorRelation> *> *hash = deg_graph->physics_relations_effector;
if (hash == nullptr) {
return nullptr;
}
/* NOTE: nullptr is a valid lookup key here as it means that the relation is not bound to a
* specific collection. */
ID *collection_orig = DEG_get_original(object_id_safe(collection));
return hash->lookup_default(collection_orig, nullptr);
}
ListBaseT<CollisionRelation> *DEG_get_collision_relations(const Depsgraph *graph,
Collection *collection,
uint modifier_type)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
const ePhysicsCollisionType type = modifier_to_collision_type(modifier_type);
Map<const ID *, ListBaseT<CollisionRelation> *> *hash =
deg_graph->physics_relations_collision[type];
if (hash == nullptr) {
return nullptr;
}
/* NOTE: nullptr is a valid lookup key here as it means that the relation is not bound to a
* specific collection. */
ID *collection_orig = DEG_get_original(object_id_safe(collection));
return hash->lookup_default(collection_orig, nullptr);
}
/********************** Depsgraph Building API ************************/
/**
* Flags to store point-cache relations which have been calculated.
* This avoid adding relations multiple times.
*
* \note This could be replaced by bit-shifting #eDepsObjectComponentType values,
* although this would limit them to integer size.
*/
enum class CollisionComponentFlag : uint8_t {
None = 0,
/** #DEG_OB_COMP_TRANSFORM is set. */
Transform = 1 << 0,
/** #DEG_OB_COMP_GEOMETRY is set. */
Geometry = 1 << 1,
/** #DEG_OB_COMP_EVAL_POSE is set. */
EvalPose = 1 << 2,
};
ENUM_OPERATORS(CollisionComponentFlag);
void DEG_add_collision_relations(DepsNodeHandle *handle,
Object *object,
Collection *collection,
uint modifier_type,
DEG_CollobjFilterFunction filter_function,
const char *name)
{
Depsgraph *depsgraph = DEG_get_graph_from_handle(handle);
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(depsgraph);
ListBaseT<CollisionRelation> *relations = build_collision_relations(
deg_graph, collection, modifier_type);
/* Expand tag objects, matching: #BKE_object_modifier_update_subframe behavior. */
/* NOTE: #eModifierType_Fluid should be included,
* leave out for the purpose of validating the fix for dynamic paint only. */
const bool use_recursive_parents = (modifier_type == eModifierType_DynamicPaint);
Map<Object *, CollisionComponentFlag> *object_component_map = nullptr;
if (use_recursive_parents) {
object_component_map = MEM_new<Map<Object *, CollisionComponentFlag>>(__func__);
}
for (CollisionRelation &relation : *relations) {
Object *ob1 = relation.ob;
if (ob1 == object) {
continue;
}
if (filter_function &&
!filter_function(ob1, BKE_modifiers_findby_type(ob1, ModifierType(modifier_type))))
{
continue;
}
if (use_recursive_parents) {
/* Add relations for `ob1` and other objects it references,
* using `object_component_map` to avoid redundant calls.
*
* When #BKE_object_modifier_update_subframe is used by a modifier,
* it's important the depsgraph tags objects this modifier uses.
*
* Without this, access to objects is not thread-safe, see: #142137.
*
* NOTE(@ideasman42): #BKE_object_modifier_update_subframe calls
* #BKE_animsys_evaluate_animdata, depending on the object type.
* Equivalent relations could be added here.
* This was not done and there are no bug reports relating to this,
* so leave as-is unless the current code is failing in a real world scenario. */
BKE_object_modifier_update_subframe_only_callback(
ob1,
true,
OBJECT_MODIFIER_UPDATE_SUBFRAME_RECURSION_DEFAULT,
modifier_type,
[&handle, &name, &object_component_map](Object *ob, const bool update_mesh) {
CollisionComponentFlag &update_flag = object_component_map->lookup_or_add_default(ob);
{
constexpr CollisionComponentFlag test_flag = CollisionComponentFlag::Transform;
if (!flag_is_set(update_flag, test_flag)) {
update_flag |= test_flag;
DEG_add_object_pointcache_relation(handle, ob, DEG_OB_COMP_TRANSFORM, name);
}
}
if (update_mesh) {
constexpr CollisionComponentFlag test_flag = CollisionComponentFlag::Geometry;
if (!flag_is_set(update_flag, test_flag)) {
update_flag |= test_flag;
DEG_add_object_pointcache_relation(handle, ob, DEG_OB_COMP_GEOMETRY, name);
}
}
if (ob->type == OB_ARMATURE) {
constexpr CollisionComponentFlag test_flag = CollisionComponentFlag::EvalPose;
if (!flag_is_set(update_flag, test_flag)) {
update_flag |= test_flag;
DEG_add_object_pointcache_relation(handle, ob, DEG_OB_COMP_EVAL_POSE, name);
}
}
});
continue;
}
DEG_add_object_pointcache_relation(handle, ob1, DEG_OB_COMP_TRANSFORM, name);
DEG_add_object_pointcache_relation(handle, ob1, DEG_OB_COMP_GEOMETRY, name);
}
if (use_recursive_parents) {
MEM_delete(object_component_map);
}
}
void DEG_add_forcefield_relations(DepsNodeHandle *handle,
Object *object,
EffectorWeights *effector_weights,
bool add_absorption,
int skip_forcefield,
const char *name)
{
Depsgraph *depsgraph = DEG_get_graph_from_handle(handle);
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(depsgraph);
ListBaseT<EffectorRelation> *relations = build_effector_relations(deg_graph,
effector_weights->group);
for (EffectorRelation &relation : *relations) {
if (relation.ob == object) {
continue;
}
if (relation.pd->forcefield == skip_forcefield) {
continue;
}
/* Relation to forcefield object, optionally including geometry.
* Use special point cache relations for automatic cache clearing. */
DEG_add_object_pointcache_relation(handle, relation.ob, DEG_OB_COMP_TRANSFORM, name);
if (relation.psys || ELEM(relation.pd->shape, PFIELD_SHAPE_SURFACE, PFIELD_SHAPE_POINTS) ||
relation.pd->forcefield == PFIELD_GUIDE)
{
/* TODO(sergey): Consider going more granular with more dedicated
* particle system operation. */
DEG_add_object_pointcache_relation(handle, relation.ob, DEG_OB_COMP_GEOMETRY, name);
}
/* Smoke flow relations. */
if (relation.pd->forcefield == PFIELD_FLUIDFLOW && relation.pd->f_source != nullptr) {
DEG_add_object_pointcache_relation(
handle, relation.pd->f_source, DEG_OB_COMP_TRANSFORM, "Fluid Force Domain");
DEG_add_object_pointcache_relation(
handle, relation.pd->f_source, DEG_OB_COMP_GEOMETRY, "Fluid Force Domain");
}
/* Absorption forces need collision relation. */
if (add_absorption && (relation.pd->flag & PFIELD_VISIBILITY)) {
DEG_add_collision_relations(
handle, object, nullptr, eModifierType_Collision, nullptr, "Force Absorption");
}
}
}
/******************************** Internal API ********************************/
namespace deg {
ListBaseT<EffectorRelation> *build_effector_relations(Depsgraph *graph, Collection *collection)
{
Map<const ID *, ListBaseT<EffectorRelation> *> *hash = graph->physics_relations_effector;
if (hash == nullptr) {
graph->physics_relations_effector = new Map<const ID *, ListBaseT<EffectorRelation> *>();
hash = graph->physics_relations_effector;
}
/* If collection is nullptr still use it as a key.
* In this case the BKE_effector_relations_create() will create relates for all bases in the
* view layer.
*/
ID *collection_id = object_id_safe(collection);
return hash->lookup_or_add_cb(collection_id, [&]() {
::blender::Depsgraph *depsgraph = reinterpret_cast<::blender::Depsgraph *>(graph);
return BKE_effector_relations_create(depsgraph, graph->scene, graph->view_layer, collection);
});
}
ListBaseT<CollisionRelation> *build_collision_relations(Depsgraph *graph,
Collection *collection,
uint modifier_type)
{
const ePhysicsCollisionType type = modifier_to_collision_type(modifier_type);
Map<const ID *, ListBaseT<CollisionRelation> *> *hash = graph->physics_relations_collision[type];
if (hash == nullptr) {
graph->physics_relations_collision[type] =
new Map<const ID *, ListBaseT<CollisionRelation> *>();
hash = graph->physics_relations_collision[type];
}
/* If collection is nullptr still use it as a key.
* In this case the BKE_collision_relations_create() will create relates for all bases in the
* view layer.
*/
ID *collection_id = object_id_safe(collection);
return hash->lookup_or_add_cb(collection_id, [&]() {
::blender::Depsgraph *depsgraph = reinterpret_cast<::blender::Depsgraph *>(graph);
return BKE_collision_relations_create(depsgraph, collection, modifier_type);
});
}
void clear_physics_relations(Depsgraph *graph)
{
if (graph->physics_relations_effector) {
for (ListBaseT<EffectorRelation> *list : graph->physics_relations_effector->values()) {
BKE_effector_relations_free(list);
}
delete graph->physics_relations_effector;
graph->physics_relations_effector = nullptr;
}
for (int i = 0; i < DEG_PHYSICS_COLLISION_NUM; i++) {
Map<const ID *, ListBaseT<CollisionRelation> *> *hash = graph->physics_relations_collision[i];
if (hash) {
for (ListBaseT<CollisionRelation> *list : hash->values()) {
BKE_collision_relations_free(list);
}
delete hash;
graph->physics_relations_collision[i] = nullptr;
}
}
}
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,30 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "DNA_listBase.h"
namespace blender {
struct Collection;
struct CollisionRelation;
struct EffectorRelation;
namespace deg {
struct Depsgraph;
ListBaseT<EffectorRelation> *build_effector_relations(Depsgraph *graph, Collection *collection);
ListBaseT<CollisionRelation> *build_collision_relations(Depsgraph *graph,
Collection *collection,
unsigned int modifier_type);
void clear_physics_relations(Depsgraph *graph);
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,413 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Implementation of Querying API
*/
#include <cstring> /* XXX: `memcpy`. */
#include "BLI_listbase.h"
#include "BKE_action.hh" /* XXX: BKE_pose_channel_find_name */
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "DNA_layer_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "RNA_access.hh"
#include "RNA_path.hh"
#include "RNA_prototypes.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "intern/depsgraph.hh"
#include "intern/eval/deg_eval_copy_on_write.h"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_id.hh"
namespace blender {
namespace deg {
static const ID *get_original_id(const ID *id)
{
if (id == nullptr) {
return nullptr;
}
if (id->orig_id == nullptr) {
return id;
}
BLI_assert((id->tag & ID_TAG_COPIED_ON_EVAL) != 0);
return id_cast<ID *>(id->orig_id);
}
static ID *get_original_id(ID *id)
{
const ID *const_id = id;
return const_cast<ID *>(get_original_id(const_id));
}
static const ID *get_evaluated_id(const Depsgraph *deg_graph, const ID *id)
{
if (id == nullptr) {
return nullptr;
}
/* TODO(sergey): This is a duplicate of Depsgraph::get_cow_id(),
* but here we never do assert, since we don't know nature of the
* incoming ID data-block. */
const IDNode *id_node = deg_graph->find_id_node(id);
if (id_node == nullptr) {
return id;
}
return id_node->id_cow;
}
static ID *get_evaluated_id(const Depsgraph *deg_graph, ID *id)
{
const ID *const_id = id;
return const_cast<ID *>(get_evaluated_id(deg_graph, const_id));
}
} // namespace deg
Scene *DEG_get_input_scene(const Depsgraph *graph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
return deg_graph->scene;
}
ViewLayer *DEG_get_input_view_layer(const Depsgraph *graph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
return deg_graph->view_layer;
}
Main *DEG_get_bmain(const Depsgraph *graph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
return deg_graph->bmain;
}
eEvaluationMode DEG_get_mode(const Depsgraph *graph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
return deg_graph->mode;
}
float DEG_get_ctime(const Depsgraph *graph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
return deg_graph->ctime;
}
bool DEG_id_type_updated(const Depsgraph *graph, short id_type)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
return deg_graph->id_type_updated[BKE_idtype_idcode_to_index(id_type)] != 0;
}
bool DEG_id_type_any_updated(const Depsgraph *graph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
/* Loop over all ID types. */
for (char id_type_index : deg_graph->id_type_updated) {
if (id_type_index) {
return true;
}
}
return false;
}
bool DEG_id_type_any_exists(const Depsgraph *depsgraph, short id_type)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(depsgraph);
return deg_graph->id_type_exist[BKE_idtype_idcode_to_index(id_type)] != 0;
}
uint32_t DEG_get_eval_flags_for_id(const Depsgraph *graph, const ID *id)
{
if (graph == nullptr) {
/* Happens when converting objects to mesh from a python script
* after modifying scene graph.
*
* Currently harmless because it's only called for temporary
* objects which are out of the DAG anyway. */
return 0;
}
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
const deg::IDNode *id_node = deg_graph->find_id_node(deg::get_original_id(id));
if (id_node == nullptr) {
/* TODO(sergey): Does it mean we need to check set scene? */
return 0;
}
return id_node->eval_flags;
}
void DEG_get_customdata_mask_for_object(const Depsgraph *graph,
Object *ob,
CustomData_MeshMasks *r_mask)
{
if (graph == nullptr) {
/* Happens when converting objects to mesh from a python script
* after modifying scene graph.
*
* Currently harmless because it's only called for temporary
* objects which are out of the DAG anyway. */
return;
}
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
const deg::IDNode *id_node = deg_graph->find_id_node(DEG_get_original(&ob->id));
if (id_node == nullptr) {
/* TODO(sergey): Does it mean we need to check set scene? */
return;
}
r_mask->vmask |= id_node->customdata_masks.vert_mask;
r_mask->emask |= id_node->customdata_masks.edge_mask;
r_mask->fmask |= id_node->customdata_masks.face_mask;
r_mask->lmask |= id_node->customdata_masks.loop_mask;
r_mask->pmask |= id_node->customdata_masks.poly_mask;
}
Scene *DEG_get_evaluated_scene(const Depsgraph *graph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
Scene *scene_cow = deg_graph->scene_cow;
/* TODO(sergey): Shall we expand data-block here? Or is it OK to assume
* that caller is OK with just a pointer in case scene is not updated yet? */
BLI_assert(scene_cow != nullptr && deg::deg_eval_copy_is_expanded(&scene_cow->id));
return scene_cow;
}
ViewLayer *DEG_get_evaluated_view_layer(const Depsgraph *graph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(graph);
Scene *scene_cow = DEG_get_evaluated_scene(graph);
if (scene_cow == nullptr) {
return nullptr; /* Happens with new, not-yet-built/evaluated graphs. */
}
/* Do name-based lookup. */
/* TODO(sergey): Can this be optimized? */
ViewLayer *view_layer_orig = deg_graph->view_layer;
ViewLayer *view_layer_cow = static_cast<ViewLayer *>(
BLI_findstring(&scene_cow->view_layers, view_layer_orig->name, offsetof(ViewLayer, name)));
BLI_assert(view_layer_cow != nullptr);
return view_layer_cow;
}
ID *DEG_get_evaluated_id(const Depsgraph *depsgraph, ID *id)
{
return deg::get_evaluated_id(reinterpret_cast<const deg::Depsgraph *>(depsgraph), id);
}
const ID *DEG_get_evaluated_id(const Depsgraph *depsgraph, const ID *id)
{
return DEG_get_evaluated_id(depsgraph, const_cast<ID *>(id));
}
void DEG_get_evaluated_rna_pointer(const Depsgraph *depsgraph,
PointerRNA *ptr,
PointerRNA *r_ptr_eval)
{
if ((ptr == nullptr) || (r_ptr_eval == nullptr)) {
return;
}
ID *orig_id = ptr->owner_id;
ID *cow_id = DEG_get_evaluated_id(depsgraph, orig_id);
if (ptr->owner_id == ptr->data) {
/* For ID pointers, it's easy... */
r_ptr_eval->owner_id = cow_id;
r_ptr_eval->data = static_cast<void *>(cow_id);
r_ptr_eval->type = ptr->type;
}
else if (ptr->type == RNA_PoseBone) {
/* HACK: Since bone keyframing is quite commonly used,
* speed things up for this case by doing a special lookup
* for bones */
const Object *ob_eval = id_cast<Object *>(cow_id);
bPoseChannel *pchan = static_cast<bPoseChannel *>(ptr->data);
bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name);
r_ptr_eval->owner_id = cow_id;
r_ptr_eval->data = pchan_eval;
r_ptr_eval->type = ptr->type;
}
else {
/* For everything else, try to get RNA Path of the BMain-pointer,
* then use that to look up what the evaluated one should be
* given the evaluated ID pointer as the new lookup point */
/* TODO: Find a faster alternative, or implement support for other
* common types too above (e.g. modifiers) */
if (const std::optional<std::string> path = RNA_path_from_ID_to_struct(ptr)) {
PointerRNA cow_id_ptr = RNA_id_pointer_create(cow_id);
if (!RNA_path_resolve(&cow_id_ptr, path->c_str(), r_ptr_eval, nullptr)) {
/* Couldn't find evaluated copy of data */
fprintf(stderr,
"%s: Couldn't resolve RNA path ('%s') relative to evaluated ID (%p) for '%s'\n",
__func__,
path->c_str(),
static_cast<void *>(cow_id),
orig_id->name);
}
}
else {
/* Path resolution failed - XXX: Hide this behind a debug flag */
fprintf(stderr,
"%s: Couldn't get RNA path for %s relative to %s\n",
__func__,
RNA_struct_identifier(ptr->type),
orig_id->name);
}
}
}
ID *DEG_get_original_id(ID *id)
{
return deg::get_original_id(id);
}
const ID *DEG_get_original_id(const ID *id)
{
return deg::get_original_id(id);
}
Depsgraph *DEG_get_depsgraph_by_id(const ID &id)
{
return id.runtime->depsgraph;
}
bool DEG_is_original_id(const ID *id)
{
/* Some explanation of the logic.
*
* What we want here is to be able to tell whether given ID is a result of dependency graph
* evaluation or not.
*
* All the data-blocks which are created by copy-on-evaluation mechanism will have will be tagged
* with ID_TAG_COPIED_ON_EVAL tag. Those data-blocks can not be original.
*
* Modifier stack evaluation might create special data-blocks which have all the modifiers
* applied, and those will be tagged with ID_TAG_COPIED_ON_EVAL_FINAL_RESULT. Such data-blocks
* can not be original as well.
*
* Localization is usually happening from evaluated data-block, or will have some special pointer
* magic which will make them to act as evaluated.
*
* NOTE: We consider ID evaluated if ANY of those flags is set. We do NOT require ALL of them. */
if (id->tag & (ID_TAG_COPIED_ON_EVAL | ID_TAG_COPIED_ON_EVAL_FINAL_RESULT | ID_TAG_LOCALIZED)) {
return false;
}
return true;
}
bool DEG_is_evaluated_id(const ID *id)
{
return !DEG_is_original_id(id);
}
bool DEG_is_fully_evaluated(const Depsgraph *depsgraph)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(depsgraph);
/* Check whether relations are up to date. */
if (deg_graph->need_update_relations) {
return false;
}
/* Check whether IDs are up to date. */
if (!deg_graph->entry_tags.is_empty()) {
return false;
}
return true;
}
bool DEG_id_is_fully_evaluated(const Depsgraph *depsgraph, const ID *id_eval)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(depsgraph);
/* Only us the original ID pointer to look up the IDNode, do not dereference it. */
const ID *id_orig = deg::get_original_id(id_eval);
const deg::IDNode *id_node = deg_graph->find_id_node(id_orig);
if (!id_node) {
return false;
}
for (deg::ComponentNode *component : id_node->components.values()) {
for (deg::OperationNode *operation : component->operations) {
if (operation->flag & deg::DEPSOP_FLAG_NEEDS_UPDATE) {
return false;
}
}
}
return true;
}
static bool operation_needs_update(const ID &id,
const deg::NodeType component_type,
const deg::OperationCode opcode)
{
const Depsgraph *depsgraph = DEG_get_depsgraph_by_id(id);
if (!depsgraph) {
return false;
}
const deg::Depsgraph &deg_graph = *reinterpret_cast<const deg::Depsgraph *>(depsgraph);
/* Only us the original ID pointer to look up the IDNode, do not dereference it. */
const ID *id_orig = deg::get_original_id(&id);
if (!id_orig) {
return false;
}
const deg::IDNode *id_node = deg_graph.find_id_node(id_orig);
if (!id_node) {
return false;
};
const deg::ComponentNode *component_node = id_node->find_component(component_type);
if (!component_node) {
return false;
}
const deg::OperationNode *operation_node = component_node->find_operation(opcode);
if (!operation_node) {
return false;
}
/* Technically, there is potential for a race condition here, because the depsgraph evaluation
* might update this flag, but it's very unlikely to cause issues right now. Maybe this should
* become an atomic eventually. */
const bool needs_update = operation_node->flag & deg::DEPSOP_FLAG_NEEDS_UPDATE;
return needs_update;
}
bool DEG_object_geometry_is_evaluated(const Object &object)
{
return !operation_needs_update(
object.id, deg::NodeType::GEOMETRY, deg::OperationCode::GEOMETRY_EVAL);
}
bool DEG_object_transform_is_evaluated(const Object &object)
{
return !operation_needs_update(
object.id, deg::NodeType::TRANSFORM, deg::OperationCode::TRANSFORM_FINAL);
}
bool DEG_collection_geometry_is_evaluated(const Collection &collection)
{
return !operation_needs_update(
collection.id, deg::NodeType::GEOMETRY, deg::OperationCode::GEOMETRY_EVAL_DONE);
}
std::optional<double> DEG_get_last_evaluation_time(const Depsgraph *depsgraph)
{
if (!DEG_is_fully_evaluated(depsgraph)) {
return std::nullopt;
}
const deg::Depsgraph &deg_graph = *reinterpret_cast<const deg::Depsgraph *>(depsgraph);
return deg_graph.debug.total_evaluation_time();
}
} // namespace blender

View File

@@ -0,0 +1,276 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Implementation of Querying and Filtering API's
*/
#include <deque>
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "intern/depsgraph.hh"
#include "intern/depsgraph_relation.hh"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_operation.hh"
namespace blender {
/* ************************ DEG TRAVERSAL ********************* */
namespace deg {
namespace {
using TraversalQueue = std::deque<OperationNode *>;
using DEGForeachOperation = void (*)(OperationNode *, void *);
bool deg_foreach_needs_visit(const OperationNode *op_node, const int flags)
{
if (flags & DEG_FOREACH_COMPONENT_IGNORE_TRANSFORM_SOLVERS) {
if (op_node->opcode == OperationCode::RIGIDBODY_SIM) {
return false;
}
}
return true;
}
void deg_foreach_dependent_operation(const Depsgraph * /*graph*/,
const IDNode *target_id_node,
eDepsObjectComponentType source_component_type,
int flags,
DEGForeachOperation callback,
void *user_data)
{
if (target_id_node == nullptr) {
/* TODO(sergey): Shall we inform or assert here about attempt to start
* iterating over non-existing ID? */
return;
}
/* Start with scheduling all operations from ID node. */
TraversalQueue queue;
Set<OperationNode *> scheduled;
for (ComponentNode *comp_node : target_id_node->components.values()) {
if (comp_node->type == NodeType::VISIBILITY) {
/* Visibility component is only used internally. It is not to be reporting dependencies to
* the outer world. */
continue;
}
if (source_component_type != DEG_OB_COMP_ANY &&
nodeTypeToObjectComponent(comp_node->type) != source_component_type)
{
continue;
}
for (OperationNode *op_node : comp_node->operations) {
if (!deg_foreach_needs_visit(op_node, flags)) {
continue;
}
queue.push_back(op_node);
scheduled.add(op_node);
}
}
/* Process the queue. */
while (!queue.empty()) {
/* get next operation node to process. */
OperationNode *op_node = queue.front();
queue.pop_front();
for (;;) {
callback(op_node, user_data);
/* Schedule outgoing operation nodes. */
if (op_node->outlinks.size() == 1) {
OperationNode *to_node = static_cast<OperationNode *>(op_node->outlinks[0]->to);
if (!scheduled.contains(to_node) && deg_foreach_needs_visit(to_node, flags)) {
scheduled.add_new(to_node);
op_node = to_node;
}
else {
break;
}
}
else {
for (Relation *rel : op_node->outlinks) {
OperationNode *to_node = static_cast<OperationNode *>(rel->to);
if (!scheduled.contains(to_node) && deg_foreach_needs_visit(to_node, flags)) {
queue.push_front(to_node);
scheduled.add_new(to_node);
}
}
break;
}
}
}
}
struct ForeachIDComponentData {
DEGForeachIDComponentCallback callback;
IDNode *target_id_node;
Set<ComponentNode *> visited;
};
void deg_foreach_dependent_component_callback(OperationNode *op_node, void *user_data_v)
{
ForeachIDComponentData *user_data = reinterpret_cast<ForeachIDComponentData *>(user_data_v);
ComponentNode *comp_node = op_node->owner;
IDNode *id_node = comp_node->owner;
if (id_node != user_data->target_id_node && !user_data->visited.contains(comp_node)) {
user_data->callback(id_node->id_orig, nodeTypeToObjectComponent(comp_node->type));
user_data->visited.add_new(comp_node);
}
}
void deg_foreach_dependent_ID_component(const Depsgraph *graph,
const ID *id,
eDepsObjectComponentType source_component_type,
int flags,
DEGForeachIDComponentCallback callback)
{
ForeachIDComponentData data;
data.callback = callback;
data.target_id_node = graph->find_id_node(id);
deg_foreach_dependent_operation(graph,
data.target_id_node,
source_component_type,
flags,
deg_foreach_dependent_component_callback,
&data);
}
struct ForeachIDData {
DEGForeachIDCallback callback;
IDNode *target_id_node;
Set<IDNode *> visited;
};
void deg_foreach_dependent_ID_callback(OperationNode *op_node, void *user_data_v)
{
ForeachIDData *user_data = reinterpret_cast<ForeachIDData *>(user_data_v);
ComponentNode *comp_node = op_node->owner;
IDNode *id_node = comp_node->owner;
if (id_node != user_data->target_id_node && !user_data->visited.contains(id_node)) {
user_data->callback(id_node->id_orig);
user_data->visited.add_new(id_node);
}
}
void deg_foreach_dependent_ID(const Depsgraph *graph, const ID *id, DEGForeachIDCallback callback)
{
ForeachIDData data;
data.callback = callback;
data.target_id_node = graph->find_id_node(id);
deg_foreach_dependent_operation(
graph, data.target_id_node, DEG_OB_COMP_ANY, 0, deg_foreach_dependent_ID_callback, &data);
}
void deg_foreach_ancestor_ID(const Depsgraph *graph, const ID *id, DEGForeachIDCallback callback)
{
/* Start with getting ID node from the graph. */
IDNode *target_id_node = graph->find_id_node(id);
if (target_id_node == nullptr) {
/* TODO(sergey): Shall we inform or assert here about attempt to start
* iterating over non-existing ID? */
return;
}
/* Start with scheduling all operations from ID node. */
TraversalQueue queue;
Set<OperationNode *> scheduled;
for (ComponentNode *comp_node : target_id_node->components.values()) {
for (OperationNode *op_node : comp_node->operations) {
queue.push_back(op_node);
scheduled.add(op_node);
}
}
Set<IDNode *> visited;
visited.add_new(target_id_node);
/* Process the queue. */
while (!queue.empty()) {
/* get next operation node to process. */
OperationNode *op_node = queue.front();
queue.pop_front();
for (;;) {
/* Check whether we need to inform callee about corresponding ID node. */
ComponentNode *comp_node = op_node->owner;
IDNode *id_node = comp_node->owner;
if (!visited.contains(id_node)) {
/* TODO(sergey): Is it orig or evaluated? */
callback(id_node->id_orig);
visited.add_new(id_node);
}
/* Schedule incoming operation nodes. */
if (op_node->inlinks.size() == 1) {
Node *from = op_node->inlinks[0]->from;
if (from->get_class() == NodeClass::OPERATION) {
OperationNode *from_node = static_cast<OperationNode *>(from);
if (scheduled.add(from_node)) {
op_node = from_node;
}
else {
break;
}
}
}
else {
for (Relation *rel : op_node->inlinks) {
Node *from = rel->from;
if (from->get_class() == NodeClass::OPERATION) {
OperationNode *from_node = static_cast<OperationNode *>(from);
if (scheduled.add(from_node)) {
queue.push_front(from_node);
}
}
}
break;
}
}
}
}
void deg_foreach_id(const Depsgraph *depsgraph, DEGForeachIDCallback callback)
{
for (const IDNode *id_node : depsgraph->id_nodes) {
callback(id_node->id_orig);
}
}
} // namespace
} // namespace deg
void DEG_foreach_dependent_ID(const Depsgraph *depsgraph,
const ID *id,
DEGForeachIDCallback callback)
{
deg::deg_foreach_dependent_ID(reinterpret_cast<const deg::Depsgraph *>(depsgraph), id, callback);
}
void DEG_foreach_dependent_ID_component(const Depsgraph *depsgraph,
const ID *id,
eDepsObjectComponentType source_component_type,
int flags,
DEGForeachIDComponentCallback callback)
{
deg::deg_foreach_dependent_ID_component(reinterpret_cast<const deg::Depsgraph *>(depsgraph),
id,
source_component_type,
flags,
callback);
}
void DEG_foreach_ancestor_ID(const Depsgraph *depsgraph,
const ID *id,
DEGForeachIDCallback callback)
{
deg::deg_foreach_ancestor_ID(reinterpret_cast<const deg::Depsgraph *>(depsgraph), id, callback);
}
void DEG_foreach_ID(const Depsgraph *depsgraph, DEGForeachIDCallback callback)
{
deg::deg_foreach_id(reinterpret_cast<const deg::Depsgraph *>(depsgraph), callback);
}
} // namespace blender

View File

@@ -0,0 +1,558 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Implementation of Querying and Filtering API's
*/
/* Silence warnings from copying deprecated fields. */
#define DNA_DEPRECATED_ALLOW
#include "BKE_duplilist.hh"
#include "BKE_idprop.hh"
#include "BKE_layer.hh"
#include "BKE_modifier.hh"
#include "BKE_node.hh"
#include "BKE_object.hh"
#include "BKE_object_types.hh"
#include "BLI_listbase.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "intern/depsgraph.hh"
#include "intern/node/deg_node_id.hh"
#ifndef NDEBUG
# include "intern/eval/deg_eval_copy_on_write.h"
#endif
namespace blender {
/* If defined, all working data will be set to an invalid state, helping
* to catch issues when areas accessing data which is considered to be no
* longer available. */
#undef INVALIDATE_WORK_DATA
#ifndef NDEBUG
# define INVALIDATE_WORK_DATA
#endif
using evil::DEG_iterator_temp_object_free_properties;
using evil::DEG_iterator_temp_object_from_dupli;
/* ************************ DEG ITERATORS ********************* */
namespace {
void deg_invalidate_iterator_work_data(DEGObjectIterData *data)
{
#ifdef INVALIDATE_WORK_DATA
BLI_assert(data != nullptr);
memset(static_cast<void *>(&data->temp_dupli_object), 0xff, sizeof(data->temp_dupli_object));
#else
(void)data;
#endif
}
bool deg_object_hide_original(eEvaluationMode eval_mode, const Object *ob, const DupliObject *dob)
{
/* Automatic hiding if this object is being instanced on verts/faces/frames
* by its parent. Ideally this should not be needed, but due to the wrong
* dependency direction in the data design there is no way to keep the object
* visible otherwise. The better solution eventually would be for objects
* to specify which object they instance, instead of through parenting.
*
* This function should not be used for meta-balls. They have custom visibility rules, as hiding
* the base meta-ball will also hide all the other balls in the group. */
if (eval_mode == DAG_EVAL_RENDER || dob) {
const int hide_original_types = OB_DUPLIVERTS | OB_DUPLIFACES;
if (!dob || !(dob->type & hide_original_types)) {
if (ob->parent && (ob->parent->transflag & hide_original_types)) {
return true;
}
}
}
return false;
}
void deg_iterator_duplis_init(DEGObjectIterData *data, Object *object)
{
/* This should have been set to default values at `DEG_iterator_objects_begin`, or at the end of
* the previous `deg_iterator_duplis_step` cycle. */
BLI_assert(data->dupli_parent == nullptr);
BLI_assert(data->dupli_object_next == nullptr);
BLI_assert(data->dupli_object_next_index == -1);
if (data->dupli_list.is_empty()) {
return;
}
data->dupli_parent = object;
data->dupli_object_next = &data->dupli_list.first();
data->dupli_object_next_index = 0;
}
/* Returns false when iterator is exhausted. */
bool deg_iterator_duplis_step(DEGObjectIterData *data)
{
if (data->dupli_list.is_empty()) {
return false;
}
while (data->dupli_object_next != nullptr) {
DupliObject *dob = data->dupli_object_next;
Object *obd = dob->ob;
if (++data->dupli_object_next_index < data->dupli_list.size()) {
data->dupli_object_next = &data->dupli_list[data->dupli_object_next_index];
}
else {
data->dupli_object_next = nullptr;
data->dupli_object_next_index = -1;
}
if (!DEG_iterator_dupli_is_visible(dob, data->eval_mode)) {
continue;
}
/* TODO: Can this be removed? included_objects is already passed to object_duplilist(). */
DEGObjectIterSettings *settings = data->settings;
if (settings->included_objects) {
Object *object_orig = DEG_get_original(obd);
if (!settings->included_objects->contains(object_orig)) {
continue;
}
}
if (data->dupli_object_current) {
DEG_iterator_temp_object_free_properties(data->dupli_object_current,
&data->temp_dupli_object);
}
data->dupli_object_current = dob;
if (DEG_iterator_temp_object_from_dupli(data->dupli_parent,
data->dupli_object_current,
data->eval_mode,
true,
&data->temp_dupli_object,
&data->temp_dupli_object_runtime))
{
data->next_object = &data->temp_dupli_object;
return true;
}
}
/* Even if the `dupli_list` is not empty, it may happen that none of its entry is displayed (e.g.
* #DEG_iterator_dupli_is_visible return `false` for all of the duplis). In such cases,
* `dupli_object_current` will also be `nullptr`, and nothing needs to be freed here.
* See also #149673 for a reproducible case. */
if (data->dupli_object_current) {
DEG_iterator_temp_object_free_properties(data->dupli_object_current, &data->temp_dupli_object);
}
data->dupli_list.clear();
data->dupli_parent = nullptr;
data->dupli_object_next = nullptr;
data->dupli_object_next_index = -1;
data->dupli_object_current = nullptr;
deg_invalidate_iterator_work_data(data);
return false;
}
/* Returns false when iterator is exhausted. */
bool deg_iterator_objects_step(DEGObjectIterData *data)
{
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(data->graph);
for (; data->id_node_index < data->num_id_nodes; data->id_node_index++) {
deg::IDNode *id_node = deg_graph->id_nodes[data->id_node_index];
/* Use the build time visibility so that the ID is not appearing/disappearing throughout
* animation export. */
if (!id_node->is_visible_on_build) {
continue;
}
const ID_Type id_type = GS(id_node->id_orig->name);
if (id_type != ID_OB) {
continue;
}
switch (id_node->linked_state) {
case deg::DEG_ID_LINKED_DIRECTLY:
if ((data->flag & DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY) == 0) {
continue;
}
break;
case deg::DEG_ID_LINKED_VIA_SET:
if ((data->flag & DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET) == 0) {
continue;
}
break;
case deg::DEG_ID_LINKED_INDIRECTLY:
if ((data->flag & DEG_ITER_OBJECT_FLAG_LINKED_INDIRECTLY) == 0) {
continue;
}
break;
}
Object *object = id_cast<Object *>(id_node->id_cow);
Object *object_orig = DEG_get_original(object);
DEGObjectIterSettings *settings = data->settings;
if (settings->included_objects) {
if (!settings->included_objects->contains(object_orig)) {
continue;
}
}
/* NOTE: The object might be invisible after the latest depsgraph evaluation, in which case
* going into its evaluated state might not be safe. For example, its evaluated mesh state
* might point to a freed data-block if the mesh is animated.
* So it is required to perform the visibility checks prior to looking into any deeper into the
* object. */
BLI_assert(deg::deg_eval_copy_is_expanded(&object->id));
object->runtime->select_id = object_orig->runtime->select_id;
const bool use_preview = object_orig == data->object_orig_with_preview;
if (use_preview) {
object_duplilist_preview(data->graph, object, data->settings->viewer_path, data->dupli_list);
deg_iterator_duplis_init(data, object);
data->id_node_index++;
return true;
}
int ob_visibility = OB_VISIBLE_ALL;
if (data->flag & DEG_ITER_OBJECT_FLAG_VISIBLE) {
ob_visibility = BKE_object_visibility(object, data->eval_mode);
if (!DEG_iterator_object_is_visible(data->eval_mode, object)) {
continue;
}
}
if (ob_visibility & OB_VISIBLE_INSTANCES) {
if ((data->flag & DEG_ITER_OBJECT_FLAG_DUPLI) &&
((object->transflag & OB_DUPLI) || object->runtime->geometry_set_eval != nullptr))
{
BLI_assert(deg::deg_validate_eval_copy_datablock(&object->id));
object_duplilist(data->graph, object, data->settings->included_objects, data->dupli_list);
deg_iterator_duplis_init(data, object);
}
}
if (ob_visibility & (OB_VISIBLE_SELF | OB_VISIBLE_PARTICLES)) {
BLI_assert(deg::deg_validate_eval_copy_datablock(&object->id));
data->next_object = object;
}
data->id_node_index++;
return true;
}
return false;
}
} // namespace
void DEGObjectIterData::transfer_from(DEGObjectIterData &other)
{
BLI_assert(this != &other);
this->settings = other.settings;
this->graph = other.graph;
this->flag = other.flag;
this->eval_mode = other.eval_mode;
this->object_orig_with_preview = other.object_orig_with_preview;
this->next_object = other.next_object;
this->dupli_parent = other.dupli_parent;
this->dupli_list = std::move(other.dupli_list);
this->dupli_object_next = other.dupli_object_next;
this->dupli_object_next_index = other.dupli_object_next_index;
this->dupli_object_current = other.dupli_object_current;
this->temp_dupli_object = dna::shallow_copy(other.temp_dupli_object);
this->temp_dupli_object_runtime = other.temp_dupli_object_runtime;
this->temp_dupli_object.runtime = &temp_dupli_object_runtime;
this->id_node_index = other.id_node_index;
this->num_id_nodes = other.num_id_nodes;
}
static Object *find_object_with_preview_geometry(const ViewerPath &viewer_path)
{
if (viewer_path.path.is_empty()) {
return nullptr;
}
const ViewerPathElem *elem = static_cast<const ViewerPathElem *>(viewer_path.path.first);
if (elem->type != VIEWER_PATH_ELEM_TYPE_ID) {
return nullptr;
}
const IDViewerPathElem *id_elem = reinterpret_cast<const IDViewerPathElem *>(elem);
if (id_elem->id == nullptr) {
return nullptr;
}
if (GS(id_elem->id->name) != ID_OB) {
return nullptr;
}
Object *object = reinterpret_cast<Object *>(id_elem->id);
if (elem->next->type != VIEWER_PATH_ELEM_TYPE_MODIFIER) {
return nullptr;
}
const ModifierViewerPathElem *modifier_elem = reinterpret_cast<const ModifierViewerPathElem *>(
elem->next);
ModifierData *md = BKE_modifiers_findby_persistent_uid(object, modifier_elem->modifier_uid);
if (md == nullptr) {
return nullptr;
}
if (!(md->mode & eModifierMode_Realtime)) {
return nullptr;
}
return reinterpret_cast<Object *>(id_elem->id);
}
void DEG_iterator_objects_begin(BLI_Iterator *iter, DEGObjectIterData *data)
{
Depsgraph *depsgraph = data->graph;
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(depsgraph);
const size_t num_id_nodes = deg_graph->id_nodes.size();
iter->data = data;
if (num_id_nodes == 0) {
iter->valid = false;
return;
}
data->next_object = nullptr;
data->dupli_parent = nullptr;
data->dupli_list.clear();
data->dupli_object_next = nullptr;
data->dupli_object_next_index = -1;
data->dupli_object_current = nullptr;
data->id_node_index = 0;
data->num_id_nodes = num_id_nodes;
data->eval_mode = DEG_get_mode(depsgraph);
deg_invalidate_iterator_work_data(data);
/* Determine if the preview of any object should be in the iterator. */
const ViewerPath *viewer_path = data->settings->viewer_path;
if (viewer_path != nullptr) {
data->object_orig_with_preview = find_object_with_preview_geometry(*viewer_path);
}
DEG_iterator_objects_next(iter);
}
void DEG_iterator_objects_next(BLI_Iterator *iter)
{
DEGObjectIterData *data = static_cast<DEGObjectIterData *>(iter->data);
while (true) {
if (data->next_object != nullptr) {
iter->current = data->next_object;
data->next_object = nullptr;
return;
}
if (deg_iterator_duplis_step(data)) {
continue;
}
if (deg_iterator_objects_step(data)) {
continue;
}
iter->valid = false;
break;
}
}
void DEG_iterator_objects_end(BLI_Iterator *iter)
{
DEGObjectIterData *data = static_cast<DEGObjectIterData *>(iter->data);
if (data != nullptr) {
/* Force crash in case the iterator data is referenced and accessed down
* the line. (#51718) */
deg_invalidate_iterator_work_data(data);
}
}
/* ************************ DEG ID ITERATOR ********************* */
static void DEG_iterator_ids_step(BLI_Iterator *iter, deg::IDNode *id_node, bool only_updated)
{
ID *id_cow = id_node->id_cow;
/* Use the build time visibility so that the ID is not appearing/disappearing throughout
* animation export.
* When the dependency graph is asked for updates report all IDs, as the user of those updates
* might need to react to updates coming from IDs which do change visibility throughout the
* life-time of the graph. */
if (!only_updated && !id_node->is_visible_on_build) {
iter->skip = true;
return;
}
if (only_updated && !(id_cow->recalc & ID_RECALC_ALL)) {
/* Node-tree is considered part of the data-block. */
bNodeTree *ntree = bke::node_tree_from_id(id_cow);
if (ntree == nullptr) {
iter->skip = true;
return;
}
if ((ntree->id.recalc & ID_RECALC_NTREE_OUTPUT) == 0) {
iter->skip = true;
return;
}
}
iter->current = id_cow;
iter->skip = false;
}
void DEG_iterator_ids_begin(BLI_Iterator *iter, DEGIDIterData *data)
{
Depsgraph *depsgraph = data->graph;
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(depsgraph);
const size_t num_id_nodes = deg_graph->id_nodes.size();
iter->data = data;
if ((num_id_nodes == 0) || (data->only_updated && !DEG_id_type_any_updated(depsgraph))) {
iter->valid = false;
return;
}
data->id_node_index = 0;
data->num_id_nodes = num_id_nodes;
deg::IDNode *id_node = deg_graph->id_nodes[data->id_node_index];
DEG_iterator_ids_step(iter, id_node, data->only_updated);
if (iter->skip) {
DEG_iterator_ids_next(iter);
}
}
void DEG_iterator_ids_next(BLI_Iterator *iter)
{
DEGIDIterData *data = static_cast<DEGIDIterData *>(iter->data);
Depsgraph *depsgraph = data->graph;
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(depsgraph);
do {
iter->skip = false;
++data->id_node_index;
if (data->id_node_index == data->num_id_nodes) {
iter->valid = false;
return;
}
deg::IDNode *id_node = deg_graph->id_nodes[data->id_node_index];
DEG_iterator_ids_step(iter, id_node, data->only_updated);
} while (iter->skip);
}
void DEG_iterator_ids_end(BLI_Iterator * /*iter*/) {}
bool DEG_iterator_object_is_visible(eEvaluationMode eval_mode, const Object *ob)
{
if (ob->type == OB_MBALL) {
return true;
}
return !deg_object_hide_original(eval_mode, ob, nullptr);
}
bool DEG_iterator_dupli_is_visible(const DupliObject *dupli, eEvaluationMode eval_mode)
{
if (dupli->no_draw) {
return false;
}
if (dupli->ob_data && GS(dupli->ob_data->name) == ID_MB) {
return false;
}
if (dupli->ob->type != OB_MBALL && deg_object_hide_original(eval_mode, dupli->ob, dupli)) {
return false;
}
return true;
}
bool evil::DEG_iterator_temp_object_from_dupli(const Object *dupli_parent,
const DupliObject *dupli,
eEvaluationMode eval_mode,
bool do_matrix_setup,
Object *r_temp_object,
bke::ObjectRuntime *r_temp_runtime)
{
*r_temp_object = dna::shallow_copy(*dupli->ob);
r_temp_object->runtime = r_temp_runtime;
*r_temp_object->runtime = *dupli->ob->runtime;
r_temp_object->base_flag = dupli_parent->base_flag | BASE_FROM_DUPLI;
r_temp_object->base_local_view_bits = dupli_parent->base_local_view_bits;
r_temp_object->runtime->local_collections_bits = dupli_parent->runtime->local_collections_bits;
r_temp_object->dt = std::min(r_temp_object->dt, dupli_parent->dt);
copy_v4_v4(r_temp_object->color, dupli_parent->color);
r_temp_object->runtime->select_id = dupli_parent->runtime->select_id;
if (dupli->ob->data != dupli->ob_data) {
BKE_object_replace_data_on_shallow_copy(r_temp_object, dupli->ob_data);
}
/* Duplicated elements shouldn't care whether their original collection is visible or not. */
r_temp_object->base_flag |= BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT;
/* TODO: Could this be computed in DEG_iterator_dupli_is_visible,
* before setting up the shallow copy? */
int ob_visibility = BKE_object_visibility(r_temp_object, eval_mode);
if ((ob_visibility & (OB_VISIBLE_SELF | OB_VISIBLE_PARTICLES)) == 0) {
return false;
}
if (do_matrix_setup) {
/* This could be avoided by refactoring make_dupli() in order to track all negative scaling
* recursively. */
bool is_neg_scale = is_negative_m4(dupli->mat);
SET_FLAG_FROM_TEST(r_temp_object->transflag, is_neg_scale, OB_NEG_SCALE);
copy_m4_m4(r_temp_object->runtime->object_to_world.ptr(), dupli->mat);
invert_m4_m4(r_temp_object->runtime->world_to_object.ptr(),
r_temp_object->object_to_world().ptr());
}
BLI_assert(deg::deg_validate_eval_copy_datablock(&r_temp_object->id));
return true;
}
static void ensure_id_properties_freed(const IDProperty *dupli_idprops,
IDProperty **temp_dupli_idprops)
{
if (*temp_dupli_idprops == nullptr) {
/* No ID properties in temp data-block -- no leak is possible. */
return;
}
if (*temp_dupli_idprops == dupli_idprops) {
/* Temp copy of object did not modify ID properties. */
return;
}
/* Free memory which is owned by temporary storage which is about to get overwritten. */
IDP_FreeProperty(*temp_dupli_idprops);
*temp_dupli_idprops = nullptr;
}
void evil::DEG_iterator_temp_object_free_properties(const DupliObject *dupli, Object *temp_object)
{
ensure_id_properties_freed(dupli->ob->id.properties, &temp_object->id.properties);
ensure_id_properties_freed(dupli->ob->id.system_properties, &temp_object->id.system_properties);
}
} // namespace blender

View File

@@ -0,0 +1,75 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "BLI_map.hh"
#include "BLI_mutex.hh"
#include "BLI_vector_set.hh"
#include "intern/depsgraph_registry.hh"
#include "intern/depsgraph.hh"
namespace blender::deg {
/* Global registry for dependency graphs associated with a main database.
*
* Threads may add or remove depsgraphs for different mains concurrently
* (for example for preview rendering), but not the same main. */
/* Use pointer for map value to ensure span returned by get_all_registered_graphs
* remains unchanged as other mains are added or removed. */
using GraphSetPtr = std::unique_ptr<VectorSet<Depsgraph *>>;
struct GraphRegistry {
Map<Main *, GraphSetPtr> map;
Mutex mutex;
};
static GraphRegistry &get_graph_registry()
{
static GraphRegistry graph_registry;
return graph_registry;
}
void register_graph(Depsgraph *depsgraph)
{
GraphRegistry &graph_registry = get_graph_registry();
Main *bmain = depsgraph->bmain;
std::lock_guard lock{graph_registry.mutex};
graph_registry.map
.lookup_or_add_cb(bmain, []() { return std::make_unique<VectorSet<Depsgraph *>>(); })
->add_new(depsgraph);
}
void unregister_graph(Depsgraph *depsgraph)
{
Main *bmain = depsgraph->bmain;
GraphRegistry &graph_registry = get_graph_registry();
std::lock_guard lock{graph_registry.mutex};
GraphSetPtr &graphs = graph_registry.map.lookup(bmain);
graphs->remove(depsgraph);
/* If this was the last depsgraph associated with the main, remove the main entry as well. */
if (graphs->is_empty()) {
graph_registry.map.remove(bmain);
}
}
Span<Depsgraph *> get_all_registered_graphs(Main *bmain)
{
GraphRegistry &graph_registry = get_graph_registry();
std::lock_guard lock{graph_registry.mutex};
GraphSetPtr *graphs = graph_registry.map.lookup_ptr(bmain);
if (graphs) {
return **graphs;
}
return {};
}
} // namespace blender::deg

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "BLI_span.hh"
namespace blender {
struct Main;
namespace deg {
struct Depsgraph;
void register_graph(Depsgraph *depsgraph);
void unregister_graph(Depsgraph *depsgraph);
Span<Depsgraph *> get_all_registered_graphs(Main *bmain);
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,23 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/depsgraph_relation.hh" /* own include */
#include "intern/node/deg_node.hh"
namespace blender::deg {
void Relation::unlink()
{
/* Sanity check. */
BLI_assert(from != nullptr && to != nullptr);
from->outlinks.remove_first_occurrence_and_reorder(this);
to->inlinks.remove_first_occurrence_and_reorder(this);
}
} // namespace blender::deg

View File

@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
namespace blender::deg {
struct Node;
/* Settings/Tags on Relationship.
* NOTE: Is a bitmask, allowing accumulation. */
enum RelationFlag {
/* "cyclic" link - when detecting cycles, this relationship was the one
* which triggers a cyclic relationship to exist in the graph. */
RELATION_FLAG_CYCLIC = (1 << 0),
/* Update flush will not go through this relation. */
RELATION_FLAG_NO_FLUSH = (1 << 1),
/* Only flush along the relation is update comes from a node which was
* affected by user input. */
RELATION_FLAG_FLUSH_USER_EDIT_ONLY = (1 << 2),
/* The relation can not be killed by the cyclic dependencies solver. */
RELATION_FLAG_GODMODE = (1 << 4),
/* Relation will check existence before being added. */
RELATION_CHECK_BEFORE_ADD = (1 << 5),
/* The relation does not participate in visibility checks. */
RELATION_NO_VISIBILITY_CHANGE = (1 << 6),
};
/* B depends on A (A -> B) */
struct Relation {
Relation(Node *from, Node *to, const char *description) : from(from), to(to), name(description)
{
}
void unlink();
/* the nodes in the relationship (since this is shared between the nodes) */
Node *from; /* A */
Node *to; /* B */
/* relationship attributes */
const char *name; /* label for debugging */
int flag = 0; /* Bitmask of RelationFlag. */
};
} // namespace blender::deg

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/node/deg_node.hh"
namespace blender {
struct ID;
struct Main;
namespace deg {
struct Depsgraph;
/* Get type of a node which corresponds to a ID_RECALC_GEOMETRY tag. */
NodeType geometry_tag_to_component(const ID *id);
/* Tag given ID for an update in all registered dependency graphs. */
void id_tag_update(Main *bmain, ID *id, unsigned int flags, eUpdateSource update_source);
/* Tag given ID for an update with in a given dependency graph. */
void graph_id_tag_update(
Main *bmain, Depsgraph *graph, ID *id, unsigned int flags, eUpdateSource update_source);
/* Tag IDs of the graph for the visibility update tags.
* Will do nothing if the graph is not tagged for visibility update. */
void graph_tag_ids_for_visible_update(Depsgraph *graph);
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,43 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Defines and code for core node types.
*/
#include <cstdlib> /* for BLI_assert() */
#include "DNA_customdata_types.h"
#include "DEG_depsgraph.hh"
#include "intern/depsgraph_type.hh"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_operation.hh"
namespace blender {
void DEG_register_node_types()
{
/* register node types */
deg::deg_register_base_depsnodes();
deg::deg_register_component_depsnodes();
deg::deg_register_operation_depsnodes();
}
void DEG_free_node_types() {}
deg::DEGCustomDataMeshMasks::DEGCustomDataMeshMasks(const CustomData_MeshMasks *other)
: vert_mask(other->vmask),
edge_mask(other->emask),
face_mask(other->fmask),
loop_mask(other->lmask),
poly_mask(other->pmask)
{
}
} // namespace blender

View File

@@ -0,0 +1,134 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Data-types for internal use in the Depsgraph
*
* All of these data-types are only really used within the "core" depsgraph.
* In particular, node types declared here form the structure of operations
* in the graph.
*/
#pragma once
#include <cstdint>
namespace blender {
/* TODO(sergey): Ideally we'll just use char* and statically allocated strings
* to avoid any possible overhead caused by string (re)allocation/formatting. */
struct CustomData_MeshMasks;
namespace deg {
/* Source of the dependency graph node update tag.
*
* NOTE: This is a bit mask, so accumulation of sources is possible.
*
* TODO(sergey): Find a better place for this. */
enum eUpdateSource {
/* Update is caused by a time change. */
DEG_UPDATE_SOURCE_TIME = (1 << 0),
/* Update caused by user directly or indirectly influencing the node. */
DEG_UPDATE_SOURCE_USER_EDIT = (1 << 1),
/* Update is happening as a special response for the relations update. */
DEG_UPDATE_SOURCE_RELATIONS = (1 << 2),
/* Update is happening due to visibility change. */
DEG_UPDATE_SOURCE_VISIBILITY = (1 << 3),
/**
* Update is happening because some side effect of the reevaluation is necessary. For example,
* baking intermediate geometry nodes happens during normal evaluation but does not actually
* change the final result. For baking we still want to reevaluate it.
*/
DEG_UPDATE_SOURCE_SIDE_EFFECT_REQUEST = (1 << 4),
};
/* C++ wrapper around DNA's CustomData_MeshMasks struct. */
struct DEGCustomDataMeshMasks {
uint64_t vert_mask;
uint64_t edge_mask;
uint64_t face_mask;
uint64_t loop_mask;
uint64_t poly_mask;
DEGCustomDataMeshMasks() : vert_mask(0), edge_mask(0), face_mask(0), loop_mask(0), poly_mask(0)
{
}
explicit DEGCustomDataMeshMasks(const CustomData_MeshMasks *other);
DEGCustomDataMeshMasks &operator|=(const DEGCustomDataMeshMasks &other)
{
this->vert_mask |= other.vert_mask;
this->edge_mask |= other.edge_mask;
this->face_mask |= other.face_mask;
this->loop_mask |= other.loop_mask;
this->poly_mask |= other.poly_mask;
return *this;
}
DEGCustomDataMeshMasks operator|(const DEGCustomDataMeshMasks &other) const
{
DEGCustomDataMeshMasks result;
result.vert_mask = this->vert_mask | other.vert_mask;
result.edge_mask = this->edge_mask | other.edge_mask;
result.face_mask = this->face_mask | other.face_mask;
result.loop_mask = this->loop_mask | other.loop_mask;
result.poly_mask = this->poly_mask | other.poly_mask;
return result;
}
bool operator==(const DEGCustomDataMeshMasks &other) const
{
return (this->vert_mask == other.vert_mask && this->edge_mask == other.edge_mask &&
this->face_mask == other.face_mask && this->loop_mask == other.loop_mask &&
this->poly_mask == other.poly_mask);
}
bool operator!=(const DEGCustomDataMeshMasks &other) const
{
return !(*this == other);
}
static DEGCustomDataMeshMasks MaskVert(const uint64_t vert_mask)
{
DEGCustomDataMeshMasks result;
result.vert_mask = vert_mask;
return result;
}
static DEGCustomDataMeshMasks MaskEdge(const uint64_t edge_mask)
{
DEGCustomDataMeshMasks result;
result.edge_mask = edge_mask;
return result;
}
static DEGCustomDataMeshMasks MaskFace(const uint64_t face_mask)
{
DEGCustomDataMeshMasks result;
result.face_mask = face_mask;
return result;
}
static DEGCustomDataMeshMasks MaskLoop(const uint64_t loop_mask)
{
DEGCustomDataMeshMasks result;
result.loop_mask = loop_mask;
return result;
}
static DEGCustomDataMeshMasks MaskPoly(const uint64_t poly_mask)
{
DEGCustomDataMeshMasks result;
result.poly_mask = poly_mask;
return result;
}
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,42 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/depsgraph_update.hh"
#include "DEG_depsgraph.hh"
namespace blender {
namespace deg {
static DEG_EditorUpdateIDCb deg_editor_update_id_cb = nullptr;
static DEG_EditorUpdateSceneCb deg_editor_update_scene_cb = nullptr;
void deg_editors_id_update(const DEGEditorUpdateContext *update_ctx, ID *id)
{
if (deg_editor_update_id_cb != nullptr) {
deg_editor_update_id_cb(update_ctx, id);
}
}
void deg_editors_scene_update(const DEGEditorUpdateContext *update_ctx, bool updated)
{
if (deg_editor_update_scene_cb != nullptr) {
deg_editor_update_scene_cb(update_ctx, updated);
}
}
} // namespace deg
void DEG_editors_set_update_cb(DEG_EditorUpdateIDCb id_func, DEG_EditorUpdateSceneCb scene_func)
{
deg::deg_editor_update_id_cb = id_func;
deg::deg_editor_update_scene_cb = scene_func;
}
} // namespace blender

View File

@@ -0,0 +1,23 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
namespace blender {
struct DEGEditorUpdateContext;
struct ID;
namespace deg {
void deg_editors_id_update(const DEGEditorUpdateContext *update_ctx, ID *id);
void deg_editors_scene_update(const DEGEditorUpdateContext *update_ctx, bool updated);
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,25 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <mutex>
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_writeback_sync.hh"
#include "depsgraph.hh"
namespace blender::deg::sync_writeback {
void add(blender::Depsgraph &depsgraph, std::function<void()> fn)
{
deg::Depsgraph &deg_graph = reinterpret_cast<deg::Depsgraph &>(depsgraph);
if (!deg_graph.is_active || deg_graph.sync_writeback == DEG_EVALUATE_SYNC_WRITEBACK_NO) {
return;
}
std::lock_guard lock{deg_graph.sync_writeback_callbacks_mutex};
deg_graph.sync_writeback_callbacks.append(std::move(fn));
}
} // namespace blender::deg::sync_writeback

View File

@@ -0,0 +1,518 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Evaluation engine entry-points for Depsgraph Engine.
*/
#include <atomic>
#include <cstdint>
#include "intern/eval/deg_eval.h"
#include "BLI_function_ref.hh"
#include "BLI_gsqueue.h"
#include "BLI_task.h"
#include "BLI_time.h"
#include "BKE_global.hh"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#ifdef WITH_PYTHON
# include "BPY_extern.hh"
#endif
#include "atomic_ops.h"
#include "intern/depsgraph.hh"
#include "intern/depsgraph_relation.hh"
#include "intern/depsgraph_tag.hh"
#include "intern/eval/deg_eval_copy_on_write.h"
#include "intern/eval/deg_eval_flush.h"
#include "intern/eval/deg_eval_stats.h"
#include "intern/eval/deg_eval_visibility.h"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_operation.hh"
namespace blender::deg {
namespace {
struct DepsgraphEvalState;
void deg_task_run_func(TaskPool *pool, void *taskdata);
void schedule_children(DepsgraphEvalState *state,
OperationNode *node,
FunctionRef<void(OperationNode *node)> schedule_fn);
/* Denotes which part of dependency graph is being evaluated. */
enum class EvaluationStage {
/* Stage 1: Only Copy-on-Write operations are to be evaluated, prior to anything else.
* This allows other operations to access its dependencies when there is a dependency cycle
* involved. */
COPY_ON_EVAL,
/* Evaluate actual ID nodes visibility based on the current state of animation and drivers. */
DYNAMIC_VISIBILITY,
/* Threaded evaluation of all possible operations. */
THREADED_EVALUATION,
/* Workaround for areas which can not be evaluated in threads.
*
* For example, meta-balls, which are iterating over all bases and are requesting dupli-lists
* to see whether there are meta-balls inside. */
SINGLE_THREADED_WORKAROUND,
};
struct DepsgraphEvalState {
Depsgraph *graph;
bool do_stats;
EvaluationStage stage;
bool need_update_pending_parents = true;
bool need_single_thread_pass = false;
};
void evaluate_node(const DepsgraphEvalState *state, OperationNode *operation_node)
{
blender::Depsgraph *depsgraph = reinterpret_cast<blender::Depsgraph *>(state->graph);
/* Sanity checks. */
BLI_assert_msg(!operation_node->is_noop(), "NOOP nodes should not actually be scheduled");
/* Perform operation. */
if (state->do_stats) {
const double start_time = BLI_time_now_seconds();
operation_node->evaluate(depsgraph);
operation_node->stats.current_time += BLI_time_now_seconds() - start_time;
}
else {
operation_node->evaluate(depsgraph);
}
/* Clear the flag early on, allowing partial updates without re-evaluating the same node multiple
* times.
* This is a thread-safe modification as the node's flags are only read for a non-scheduled nodes
* and this node has been scheduled. */
operation_node->flag &= ~DEPSOP_FLAG_CLEAR_ON_EVAL;
}
void deg_task_run_func(TaskPool *pool, void *taskdata)
{
void *userdata_v = BLI_task_pool_user_data(pool);
DepsgraphEvalState *state = static_cast<DepsgraphEvalState *>(userdata_v);
/* Evaluate node. */
OperationNode *operation_node = reinterpret_cast<OperationNode *>(taskdata);
evaluate_node(state, operation_node);
/* Schedule children. */
schedule_children(state, operation_node, [&](OperationNode *node) {
BLI_task_pool_push(pool, deg_task_run_func, node, false, nullptr);
});
}
bool check_operation_node_visible(const DepsgraphEvalState *state, OperationNode *op_node)
{
const ComponentNode *comp_node = op_node->owner;
/* Special case for copy-on-eval component: it is to be always evaluated, to keep copied
* "database" in a consistent state. */
if (comp_node->type == NodeType::COPY_ON_EVAL) {
return true;
}
/* Special case for dynamic visibility pass: the actual visibility is not yet known, so limit to
* only operations which affects visibility. */
if (state->stage == EvaluationStage::DYNAMIC_VISIBILITY) {
return op_node->flag & OperationFlag::DEPSOP_FLAG_AFFECTS_VISIBILITY;
}
return comp_node->affects_visible_id;
}
void calculate_pending_parents_for_node(const DepsgraphEvalState *state, OperationNode *node)
{
/* Update counters, applies for both visible and invisible IDs. */
node->num_links_pending = 0;
node->scheduled = false;
/* Invisible IDs requires no pending operations. */
if (!check_operation_node_visible(state, node)) {
return;
}
/* No need to bother with anything if node is not tagged for update. */
if ((node->flag & DEPSOP_FLAG_NEEDS_UPDATE) == 0) {
return;
}
for (Relation *rel : node->inlinks) {
if (rel->from->type == NodeType::OPERATION && (rel->flag & RELATION_FLAG_CYCLIC) == 0) {
OperationNode *from = static_cast<OperationNode *>(rel->from);
/* TODO(sergey): This is how old layer system was checking for the
* calculation, but how is it possible that visible object depends
* on an invisible? This is something what is prohibited after
* deg_graph_build_flush_layers(). */
if (!check_operation_node_visible(state, from)) {
continue;
}
/* No need to wait for operation which is up to date. */
if ((from->flag & DEPSOP_FLAG_NEEDS_UPDATE) == 0) {
continue;
}
++node->num_links_pending;
}
}
}
void calculate_pending_parents_if_needed(DepsgraphEvalState *state)
{
if (!state->need_update_pending_parents) {
return;
}
for (OperationNode *node : state->graph->operations) {
calculate_pending_parents_for_node(state, node);
}
state->need_update_pending_parents = false;
}
void initialize_execution(DepsgraphEvalState *state, Depsgraph *graph)
{
/* Clear tags and other things which needs to be clear. */
if (state->do_stats) {
for (OperationNode *node : graph->operations) {
node->stats.reset_current();
}
}
}
bool is_metaball_object_operation(const OperationNode *operation_node)
{
const ComponentNode *component_node = operation_node->owner;
const IDNode *id_node = component_node->owner;
/* This runs after the COPY_ON_EVAL stage which creates id_cow. */
BLI_assert(id_node->id_cow);
if (GS(id_node->id_cow->name) != ID_OB) {
return false;
}
const Object *object = reinterpret_cast<const Object *>(id_node->id_cow);
return object->type == OB_MBALL;
}
/* Simulation modifiers with sub-frames (fluid domain, dynamic paint canvas) perform direct updates
* of other objects, which can cause race conditions over certain data (#115636). Unless and until
* sub-steps are fully supported in depsgraph evaluation such objects must use single-threaded
* evaluation. */
bool is_modifier_subframe_operation(const OperationNode *operation_node)
{
const ComponentNode *component_node = operation_node->owner;
const IDNode *id_node = component_node->owner;
/* This runs after the COPY_ON_EVAL stage which creates id_cow. */
BLI_assert(id_node->id_cow);
if (GS(id_node->id_cow->name) != ID_OB) {
return false;
}
const Object *object = reinterpret_cast<const Object *>(id_node->id_cow);
for (const ModifierData &md : object->modifiers) {
if (md.type == eModifierType_Fluid) {
const auto &fmd = reinterpret_cast<const FluidModifierData &>(md);
if (fmd.type == MOD_FLUID_TYPE_DOMAIN) {
return true;
}
}
if (md.type == eModifierType_DynamicPaint) {
const auto &dmd = reinterpret_cast<const DynamicPaintModifierData &>(md);
if (dmd.type == MOD_DYNAMICPAINT_TYPE_CANVAS) {
return true;
}
}
}
return false;
}
bool need_evaluate_operation_at_stage(DepsgraphEvalState *state,
const OperationNode *operation_node)
{
const ComponentNode *component_node = operation_node->owner;
switch (state->stage) {
case EvaluationStage::COPY_ON_EVAL:
return (component_node->type == NodeType::COPY_ON_EVAL);
case EvaluationStage::DYNAMIC_VISIBILITY:
return operation_node->flag & OperationFlag::DEPSOP_FLAG_AFFECTS_VISIBILITY;
case EvaluationStage::THREADED_EVALUATION:
if (is_metaball_object_operation(operation_node)) {
state->need_single_thread_pass = true;
return false;
}
if (is_modifier_subframe_operation(operation_node)) {
state->need_single_thread_pass = true;
return false;
}
return true;
case EvaluationStage::SINGLE_THREADED_WORKAROUND:
return true;
}
BLI_assert_msg(0, "Unhandled evaluation stage, should never happen.");
return false;
}
/* Schedule a node if it needs evaluation.
* dec_parents: Decrement pending parents count, true when child nodes are
* scheduled after a task has been completed.
*/
void schedule_node(DepsgraphEvalState *state,
OperationNode *node,
bool dec_parents,
const FunctionRef<void(OperationNode *node)> schedule_fn)
{
/* No need to schedule nodes of invisible ID. */
if (!check_operation_node_visible(state, node)) {
return;
}
/* No need to schedule operations which are not tagged for update, they are
* considered to be up to date. */
if ((node->flag & DEPSOP_FLAG_NEEDS_UPDATE) == 0) {
return;
}
/* TODO(sergey): This is not strictly speaking safe to read
* num_links_pending. */
if (dec_parents) {
BLI_assert(node->num_links_pending > 0);
atomic_sub_and_fetch_uint32(&node->num_links_pending, 1);
}
/* Cal not schedule operation while its dependencies are not yet
* evaluated. */
if (node->num_links_pending != 0) {
return;
}
/* During the copy-on-eval stage only schedule copy-on-eval nodes. */
if (!need_evaluate_operation_at_stage(state, node)) {
return;
}
/* Actually schedule the node. */
bool is_scheduled = atomic_fetch_and_or_uint8(reinterpret_cast<uint8_t *>(&node->scheduled),
uint8_t(true));
if (!is_scheduled) {
if (node->is_noop()) {
/* Clear flags to avoid affecting subsequent update propagation.
* For normal nodes these are cleared when it is evaluated. */
node->flag &= ~DEPSOP_FLAG_CLEAR_ON_EVAL;
/* skip NOOP node, schedule children right away */
schedule_children(state, node, schedule_fn);
}
else {
/* children are scheduled once this task is completed */
schedule_fn(node);
}
}
}
void schedule_graph(DepsgraphEvalState *state,
const FunctionRef<void(OperationNode *node)> schedule_fn)
{
for (OperationNode *node : state->graph->operations) {
schedule_node(state, node, false, schedule_fn);
}
}
void schedule_children(DepsgraphEvalState *state,
OperationNode *node,
const FunctionRef<void(OperationNode *node)> schedule_fn)
{
for (Relation *rel : node->outlinks) {
OperationNode *child = static_cast<OperationNode *>(rel->to);
BLI_assert(child->type == NodeType::OPERATION);
if (child->scheduled) {
/* Happens when having cyclic dependencies. */
continue;
}
schedule_node(state, child, (rel->flag & RELATION_FLAG_CYCLIC) == 0, schedule_fn);
}
}
/* Evaluate given stage of the dependency graph evaluation using multiple threads.
*
* NOTE: Will assign the `state->stage` to the given stage. */
void evaluate_graph_threaded_stage(DepsgraphEvalState *state,
TaskPool *task_pool,
const EvaluationStage stage)
{
state->stage = stage;
calculate_pending_parents_if_needed(state);
schedule_graph(state, [&](OperationNode *node) {
BLI_task_pool_push(task_pool, deg_task_run_func, node, false, nullptr);
});
BLI_task_pool_work_and_wait(task_pool);
}
/* Evaluate remaining operations of the dependency graph in a single threaded manner. */
void evaluate_graph_single_threaded_if_needed(DepsgraphEvalState *state)
{
if (!state->need_single_thread_pass) {
return;
}
BLI_assert(!state->need_update_pending_parents);
state->stage = EvaluationStage::SINGLE_THREADED_WORKAROUND;
GSQueue *evaluation_queue = BLI_gsqueue_new(sizeof(OperationNode *));
auto schedule_node_to_queue = [&](OperationNode *node) {
BLI_gsqueue_push(evaluation_queue, &node);
};
schedule_graph(state, schedule_node_to_queue);
while (!BLI_gsqueue_is_empty(evaluation_queue)) {
OperationNode *operation_node;
BLI_gsqueue_pop(evaluation_queue, &operation_node);
evaluate_node(state, operation_node);
schedule_children(state, operation_node, schedule_node_to_queue);
}
BLI_gsqueue_free(evaluation_queue);
}
void depsgraph_ensure_view_layer(Depsgraph *graph)
{
/* We update evaluated scene in the following cases:
* - It was not expanded yet.
* - It was tagged for update of evaluated component.
* This allows us to have proper view layer pointer. */
Scene *scene_cow = graph->scene_cow;
if (deg_eval_copy_is_expanded(&scene_cow->id) &&
(scene_cow->id.recalc & ID_RECALC_SYNC_TO_EVAL) == 0)
{
return;
}
const IDNode *scene_id_node = graph->find_id_node(&graph->scene->id);
deg_update_eval_copy_datablock(graph, scene_id_node);
}
TaskPool *deg_evaluate_task_pool_create(DepsgraphEvalState *state)
{
if (G.debug & G_DEBUG_DEPSGRAPH_NO_THREADS) {
return BLI_task_pool_create_no_threads(state);
}
return BLI_task_pool_create_suspended(state, TASK_PRIORITY_HIGH);
}
} // namespace
void deg_evaluate_on_refresh(Depsgraph *graph)
{
/* Nothing to update, early out. */
if (graph->entry_tags.is_empty()) {
return;
}
/* The update counts can be used to check if the Depsgraph was changed since the last time it was
* cached by comparing its current update count with the one stored at the moment the Depsgraph
* data were cached.
*
* A global atomic is used as opposed to incrementing the update count per Depsgraph to protect
* against the case where the Depsgraph is being recreated for each update and used to feed the
* same running engine instances. This can happen when using a brute force update pattern (see
* #135635). */
static std::atomic<uint64_t> global_update_count = 0;
graph->update_count = global_update_count.fetch_add(1) + 1;
graph->debug.begin_graph_evaluation();
#ifdef WITH_PYTHON
/* Release the GIL so that Python drivers can be evaluated. See #91046. */
BPy_BEGIN_ALLOW_THREADS;
#endif
graph->is_evaluating = true;
depsgraph_ensure_view_layer(graph);
/* Set up evaluation state. */
DepsgraphEvalState state;
state.graph = graph;
state.do_stats = graph->debug.do_time_debug();
/* Prepare all nodes for evaluation. */
initialize_execution(&state, graph);
/* Evaluation happens in several incremental steps:
*
* - Start with the copy-on-evaluation operations which never form dependency cycles. This will
* ensure that if a dependency graph has a cycle evaluation functions will always "see" valid
* expanded datablock. It might not be evaluated yet, but at least the datablock will be valid.
*
* - If there is potentially dynamically changing visibility in the graph update the actual
* nodes visibilities, so that actual heavy data evaluation can benefit from knowledge that
* something heavy is not currently visible.
*
* - Multi-threaded evaluation of all possible nodes.
* Certain operations (and their subtrees) could be ignored. For example, meta-balls are not
* safe from threading point of view, so the threaded evaluation will stop at the metaball
* operation node.
*
* - Single-threaded pass of all remaining operations. */
TaskPool *task_pool = deg_evaluate_task_pool_create(&state);
evaluate_graph_threaded_stage(&state, task_pool, EvaluationStage::COPY_ON_EVAL);
if (graph->has_animated_visibility || graph->need_update_nodes_visibility) {
/* Update pending parents including only the ones which are affecting operations which are
* affecting visibility. */
state.need_update_pending_parents = true;
evaluate_graph_threaded_stage(&state, task_pool, EvaluationStage::DYNAMIC_VISIBILITY);
deg_graph_flush_visibility_flags_if_needed(graph);
/* Update parents to an updated visibility and evaluation stage.
*
* Need to do it regardless of whether visibility is actually changed or not: current state of
* the pending parents are all zeroes because it was previously calculated for only visibility
* related nodes and those are fully evaluated by now. */
state.need_update_pending_parents = true;
}
evaluate_graph_threaded_stage(&state, task_pool, EvaluationStage::THREADED_EVALUATION);
BLI_task_pool_free(task_pool);
evaluate_graph_single_threaded_if_needed(&state);
/* Finalize statistics gathering. This is because we only gather single
* operation timing here, without aggregating anything to avoid any extra
* synchronization. */
if (state.do_stats) {
deg_eval_stats_aggregate(graph);
}
/* Clear any uncleared tags. */
deg_graph_clear_tags(graph);
graph->is_evaluating = false;
#ifdef WITH_PYTHON
BPy_END_ALLOW_THREADS;
#endif
graph->debug.end_graph_evaluation();
}
} // namespace blender::deg

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Evaluation engine entry-points for Depsgraph Engine.
*/
#pragma once
namespace blender::deg {
struct Depsgraph;
/**
* Evaluate all nodes tagged for updating,
* \warning This is usually done as part of main loop, but may also be
* called from frame-change update.
*
* \note Time sources should be all valid!
*/
void deg_evaluate_on_refresh(Depsgraph *graph);
} // namespace blender::deg

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include <cstddef>
#include "DNA_ID.h"
namespace blender {
struct Depsgraph;
struct ID;
/* Uncomment this to have verbose log about original and evaluated 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;
class DepsgraphNodeBuilder;
struct IDNode;
/**
* Makes sure given evaluated data-block is brought back to state of the original
* data-block.
*/
ID *deg_update_eval_copy_datablock(const Depsgraph *depsgraph, const IDNode *id_node);
ID *deg_update_eval_copy_datablock(const Depsgraph *depsgraph, struct ID *id_orig);
/** Helper function which frees memory used by copy-on-written data-block. */
void deg_free_eval_copy_datablock(struct ID *id_cow);
/**
* Callback function for depsgraph operation node which ensures evaluated
* data-block is ready for use by further evaluation routines.
*/
void deg_create_eval_copy(blender::Depsgraph *depsgraph, const struct IDNode *id_node);
/**
* Check that given ID is properly expanded and does not have any shallow
* copies inside.
*/
bool deg_validate_eval_copy_datablock(ID *id_cow);
/** Tag given ID block as being copy-on-eval. */
void deg_tag_eval_copy_id(Depsgraph &depsgraph, struct ID *id_cow, const struct ID *id_orig);
/**
* Check whether ID data-block is expanded.
*
* TODO(sergey): Make it an inline function or a macro.
*/
bool deg_eval_copy_is_expanded(const struct ID *id_cow);
/**
* Check whether an evaluated data-block copy is needed for given ID.
*
* There are some exceptions on data-blocks which are covered by dependency graph
* but which we don't want to start duplicating.
*
* This includes images.
*/
bool deg_eval_copy_is_needed(const ID *id_orig);
bool deg_eval_copy_is_needed(const ID_Type id_type);
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,361 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Core routines for how the Depsgraph works.
*/
#include "intern/eval/deg_eval_flush.h"
#include <algorithm>
#include <deque>
#include "BLI_listbase.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "BKE_global.hh"
#include "BKE_key.hh"
#include "BKE_object.hh"
#include "BKE_scene.hh"
#include "DRW_engine.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_debug.hh"
#include "intern/debug/deg_debug.h"
#include "intern/depsgraph.hh"
#include "intern/depsgraph_relation.hh"
#include "intern/depsgraph_update.hh"
#include "intern/node/deg_node.hh"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_factory.hh"
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_operation.hh"
#include "intern/node/deg_node_time.hh"
#include "intern/eval/deg_eval_copy_on_write.h"
/* Invalidate data-block data when update is flushed on it.
*
* The idea of this is to help catching cases when area is accessing data which
* is not yet evaluated, which could happen due to missing relations. The issue
* is that usually that data will be kept from previous frame, and it looks to
* be plausible.
*
* This ensures that data does not look plausible, making it much easier to
* catch usage of invalid state. */
#undef INVALIDATE_ON_FLUSH
namespace blender::deg {
enum {
ID_STATE_NONE = 0,
ID_STATE_MODIFIED = 1,
};
enum {
COMPONENT_STATE_NONE = 0,
COMPONENT_STATE_SCHEDULED = 1,
COMPONENT_STATE_DONE = 2,
};
using FlushQueue = std::deque<OperationNode *>;
namespace {
void flush_init_id_node_func(void *__restrict data_v,
const int i,
const TaskParallelTLS *__restrict /*tls*/)
{
Depsgraph *graph = static_cast<Depsgraph *>(data_v);
IDNode *id_node = graph->id_nodes[i];
id_node->custom_flags = ID_STATE_NONE;
for (ComponentNode *comp_node : id_node->components.values()) {
comp_node->custom_flags = COMPONENT_STATE_NONE;
}
}
inline void flush_prepare(Depsgraph *graph)
{
for (OperationNode *node : graph->operations) {
node->scheduled = false;
}
{
const int num_id_nodes = graph->id_nodes.size();
TaskParallelSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.min_iter_per_thread = 1024;
BLI_task_parallel_range(0, num_id_nodes, graph, flush_init_id_node_func, &settings);
}
}
inline void flush_schedule_entrypoints(Depsgraph *graph, FlushQueue *queue)
{
for (OperationNode *op_node : graph->entry_tags) {
queue->push_back(op_node);
op_node->scheduled = true;
DEG_DEBUG_PRINTF((::blender::Depsgraph *)graph,
EVAL,
"Operation is entry point for update: %s\n",
op_node->identifier().c_str());
}
}
inline void flush_handle_id_node(IDNode *id_node)
{
id_node->custom_flags = ID_STATE_MODIFIED;
}
/* TODO(sergey): We can reduce number of arguments here. */
inline void flush_handle_component_node(IDNode *id_node,
ComponentNode *comp_node,
FlushQueue *queue)
{
/* We only handle component once. */
if (comp_node->custom_flags == COMPONENT_STATE_DONE) {
return;
}
comp_node->custom_flags = COMPONENT_STATE_DONE;
/* Tag all required operations in component for update, unless this is a
* special component where we don't want all operations to be tagged.
*
* TODO(sergey): Make this a more generic solution. */
if (!ELEM(comp_node->type, NodeType::PARTICLE_SETTINGS, NodeType::PARTICLE_SYSTEM)) {
const bool is_geometry_component = comp_node->type == NodeType::GEOMETRY;
for (OperationNode *op : comp_node->operations) {
/* Special case for the visibility operation in the geometry component.
*
* This operation is a part of the geometry component so that manual tag for geometry recalc
* ensures that the visibility is re-evaluated. This operation is not to be re-evaluated when
* an update is flushed to the geometry component via a time dependency or a driver targeting
* a modifier. Skipping update in this case avoids CPU time unnecessarily spent looping over
* modifiers and looking up operations by name in the visibility evaluation function. */
if (is_geometry_component && op->opcode == OperationCode::VISIBILITY) {
continue;
}
op->flag |= DEPSOP_FLAG_NEEDS_UPDATE;
}
}
/* when some target changes bone, we might need to re-run the
* whole IK solver, otherwise result might be unpredictable. */
if (comp_node->type == NodeType::BONE) {
ComponentNode *pose_comp = id_node->find_component(NodeType::EVAL_POSE);
BLI_assert(pose_comp != nullptr);
if (pose_comp->custom_flags == COMPONENT_STATE_NONE) {
queue->push_front(pose_comp->get_entry_operation());
pose_comp->custom_flags = COMPONENT_STATE_SCHEDULED;
}
}
}
/* Schedule children of the given operation node for traversal.
*
* One of the children will bypass the queue and will be returned as a function
* return value, so it can start being handled right away, without building too
* much of a queue.
*/
inline OperationNode *flush_schedule_children(OperationNode *op_node, FlushQueue *queue)
{
if (op_node->flag & DEPSOP_FLAG_USER_MODIFIED) {
IDNode *id_node = op_node->owner->owner;
id_node->is_user_modified = true;
}
OperationNode *result = nullptr;
for (Relation *rel : op_node->outlinks) {
/* Flush is forbidden, completely. */
if (rel->flag & RELATION_FLAG_NO_FLUSH) {
continue;
}
/* Relation only allows flushes on user changes, but the node was not
* affected by user. */
if ((rel->flag & RELATION_FLAG_FLUSH_USER_EDIT_ONLY) &&
(op_node->flag & DEPSOP_FLAG_USER_MODIFIED) == 0)
{
continue;
}
OperationNode *to_node = static_cast<OperationNode *>(rel->to);
/* Always flush flushable flags, so children always know what happened
* to their parents. */
to_node->flag |= (op_node->flag & DEPSOP_FLAG_FLUSH);
/* Flush update over the relation, if it was not flushed yet. */
if (to_node->scheduled) {
continue;
}
if (result != nullptr) {
queue->push_front(to_node);
}
else {
result = to_node;
}
to_node->scheduled = true;
}
return result;
}
/* NOTE: It will also accumulate flags from changed components. */
void flush_editors_id_update(Depsgraph *graph, const DEGEditorUpdateContext *update_ctx)
{
for (IDNode *id_node : graph->id_nodes) {
if (id_node->custom_flags != ID_STATE_MODIFIED) {
continue;
}
DEG_graph_id_type_tag(reinterpret_cast<::blender::Depsgraph *>(graph),
GS(id_node->id_orig->name));
/* TODO(sergey): Do we need to pass original or evaluated ID here? */
ID *id_orig = id_node->id_orig;
ID *id_cow = id_node->id_cow;
/* Gather recalc flags from all changed components. */
for (ComponentNode *comp_node : id_node->components.values()) {
if (comp_node->custom_flags != COMPONENT_STATE_DONE) {
continue;
}
DepsNodeFactory *factory = type_get_factory(comp_node->type);
BLI_assert(factory != nullptr);
id_cow->recalc |= factory->id_recalc_tag();
}
DEG_DEBUG_PRINTF((blender::Depsgraph *)graph,
EVAL,
"Accumulated recalc bits for %s: %u\n",
id_orig->name,
uint(id_cow->recalc));
/* Inform editors. Only if the data-block is being evaluated a second
* time, to distinguish between user edits and initial evaluation when
* the data-block becomes visible.
*
* TODO: image data-blocks do not use copy-on-eval, so might not be detected
* correctly. */
if (deg_eval_copy_is_expanded(id_cow)) {
if (graph->is_active && id_node->is_user_modified) {
deg_editors_id_update(update_ctx, id_orig);
}
}
}
}
#ifdef INVALIDATE_ON_FLUSH
void invalidate_tagged_evaluated_transform(ID *id)
{
const ID_Type id_type = GS(id->name);
switch (id_type) {
case ID_OB: {
Object *object = (Object *)id;
std::fill_n((float *)object->object_to_world().ptr(), 16, NAN);
break;
}
default:
break;
}
}
void invalidate_tagged_evaluated_geometry(ID *id)
{
const ID_Type id_type = GS(id->name);
switch (id_type) {
case ID_OB: {
Object *object = (Object *)id;
BKE_object_free_derived_caches(object);
break;
}
default:
break;
}
}
#endif
void invalidate_tagged_evaluated_data(Depsgraph *graph)
{
#ifdef INVALIDATE_ON_FLUSH
for (IDNode *id_node : graph->id_nodes) {
if (id_node->custom_flags != ID_STATE_MODIFIED) {
continue;
}
ID *id_cow = id_node->id_cow;
if (!deg_eval_copy_is_expanded(id_cow)) {
continue;
}
for (ComponentNode *comp_node : id_node->components.values()) {
if (comp_node->custom_flags != COMPONENT_STATE_DONE) {
continue;
}
switch (comp_node->type) {
case ID_RECALC_TRANSFORM:
invalidate_tagged_evaluated_transform(id_cow);
break;
case ID_RECALC_GEOMETRY:
invalidate_tagged_evaluated_geometry(id_cow);
break;
default:
break;
}
}
}
#else
(void)graph;
#endif
}
} // namespace
void deg_graph_flush_updates(Depsgraph *graph)
{
/* Sanity checks. */
BLI_assert(graph != nullptr);
Main *bmain = graph->bmain;
graph->time_source->flush_update_tag(graph);
/* Nothing to update, early out. */
if (graph->entry_tags.is_empty()) {
return;
}
/* Reset all flags, get ready for the flush. */
flush_prepare(graph);
/* Starting from the tagged "entry" nodes, flush outwards. */
FlushQueue queue;
flush_schedule_entrypoints(graph, &queue);
/* Prepare update context for editors. */
DEGEditorUpdateContext update_ctx;
update_ctx.bmain = bmain;
update_ctx.depsgraph = reinterpret_cast<::blender::Depsgraph *>(graph);
update_ctx.scene = graph->scene;
update_ctx.view_layer = graph->view_layer;
/* Do actual flush. */
while (!queue.empty()) {
OperationNode *op_node = queue.front();
queue.pop_front();
while (op_node != nullptr) {
/* Tag operation as required for update. */
op_node->flag |= DEPSOP_FLAG_NEEDS_UPDATE;
/* Inform corresponding ID and component nodes about the change. */
ComponentNode *comp_node = op_node->owner;
IDNode *id_node = comp_node->owner;
flush_handle_id_node(id_node);
flush_handle_component_node(id_node, comp_node, &queue);
/* Flush to nodes along links. */
op_node = flush_schedule_children(op_node, &queue);
}
}
/* Inform editors about all changes. */
flush_editors_id_update(graph, &update_ctx);
/* Reset evaluation result tagged which is tagged for update to some state
* which is obvious to catch. */
invalidate_tagged_evaluated_data(graph);
}
void deg_graph_clear_tags(Depsgraph *graph)
{
/* Clear any entry tags which haven't been flushed. */
graph->entry_tags.clear();
graph->time_source->tagged_for_update = false;
}
} // namespace blender::deg

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Core routines for how the Depsgraph works.
*/
#pragma once
namespace blender::deg {
struct Depsgraph;
/**
* Flush updates from tagged nodes outwards until all affected nodes are tagged.
*/
void deg_graph_flush_updates(struct Depsgraph *graph);
/**
* Clear tags from all operation nodes.
*/
void deg_graph_clear_tags(struct Depsgraph *graph);
} // namespace blender::deg

View File

@@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/eval/deg_eval_runtime_backup.h"
#include "intern/eval/deg_eval_copy_on_write.h"
#include "DRW_engine.hh"
namespace blender::deg {
RuntimeBackup::RuntimeBackup(const Depsgraph *depsgraph)
: have_backup(false),
id_data({nullptr}),
animation_backup(depsgraph),
scene_backup(depsgraph),
sound_backup(depsgraph),
object_backup(depsgraph),
movieclip_backup(depsgraph),
volume_backup(depsgraph)
{
}
void RuntimeBackup::init_from_id(ID *id)
{
if (!deg_eval_copy_is_expanded(id)) {
return;
}
have_backup = true;
/* Clear, so freeing the expanded data doesn't touch this Python reference. */
id_data.py_instance = id->py_instance;
id->py_instance = nullptr;
animation_backup.init_from_id(id);
const ID_Type id_type = GS(id->name);
switch (id_type) {
case ID_OB:
object_backup.init_from_object(reinterpret_cast<Object *>(id));
break;
case ID_SCE:
scene_backup.init_from_scene(reinterpret_cast<Scene *>(id));
break;
case ID_SO:
sound_backup.init_from_sound(reinterpret_cast<bSound *>(id));
break;
case ID_MC:
movieclip_backup.init_from_movieclip(reinterpret_cast<MovieClip *>(id));
break;
case ID_VO:
volume_backup.init_from_volume(reinterpret_cast<Volume *>(id));
break;
default:
break;
}
}
void RuntimeBackup::restore_to_id(ID *id)
{
if (!have_backup) {
return;
}
id->py_instance = id_data.py_instance;
animation_backup.restore_to_id(id);
const ID_Type id_type = GS(id->name);
switch (id_type) {
case ID_OB:
object_backup.restore_to_object(reinterpret_cast<Object *>(id));
break;
case ID_SCE:
scene_backup.restore_to_scene(reinterpret_cast<Scene *>(id));
break;
case ID_SO:
sound_backup.restore_to_sound(reinterpret_cast<bSound *>(id));
break;
case ID_MC:
movieclip_backup.restore_to_movieclip(reinterpret_cast<MovieClip *>(id));
break;
case ID_VO:
volume_backup.restore_to_volume(reinterpret_cast<Volume *>(id));
break;
default:
break;
}
}
} // namespace blender::deg

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "DNA_ID.h"
#include "intern/eval/deg_eval_runtime_backup_animation.h"
#include "intern/eval/deg_eval_runtime_backup_movieclip.h"
#include "intern/eval/deg_eval_runtime_backup_object.h"
#include "intern/eval/deg_eval_runtime_backup_scene.h"
#include "intern/eval/deg_eval_runtime_backup_sound.h"
#include "intern/eval/deg_eval_runtime_backup_volume.h"
namespace blender::deg {
struct Depsgraph;
class RuntimeBackup {
public:
explicit RuntimeBackup(const Depsgraph *depsgraph);
/* NOTE: Will reset all runtime fields which has been backed up to nullptr. */
void init_from_id(ID *id);
/* Restore fields to the given ID. */
void restore_to_id(ID *id);
/* Denotes whether init_from_id did put anything into the backup storage.
* This will not be the case when init_from_id() is called for an ID which has never been
* copied-on-eval. In this case there is no need to backup or restore anything.
*
* It also allows to have restore() logic to be symmetrical to init() without need to worry
* that init() might not have happened.
*
* In practice this is used by audio system to lock audio while scene is going through
* copy-on-evaluation mechanism. */
bool have_backup;
/* Struct members of the ID pointer. */
struct {
void *py_instance;
} id_data;
AnimationBackup animation_backup;
SceneBackup scene_backup;
SoundBackup sound_backup;
ObjectRuntimeBackup object_backup;
MovieClipBackup movieclip_backup;
VolumeBackup volume_backup;
};
} // namespace blender::deg

View File

@@ -0,0 +1,101 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/eval/deg_eval_runtime_backup_animation.h"
#include "DNA_anim_types.h"
#include "BKE_anim_data.hh"
#include "BKE_animsys.h"
#include "RNA_access.hh"
#include "RNA_types.hh"
#include "intern/depsgraph.hh"
namespace blender::deg {
AnimationValueBackup::AnimationValueBackup(const std::string &rna_path,
int array_index,
float value)
: rna_path(rna_path), array_index(array_index), value(value)
{
}
AnimationBackup::AnimationBackup(const Depsgraph *depsgraph)
{
meed_value_backup = !depsgraph->is_active;
reset();
}
void AnimationBackup::reset() {}
void AnimationBackup::init_from_id(ID *id)
{
/* NOTE: This animation backup nicely preserves values which are animated and
* are not touched by frame/depsgraph post_update handler.
*
* But it makes it impossible to have user edits to animated properties: for
* example, translation of object with animated location will not work with
* the current version of backup. */
return;
PointerRNA id_pointer_rna = RNA_id_pointer_create(id);
BKE_fcurves_id_cb(id, [&](ID *cb_id, FCurve *fcurve) {
if (fcurve->rna_path == nullptr || fcurve->rna_path[0] == '\0') {
return;
}
if (id != cb_id) {
return;
}
/* Resolve path to the property. */
PathResolvedRNA resolved_rna;
if (!BKE_animsys_rna_path_resolve(
&id_pointer_rna, fcurve->rna_path, fcurve->array_index, &resolved_rna))
{
return;
}
/* Read property value. */
float value;
if (!BKE_animsys_read_from_rna_path(&resolved_rna, &value)) {
return;
}
this->values_backup.append({fcurve->rna_path, fcurve->array_index, value});
});
}
void AnimationBackup::restore_to_id(ID *id)
{
return;
PointerRNA id_pointer_rna = RNA_id_pointer_create(id);
for (const AnimationValueBackup &value_backup : values_backup) {
/* Resolve path to the property.
*
* NOTE: Do it again (after storing), since the sub-data pointers might be
* changed after copy-on-evaluation. */
PathResolvedRNA resolved_rna;
if (!BKE_animsys_rna_path_resolve(&id_pointer_rna,
value_backup.rna_path.c_str(),
value_backup.array_index,
&resolved_rna))
{
return;
}
/* Write property value. */
if (!BKE_animsys_write_to_rna_path(&resolved_rna, value_backup.value)) {
return;
}
}
}
} // namespace blender::deg

View File

@@ -0,0 +1,54 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include <string>
#include "BLI_vector.hh"
namespace blender {
struct ID;
namespace deg {
struct Depsgraph;
class AnimationValueBackup {
public:
AnimationValueBackup() = default;
AnimationValueBackup(const std::string &rna_path, int array_index, float value);
AnimationValueBackup(const AnimationValueBackup &other) = default;
AnimationValueBackup(AnimationValueBackup &&other) noexcept = default;
AnimationValueBackup &operator=(const AnimationValueBackup &other) = default;
AnimationValueBackup &operator=(AnimationValueBackup &&other) = default;
std::string rna_path;
int array_index;
float value;
};
/* Backup of animated properties values. */
class AnimationBackup {
public:
AnimationBackup(const Depsgraph *depsgraph);
void reset();
void init_from_id(ID *id);
void restore_to_id(ID *id);
bool meed_value_backup;
Vector<AnimationValueBackup> values_backup;
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,20 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/eval/deg_eval_runtime_backup_modifier.h"
#include "DNA_modifier_types.h"
namespace blender::deg {
ModifierDataBackup::ModifierDataBackup(ModifierData *modifier_data)
: type(modifier_data->type), runtime(modifier_data->runtime)
{
}
} // namespace blender::deg

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include "DNA_modifier_types.h"
namespace blender {
struct ModifierData;
namespace deg {
class ModifierDataBackup {
public:
explicit ModifierDataBackup(ModifierData *modifier_data);
ModifierType type;
void *runtime;
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,44 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/eval/deg_eval_runtime_backup_movieclip.h"
#include "DNA_movieclip_types.h"
namespace blender::deg {
MovieClipBackup::MovieClipBackup(const Depsgraph * /*depsgraph*/)
{
reset();
}
void MovieClipBackup::reset()
{
anim = nullptr;
cache = nullptr;
}
void MovieClipBackup::init_from_movieclip(MovieClip *movieclip)
{
anim = movieclip->anim;
cache = movieclip->cache;
/* Clear pointers stored in the movie clip, so they are not freed when copied-on-written
* datablock is freed for re-allocation. */
movieclip->anim = nullptr;
movieclip->cache = nullptr;
}
void MovieClipBackup::restore_to_movieclip(MovieClip *movieclip)
{
movieclip->anim = anim;
movieclip->cache = cache;
reset();
}
} // namespace blender::deg

View File

@@ -0,0 +1,36 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
namespace blender {
struct MovieClip;
struct MovieClipCache;
struct MovieReader;
namespace deg {
struct Depsgraph;
/* Backup of movie clip runtime data. */
class MovieClipBackup {
public:
MovieClipBackup(const Depsgraph *depsgraph);
void reset();
void init_from_movieclip(MovieClip *movieclip);
void restore_to_movieclip(MovieClip *movieclip);
struct MovieReader *anim;
struct MovieClipCache *cache;
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,192 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "BLI_session_uid.h"
#include "intern/eval/deg_eval_runtime_backup_object.h"
#include <cstring>
#include "DNA_mesh_types.h"
#include "BLI_listbase.h"
#include "BKE_action.hh"
#include "BKE_light_linking.h"
#include "BKE_mesh_types.hh"
#include "BKE_modifier.hh"
#include "BKE_object.hh"
#include "BKE_object_types.hh"
namespace blender::deg {
ObjectRuntimeBackup::ObjectRuntimeBackup(const Depsgraph * /*depsgraph*/)
: base_flag(0), base_local_view_bits(0)
{
/* TODO(sergey): Use something like BKE_object_runtime_reset(). */
runtime = {};
}
void ObjectRuntimeBackup::init_from_object(Object *object)
{
/* Store evaluated mesh and curve_cache, and make sure we don't free it. */
runtime = *object->runtime;
if (object->light_linking) {
light_linking_runtime = object->light_linking->runtime;
}
BKE_object_runtime_reset(object);
/* Keep bounding-box (for now at least). */
object->runtime->bounds_eval = runtime.bounds_eval;
/* Object update will override actual object->data to an evaluated version.
* Need to make sure we don't have data set to evaluated one before free
* anything. */
object->data = runtime.data_orig;
/* Make a backup of base flags. */
base_flag = object->base_flag;
base_local_view_bits = object->base_local_view_bits;
/* Backup runtime data of all modifiers. */
backup_modifier_runtime_data(object);
/* Backup runtime data of all pose channels. */
backup_pose_channel_runtime_data(object);
}
void ObjectRuntimeBackup::backup_modifier_runtime_data(Object *object)
{
for (ModifierData &modifier_data : object->modifiers) {
if (modifier_data.runtime == nullptr) {
continue;
}
modifier_runtime_data.add(modifier_data.persistent_uid, ModifierDataBackup(&modifier_data));
modifier_data.runtime = nullptr;
}
}
void ObjectRuntimeBackup::backup_pose_channel_runtime_data(Object *object)
{
if (object->pose != nullptr) {
for (bPoseChannel &pchan : object->pose->chanbase) {
const SessionUID &session_uid = pchan.runtime.session_uid;
BLI_assert(BLI_session_uid_is_generated(&session_uid));
pose_channel_runtime_data.add(session_uid, pchan.runtime);
BKE_pose_channel_runtime_reset(&pchan.runtime);
}
}
}
void ObjectRuntimeBackup::restore_to_object(Object *object)
{
ID *data_orig = object->runtime->data_orig;
ID *data_eval = runtime.data_eval;
std::optional<Bounds<float3>> bounds = object->runtime->bounds_eval;
SculptSession *sculpt_session = object->runtime->sculpt_session;
*object->runtime = runtime;
object->runtime->data_orig = data_orig;
object->runtime->bounds_eval = bounds;
object->runtime->sculpt_session = sculpt_session;
if (ELEM(object->type, OB_MESH, OB_LATTICE, OB_CURVES_LEGACY, OB_FONT) && data_eval != nullptr) {
if (object->id.recalc & ID_RECALC_GEOMETRY) {
/* If geometry is tagged for update it means, that part of
* evaluated mesh are not valid anymore. In this case we can not
* have any "persistent" pointers to point to an invalid data.
*
* We restore object's data datablock to an original copy of
* that datablock. */
object->data = data_orig;
/* After that, immediately free the invalidated caches. */
BKE_object_free_derived_caches(object);
}
else {
/* Do same thing as object update: override actual object data pointer with evaluated
* datablock, but only if the evaluated data has the same type as the original data. */
if (GS(((ID *)object->data)->name) == GS(data_eval->name)) {
object->data = data_eval;
}
/* Evaluated mesh simply copied edit_mesh pointer from
* original mesh during update, need to make sure no dead
* pointers are left behind. */
if (object->type == OB_MESH) {
Mesh *mesh_eval = id_cast<Mesh *>(data_eval);
Mesh *mesh_orig = id_cast<Mesh *>(data_orig);
mesh_eval->runtime->edit_mesh = mesh_orig->runtime->edit_mesh;
}
}
}
else if (ELEM(object->type, OB_CURVES, OB_POINTCLOUD, OB_VOLUME, OB_GREASE_PENCIL)) {
if (object->id.recalc & ID_RECALC_GEOMETRY) {
/* Free evaluated caches. */
object->data = data_orig;
BKE_object_free_derived_caches(object);
}
else {
object->data = object->runtime->data_eval;
}
}
if (light_linking_runtime) {
/* Lazily allocate light linking on the evaluated object for the cases when the object is only
* a receiver or a blocker and does not need its own LightLinking on the original object. */
BKE_light_linking_ensure(object);
object->light_linking->runtime = *light_linking_runtime;
}
object->base_flag = base_flag;
object->base_local_view_bits = base_local_view_bits;
/* Restore modifier's runtime data.
* NOTE: Data of unused modifiers will be freed there. */
restore_modifier_runtime_data(object);
restore_pose_channel_runtime_data(object);
}
void ObjectRuntimeBackup::restore_modifier_runtime_data(Object *object)
{
for (ModifierData &modifier_data : object->modifiers) {
std::optional<ModifierDataBackup> backup = modifier_runtime_data.pop_try(
modifier_data.persistent_uid);
if (backup.has_value()) {
modifier_data.runtime = backup->runtime;
}
}
for (ModifierDataBackup &backup : modifier_runtime_data.values()) {
const ModifierTypeInfo *modifier_type_info = BKE_modifier_get_info(backup.type);
BLI_assert(modifier_type_info != nullptr);
modifier_type_info->free_runtime_data(backup.runtime);
if (backup.type == eModifierType_Subsurf) {
if (object->type == OB_MESH) {
Mesh *mesh = id_cast<Mesh *>(object->data);
if (mesh->runtime->subsurf_runtime_data == backup.runtime) {
mesh->runtime->subsurf_runtime_data = nullptr;
}
}
}
}
}
void ObjectRuntimeBackup::restore_pose_channel_runtime_data(Object *object)
{
if (object->pose != nullptr) {
for (bPoseChannel &pchan : object->pose->chanbase) {
const SessionUID &session_uid = pchan.runtime.session_uid;
std::optional<bPoseChannel_Runtime> runtime = pose_channel_runtime_data.pop_try(session_uid);
if (runtime.has_value()) {
pchan.runtime = *runtime;
}
}
}
for (bPoseChannel_Runtime &runtime : pose_channel_runtime_data.values()) {
BKE_pose_channel_runtime_free(&runtime);
}
}
} // namespace blender::deg

View File

@@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#pragma once
#include <optional>
#include "DNA_object_types.h"
#include "DNA_session_uid_types.h"
#include "BKE_object_types.hh"
#include "BLI_map.hh"
#include "intern/eval/deg_eval_runtime_backup_modifier.h"
namespace blender {
struct Object;
namespace deg {
struct Depsgraph;
class ObjectRuntimeBackup {
public:
ObjectRuntimeBackup(const Depsgraph *depsgraph);
/* Make a backup of object's evaluation runtime data, additionally
* make object to be safe for free without invalidating backed up
* pointers. */
void init_from_object(Object *object);
void backup_modifier_runtime_data(Object *object);
void backup_pose_channel_runtime_data(Object *object);
/* Restore all fields to the given object. */
void restore_to_object(Object *object);
/* NOTE: Will free all runtime data which has not been restored. */
void restore_modifier_runtime_data(Object *object);
void restore_pose_channel_runtime_data(Object *object);
bke::ObjectRuntime runtime;
std::optional<LightLinkingRuntime> light_linking_runtime;
short base_flag;
unsigned short base_local_view_bits;
Map<int, ModifierDataBackup> modifier_runtime_data;
Map<SessionUID, bPoseChannel_Runtime> pose_channel_runtime_data;
};
} // namespace deg
} // namespace blender

View File

@@ -0,0 +1,9 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*/
#include "intern/eval/deg_eval_runtime_backup_pose.h"

Some files were not shown because too many files have changed in this diff Show More