Refactor: Geometry Nodes: use faster IDProperty group lookup instead of custom VectorSet

Geometry Nodes used a `PropertiesVectorSet` in some places to allow
constant-time `IDProperty` access by name in a group. This used to be a
bottleneck in node groups with hundreds of inputs. Now this separate hash table
is not necessary anymore, because it's `IDProperty` groups now support this
directly since 6cb2226f13.

This also adds a `IDP_GetPropertyFromGroup_null` utility function to be able to
do this refactor more safely. Null pointers were allowed before.

Pull Request: https://projects.blender.org/blender/blender/pulls/146152
This commit is contained in:
Jacques Lucke
2025-09-12 15:43:14 +02:00
parent 4e629a6cb1
commit ad27211b77
10 changed files with 54 additions and 66 deletions

View File

@@ -187,6 +187,10 @@ void IDP_FreeFromGroup(IDProperty *group, IDProperty *prop) ATTR_NONNULL();
IDProperty *IDP_GetPropertyFromGroup(const IDProperty *prop,
blender::StringRef name) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/** Same as above, but allows the property to be null, in which case null is returned. */
IDProperty *IDP_GetPropertyFromGroup_null(const IDProperty *prop,
blender::StringRef name) ATTR_WARN_UNUSED_RESULT;
/**
* Same as #IDP_GetPropertyFromGroup but ensure the `type` matches.
*/

View File

@@ -757,6 +757,14 @@ IDProperty *IDP_GetPropertyFromGroup(const IDProperty *prop, const blender::Stri
return prop->data.children_map->children.lookup_key_default_as(name, nullptr);
}
IDProperty *IDP_GetPropertyFromGroup_null(const IDProperty *prop, const blender::StringRef name)
{
if (!prop) {
return nullptr;
}
return IDP_GetPropertyFromGroup(prop, name);
}
IDProperty *IDP_GetPropertyTypeFromGroup(const IDProperty *prop,
const blender::StringRef name,
const char type)

View File

@@ -778,11 +778,7 @@ static wmOperatorStatus run_node_group_exec(bContext *C, wmOperator *op)
*depsgraph_active, *object, operator_eval_data, orig_mesh_states);
bke::GeometrySet new_geometry = nodes::execute_geometry_nodes_on_geometry(
*node_tree,
nodes::build_properties_vector_set(properties),
compute_context,
call_data,
std::move(geometry_orig));
*node_tree, properties, compute_context, call_data, std::move(geometry_orig));
store_result_geometry(
*C, *op, *depsgraph_active, *bmain, *scene, *object, rv3d, std::move(new_geometry));

View File

@@ -901,7 +901,7 @@ static void find_socket_log_contexts(const NodesModifierData &nmd,
* the object as a parameter, so it's likely better to this check as a separate step.
*/
static void check_property_socket_sync(const Object *ob,
const nodes::PropertiesVectorSet &properties,
const IDProperty *properties,
ModifierData *md)
{
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
@@ -926,7 +926,7 @@ static void check_property_socket_sync(const Object *ob,
continue;
}
IDProperty *property = properties.lookup_key_default_as(socket->identifier, nullptr);
IDProperty *property = IDP_GetPropertyFromGroup_null(properties, socket->identifier);
if (property == nullptr) {
if (!ELEM(type, SOCK_GEOMETRY, SOCK_MATRIX, SOCK_BUNDLE, SOCK_CLOSURE)) {
BKE_modifier_set_error(
@@ -1823,11 +1823,8 @@ static void modifyGeometry(ModifierData *md,
return;
}
nodes::PropertiesVectorSet properties = nodes::build_properties_vector_set(
nmd->settings.properties);
const bNodeTree &tree = *nmd->node_group;
check_property_socket_sync(ctx->object, properties, md);
check_property_socket_sync(ctx->object, nmd->settings.properties, md);
tree.ensure_topology_cache();
const bNode *output_node = tree.group_output_node();
@@ -1895,8 +1892,11 @@ static void modifyGeometry(ModifierData *md,
bke::ModifierComputeContext modifier_compute_context{nullptr, *nmd};
geometry_set = nodes::execute_geometry_nodes_on_geometry(
tree, properties, modifier_compute_context, call_data, std::move(geometry_set));
geometry_set = nodes::execute_geometry_nodes_on_geometry(tree,
nmd->settings.properties,
modifier_compute_context,
call_data,
std::move(geometry_set));
if (logging_enabled(ctx)) {
nmd_orig->runtime->eval_log = std::move(eval_log);

View File

@@ -40,14 +40,7 @@ struct IDPropNameGetter {
}
};
/**
* Use a #VectorSet to store properties for constant time lookup, to avoid slowdown with many
* inputs.
*/
using PropertiesVectorSet = CustomIDVectorSet<IDProperty *, IDPropNameGetter, 16>;
PropertiesVectorSet build_properties_vector_set(const IDProperty *properties);
std::optional<StringRef> input_attribute_name_get(const PropertiesVectorSet &properties,
std::optional<StringRef> input_attribute_name_get(const IDProperty *properties,
const bNodeTreeInterfaceSocket &io_input);
/**
@@ -71,7 +64,7 @@ std::unique_ptr<IDProperty, bke::idprop::IDPropertyDeleter> id_property_create_f
bool use_name_for_ids);
bke::GeometrySet execute_geometry_nodes_on_geometry(const bNodeTree &btree,
const PropertiesVectorSet &properties_set,
const IDProperty *properties,
const ComputeContext &base_compute_context,
GeoNodesCallData &call_data,
bke::GeometrySet input_geometry);
@@ -90,7 +83,8 @@ void update_output_properties_from_node_tree(const bNodeTree &tree,
* fully evaluate the node tree (would be way to slow), and does not support all socket types. So
* this function may return #InferenceValue::Unknown for some sockets.
*/
Vector<InferenceValue> get_geometry_nodes_input_inference_values(
const bNodeTree &btree, const PropertiesVectorSet &properties, ResourceScope &scope);
Vector<InferenceValue> get_geometry_nodes_input_inference_values(const bNodeTree &btree,
const IDProperty *properties,
ResourceScope &scope);
} // namespace blender::nodes

View File

@@ -89,7 +89,7 @@ void infer_group_interface_inputs_usage(const bNodeTree &group,
* This is used with the geometry nodes modifier and node tools.
*/
void infer_group_interface_inputs_usage(const bNodeTree &group,
const PropertiesVectorSet &properties,
const IDProperty *properties,
MutableSpan<SocketUsage> r_input_usages);
} // namespace blender::nodes::socket_usage_inference

View File

@@ -81,7 +81,7 @@ struct DrawGroupInputsContext {
const bContext &C;
bNodeTree *tree;
geo_log::GeoTreeLog *tree_log;
nodes::PropertiesVectorSet properties;
IDProperty *properties;
PointerRNA *properties_ptr;
PointerRNA *bmain_ptr;
Array<nodes::socket_usage_inference::SocketUsage> input_usages;
@@ -472,7 +472,7 @@ static void draw_property_for_socket(DrawGroupInputsContext &ctx,
{
const StringRefNull identifier = socket.identifier;
/* The property should be created in #MOD_nodes_update_interface with the correct type. */
IDProperty *property = ctx.properties.lookup_key_default_as(identifier, nullptr);
IDProperty *property = IDP_GetPropertyFromGroup_null(ctx.properties, identifier);
/* IDProperties can be removed with python, so there could be a situation where
* there isn't a property for a socket or it doesn't have the correct type. */
@@ -667,7 +667,7 @@ static void draw_interface_panel_content(DrawGroupInputsContext &ctx,
const StringRef panel_name = sub_interface_panel.name;
if (toggle_socket && !(toggle_socket->flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) {
const StringRefNull identifier = toggle_socket->identifier;
IDProperty *property = ctx.properties.lookup_key_default_as(identifier, nullptr);
IDProperty *property = IDP_GetPropertyFromGroup_null(ctx.properties, identifier);
/* IDProperties can be removed with python, so there could be a situation where
* there isn't a property for a socket or it doesn't have the correct type. */
if (property == nullptr || !nodes::id_property_type_matches_socket(
@@ -805,7 +805,7 @@ static void draw_property_for_output_socket(DrawGroupInputsContext &ctx,
static void draw_output_attributes_panel(DrawGroupInputsContext &ctx, uiLayout *layout)
{
if (ctx.tree != nullptr && !ctx.properties.is_empty()) {
if (ctx.tree != nullptr && !ctx.properties) {
for (const bNodeTreeInterfaceSocket *socket : ctx.tree->interface_outputs()) {
const bke::bNodeSocketType *typeinfo = socket->socket_typeinfo();
const eNodeSocketDatatype type = typeinfo ? typeinfo->type : SOCK_CUSTOM;
@@ -922,7 +922,7 @@ void draw_geometry_nodes_modifier_ui(const bContext &C, PointerRNA *modifier_ptr
DrawGroupInputsContext ctx{C,
nmd.node_group,
get_root_tree_log(nmd),
nodes::build_properties_vector_set(nmd.settings.properties),
nmd.settings.properties,
modifier_ptr,
&bmain_ptr};
@@ -999,8 +999,7 @@ void draw_geometry_nodes_operator_redo_ui(const bContext &C,
Main &bmain = *CTX_data_main(&C);
PointerRNA bmain_ptr = RNA_main_pointer_create(&bmain);
DrawGroupInputsContext ctx{
C, &tree, tree_log, nodes::build_properties_vector_set(op.properties), op.ptr, &bmain_ptr};
DrawGroupInputsContext ctx{C, &tree, tree_log, op.properties, op.ptr, &bmain_ptr};
ctx.panel_open_property_fn = [&](const bNodeTreeInterfacePanel &io_panel) -> PanelOpenProperty {
Panel *root_panel = layout.root_panel();
LayoutPanelState *state = BKE_panel_layout_panel_state_ensure(

View File

@@ -467,19 +467,6 @@ bool id_property_type_matches_socket(const bNodeTreeInterfaceSocket &socket,
socket, property, nullptr, use_name_for_ids);
}
PropertiesVectorSet build_properties_vector_set(const IDProperty *properties)
{
if (!properties) {
return {};
}
PropertiesVectorSet set;
set.reserve(BLI_listbase_count(&properties->data.group));
LISTBASE_FOREACH (IDProperty *, prop, &properties->data.group) {
set.add_new(prop);
}
return set;
}
static bke::SocketValueVariant init_socket_cpp_value_from_property(
const IDProperty &property, const eNodeSocketDatatype socket_value_type)
{
@@ -601,11 +588,11 @@ static bke::SocketValueVariant init_socket_cpp_value_from_property(
}
}
std::optional<StringRef> input_attribute_name_get(const PropertiesVectorSet &properties,
std::optional<StringRef> input_attribute_name_get(const IDProperty *properties,
const bNodeTreeInterfaceSocket &io_input)
{
IDProperty *use_attribute = properties.lookup_key_default_as(
io_input.identifier + input_use_attribute_suffix, nullptr);
IDProperty *use_attribute = IDP_GetPropertyFromGroup_null(
properties, io_input.identifier + input_use_attribute_suffix);
if (!use_attribute) {
return std::nullopt;
}
@@ -620,20 +607,20 @@ std::optional<StringRef> input_attribute_name_get(const PropertiesVectorSet &pro
}
}
const IDProperty *property_attribute_name = properties.lookup_key_default_as(
io_input.identifier + input_attribute_name_suffix, nullptr);
const IDProperty *property_attribute_name = IDP_GetPropertyFromGroup_null(
properties, io_input.identifier + input_attribute_name_suffix);
return IDP_string_get(property_attribute_name);
}
static bke::SocketValueVariant initialize_group_input(const bNodeTree &tree,
const PropertiesVectorSet &properties,
const IDProperty *properties,
const int input_index)
{
const bNodeTreeInterfaceSocket &io_input = *tree.interface_inputs()[input_index];
const bke::bNodeSocketType *typeinfo = io_input.socket_typeinfo();
const eNodeSocketDatatype socket_data_type = typeinfo ? typeinfo->type : SOCK_CUSTOM;
const IDProperty *property = properties.lookup_key_default_as(io_input.identifier, nullptr);
const IDProperty *property = IDP_GetPropertyFromGroup_null(properties, io_input.identifier);
if (property == nullptr) {
return typeinfo->get_geometry_nodes_cpp_value(io_input.socket_data);
}
@@ -652,7 +639,8 @@ static bke::SocketValueVariant initialize_group_input(const bNodeTree &tree,
return bke::SocketValueVariant::From(std::move(attribute_field));
}
if (is_layer_selection_field(io_input)) {
const IDProperty *property_layer_name = properties.lookup_key_as(io_input.identifier);
const IDProperty *property_layer_name = IDP_GetPropertyFromGroup_null(properties,
io_input.identifier);
StringRef layer_name = IDP_string_get(property_layer_name);
fn::GField selection_field(std::make_shared<bke::NamedLayerSelectionFieldInput>(layer_name),
0);
@@ -678,9 +666,7 @@ struct OutputAttributeToStore {
* can be evaluated together.
*/
static MultiValueMap<bke::AttrDomain, OutputAttributeInfo> find_output_attributes_to_store(
const bNodeTree &tree,
const PropertiesVectorSet &properties,
Span<GMutablePointer> output_values)
const bNodeTree &tree, const IDProperty *properties, Span<GMutablePointer> output_values)
{
const bNode &output_node = *tree.group_output_node();
MultiValueMap<bke::AttrDomain, OutputAttributeInfo> outputs_by_domain;
@@ -690,7 +676,7 @@ static MultiValueMap<bke::AttrDomain, OutputAttributeInfo> find_output_attribute
}
const std::string prop_name = socket->identifier + input_attribute_name_suffix;
const IDProperty *prop = properties.lookup_key_default_as(prop_name, nullptr);
const IDProperty *prop = IDP_GetPropertyFromGroup_null(properties, prop_name);
if (prop == nullptr) {
continue;
}
@@ -806,7 +792,7 @@ static void store_computed_output_attributes(
static void store_output_attributes(bke::GeometrySet &geometry,
const bNodeTree &tree,
const PropertiesVectorSet &properties,
const IDProperty *properties,
Span<GMutablePointer> output_values)
{
/* All new attribute values have to be computed before the geometry is actually changed. This is
@@ -847,7 +833,7 @@ static void store_output_attributes(bke::GeometrySet &geometry,
}
bke::GeometrySet execute_geometry_nodes_on_geometry(const bNodeTree &btree,
const PropertiesVectorSet &properties_set,
const IDProperty *properties,
const ComputeContext &base_compute_context,
GeoNodesCallData &call_data,
bke::GeometrySet input_geometry)
@@ -894,7 +880,7 @@ bke::GeometrySet execute_geometry_nodes_on_geometry(const bNodeTree &btree,
continue;
}
bke::SocketValueVariant value = initialize_group_input(btree, properties_set, i);
bke::SocketValueVariant value = initialize_group_input(btree, properties, i);
param_inputs[function.inputs.main[i]] = &scope.construct<bke::SocketValueVariant>(
std::move(value));
}
@@ -937,7 +923,7 @@ bke::GeometrySet execute_geometry_nodes_on_geometry(const bNodeTree &btree,
bke::GeometrySet output_geometry =
param_outputs[0].get<bke::SocketValueVariant>()->extract<bke::GeometrySet>();
store_output_attributes(output_geometry, btree, properties_set, param_outputs);
store_output_attributes(output_geometry, btree, properties, param_outputs);
for (const int i : IndexRange(num_outputs)) {
if (param_set_outputs[i]) {
@@ -1067,8 +1053,9 @@ void update_output_properties_from_node_tree(const bNodeTree &tree,
}
}
Vector<InferenceValue> get_geometry_nodes_input_inference_values(
const bNodeTree &btree, const PropertiesVectorSet &properties, ResourceScope &scope)
Vector<InferenceValue> get_geometry_nodes_input_inference_values(const bNodeTree &btree,
const IDProperty *properties,
ResourceScope &scope)
{
/* Assume that all inputs have unknown values by default. */
Vector<InferenceValue> inference_values(btree.interface_inputs().size(),
@@ -1085,7 +1072,7 @@ Vector<InferenceValue> get_geometry_nodes_input_inference_values(
if (!stype->base_cpp_type || !stype->geometry_nodes_default_value) {
continue;
}
const IDProperty *property = properties.lookup_key_default_as(io_input.identifier, nullptr);
const IDProperty *property = IDP_GetPropertyFromGroup_null(properties, io_input.identifier);
if (!property) {
continue;
}

View File

@@ -383,7 +383,7 @@ static void foreach_active_gizmo_exposed_to_modifier(
tree.ensure_interface_cache();
Array<nodes::socket_usage_inference::SocketUsage> input_usages(tree.interface_inputs().size());
nodes::socket_usage_inference::infer_group_interface_inputs_usage(
tree, nodes::build_properties_vector_set(nmd.settings.properties), input_usages);
tree, nmd.settings.properties, input_usages);
const ComputeContext &root_compute_context = compute_context_cache.for_modifier(nullptr, nmd);
for (auto &&item : tree.runtime->gizmo_propagation->gizmo_inputs_by_group_inputs.items()) {

View File

@@ -720,7 +720,7 @@ void infer_group_interface_inputs_usage(const bNodeTree &group,
}
void infer_group_interface_inputs_usage(const bNodeTree &group,
const PropertiesVectorSet &properties,
const IDProperty *properties,
MutableSpan<SocketUsage> r_input_usages)
{
ResourceScope scope;