Geometry Nodes: Only create instance IDs when they exist

Instance IDs serve no purpose for rendering when they aren't stable from
one frame to the next, and if the index is used in the end anyway, there
is no point in storing a vector of IDs and copying it around.

This commit exposes the `id` attribute on the instances component,
makes it optional-- only generated by default with the distribute points
on faces node.

Since the string to curves node only added the index as each instance's
ID, I removed it. This means that it would be necessary to add the ID
data manually if the initial index actually helps (when deleting only
certain characters, for example).

Differential Revision: https://developer.blender.org/D12980
This commit is contained in:
Hans Goudey 2021-10-26 12:50:39 -05:00
parent b6d2bee28f
commit 9fa304bf13
Notes: blender-bot 2023-02-14 12:01:57 +01:00
Referenced by issue #92413, Only create instance ids when points have ids in the Instance on Points node
9 changed files with 151 additions and 34 deletions

View File

@ -621,7 +621,9 @@ class InstancesComponent : public GeometryComponent {
blender::Vector<blender::float4x4> instance_transforms_;
/**
* IDs of the instances. They are used for consistency over multiple frames for things like
* motion blur.
* motion blur. Proper stable ID data that actually helps when rendering can only be generated
* in some situations, so this vector is allowed to be empty, in which case the index of each
* instance will be used for the final ID.
*/
blender::Vector<int> instance_ids_;
@ -643,7 +645,7 @@ class InstancesComponent : public GeometryComponent {
void resize(int capacity);
int add_reference(const InstanceReference &reference);
void add_instance(int instance_handle, const blender::float4x4 &transform, const int id = -1);
void add_instance(int instance_handle, const blender::float4x4 &transform);
blender::Span<InstanceReference> references() const;
void remove_unused_references();
@ -658,6 +660,9 @@ class InstancesComponent : public GeometryComponent {
blender::MutableSpan<int> instance_ids();
blender::Span<int> instance_ids() const;
blender::MutableSpan<int> instance_ids_ensure();
void instance_ids_clear();
int instances_amount() const;
int references_amount() const;

View File

@ -60,7 +60,9 @@ void InstancesComponent::reserve(int min_capacity)
{
instance_reference_handles_.reserve(min_capacity);
instance_transforms_.reserve(min_capacity);
instance_ids_.reserve(min_capacity);
if (!instance_ids_.is_empty()) {
this->instance_ids_ensure();
}
}
/**
@ -73,7 +75,9 @@ void InstancesComponent::resize(int capacity)
{
instance_reference_handles_.resize(capacity);
instance_transforms_.resize(capacity);
instance_ids_.resize(capacity);
if (!instance_ids_.is_empty()) {
this->instance_ids_ensure();
}
}
void InstancesComponent::clear()
@ -85,15 +89,15 @@ void InstancesComponent::clear()
references_.clear();
}
void InstancesComponent::add_instance(const int instance_handle,
const float4x4 &transform,
const int id)
void InstancesComponent::add_instance(const int instance_handle, const float4x4 &transform)
{
BLI_assert(instance_handle >= 0);
BLI_assert(instance_handle < references_.size());
instance_reference_handles_.append(instance_handle);
instance_transforms_.append(transform);
instance_ids_.append(id);
if (!instance_ids_.is_empty()) {
this->instance_ids_ensure();
}
}
blender::Span<int> InstancesComponent::instance_reference_handles() const
@ -124,6 +128,22 @@ blender::Span<int> InstancesComponent::instance_ids() const
return instance_ids_;
}
/**
* Make sure the ID storage size matches the number of instances. By directly resizing the
* component's vectors internally, it is possible to be in a situation where the IDs are not
* empty but they do not have the correct size; this function resolves that.
*/
blender::MutableSpan<int> InstancesComponent::instance_ids_ensure()
{
instance_ids_.append_n_times(0, this->instances_amount() - instance_ids_.size());
return instance_ids_;
}
void InstancesComponent::instance_ids_clear()
{
instance_ids_.clear_and_make_inline();
}
/**
* With write access to the instances component, the data in the instanced geometry sets can be
* changed. This is a function on the component rather than each reference to ensure `const`
@ -327,8 +347,16 @@ static blender::Array<int> generate_unique_instance_ids(Span<int> original_ids)
blender::Span<int> InstancesComponent::almost_unique_ids() const
{
std::lock_guard lock(almost_unique_ids_mutex_);
if (almost_unique_ids_.size() != instance_ids_.size()) {
almost_unique_ids_ = generate_unique_instance_ids(instance_ids_);
if (instance_ids().is_empty()) {
almost_unique_ids_.reinitialize(this->instances_amount());
for (const int i : almost_unique_ids_.index_range()) {
almost_unique_ids_[i] = i;
}
}
else {
if (almost_unique_ids_.size() != instance_ids_.size()) {
almost_unique_ids_ = generate_unique_instance_ids(instance_ids_);
}
}
return almost_unique_ids_;
}
@ -398,11 +426,82 @@ class InstancePositionAttributeProvider final : public BuiltinAttributeProvider
}
};
class InstanceIDAttributeProvider final : public BuiltinAttributeProvider {
public:
InstanceIDAttributeProvider()
: BuiltinAttributeProvider(
"id", ATTR_DOMAIN_POINT, CD_PROP_INT32, Creatable, Writable, Deletable)
{
}
GVArrayPtr try_get_for_read(const GeometryComponent &component) const final
{
const InstancesComponent &instances = static_cast<const InstancesComponent &>(component);
if (instances.instance_ids().is_empty()) {
return {};
}
return std::make_unique<fn::GVArray_For_Span<int>>(instances.instance_ids());
}
GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final
{
InstancesComponent &instances = static_cast<InstancesComponent &>(component);
if (instances.instance_ids().is_empty()) {
return {};
}
return std::make_unique<fn::GVMutableArray_For_MutableSpan<int>>(instances.instance_ids());
}
bool try_delete(GeometryComponent &component) const final
{
InstancesComponent &instances = static_cast<InstancesComponent &>(component);
if (instances.instance_ids().is_empty()) {
return false;
}
instances.instance_ids_clear();
return true;
}
bool try_create(GeometryComponent &component, const AttributeInit &initializer) const final
{
InstancesComponent &instances = static_cast<InstancesComponent &>(component);
if (instances.instances_amount() == 0) {
return false;
}
MutableSpan<int> ids = instances.instance_ids_ensure();
switch (initializer.type) {
case AttributeInit::Type::Default: {
ids.fill(0);
break;
}
case AttributeInit::Type::VArray: {
const GVArray *varray = static_cast<const AttributeInitVArray &>(initializer).varray;
varray->materialize_to_uninitialized(IndexRange(varray->size()), ids.data());
break;
}
case AttributeInit::Type::MoveArray: {
void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
ids.copy_from({static_cast<int *>(source_data), instances.instances_amount()});
MEM_freeN(source_data);
break;
}
}
return true;
}
bool exists(const GeometryComponent &component) const final
{
const InstancesComponent &instances = static_cast<const InstancesComponent &>(component);
return !instances.instance_ids().is_empty();
}
};
static ComponentAttributeProviders create_attribute_providers_for_instances()
{
static InstancePositionAttributeProvider position;
static InstanceIDAttributeProvider id;
return ComponentAttributeProviders({&position}, {});
return ComponentAttributeProviders({&position, &id}, {});
}
} // namespace blender::bke

View File

@ -466,14 +466,16 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values(
});
}
Span<int> ids = component_->instance_ids();
if (STREQ(column_id.name, "ID")) {
/* Make the column a bit wider by default, since the IDs tend to be large numbers. */
return column_values_from_function(
SPREADSHEET_VALUE_TYPE_INT32,
column_id.name,
size,
[ids](int index, CellValue &r_cell_value) { r_cell_value.value_int = ids[index]; },
5.5f);
if (!ids.is_empty()) {
if (STREQ(column_id.name, "ID")) {
/* Make the column a bit wider by default, since the IDs tend to be large numbers. */
return column_values_from_function(
SPREADSHEET_VALUE_TYPE_INT32,
column_id.name,
size,
[ids](int index, CellValue &r_cell_value) { r_cell_value.value_int = ids[index]; },
5.5f);
}
}
return {};
}

View File

@ -917,6 +917,10 @@ static void store_output_value_in_geometry(GeometrySet &geometry_set,
CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>();
store_field_on_geometry_component(component, attribute_name, domain, field);
}
if (geometry_set.has_instances()) {
InstancesComponent &component = geometry_set.get_component_for_write<InstancesComponent>();
store_field_on_geometry_component(component, attribute_name, domain, field);
}
}
/**

View File

@ -184,7 +184,7 @@ static void add_instances_from_component(InstancesComponent &instances,
instances.resize(start_len + domain_size);
MutableSpan<int> handles = instances.instance_reference_handles().slice(start_len, domain_size);
MutableSpan<float4x4> transforms = instances.instance_transforms().slice(start_len, domain_size);
MutableSpan<int> instance_ids = instances.instance_ids().slice(start_len, domain_size);
MutableSpan<int> instance_ids = instances.instance_ids_ensure().slice(start_len, domain_size);
/* Skip all of the randomness handling if there is only a single possible instance
* (anything except for collection mode with "Whole Collection" turned off). */

View File

@ -77,7 +77,6 @@ static void add_instances_from_component(InstancesComponent &dst_component,
select_len);
MutableSpan<float4x4> dst_transforms = dst_component.instance_transforms().slice(start_len,
select_len);
MutableSpan<int> dst_stable_ids = dst_component.instance_ids().slice(start_len, select_len);
FieldEvaluator field_evaluator{field_context, domain_size};
const VArray<bool> *pick_instance = nullptr;
@ -86,7 +85,6 @@ static void add_instances_from_component(InstancesComponent &dst_component,
const VArray<float3> *scales = nullptr;
/* The evaluator could use the component's stable IDs as a destination directly, but only the
* selected indices should be copied. */
GVArray_Typed<int> stable_ids = src_component.attribute_get_for_read("id", ATTR_DOMAIN_POINT, 0);
field_evaluator.add(params.get_input<Field<bool>>("Pick Instance"), &pick_instance);
field_evaluator.add(params.get_input<Field<int>>("Instance Index"), &indices);
field_evaluator.add(params.get_input<Field<float3>>("Rotation"), &rotations);
@ -119,7 +117,6 @@ static void add_instances_from_component(InstancesComponent &dst_component,
threading::parallel_for(selection.index_range(), 1024, [&](IndexRange selection_range) {
for (const int range_i : selection_range) {
const int64_t i = selection[range_i];
dst_stable_ids[range_i] = (*stable_ids)[i];
/* Compute base transform for every instances. */
float4x4 &dst_transform = dst_transforms[range_i];
@ -157,6 +154,17 @@ static void add_instances_from_component(InstancesComponent &dst_component,
}
});
GVArrayPtr id_attribute = src_component.attribute_try_get_for_read(
"id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
if (id_attribute) {
GVArray_Typed<int> ids{*id_attribute};
VArray_Span<int> ids_span{ids};
MutableSpan<int> dst_ids = dst_component.instance_ids_ensure();
for (const int64_t i : selection.index_range()) {
dst_ids[i] = ids_span[selection[i]];
}
}
if (pick_instance->is_single()) {
if (pick_instance->get_internal_single()) {
if (instance.has_realized_data()) {

View File

@ -77,13 +77,15 @@ static void convert_instances_to_points(GeometrySet &geometry_set,
const VArray<float> &radii = evaluator.get_evaluated<float>(1);
copy_attribute_to_points(radii, selection, {pointcloud->radius, pointcloud->totpoint});
OutputAttribute_Typed<int> id_attribute = points.attribute_try_get_for_output<int>(
"id", ATTR_DOMAIN_POINT, 0);
MutableSpan<int> ids = id_attribute.as_span();
for (const int i : selection.index_range()) {
ids[i] = instances.instance_ids()[selection[i]];
if (!instances.instance_ids().is_empty()) {
OutputAttribute_Typed<int> id_attribute = points.attribute_try_get_for_output<int>(
"id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
MutableSpan<int> ids = id_attribute.as_span();
for (const int i : selection.index_range()) {
ids[i] = instances.instance_ids()[selection[i]];
}
id_attribute.save();
}
id_attribute.save();
}
static void geo_node_instances_to_points_exec(GeoNodeExecParams params)

View File

@ -270,17 +270,16 @@ static void join_components(Span<const InstancesComponent *> src_components, Geo
}
Span<float4x4> src_transforms = src_component->instance_transforms();
Span<int> src_ids = src_component->instance_ids();
Span<int> src_reference_handles = src_component->instance_reference_handles();
for (const int i : src_transforms.index_range()) {
const int src_handle = src_reference_handles[i];
const int dst_handle = handle_map[src_handle];
const float4x4 &transform = src_transforms[i];
const int id = src_ids[i];
dst_component.add_instance(dst_handle, transform, id);
dst_component.add_instance(dst_handle, transform);
}
}
join_attributes(to_base_components(src_components), dst_component, {"position"});
}
static void join_components(Span<const VolumeComponent *> src_components, GeometrySet &result)

View File

@ -242,13 +242,11 @@ static void add_instances_from_handles(InstancesComponent &instances,
instances.resize(positions.size());
MutableSpan<int> handles = instances.instance_reference_handles();
MutableSpan<float4x4> transforms = instances.instance_transforms();
MutableSpan<int> instance_ids = instances.instance_ids();
threading::parallel_for(IndexRange(positions.size()), 256, [&](IndexRange range) {
for (const int i : range) {
handles[i] = char_handles.lookup(charcodes[i]);
transforms[i] = float4x4::from_location({positions[i].x, positions[i].y, 0});
instance_ids[i] = i;
}
});
}