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:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user