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