Geometry Nodes: Use implicit sharing in store/capture attribute nodes
Some fields reference attributes directly. When the referenced attribute has the requested type and domain, the captured/stored attribute can share its array, avoiding the cost of duplication and reducing memory usage, at least temporarily until either attribute is modified. This only works when the attribute doesn't need validation and when the selection input isn't used, since those potentially need to change values in the arrays. I saw this save 200MB and 11 ms of copying for a simple grid with 16 million points (creating the grid takes about 60ms). Pull Request: https://projects.blender.org/blender/blender/pulls/107357
This commit is contained in:
@@ -407,6 +407,55 @@ bool NormalFieldInput::is_equal_to(const fn::FieldNode &other) const
|
||||
return dynamic_cast<const NormalFieldInput *>(&other) != nullptr;
|
||||
}
|
||||
|
||||
static std::optional<AttributeIDRef> try_get_field_direct_attribute_id(const fn::GField &any_field)
|
||||
{
|
||||
if (const auto *field = dynamic_cast<const AttributeFieldInput *>(&any_field.node())) {
|
||||
return field->attribute_name();
|
||||
}
|
||||
if (const auto *field = dynamic_cast<const AnonymousAttributeFieldInput *>(&any_field.node())) {
|
||||
return *field->anonymous_id();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool attribute_kind_matches(const AttributeMetaData meta_data,
|
||||
const eAttrDomain domain,
|
||||
const eCustomDataType data_type)
|
||||
{
|
||||
return meta_data.domain == domain && meta_data.data_type == data_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some fields reference attributes directly. When the referenced attribute has the requested type
|
||||
* and domain, use implicit sharing to avoid duplication when creating the captured attribute.
|
||||
*/
|
||||
static bool try_add_shared_field_attribute(MutableAttributeAccessor attributes,
|
||||
const AttributeIDRef &id_to_create,
|
||||
const eAttrDomain domain,
|
||||
const fn::GField &field)
|
||||
{
|
||||
const std::optional<AttributeIDRef> field_id = try_get_field_direct_attribute_id(field);
|
||||
if (!field_id) {
|
||||
return false;
|
||||
}
|
||||
const std::optional<AttributeMetaData> meta_data = attributes.lookup_meta_data(*field_id);
|
||||
if (!meta_data) {
|
||||
return false;
|
||||
}
|
||||
const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(field.cpp_type());
|
||||
if (!attribute_kind_matches(*meta_data, domain, data_type)) {
|
||||
/* Avoid costly domain and type interpolation, which would make sharing impossible. */
|
||||
return false;
|
||||
}
|
||||
const GAttributeReader attribute = attributes.lookup(*field_id, domain, data_type);
|
||||
if (!attribute.sharing_info || !attribute.varray.is_span()) {
|
||||
return false;
|
||||
}
|
||||
const AttributeInitShared init(attribute.varray.get_internal_span().data(),
|
||||
*attribute.sharing_info);
|
||||
return attributes.add(id_to_create, domain, data_type, init);
|
||||
}
|
||||
|
||||
bool try_capture_field_on_geometry(GeometryComponent &component,
|
||||
const AttributeIDRef &attribute_id,
|
||||
const eAttrDomain domain,
|
||||
@@ -427,11 +476,11 @@ bool try_capture_field_on_geometry(GeometryComponent &component,
|
||||
const bke::AttributeValidator validator = attributes.lookup_validator(attribute_id);
|
||||
|
||||
const std::optional<AttributeMetaData> meta_data = attributes.lookup_meta_data(attribute_id);
|
||||
const bool attribute_exists = meta_data && meta_data->domain == domain &&
|
||||
meta_data->data_type == data_type;
|
||||
const bool attribute_matches = meta_data &&
|
||||
attribute_kind_matches(*meta_data, domain, data_type);
|
||||
|
||||
/* We are writing to an attribute that exists already with the correct domain and type. */
|
||||
if (attribute_exists) {
|
||||
/* We are writing to an attribute that exists already with the correct domain and type. */
|
||||
if (attribute_matches) {
|
||||
if (GSpanAttributeWriter dst_attribute = attributes.lookup_for_write_span(attribute_id)) {
|
||||
const IndexMask mask{IndexMask(domain_size)};
|
||||
|
||||
@@ -450,11 +499,19 @@ bool try_capture_field_on_geometry(GeometryComponent &component,
|
||||
}
|
||||
}
|
||||
|
||||
const bool selection_is_full = !selection.node().depends_on_input() &&
|
||||
fn::evaluate_constant_field(selection);
|
||||
|
||||
if (!validator && selection_is_full) {
|
||||
if (try_add_shared_field_attribute(attributes, attribute_id, domain, field)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Could avoid allocating a new buffer if:
|
||||
* - The field does not depend on that attribute (we can't easily check for that yet). */
|
||||
void *buffer = MEM_mallocN_aligned(type.size() * domain_size, type.alignment(), __func__);
|
||||
if (selection.node().depends_on_input() || !fn::evaluate_constant_field(selection)) {
|
||||
/* If every element might not be selected, the buffer must be initialized. */
|
||||
if (!selection_is_full) {
|
||||
type.value_initialize_n(buffer, domain_size);
|
||||
}
|
||||
fn::FieldEvaluator evaluator{field_context, &mask};
|
||||
@@ -463,7 +520,7 @@ bool try_capture_field_on_geometry(GeometryComponent &component,
|
||||
evaluator.set_selection(selection);
|
||||
evaluator.evaluate();
|
||||
|
||||
if (attribute_exists) {
|
||||
if (attribute_matches) {
|
||||
if (GAttributeWriter attribute = attributes.lookup_for_write(attribute_id)) {
|
||||
attribute.varray.set_all(buffer);
|
||||
attribute.finish();
|
||||
|
||||
Reference in New Issue
Block a user