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
This commit is contained in:
Jacques Lucke
2025-10-13 18:23:50 +02:00
parent 7d3810a1b5
commit d690ffd44f
5 changed files with 85 additions and 6 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -6,6 +6,9 @@
#include <memory>
#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<nodes::socket_usage_inference::SocketUsage> inputs;
Array<nodes::socket_usage_inference::SocketUsage> 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<bke::bake::ModifierCache> 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);

View File

@@ -10,6 +10,7 @@
#include <fmt/format.h>
#include <sstream>
#include <string>
#include <xxhash.h>
#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<nodes::InferenceValue> 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;

View File

@@ -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);
}