From d690ffd44fcc249e319939934330b8b92967b57d Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 13 Oct 2025 18:23:50 +0200 Subject: [PATCH] Fix #146949: Geometry Nodes: cache usage inference on modifier This speeds up drawing of the UI of the Geometry Nodes modifier, especially for very large node trees like in the files from #146949. A couple previous commits and #147883 speed up the inferencing significantly already. However, there is a certain limit to how fast this can be on large complex node trees. While there is some more optimization potential, this patch implements a cache on the Geometry Nodes modifier level. This means that as long as the inputs and the node tree stays the same, the inferencing only has to happen once, instead of on every redraw. The main difficulty with this cache is that we don't have a good way to invalidate it eagerly when any of the modifier inputs changes. That can hopefully become simpler with #138117. For now, we have to check all input values on redraw to check if anything has changed compared to the last redraw and recompute the cache if there is any change. Pull Request: https://projects.blender.org/blender/blender/pulls/147930 --- source/blender/blenkernel/intern/cpp_types.cc | 3 +- source/blender/modifiers/CMakeLists.txt | 1 + source/blender/modifiers/MOD_nodes.hh | 20 +++++++ source/blender/modifiers/intern/MOD_nodes.cc | 59 +++++++++++++++++++ .../nodes/intern/geometry_nodes_caller_ui.cc | 8 +-- 5 files changed, 85 insertions(+), 6 deletions(-) diff --git a/source/blender/blenkernel/intern/cpp_types.cc b/source/blender/blenkernel/intern/cpp_types.cc index b824c57f650..f5822325e6d 100644 --- a/source/blender/blenkernel/intern/cpp_types.cc +++ b/source/blender/blenkernel/intern/cpp_types.cc @@ -36,7 +36,8 @@ BLI_CPP_TYPE_MAKE(Image *, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(Material *, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(MStringProperty, CPPTypeFlags::None); -BLI_CPP_TYPE_MAKE(blender::nodes::MenuValue, CPPTypeFlags::EqualityComparable); +BLI_CPP_TYPE_MAKE(blender::nodes::MenuValue, + CPPTypeFlags::Hashable | CPPTypeFlags::EqualityComparable); BLI_CPP_TYPE_MAKE(blender::nodes::BundlePtr, CPPTypeFlags::EqualityComparable); BLI_CPP_TYPE_MAKE(blender::nodes::ClosurePtr, CPPTypeFlags::EqualityComparable); BLI_CPP_TYPE_MAKE(blender::nodes::ListPtr, CPPTypeFlags::EqualityComparable); diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index 07d33f39bc3..ce8eff402ee 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -143,6 +143,7 @@ set(LIB PRIVATE bf::geometry PRIVATE bf::intern::guardedalloc PRIVATE bf::extern::fmtlib + PRIVATE bf::extern::xxhash PRIVATE bf::nodes PRIVATE bf::render PRIVATE bf::windowmanager diff --git a/source/blender/modifiers/MOD_nodes.hh b/source/blender/modifiers/MOD_nodes.hh index 449bb85c39f..d98432a7ac3 100644 --- a/source/blender/modifiers/MOD_nodes.hh +++ b/source/blender/modifiers/MOD_nodes.hh @@ -6,6 +6,9 @@ #include +#include "BLI_array.hh" +#include "NOD_socket_usage_inference_fwd.hh" + struct NodesModifierData; struct NodesModifierDataBlock; struct Object; @@ -28,6 +31,18 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd); namespace blender { +class NodesModifierUsageInferenceCache { + private: + uint64_t input_values_hash_ = 0; + + public: + Array inputs; + Array outputs; + + void ensure(const NodesModifierData &nmd); + void reset(); +}; + struct NodesModifierRuntime { /** * Contains logged information from the last evaluation. @@ -42,6 +57,11 @@ struct NodesModifierRuntime { * used by the evaluated modifier. */ std::shared_ptr cache; + /** + * Cache the usage of the node group inputs and outputs to accelerate drawing the UI when no + * properties change. + */ + NodesModifierUsageInferenceCache usage_cache; }; void nodes_modifier_data_block_destruct(NodesModifierDataBlock *data_block, bool do_id_user); diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 07ed02c9303..f7b0abd067c 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include "MEM_guardedalloc.h" @@ -451,6 +452,7 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) update_id_properties_from_node_group(nmd); update_bakes_from_node_group(*nmd); update_panels_from_node_group(*nmd); + nmd->runtime->usage_cache.reset(); DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY); } @@ -1952,6 +1954,63 @@ static void modify_geometry_set(ModifierData *md, modifyGeometry(md, ctx, *geometry_set); } +void NodesModifierUsageInferenceCache::ensure(const NodesModifierData &nmd) +{ + if (!nmd.node_group) { + this->reset(); + return; + } + if (ID_MISSING(&nmd.node_group->id)) { + this->reset(); + return; + } + const bNodeTree &tree = *nmd.node_group; + tree.ensure_interface_cache(); + tree.ensure_topology_cache(); + ResourceScope scope; + const Vector group_input_values = + nodes::get_geometry_nodes_input_inference_values(tree, nmd.settings.properties, scope); + + /* Compute the hash of the input values. This has to be done everytime currently, because there + * is no reliable callback yet that is called any of the modifier properties changes. */ + XXH3_state_t *state = XXH3_createState(); + XXH3_64bits_reset(state); + BLI_SCOPED_DEFER([&]() { XXH3_freeState(state); }); + for (const int input_i : IndexRange(nmd.node_group->interface_inputs().size())) { + const nodes::InferenceValue &value = group_input_values[input_i]; + XXH3_64bits_update(state, &input_i, sizeof(input_i)); + if (value.is_primitive_value()) { + const void *value_ptr = value.get_primitive_ptr(); + const bNodeTreeInterfaceSocket &io_socket = *nmd.node_group->interface_inputs()[input_i]; + const CPPType &base_type = *io_socket.socket_typeinfo()->base_cpp_type; + uint64_t value_hash = base_type.hash_or_fallback(value_ptr, 0); + XXH3_64bits_update(state, &value_hash, sizeof(value_hash)); + } + } + const uint64_t new_input_values_hash = XXH3_64bits_digest(state); + if (new_input_values_hash == input_values_hash_) { + if (this->inputs.size() == tree.interface_inputs().size() && + this->outputs.size() == tree.interface_outputs().size()) + { + /* The cache is up to date, so return early. */ + return; + } + } + /* Compute the new usage inference result. */ + this->inputs.reinitialize(tree.interface_inputs().size()); + this->outputs.reinitialize(tree.interface_outputs().size()); + nodes::socket_usage_inference::infer_group_interface_usage( + tree, group_input_values, inputs, outputs); + input_values_hash_ = new_input_values_hash; +} + +void NodesModifierUsageInferenceCache::reset() +{ + input_values_hash_ = 0; + this->inputs = {}; + this->outputs = {}; +} + static void panel_draw(const bContext *C, Panel *panel) { uiLayout *layout = panel->layout; diff --git a/source/blender/nodes/intern/geometry_nodes_caller_ui.cc b/source/blender/nodes/intern/geometry_nodes_caller_ui.cc index dbaabb835d9..88835b3944d 100644 --- a/source/blender/nodes/intern/geometry_nodes_caller_ui.cc +++ b/source/blender/nodes/intern/geometry_nodes_caller_ui.cc @@ -1008,11 +1008,9 @@ void draw_geometry_nodes_modifier_ui(const bContext &C, PointerRNA *modifier_ptr } if (nmd.node_group != nullptr && nmd.settings.properties != nullptr) { - nmd.node_group->ensure_interface_cache(); - ctx.input_usages.reinitialize(nmd.node_group->interface_inputs().size()); - ctx.output_usages.reinitialize(nmd.node_group->interface_outputs().size()); - nodes::socket_usage_inference::infer_group_interface_usage( - *nmd.node_group, ctx.properties, ctx.input_usages, ctx.output_usages); + nmd.runtime->usage_cache.ensure(nmd); + ctx.input_usages = nmd.runtime->usage_cache.inputs; + ctx.output_usages = nmd.runtime->usage_cache.outputs; draw_interface_panel_content(ctx, &layout, nmd.node_group->tree_interface.root_panel); }