2023-08-15 16:20:26 +02:00
|
|
|
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
2023-05-31 16:19:06 +02:00
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
2021-09-09 12:54:20 +02:00
|
|
|
|
2022-11-22 19:49:51 +01:00
|
|
|
#include "BLI_array_utils.hh"
|
2021-09-09 12:54:20 +02:00
|
|
|
#include "BLI_map.hh"
|
|
|
|
#include "BLI_multi_value_map.hh"
|
|
|
|
#include "BLI_set.hh"
|
|
|
|
#include "BLI_stack.hh"
|
|
|
|
#include "BLI_vector_set.hh"
|
|
|
|
|
|
|
|
#include "FN_field.hh"
|
2022-04-21 16:11:26 +02:00
|
|
|
#include "FN_multi_function_builder.hh"
|
2021-12-13 13:28:33 +01:00
|
|
|
#include "FN_multi_function_procedure.hh"
|
|
|
|
#include "FN_multi_function_procedure_builder.hh"
|
|
|
|
#include "FN_multi_function_procedure_executor.hh"
|
|
|
|
#include "FN_multi_function_procedure_optimization.hh"
|
2021-09-09 12:54:20 +02:00
|
|
|
|
|
|
|
namespace blender::fn {
|
|
|
|
|
2022-09-15 07:27:21 +02:00
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/** \name Field Evaluation
|
|
|
|
* \{ */
|
2021-09-09 12:54:20 +02:00
|
|
|
|
|
|
|
struct FieldTreeInfo {
|
|
|
|
/**
|
|
|
|
* When fields are built, they only have references to the fields that they depend on. This map
|
|
|
|
* allows traversal of fields in the opposite direction. So for every field it stores the other
|
|
|
|
* fields that depend on it directly.
|
|
|
|
*/
|
|
|
|
MultiValueMap<GFieldRef, GFieldRef> field_users;
|
|
|
|
/**
|
2022-02-23 08:24:08 +01:00
|
|
|
* The same field input may exist in the field tree as separate nodes due to the way
|
2021-09-09 12:54:20 +02:00
|
|
|
* the tree is constructed. This set contains every different input only once.
|
|
|
|
*/
|
|
|
|
VectorSet<std::reference_wrapper<const FieldInput>> deduplicated_field_inputs;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Collects some information from the field tree that is required by later steps.
|
|
|
|
*/
|
|
|
|
static FieldTreeInfo preprocess_field_tree(Span<GFieldRef> entry_fields)
|
|
|
|
{
|
|
|
|
FieldTreeInfo field_tree_info;
|
|
|
|
|
|
|
|
Stack<GFieldRef> fields_to_check;
|
|
|
|
Set<GFieldRef> handled_fields;
|
|
|
|
|
|
|
|
for (GFieldRef field : entry_fields) {
|
|
|
|
if (handled_fields.add(field)) {
|
|
|
|
fields_to_check.push(field);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!fields_to_check.is_empty()) {
|
|
|
|
GFieldRef field = fields_to_check.pop();
|
2022-01-02 14:27:16 +01:00
|
|
|
const FieldNode &field_node = field.node();
|
|
|
|
switch (field_node.node_type()) {
|
|
|
|
case FieldNodeType::Input: {
|
|
|
|
const FieldInput &field_input = static_cast<const FieldInput &>(field_node);
|
|
|
|
field_tree_info.deduplicated_field_inputs.add(field_input);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case FieldNodeType::Operation: {
|
|
|
|
const FieldOperation &operation = static_cast<const FieldOperation &>(field_node);
|
|
|
|
for (const GFieldRef operation_input : operation.inputs()) {
|
|
|
|
field_tree_info.field_users.add(operation_input, field);
|
|
|
|
if (handled_fields.add(operation_input)) {
|
|
|
|
fields_to_check.push(operation_input);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case FieldNodeType::Constant: {
|
|
|
|
/* Nothing to do. */
|
|
|
|
break;
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return field_tree_info;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves the data from the context that is passed as input into the field.
|
|
|
|
*/
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
static Vector<GVArray> get_field_context_inputs(
|
2021-09-09 12:54:20 +02:00
|
|
|
ResourceScope &scope,
|
BLI: refactor IndexMask for better performance and memory usage
Goals of this refactor:
* Reduce memory consumption of `IndexMask`. The old `IndexMask` uses an
`int64_t` for each index which is more than necessary in pretty much all
practical cases currently. Using `int32_t` might still become limiting
in the future in case we use this to index e.g. byte buffers larger than
a few gigabytes. We also don't want to template `IndexMask`, because
that would cause a split in the "ecosystem", or everything would have to
be implemented twice or templated.
* Allow for more multi-threading. The old `IndexMask` contains a single
array. This is generally good but has the problem that it is hard to fill
from multiple-threads when the final size is not known from the beginning.
This is commonly the case when e.g. converting an array of bool to an
index mask. Currently, this kind of code only runs on a single thread.
* Allow for efficient set operations like join, intersect and difference.
It should be possible to multi-thread those operations.
* It should be possible to iterate over an `IndexMask` very efficiently.
The most important part of that is to avoid all memory access when iterating
over continuous ranges. For some core nodes (e.g. math nodes), we generate
optimized code for the cases of irregular index masks and simple index ranges.
To achieve these goals, a few compromises had to made:
* Slicing of the mask (at specific indices) and random element access is
`O(log #indices)` now, but with a low constant factor. It should be possible
to split a mask into n approximately equally sized parts in `O(n)` though,
making the time per split `O(1)`.
* Using range-based for loops does not work well when iterating over a nested
data structure like the new `IndexMask`. Therefor, `foreach_*` functions with
callbacks have to be used. To avoid extra code complexity at the call site,
the `foreach_*` methods support multi-threading out of the box.
The new data structure splits an `IndexMask` into an arbitrary number of ordered
`IndexMaskSegment`. Each segment can contain at most `2^14 = 16384` indices. The
indices within a segment are stored as `int16_t`. Each segment has an additional
`int64_t` offset which allows storing arbitrary `int64_t` indices. This approach
has the main benefits that segments can be processed/constructed individually on
multiple threads without a serial bottleneck. Also it reduces the memory
requirements significantly.
For more details see comments in `BLI_index_mask.hh`.
I did a few tests to verify that the data structure generally improves
performance and does not cause regressions:
* Our field evaluation benchmarks take about as much as before. This is to be
expected because we already made sure that e.g. add node evaluation is
vectorized. The important thing here is to check that changes to the way we
iterate over the indices still allows for auto-vectorization.
* Memory usage by a mask is about 1/4 of what it was before in the average case.
That's mainly caused by the switch from `int64_t` to `int16_t` for indices.
In the worst case, the memory requirements can be larger when there are many
indices that are very far away. However, when they are far away from each other,
that indicates that there aren't many indices in total. In common cases, memory
usage can be way lower than 1/4 of before, because sub-ranges use static memory.
* For some more specific numbers I benchmarked `IndexMask::from_bools` in
`index_mask_from_selection` on 10.000.000 elements at various probabilities for
`true` at every index:
```
Probability Old New
0 4.6 ms 0.8 ms
0.001 5.1 ms 1.3 ms
0.2 8.4 ms 1.8 ms
0.5 15.3 ms 3.0 ms
0.8 20.1 ms 3.0 ms
0.999 25.1 ms 1.7 ms
1 13.5 ms 1.1 ms
```
Pull Request: https://projects.blender.org/blender/blender/pulls/104629
2023-05-24 18:11:41 +02:00
|
|
|
const IndexMask &mask,
|
2021-09-09 12:54:20 +02:00
|
|
|
const FieldContext &context,
|
|
|
|
const Span<std::reference_wrapper<const FieldInput>> field_inputs)
|
|
|
|
{
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
Vector<GVArray> field_context_inputs;
|
2021-09-09 12:54:20 +02:00
|
|
|
for (const FieldInput &field_input : field_inputs) {
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
GVArray varray = context.get_varray_for_input(field_input, mask, scope);
|
|
|
|
if (!varray) {
|
2021-09-09 12:54:20 +02:00
|
|
|
const CPPType &type = field_input.cpp_type();
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
varray = GVArray::ForSingleDefault(type, mask.min_array_size());
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
field_context_inputs.append(varray);
|
|
|
|
}
|
|
|
|
return field_context_inputs;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \return A set that contains all fields from the field tree that depend on an input that varies
|
|
|
|
* for different indices.
|
|
|
|
*/
|
|
|
|
static Set<GFieldRef> find_varying_fields(const FieldTreeInfo &field_tree_info,
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
Span<GVArray> field_context_inputs)
|
2021-09-09 12:54:20 +02:00
|
|
|
{
|
|
|
|
Set<GFieldRef> found_fields;
|
|
|
|
Stack<GFieldRef> fields_to_check;
|
|
|
|
|
|
|
|
/* The varying fields are the ones that depend on inputs that are not constant. Therefore we
|
|
|
|
* start the tree search at the non-constant input fields and traverse through all fields that
|
|
|
|
* depend on them. */
|
|
|
|
for (const int i : field_context_inputs.index_range()) {
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
const GVArray &varray = field_context_inputs[i];
|
|
|
|
if (varray.is_single()) {
|
2021-09-09 12:54:20 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const FieldInput &field_input = field_tree_info.deduplicated_field_inputs[i];
|
|
|
|
const GFieldRef field_input_field{field_input, 0};
|
|
|
|
const Span<GFieldRef> users = field_tree_info.field_users.lookup(field_input_field);
|
|
|
|
for (const GFieldRef &field : users) {
|
|
|
|
if (found_fields.add(field)) {
|
|
|
|
fields_to_check.push(field);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (!fields_to_check.is_empty()) {
|
|
|
|
GFieldRef field = fields_to_check.pop();
|
|
|
|
const Span<GFieldRef> users = field_tree_info.field_users.lookup(field);
|
|
|
|
for (GFieldRef field : users) {
|
|
|
|
if (found_fields.add(field)) {
|
|
|
|
fields_to_check.push(field);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return found_fields;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-02-23 08:24:08 +01:00
|
|
|
* Builds the #procedure so that it computes the fields.
|
2021-09-09 12:54:20 +02:00
|
|
|
*/
|
2023-01-07 17:32:28 +01:00
|
|
|
static void build_multi_function_procedure_for_fields(mf::Procedure &procedure,
|
2021-09-09 12:54:20 +02:00
|
|
|
ResourceScope &scope,
|
|
|
|
const FieldTreeInfo &field_tree_info,
|
|
|
|
Span<GFieldRef> output_fields)
|
|
|
|
{
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::ProcedureBuilder builder{procedure};
|
2021-09-09 12:54:20 +02:00
|
|
|
/* Every input, intermediate and output field corresponds to a variable in the procedure. */
|
2023-01-07 17:32:28 +01:00
|
|
|
Map<GFieldRef, mf::Variable *> variable_by_field;
|
2021-09-09 12:54:20 +02:00
|
|
|
|
|
|
|
/* Start by adding the field inputs as parameters to the procedure. */
|
|
|
|
for (const FieldInput &field_input : field_tree_info.deduplicated_field_inputs) {
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::Variable &variable = builder.add_input_parameter(
|
|
|
|
mf::DataType::ForSingle(field_input.cpp_type()), field_input.debug_name());
|
2021-09-09 12:54:20 +02:00
|
|
|
variable_by_field.add_new({field_input, 0}, &variable);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Utility struct that is used to do proper depth first search traversal of the tree below. */
|
|
|
|
struct FieldWithIndex {
|
|
|
|
GFieldRef field;
|
|
|
|
int current_input_index = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
for (GFieldRef field : output_fields) {
|
|
|
|
/* We start a new stack for each output field to make sure that a field pushed later to the
|
|
|
|
* stack does never depend on a field that was pushed before. */
|
|
|
|
Stack<FieldWithIndex> fields_to_check;
|
|
|
|
fields_to_check.push({field, 0});
|
|
|
|
while (!fields_to_check.is_empty()) {
|
|
|
|
FieldWithIndex &field_with_index = fields_to_check.peek();
|
|
|
|
const GFieldRef &field = field_with_index.field;
|
|
|
|
if (variable_by_field.contains(field)) {
|
|
|
|
/* The field has been handled already. */
|
|
|
|
fields_to_check.pop();
|
|
|
|
continue;
|
|
|
|
}
|
2022-01-02 14:27:16 +01:00
|
|
|
const FieldNode &field_node = field.node();
|
|
|
|
switch (field_node.node_type()) {
|
|
|
|
case FieldNodeType::Input: {
|
|
|
|
/* Field inputs should already be handled above. */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case FieldNodeType::Operation: {
|
|
|
|
const FieldOperation &operation_node = static_cast<const FieldOperation &>(field.node());
|
|
|
|
const Span<GField> operation_inputs = operation_node.inputs();
|
|
|
|
|
|
|
|
if (field_with_index.current_input_index < operation_inputs.size()) {
|
|
|
|
/* Not all inputs are handled yet. Push the next input field to the stack and increment
|
|
|
|
* the input index. */
|
|
|
|
fields_to_check.push({operation_inputs[field_with_index.current_input_index]});
|
|
|
|
field_with_index.current_input_index++;
|
2021-09-14 14:52:44 +02:00
|
|
|
}
|
|
|
|
else {
|
2022-01-02 14:27:16 +01:00
|
|
|
/* All inputs variables are ready, now gather all variables that are used by the
|
|
|
|
* function and call it. */
|
2023-01-07 17:32:28 +01:00
|
|
|
const mf::MultiFunction &multi_function = operation_node.multi_function();
|
|
|
|
Vector<mf::Variable *> variables(multi_function.param_amount());
|
2022-01-02 14:27:16 +01:00
|
|
|
|
|
|
|
int param_input_index = 0;
|
|
|
|
int param_output_index = 0;
|
|
|
|
for (const int param_index : multi_function.param_indices()) {
|
2023-01-07 17:32:28 +01:00
|
|
|
const mf::ParamType param_type = multi_function.param_type(param_index);
|
|
|
|
const mf::ParamType::InterfaceType interface_type = param_type.interface_type();
|
|
|
|
if (interface_type == mf::ParamType::Input) {
|
2022-01-02 14:27:16 +01:00
|
|
|
const GField &input_field = operation_inputs[param_input_index];
|
|
|
|
variables[param_index] = variable_by_field.lookup(input_field);
|
|
|
|
param_input_index++;
|
|
|
|
}
|
2023-01-07 17:32:28 +01:00
|
|
|
else if (interface_type == mf::ParamType::Output) {
|
2022-01-02 14:27:16 +01:00
|
|
|
const GFieldRef output_field{operation_node, param_output_index};
|
|
|
|
const bool output_is_ignored =
|
|
|
|
field_tree_info.field_users.lookup(output_field).is_empty() &&
|
|
|
|
!output_fields.contains(output_field);
|
|
|
|
if (output_is_ignored) {
|
|
|
|
/* Ignored outputs don't need a variable. */
|
|
|
|
variables[param_index] = nullptr;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* Create a new variable for used outputs. */
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::Variable &new_variable = procedure.new_variable(param_type.data_type());
|
2022-01-02 14:27:16 +01:00
|
|
|
variables[param_index] = &new_variable;
|
|
|
|
variable_by_field.add_new(output_field, &new_variable);
|
|
|
|
}
|
|
|
|
param_output_index++;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
builder.add_call_with_all_variables(multi_function, variables);
|
2021-09-14 14:52:44 +02:00
|
|
|
}
|
2022-01-02 14:27:16 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case FieldNodeType::Constant: {
|
|
|
|
const FieldConstant &constant_node = static_cast<const FieldConstant &>(field_node);
|
2023-01-07 17:32:28 +01:00
|
|
|
const mf::MultiFunction &fn = procedure.construct_function<mf::CustomMF_GenericConstant>(
|
2022-01-02 14:27:16 +01:00
|
|
|
constant_node.type(), constant_node.value().get(), false);
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::Variable &new_variable = *builder.add_call<1>(fn)[0];
|
2022-01-02 14:27:16 +01:00
|
|
|
variable_by_field.add_new(field, &new_variable);
|
|
|
|
break;
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add output parameters to the procedure. */
|
2023-01-07 17:32:28 +01:00
|
|
|
Set<mf::Variable *> already_output_variables;
|
2021-09-09 12:54:20 +02:00
|
|
|
for (const GFieldRef &field : output_fields) {
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::Variable *variable = variable_by_field.lookup(field);
|
2021-09-09 12:54:20 +02:00
|
|
|
if (!already_output_variables.add(variable)) {
|
|
|
|
/* One variable can be output at most once. To output the same value twice, we have to make
|
|
|
|
* a copy first. */
|
2023-01-07 17:32:28 +01:00
|
|
|
const mf::MultiFunction ©_fn = scope.construct<mf::CustomMF_GenericCopy>(
|
|
|
|
variable->data_type());
|
2021-09-09 12:54:20 +02:00
|
|
|
variable = builder.add_call<1>(copy_fn, {variable})[0];
|
|
|
|
}
|
|
|
|
builder.add_output_parameter(*variable);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Remove the variables that should not be destructed from the map. */
|
|
|
|
for (const GFieldRef &field : output_fields) {
|
|
|
|
variable_by_field.remove(field);
|
|
|
|
}
|
|
|
|
/* Add destructor calls for the remaining variables. */
|
2023-01-07 17:32:28 +01:00
|
|
|
for (mf::Variable *variable : variable_by_field.values()) {
|
2021-09-09 12:54:20 +02:00
|
|
|
builder.add_destruct(*variable);
|
|
|
|
}
|
|
|
|
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::ReturnInstruction &return_instr = builder.add_return();
|
2021-12-13 13:28:33 +01:00
|
|
|
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::procedure_optimization::move_destructs_up(procedure, return_instr);
|
2021-09-09 12:54:20 +02:00
|
|
|
|
|
|
|
// std::cout << procedure.to_dot() << "\n";
|
|
|
|
BLI_assert(procedure.validate());
|
|
|
|
}
|
|
|
|
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
Vector<GVArray> evaluate_fields(ResourceScope &scope,
|
|
|
|
Span<GFieldRef> fields_to_evaluate,
|
BLI: refactor IndexMask for better performance and memory usage
Goals of this refactor:
* Reduce memory consumption of `IndexMask`. The old `IndexMask` uses an
`int64_t` for each index which is more than necessary in pretty much all
practical cases currently. Using `int32_t` might still become limiting
in the future in case we use this to index e.g. byte buffers larger than
a few gigabytes. We also don't want to template `IndexMask`, because
that would cause a split in the "ecosystem", or everything would have to
be implemented twice or templated.
* Allow for more multi-threading. The old `IndexMask` contains a single
array. This is generally good but has the problem that it is hard to fill
from multiple-threads when the final size is not known from the beginning.
This is commonly the case when e.g. converting an array of bool to an
index mask. Currently, this kind of code only runs on a single thread.
* Allow for efficient set operations like join, intersect and difference.
It should be possible to multi-thread those operations.
* It should be possible to iterate over an `IndexMask` very efficiently.
The most important part of that is to avoid all memory access when iterating
over continuous ranges. For some core nodes (e.g. math nodes), we generate
optimized code for the cases of irregular index masks and simple index ranges.
To achieve these goals, a few compromises had to made:
* Slicing of the mask (at specific indices) and random element access is
`O(log #indices)` now, but with a low constant factor. It should be possible
to split a mask into n approximately equally sized parts in `O(n)` though,
making the time per split `O(1)`.
* Using range-based for loops does not work well when iterating over a nested
data structure like the new `IndexMask`. Therefor, `foreach_*` functions with
callbacks have to be used. To avoid extra code complexity at the call site,
the `foreach_*` methods support multi-threading out of the box.
The new data structure splits an `IndexMask` into an arbitrary number of ordered
`IndexMaskSegment`. Each segment can contain at most `2^14 = 16384` indices. The
indices within a segment are stored as `int16_t`. Each segment has an additional
`int64_t` offset which allows storing arbitrary `int64_t` indices. This approach
has the main benefits that segments can be processed/constructed individually on
multiple threads without a serial bottleneck. Also it reduces the memory
requirements significantly.
For more details see comments in `BLI_index_mask.hh`.
I did a few tests to verify that the data structure generally improves
performance and does not cause regressions:
* Our field evaluation benchmarks take about as much as before. This is to be
expected because we already made sure that e.g. add node evaluation is
vectorized. The important thing here is to check that changes to the way we
iterate over the indices still allows for auto-vectorization.
* Memory usage by a mask is about 1/4 of what it was before in the average case.
That's mainly caused by the switch from `int64_t` to `int16_t` for indices.
In the worst case, the memory requirements can be larger when there are many
indices that are very far away. However, when they are far away from each other,
that indicates that there aren't many indices in total. In common cases, memory
usage can be way lower than 1/4 of before, because sub-ranges use static memory.
* For some more specific numbers I benchmarked `IndexMask::from_bools` in
`index_mask_from_selection` on 10.000.000 elements at various probabilities for
`true` at every index:
```
Probability Old New
0 4.6 ms 0.8 ms
0.001 5.1 ms 1.3 ms
0.2 8.4 ms 1.8 ms
0.5 15.3 ms 3.0 ms
0.8 20.1 ms 3.0 ms
0.999 25.1 ms 1.7 ms
1 13.5 ms 1.1 ms
```
Pull Request: https://projects.blender.org/blender/blender/pulls/104629
2023-05-24 18:11:41 +02:00
|
|
|
const IndexMask &mask,
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
const FieldContext &context,
|
|
|
|
Span<GVMutableArray> dst_varrays)
|
2021-09-09 12:54:20 +02:00
|
|
|
{
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
Vector<GVArray> r_varrays(fields_to_evaluate.size());
|
|
|
|
Array<bool> is_output_written_to_dst(fields_to_evaluate.size(), false);
|
2021-09-09 12:54:20 +02:00
|
|
|
const int array_size = mask.min_array_size();
|
|
|
|
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
if (mask.is_empty()) {
|
|
|
|
for (const int i : fields_to_evaluate.index_range()) {
|
|
|
|
const CPPType &type = fields_to_evaluate[i].cpp_type();
|
|
|
|
r_varrays[i] = GVArray::ForEmpty(type);
|
|
|
|
}
|
|
|
|
return r_varrays;
|
|
|
|
}
|
|
|
|
|
2021-09-09 12:54:20 +02:00
|
|
|
/* Destination arrays are optional. Create a small utility method to access them. */
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
auto get_dst_varray = [&](int index) -> GVMutableArray {
|
2021-09-09 12:54:20 +02:00
|
|
|
if (dst_varrays.is_empty()) {
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
const GVMutableArray &varray = dst_varrays[index];
|
|
|
|
if (!varray) {
|
|
|
|
return {};
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
BLI_assert(varray.size() >= array_size);
|
|
|
|
return varray;
|
2021-09-09 12:54:20 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Traverse the field tree and prepare some data that is used in later steps. */
|
|
|
|
FieldTreeInfo field_tree_info = preprocess_field_tree(fields_to_evaluate);
|
|
|
|
|
|
|
|
/* Get inputs that will be passed into the field when evaluated. */
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
Vector<GVArray> field_context_inputs = get_field_context_inputs(
|
2021-09-09 12:54:20 +02:00
|
|
|
scope, mask, context, field_tree_info.deduplicated_field_inputs);
|
|
|
|
|
2022-01-02 14:27:16 +01:00
|
|
|
/* Finish fields that don't need any processing directly. */
|
2021-09-09 12:54:20 +02:00
|
|
|
for (const int out_index : fields_to_evaluate.index_range()) {
|
|
|
|
const GFieldRef &field = fields_to_evaluate[out_index];
|
2022-01-02 14:27:16 +01:00
|
|
|
const FieldNode &field_node = field.node();
|
|
|
|
switch (field_node.node_type()) {
|
|
|
|
case FieldNodeType::Input: {
|
|
|
|
const FieldInput &field_input = static_cast<const FieldInput &>(field.node());
|
|
|
|
const int field_input_index = field_tree_info.deduplicated_field_inputs.index_of(
|
|
|
|
field_input);
|
|
|
|
const GVArray &varray = field_context_inputs[field_input_index];
|
|
|
|
r_varrays[out_index] = varray;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case FieldNodeType::Constant: {
|
|
|
|
const FieldConstant &field_constant = static_cast<const FieldConstant &>(field.node());
|
|
|
|
r_varrays[out_index] = GVArray::ForSingleRef(
|
|
|
|
field_constant.type(), mask.min_array_size(), field_constant.value().get());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case FieldNodeType::Operation: {
|
|
|
|
break;
|
|
|
|
}
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Set<GFieldRef> varying_fields = find_varying_fields(field_tree_info, field_context_inputs);
|
|
|
|
|
|
|
|
/* Separate fields into two categories. Those that are constant and need to be evaluated only
|
|
|
|
* once, and those that need to be evaluated for every index. */
|
|
|
|
Vector<GFieldRef> varying_fields_to_evaluate;
|
|
|
|
Vector<int> varying_field_indices;
|
|
|
|
Vector<GFieldRef> constant_fields_to_evaluate;
|
|
|
|
Vector<int> constant_field_indices;
|
|
|
|
for (const int i : fields_to_evaluate.index_range()) {
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
if (r_varrays[i]) {
|
2021-09-09 12:54:20 +02:00
|
|
|
/* Already done. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
GFieldRef field = fields_to_evaluate[i];
|
|
|
|
if (varying_fields.contains(field)) {
|
|
|
|
varying_fields_to_evaluate.append(field);
|
|
|
|
varying_field_indices.append(i);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
constant_fields_to_evaluate.append(field);
|
|
|
|
constant_field_indices.append(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Evaluate varying fields if necessary. */
|
|
|
|
if (!varying_fields_to_evaluate.is_empty()) {
|
|
|
|
/* Build the procedure for those fields. */
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::Procedure procedure;
|
2021-09-09 12:54:20 +02:00
|
|
|
build_multi_function_procedure_for_fields(
|
|
|
|
procedure, scope, field_tree_info, varying_fields_to_evaluate);
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::ProcedureExecutor procedure_executor{procedure};
|
2021-09-15 11:02:39 +02:00
|
|
|
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::ParamsBuilder mf_params{procedure_executor, &mask};
|
|
|
|
mf::ContextBuilder mf_context;
|
2021-09-09 12:54:20 +02:00
|
|
|
|
|
|
|
/* Provide inputs to the procedure executor. */
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
for (const GVArray &varray : field_context_inputs) {
|
|
|
|
mf_params.add_readonly_single_input(varray);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (const int i : varying_fields_to_evaluate.index_range()) {
|
|
|
|
const GFieldRef &field = varying_fields_to_evaluate[i];
|
|
|
|
const CPPType &type = field.cpp_type();
|
|
|
|
const int out_index = varying_field_indices[i];
|
|
|
|
|
|
|
|
/* Try to get an existing virtual array that the result should be written into. */
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
GVMutableArray dst_varray = get_dst_varray(out_index);
|
2021-09-09 12:54:20 +02:00
|
|
|
void *buffer;
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
if (!dst_varray || !dst_varray.is_span()) {
|
2021-09-09 12:54:20 +02:00
|
|
|
/* Allocate a new buffer for the computed result. */
|
|
|
|
buffer = scope.linear_allocator().allocate(type.size() * array_size, type.alignment());
|
|
|
|
|
2021-09-26 23:19:31 +02:00
|
|
|
if (!type.is_trivially_destructible()) {
|
|
|
|
/* Destruct values in the end. */
|
|
|
|
scope.add_destruct_call(
|
|
|
|
[buffer, mask, &type]() { type.destruct_indices(buffer, mask); });
|
|
|
|
}
|
2021-09-09 12:54:20 +02:00
|
|
|
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
r_varrays[out_index] = GVArray::ForSpan({type, buffer, array_size});
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* Write the result into the existing span. */
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
buffer = dst_varray.get_internal_span().data();
|
2021-09-09 12:54:20 +02:00
|
|
|
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
r_varrays[out_index] = dst_varray;
|
|
|
|
is_output_written_to_dst[out_index] = true;
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Pass output buffer to the procedure executor. */
|
|
|
|
const GMutableSpan span{type, buffer, array_size};
|
|
|
|
mf_params.add_uninitialized_single_output(span);
|
|
|
|
}
|
|
|
|
|
Geometry Nodes: refactor multi-threading in field evaluation
Previously, there was a fixed grain size for all multi-functions. That was
not sufficient because some functions could benefit a lot from smaller
grain sizes.
This refactors adds a new `MultiFunction::call_auto` method which has the
same effect as just calling `MultiFunction::call` but additionally figures
out how to execute the specific multi-function efficiently. It determines
a good grain size and decides whether the mask indices should be shifted
or not.
Most multi-function evaluations benefit from this, but medium sized work
loads (1000 - 50000 elements) benefit from it the most. Especially when
expensive multi-functions (e.g. noise) is involved. This is because for
smaller work loads, threading is rarely used and for larger work loads
threading worked fine before already.
With this patch, multi-functions can specify execution hints, that allow
the caller to execute it most efficiently. These execution hints still
have to be added to more functions.
Some performance measurements of a field evaluation involving noise and
math nodes, ordered by the number of elements being evaluated:
```
1,000,000: 133 ms -> 120 ms
100,000: 30 ms -> 18 ms
10,000: 20 ms -> 2.7 ms
1,000: 4 ms -> 0.5 ms
100: 0.5 ms -> 0.4 ms
```
2021-11-26 11:05:47 +01:00
|
|
|
procedure_executor.call_auto(mask, mf_params, mf_context);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Evaluate constant fields if necessary. */
|
|
|
|
if (!constant_fields_to_evaluate.is_empty()) {
|
|
|
|
/* Build the procedure for those fields. */
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::Procedure procedure;
|
2021-09-09 12:54:20 +02:00
|
|
|
build_multi_function_procedure_for_fields(
|
|
|
|
procedure, scope, field_tree_info, constant_fields_to_evaluate);
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::ProcedureExecutor procedure_executor{procedure};
|
BLI: refactor IndexMask for better performance and memory usage
Goals of this refactor:
* Reduce memory consumption of `IndexMask`. The old `IndexMask` uses an
`int64_t` for each index which is more than necessary in pretty much all
practical cases currently. Using `int32_t` might still become limiting
in the future in case we use this to index e.g. byte buffers larger than
a few gigabytes. We also don't want to template `IndexMask`, because
that would cause a split in the "ecosystem", or everything would have to
be implemented twice or templated.
* Allow for more multi-threading. The old `IndexMask` contains a single
array. This is generally good but has the problem that it is hard to fill
from multiple-threads when the final size is not known from the beginning.
This is commonly the case when e.g. converting an array of bool to an
index mask. Currently, this kind of code only runs on a single thread.
* Allow for efficient set operations like join, intersect and difference.
It should be possible to multi-thread those operations.
* It should be possible to iterate over an `IndexMask` very efficiently.
The most important part of that is to avoid all memory access when iterating
over continuous ranges. For some core nodes (e.g. math nodes), we generate
optimized code for the cases of irregular index masks and simple index ranges.
To achieve these goals, a few compromises had to made:
* Slicing of the mask (at specific indices) and random element access is
`O(log #indices)` now, but with a low constant factor. It should be possible
to split a mask into n approximately equally sized parts in `O(n)` though,
making the time per split `O(1)`.
* Using range-based for loops does not work well when iterating over a nested
data structure like the new `IndexMask`. Therefor, `foreach_*` functions with
callbacks have to be used. To avoid extra code complexity at the call site,
the `foreach_*` methods support multi-threading out of the box.
The new data structure splits an `IndexMask` into an arbitrary number of ordered
`IndexMaskSegment`. Each segment can contain at most `2^14 = 16384` indices. The
indices within a segment are stored as `int16_t`. Each segment has an additional
`int64_t` offset which allows storing arbitrary `int64_t` indices. This approach
has the main benefits that segments can be processed/constructed individually on
multiple threads without a serial bottleneck. Also it reduces the memory
requirements significantly.
For more details see comments in `BLI_index_mask.hh`.
I did a few tests to verify that the data structure generally improves
performance and does not cause regressions:
* Our field evaluation benchmarks take about as much as before. This is to be
expected because we already made sure that e.g. add node evaluation is
vectorized. The important thing here is to check that changes to the way we
iterate over the indices still allows for auto-vectorization.
* Memory usage by a mask is about 1/4 of what it was before in the average case.
That's mainly caused by the switch from `int64_t` to `int16_t` for indices.
In the worst case, the memory requirements can be larger when there are many
indices that are very far away. However, when they are far away from each other,
that indicates that there aren't many indices in total. In common cases, memory
usage can be way lower than 1/4 of before, because sub-ranges use static memory.
* For some more specific numbers I benchmarked `IndexMask::from_bools` in
`index_mask_from_selection` on 10.000.000 elements at various probabilities for
`true` at every index:
```
Probability Old New
0 4.6 ms 0.8 ms
0.001 5.1 ms 1.3 ms
0.2 8.4 ms 1.8 ms
0.5 15.3 ms 3.0 ms
0.8 20.1 ms 3.0 ms
0.999 25.1 ms 1.7 ms
1 13.5 ms 1.1 ms
```
Pull Request: https://projects.blender.org/blender/blender/pulls/104629
2023-05-24 18:11:41 +02:00
|
|
|
const IndexMask mask(1);
|
|
|
|
mf::ParamsBuilder mf_params{procedure_executor, &mask};
|
2023-01-07 17:32:28 +01:00
|
|
|
mf::ContextBuilder mf_context;
|
2021-09-09 12:54:20 +02:00
|
|
|
|
|
|
|
/* Provide inputs to the procedure executor. */
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
for (const GVArray &varray : field_context_inputs) {
|
|
|
|
mf_params.add_readonly_single_input(varray);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (const int i : constant_fields_to_evaluate.index_range()) {
|
|
|
|
const GFieldRef &field = constant_fields_to_evaluate[i];
|
|
|
|
const CPPType &type = field.cpp_type();
|
|
|
|
/* Allocate memory where the computed value will be stored in. */
|
|
|
|
void *buffer = scope.linear_allocator().allocate(type.size(), type.alignment());
|
|
|
|
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
if (!type.is_trivially_destructible()) {
|
2021-09-26 23:19:31 +02:00
|
|
|
/* Destruct value in the end. */
|
|
|
|
scope.add_destruct_call([buffer, &type]() { type.destruct(buffer); });
|
|
|
|
}
|
2021-09-09 12:54:20 +02:00
|
|
|
|
|
|
|
/* Pass output buffer to the procedure executor. */
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
mf_params.add_uninitialized_single_output({type, buffer, 1});
|
2021-09-09 12:54:20 +02:00
|
|
|
|
|
|
|
/* Create virtual array that can be used after the procedure has been executed below. */
|
|
|
|
const int out_index = constant_field_indices[i];
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
r_varrays[out_index] = GVArray::ForSingleRef(type, array_size, buffer);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
|
BLI: refactor IndexMask for better performance and memory usage
Goals of this refactor:
* Reduce memory consumption of `IndexMask`. The old `IndexMask` uses an
`int64_t` for each index which is more than necessary in pretty much all
practical cases currently. Using `int32_t` might still become limiting
in the future in case we use this to index e.g. byte buffers larger than
a few gigabytes. We also don't want to template `IndexMask`, because
that would cause a split in the "ecosystem", or everything would have to
be implemented twice or templated.
* Allow for more multi-threading. The old `IndexMask` contains a single
array. This is generally good but has the problem that it is hard to fill
from multiple-threads when the final size is not known from the beginning.
This is commonly the case when e.g. converting an array of bool to an
index mask. Currently, this kind of code only runs on a single thread.
* Allow for efficient set operations like join, intersect and difference.
It should be possible to multi-thread those operations.
* It should be possible to iterate over an `IndexMask` very efficiently.
The most important part of that is to avoid all memory access when iterating
over continuous ranges. For some core nodes (e.g. math nodes), we generate
optimized code for the cases of irregular index masks and simple index ranges.
To achieve these goals, a few compromises had to made:
* Slicing of the mask (at specific indices) and random element access is
`O(log #indices)` now, but with a low constant factor. It should be possible
to split a mask into n approximately equally sized parts in `O(n)` though,
making the time per split `O(1)`.
* Using range-based for loops does not work well when iterating over a nested
data structure like the new `IndexMask`. Therefor, `foreach_*` functions with
callbacks have to be used. To avoid extra code complexity at the call site,
the `foreach_*` methods support multi-threading out of the box.
The new data structure splits an `IndexMask` into an arbitrary number of ordered
`IndexMaskSegment`. Each segment can contain at most `2^14 = 16384` indices. The
indices within a segment are stored as `int16_t`. Each segment has an additional
`int64_t` offset which allows storing arbitrary `int64_t` indices. This approach
has the main benefits that segments can be processed/constructed individually on
multiple threads without a serial bottleneck. Also it reduces the memory
requirements significantly.
For more details see comments in `BLI_index_mask.hh`.
I did a few tests to verify that the data structure generally improves
performance and does not cause regressions:
* Our field evaluation benchmarks take about as much as before. This is to be
expected because we already made sure that e.g. add node evaluation is
vectorized. The important thing here is to check that changes to the way we
iterate over the indices still allows for auto-vectorization.
* Memory usage by a mask is about 1/4 of what it was before in the average case.
That's mainly caused by the switch from `int64_t` to `int16_t` for indices.
In the worst case, the memory requirements can be larger when there are many
indices that are very far away. However, when they are far away from each other,
that indicates that there aren't many indices in total. In common cases, memory
usage can be way lower than 1/4 of before, because sub-ranges use static memory.
* For some more specific numbers I benchmarked `IndexMask::from_bools` in
`index_mask_from_selection` on 10.000.000 elements at various probabilities for
`true` at every index:
```
Probability Old New
0 4.6 ms 0.8 ms
0.001 5.1 ms 1.3 ms
0.2 8.4 ms 1.8 ms
0.5 15.3 ms 3.0 ms
0.8 20.1 ms 3.0 ms
0.999 25.1 ms 1.7 ms
1 13.5 ms 1.1 ms
```
Pull Request: https://projects.blender.org/blender/blender/pulls/104629
2023-05-24 18:11:41 +02:00
|
|
|
procedure_executor.call(mask, mf_params, mf_context);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
/* Copy data to supplied destination arrays if necessary. In some cases the evaluation above
|
|
|
|
* has written the computed data in the right place already. */
|
2021-09-09 12:54:20 +02:00
|
|
|
if (!dst_varrays.is_empty()) {
|
|
|
|
for (const int out_index : fields_to_evaluate.index_range()) {
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
GVMutableArray dst_varray = get_dst_varray(out_index);
|
|
|
|
if (!dst_varray) {
|
2021-09-09 12:54:20 +02:00
|
|
|
/* Caller did not provide a destination for this output. */
|
|
|
|
continue;
|
|
|
|
}
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
const GVArray &computed_varray = r_varrays[out_index];
|
|
|
|
BLI_assert(computed_varray.type() == dst_varray.type());
|
|
|
|
if (is_output_written_to_dst[out_index]) {
|
2021-09-09 12:54:20 +02:00
|
|
|
/* The result has been written into the destination provided by the caller already. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Still have to copy over the data in the destination provided by the caller. */
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
if (dst_varray.is_span()) {
|
2022-12-02 21:14:14 +01:00
|
|
|
array_utils::copy(computed_varray,
|
|
|
|
mask,
|
|
|
|
dst_varray.get_internal_span().take_front(mask.min_array_size()));
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* Slower materialize into a different structure. */
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
const CPPType &type = computed_varray.type();
|
2022-04-07 09:48:07 +02:00
|
|
|
threading::parallel_for(mask.index_range(), 2048, [&](const IndexRange range) {
|
|
|
|
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
|
BLI: refactor IndexMask for better performance and memory usage
Goals of this refactor:
* Reduce memory consumption of `IndexMask`. The old `IndexMask` uses an
`int64_t` for each index which is more than necessary in pretty much all
practical cases currently. Using `int32_t` might still become limiting
in the future in case we use this to index e.g. byte buffers larger than
a few gigabytes. We also don't want to template `IndexMask`, because
that would cause a split in the "ecosystem", or everything would have to
be implemented twice or templated.
* Allow for more multi-threading. The old `IndexMask` contains a single
array. This is generally good but has the problem that it is hard to fill
from multiple-threads when the final size is not known from the beginning.
This is commonly the case when e.g. converting an array of bool to an
index mask. Currently, this kind of code only runs on a single thread.
* Allow for efficient set operations like join, intersect and difference.
It should be possible to multi-thread those operations.
* It should be possible to iterate over an `IndexMask` very efficiently.
The most important part of that is to avoid all memory access when iterating
over continuous ranges. For some core nodes (e.g. math nodes), we generate
optimized code for the cases of irregular index masks and simple index ranges.
To achieve these goals, a few compromises had to made:
* Slicing of the mask (at specific indices) and random element access is
`O(log #indices)` now, but with a low constant factor. It should be possible
to split a mask into n approximately equally sized parts in `O(n)` though,
making the time per split `O(1)`.
* Using range-based for loops does not work well when iterating over a nested
data structure like the new `IndexMask`. Therefor, `foreach_*` functions with
callbacks have to be used. To avoid extra code complexity at the call site,
the `foreach_*` methods support multi-threading out of the box.
The new data structure splits an `IndexMask` into an arbitrary number of ordered
`IndexMaskSegment`. Each segment can contain at most `2^14 = 16384` indices. The
indices within a segment are stored as `int16_t`. Each segment has an additional
`int64_t` offset which allows storing arbitrary `int64_t` indices. This approach
has the main benefits that segments can be processed/constructed individually on
multiple threads without a serial bottleneck. Also it reduces the memory
requirements significantly.
For more details see comments in `BLI_index_mask.hh`.
I did a few tests to verify that the data structure generally improves
performance and does not cause regressions:
* Our field evaluation benchmarks take about as much as before. This is to be
expected because we already made sure that e.g. add node evaluation is
vectorized. The important thing here is to check that changes to the way we
iterate over the indices still allows for auto-vectorization.
* Memory usage by a mask is about 1/4 of what it was before in the average case.
That's mainly caused by the switch from `int64_t` to `int16_t` for indices.
In the worst case, the memory requirements can be larger when there are many
indices that are very far away. However, when they are far away from each other,
that indicates that there aren't many indices in total. In common cases, memory
usage can be way lower than 1/4 of before, because sub-ranges use static memory.
* For some more specific numbers I benchmarked `IndexMask::from_bools` in
`index_mask_from_selection` on 10.000.000 elements at various probabilities for
`true` at every index:
```
Probability Old New
0 4.6 ms 0.8 ms
0.001 5.1 ms 1.3 ms
0.2 8.4 ms 1.8 ms
0.5 15.3 ms 3.0 ms
0.8 20.1 ms 3.0 ms
0.999 25.1 ms 1.7 ms
1 13.5 ms 1.1 ms
```
Pull Request: https://projects.blender.org/blender/blender/pulls/104629
2023-05-24 18:11:41 +02:00
|
|
|
mask.slice(range).foreach_segment([&](auto segment) {
|
|
|
|
for (const int i : segment) {
|
|
|
|
computed_varray.get_to_uninitialized(i, buffer);
|
|
|
|
dst_varray.set_by_relocate(i, buffer);
|
|
|
|
}
|
|
|
|
});
|
2022-04-07 09:48:07 +02:00
|
|
|
});
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
r_varrays[out_index] = dst_varray;
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return r_varrays;
|
|
|
|
}
|
|
|
|
|
|
|
|
void evaluate_constant_field(const GField &field, void *r_value)
|
|
|
|
{
|
2021-10-24 17:53:19 +02:00
|
|
|
if (field.node().depends_on_input()) {
|
|
|
|
const CPPType &type = field.cpp_type();
|
2022-03-29 09:28:46 +02:00
|
|
|
type.value_initialize(r_value);
|
2021-10-24 17:53:19 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-09-09 12:54:20 +02:00
|
|
|
ResourceScope scope;
|
|
|
|
FieldContext context;
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
Vector<GVArray> varrays = evaluate_fields(scope, {field}, IndexRange(1), context);
|
|
|
|
varrays[0].get_to_uninitialized(0, r_value);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
|
2021-09-11 13:05:20 +02:00
|
|
|
GField make_field_constant_if_possible(GField field)
|
|
|
|
{
|
|
|
|
if (field.node().depends_on_input()) {
|
|
|
|
return field;
|
|
|
|
}
|
|
|
|
const CPPType &type = field.cpp_type();
|
|
|
|
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
|
|
|
|
evaluate_constant_field(field, buffer);
|
2021-11-23 14:47:25 +01:00
|
|
|
GField new_field = make_constant_field(type, buffer);
|
2021-09-11 13:05:20 +02:00
|
|
|
type.destruct(buffer);
|
2021-11-23 14:47:25 +01:00
|
|
|
return new_field;
|
|
|
|
}
|
|
|
|
|
2022-06-05 16:46:09 +02:00
|
|
|
Field<bool> invert_boolean_field(const Field<bool> &field)
|
|
|
|
{
|
2023-01-07 17:32:28 +01:00
|
|
|
static auto not_fn = mf::build::SI1_SO<bool, bool>(
|
|
|
|
"Not", [](bool a) { return !a; }, mf::build::exec_presets::AllSpanOrSingle());
|
2023-04-23 20:03:50 +02:00
|
|
|
auto not_op = FieldOperation::Create(not_fn, {field});
|
2022-06-05 16:46:09 +02:00
|
|
|
return Field<bool>(not_op);
|
|
|
|
}
|
|
|
|
|
2021-11-23 14:47:25 +01:00
|
|
|
GField make_constant_field(const CPPType &type, const void *value)
|
|
|
|
{
|
2022-01-02 14:27:16 +01:00
|
|
|
auto constant_node = std::make_shared<FieldConstant>(type, value);
|
|
|
|
return GField{std::move(constant_node)};
|
2021-09-11 13:05:20 +02:00
|
|
|
}
|
|
|
|
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
GVArray FieldContext::get_varray_for_input(const FieldInput &field_input,
|
BLI: refactor IndexMask for better performance and memory usage
Goals of this refactor:
* Reduce memory consumption of `IndexMask`. The old `IndexMask` uses an
`int64_t` for each index which is more than necessary in pretty much all
practical cases currently. Using `int32_t` might still become limiting
in the future in case we use this to index e.g. byte buffers larger than
a few gigabytes. We also don't want to template `IndexMask`, because
that would cause a split in the "ecosystem", or everything would have to
be implemented twice or templated.
* Allow for more multi-threading. The old `IndexMask` contains a single
array. This is generally good but has the problem that it is hard to fill
from multiple-threads when the final size is not known from the beginning.
This is commonly the case when e.g. converting an array of bool to an
index mask. Currently, this kind of code only runs on a single thread.
* Allow for efficient set operations like join, intersect and difference.
It should be possible to multi-thread those operations.
* It should be possible to iterate over an `IndexMask` very efficiently.
The most important part of that is to avoid all memory access when iterating
over continuous ranges. For some core nodes (e.g. math nodes), we generate
optimized code for the cases of irregular index masks and simple index ranges.
To achieve these goals, a few compromises had to made:
* Slicing of the mask (at specific indices) and random element access is
`O(log #indices)` now, but with a low constant factor. It should be possible
to split a mask into n approximately equally sized parts in `O(n)` though,
making the time per split `O(1)`.
* Using range-based for loops does not work well when iterating over a nested
data structure like the new `IndexMask`. Therefor, `foreach_*` functions with
callbacks have to be used. To avoid extra code complexity at the call site,
the `foreach_*` methods support multi-threading out of the box.
The new data structure splits an `IndexMask` into an arbitrary number of ordered
`IndexMaskSegment`. Each segment can contain at most `2^14 = 16384` indices. The
indices within a segment are stored as `int16_t`. Each segment has an additional
`int64_t` offset which allows storing arbitrary `int64_t` indices. This approach
has the main benefits that segments can be processed/constructed individually on
multiple threads without a serial bottleneck. Also it reduces the memory
requirements significantly.
For more details see comments in `BLI_index_mask.hh`.
I did a few tests to verify that the data structure generally improves
performance and does not cause regressions:
* Our field evaluation benchmarks take about as much as before. This is to be
expected because we already made sure that e.g. add node evaluation is
vectorized. The important thing here is to check that changes to the way we
iterate over the indices still allows for auto-vectorization.
* Memory usage by a mask is about 1/4 of what it was before in the average case.
That's mainly caused by the switch from `int64_t` to `int16_t` for indices.
In the worst case, the memory requirements can be larger when there are many
indices that are very far away. However, when they are far away from each other,
that indicates that there aren't many indices in total. In common cases, memory
usage can be way lower than 1/4 of before, because sub-ranges use static memory.
* For some more specific numbers I benchmarked `IndexMask::from_bools` in
`index_mask_from_selection` on 10.000.000 elements at various probabilities for
`true` at every index:
```
Probability Old New
0 4.6 ms 0.8 ms
0.001 5.1 ms 1.3 ms
0.2 8.4 ms 1.8 ms
0.5 15.3 ms 3.0 ms
0.8 20.1 ms 3.0 ms
0.999 25.1 ms 1.7 ms
1 13.5 ms 1.1 ms
```
Pull Request: https://projects.blender.org/blender/blender/pulls/104629
2023-05-24 18:11:41 +02:00
|
|
|
const IndexMask &mask,
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
ResourceScope &scope) const
|
2021-09-09 12:54:20 +02:00
|
|
|
{
|
|
|
|
/* By default ask the field input to create the varray. Another field context might overwrite
|
|
|
|
* the context here. */
|
|
|
|
return field_input.get_varray_for_context(*this, mask, scope);
|
|
|
|
}
|
|
|
|
|
2021-09-24 16:02:59 +02:00
|
|
|
IndexFieldInput::IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index")
|
|
|
|
{
|
2021-10-26 15:32:01 +02:00
|
|
|
category_ = Category::Generated;
|
2021-09-24 16:02:59 +02:00
|
|
|
}
|
|
|
|
|
BLI: refactor IndexMask for better performance and memory usage
Goals of this refactor:
* Reduce memory consumption of `IndexMask`. The old `IndexMask` uses an
`int64_t` for each index which is more than necessary in pretty much all
practical cases currently. Using `int32_t` might still become limiting
in the future in case we use this to index e.g. byte buffers larger than
a few gigabytes. We also don't want to template `IndexMask`, because
that would cause a split in the "ecosystem", or everything would have to
be implemented twice or templated.
* Allow for more multi-threading. The old `IndexMask` contains a single
array. This is generally good but has the problem that it is hard to fill
from multiple-threads when the final size is not known from the beginning.
This is commonly the case when e.g. converting an array of bool to an
index mask. Currently, this kind of code only runs on a single thread.
* Allow for efficient set operations like join, intersect and difference.
It should be possible to multi-thread those operations.
* It should be possible to iterate over an `IndexMask` very efficiently.
The most important part of that is to avoid all memory access when iterating
over continuous ranges. For some core nodes (e.g. math nodes), we generate
optimized code for the cases of irregular index masks and simple index ranges.
To achieve these goals, a few compromises had to made:
* Slicing of the mask (at specific indices) and random element access is
`O(log #indices)` now, but with a low constant factor. It should be possible
to split a mask into n approximately equally sized parts in `O(n)` though,
making the time per split `O(1)`.
* Using range-based for loops does not work well when iterating over a nested
data structure like the new `IndexMask`. Therefor, `foreach_*` functions with
callbacks have to be used. To avoid extra code complexity at the call site,
the `foreach_*` methods support multi-threading out of the box.
The new data structure splits an `IndexMask` into an arbitrary number of ordered
`IndexMaskSegment`. Each segment can contain at most `2^14 = 16384` indices. The
indices within a segment are stored as `int16_t`. Each segment has an additional
`int64_t` offset which allows storing arbitrary `int64_t` indices. This approach
has the main benefits that segments can be processed/constructed individually on
multiple threads without a serial bottleneck. Also it reduces the memory
requirements significantly.
For more details see comments in `BLI_index_mask.hh`.
I did a few tests to verify that the data structure generally improves
performance and does not cause regressions:
* Our field evaluation benchmarks take about as much as before. This is to be
expected because we already made sure that e.g. add node evaluation is
vectorized. The important thing here is to check that changes to the way we
iterate over the indices still allows for auto-vectorization.
* Memory usage by a mask is about 1/4 of what it was before in the average case.
That's mainly caused by the switch from `int64_t` to `int16_t` for indices.
In the worst case, the memory requirements can be larger when there are many
indices that are very far away. However, when they are far away from each other,
that indicates that there aren't many indices in total. In common cases, memory
usage can be way lower than 1/4 of before, because sub-ranges use static memory.
* For some more specific numbers I benchmarked `IndexMask::from_bools` in
`index_mask_from_selection` on 10.000.000 elements at various probabilities for
`true` at every index:
```
Probability Old New
0 4.6 ms 0.8 ms
0.001 5.1 ms 1.3 ms
0.2 8.4 ms 1.8 ms
0.5 15.3 ms 3.0 ms
0.8 20.1 ms 3.0 ms
0.999 25.1 ms 1.7 ms
1 13.5 ms 1.1 ms
```
Pull Request: https://projects.blender.org/blender/blender/pulls/104629
2023-05-24 18:11:41 +02:00
|
|
|
GVArray IndexFieldInput::get_index_varray(const IndexMask &mask)
|
2021-09-24 16:02:59 +02:00
|
|
|
{
|
|
|
|
auto index_func = [](int i) { return i; };
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
return VArray<int>::ForFunc(mask.min_array_size(), index_func);
|
2021-09-24 16:02:59 +02:00
|
|
|
}
|
|
|
|
|
2022-10-04 00:37:25 +02:00
|
|
|
GVArray IndexFieldInput::get_varray_for_context(const fn::FieldContext & /*context*/,
|
BLI: refactor IndexMask for better performance and memory usage
Goals of this refactor:
* Reduce memory consumption of `IndexMask`. The old `IndexMask` uses an
`int64_t` for each index which is more than necessary in pretty much all
practical cases currently. Using `int32_t` might still become limiting
in the future in case we use this to index e.g. byte buffers larger than
a few gigabytes. We also don't want to template `IndexMask`, because
that would cause a split in the "ecosystem", or everything would have to
be implemented twice or templated.
* Allow for more multi-threading. The old `IndexMask` contains a single
array. This is generally good but has the problem that it is hard to fill
from multiple-threads when the final size is not known from the beginning.
This is commonly the case when e.g. converting an array of bool to an
index mask. Currently, this kind of code only runs on a single thread.
* Allow for efficient set operations like join, intersect and difference.
It should be possible to multi-thread those operations.
* It should be possible to iterate over an `IndexMask` very efficiently.
The most important part of that is to avoid all memory access when iterating
over continuous ranges. For some core nodes (e.g. math nodes), we generate
optimized code for the cases of irregular index masks and simple index ranges.
To achieve these goals, a few compromises had to made:
* Slicing of the mask (at specific indices) and random element access is
`O(log #indices)` now, but with a low constant factor. It should be possible
to split a mask into n approximately equally sized parts in `O(n)` though,
making the time per split `O(1)`.
* Using range-based for loops does not work well when iterating over a nested
data structure like the new `IndexMask`. Therefor, `foreach_*` functions with
callbacks have to be used. To avoid extra code complexity at the call site,
the `foreach_*` methods support multi-threading out of the box.
The new data structure splits an `IndexMask` into an arbitrary number of ordered
`IndexMaskSegment`. Each segment can contain at most `2^14 = 16384` indices. The
indices within a segment are stored as `int16_t`. Each segment has an additional
`int64_t` offset which allows storing arbitrary `int64_t` indices. This approach
has the main benefits that segments can be processed/constructed individually on
multiple threads without a serial bottleneck. Also it reduces the memory
requirements significantly.
For more details see comments in `BLI_index_mask.hh`.
I did a few tests to verify that the data structure generally improves
performance and does not cause regressions:
* Our field evaluation benchmarks take about as much as before. This is to be
expected because we already made sure that e.g. add node evaluation is
vectorized. The important thing here is to check that changes to the way we
iterate over the indices still allows for auto-vectorization.
* Memory usage by a mask is about 1/4 of what it was before in the average case.
That's mainly caused by the switch from `int64_t` to `int16_t` for indices.
In the worst case, the memory requirements can be larger when there are many
indices that are very far away. However, when they are far away from each other,
that indicates that there aren't many indices in total. In common cases, memory
usage can be way lower than 1/4 of before, because sub-ranges use static memory.
* For some more specific numbers I benchmarked `IndexMask::from_bools` in
`index_mask_from_selection` on 10.000.000 elements at various probabilities for
`true` at every index:
```
Probability Old New
0 4.6 ms 0.8 ms
0.001 5.1 ms 1.3 ms
0.2 8.4 ms 1.8 ms
0.5 15.3 ms 3.0 ms
0.8 20.1 ms 3.0 ms
0.999 25.1 ms 1.7 ms
1 13.5 ms 1.1 ms
```
Pull Request: https://projects.blender.org/blender/blender/pulls/104629
2023-05-24 18:11:41 +02:00
|
|
|
const IndexMask &mask,
|
2022-10-04 00:37:25 +02:00
|
|
|
ResourceScope & /*scope*/) const
|
2021-10-20 17:54:54 +02:00
|
|
|
{
|
|
|
|
/* TODO: Investigate a similar method to IndexRange::as_span() */
|
2021-12-06 19:05:29 +01:00
|
|
|
return get_index_varray(mask);
|
2021-10-20 17:54:54 +02:00
|
|
|
}
|
|
|
|
|
2021-10-19 03:13:37 +02:00
|
|
|
uint64_t IndexFieldInput::hash() const
|
|
|
|
{
|
|
|
|
/* Some random constant hash. */
|
|
|
|
return 128736487678;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IndexFieldInput::is_equal_to(const fn::FieldNode &other) const
|
|
|
|
{
|
|
|
|
return dynamic_cast<const IndexFieldInput *>(&other) != nullptr;
|
|
|
|
}
|
|
|
|
|
2022-09-15 07:27:21 +02:00
|
|
|
/** \} */
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
2022-09-16 10:13:19 +02:00
|
|
|
/** \name #FieldNode
|
2022-09-15 07:27:21 +02:00
|
|
|
* \{ */
|
2022-02-04 17:18:41 +01:00
|
|
|
|
|
|
|
/* Avoid generating the destructor in every translation unit. */
|
|
|
|
FieldNode::~FieldNode() = default;
|
|
|
|
|
2023-01-03 12:37:18 +01:00
|
|
|
void FieldNode::for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const
|
|
|
|
{
|
|
|
|
if (field_inputs_) {
|
|
|
|
for (const FieldInput &field_input : field_inputs_->deduplicated_nodes) {
|
|
|
|
fn(field_input);
|
|
|
|
if (&field_input != this) {
|
|
|
|
field_input.for_each_field_input_recursive(fn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-15 07:27:21 +02:00
|
|
|
/** \} */
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
2022-09-16 10:13:19 +02:00
|
|
|
/** \name #FieldOperation
|
2022-09-15 07:27:21 +02:00
|
|
|
* \{ */
|
2021-09-11 13:05:20 +02:00
|
|
|
|
2023-01-07 17:32:28 +01:00
|
|
|
FieldOperation::FieldOperation(std::shared_ptr<const mf::MultiFunction> function,
|
2021-09-11 13:05:20 +02:00
|
|
|
Vector<GField> inputs)
|
|
|
|
: FieldOperation(*function, std::move(inputs))
|
|
|
|
{
|
|
|
|
owned_function_ = std::move(function);
|
|
|
|
}
|
|
|
|
|
2022-02-04 17:18:41 +01:00
|
|
|
/* Avoid generating the destructor in every translation unit. */
|
|
|
|
FieldOperation::~FieldOperation() = default;
|
|
|
|
|
2021-12-11 11:25:32 +01:00
|
|
|
/**
|
|
|
|
* Returns the field inputs used by all the provided fields.
|
|
|
|
* This tries to reuse an existing #FieldInputs whenever possible to avoid copying it.
|
|
|
|
*/
|
|
|
|
static std::shared_ptr<const FieldInputs> combine_field_inputs(Span<GField> fields)
|
2021-09-11 13:05:20 +02:00
|
|
|
{
|
2021-12-11 11:25:32 +01:00
|
|
|
/* The #FieldInputs that we try to reuse if possible. */
|
|
|
|
const std::shared_ptr<const FieldInputs> *field_inputs_candidate = nullptr;
|
2021-09-11 13:05:20 +02:00
|
|
|
for (const GField &field : fields) {
|
2021-12-11 11:25:32 +01:00
|
|
|
const std::shared_ptr<const FieldInputs> &field_inputs = field.node().field_inputs();
|
|
|
|
/* Only try to reuse non-empty #FieldInputs. */
|
|
|
|
if (field_inputs && !field_inputs->nodes.is_empty()) {
|
|
|
|
if (field_inputs_candidate == nullptr) {
|
|
|
|
field_inputs_candidate = &field_inputs;
|
|
|
|
}
|
|
|
|
else if ((*field_inputs_candidate)->nodes.size() < field_inputs->nodes.size()) {
|
|
|
|
/* Always try to reuse the #FieldInputs that has the most nodes already. */
|
|
|
|
field_inputs_candidate = &field_inputs;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (field_inputs_candidate == nullptr) {
|
|
|
|
/* None of the field depends on an input. */
|
|
|
|
return {};
|
|
|
|
}
|
2021-12-13 13:51:28 +01:00
|
|
|
/* Check if all inputs are in the candidate. */
|
2021-12-11 11:25:32 +01:00
|
|
|
Vector<const FieldInput *> inputs_not_in_candidate;
|
|
|
|
for (const GField &field : fields) {
|
|
|
|
const std::shared_ptr<const FieldInputs> &field_inputs = field.node().field_inputs();
|
|
|
|
if (!field_inputs) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (&field_inputs == field_inputs_candidate) {
|
|
|
|
continue;
|
2021-09-11 13:05:20 +02:00
|
|
|
}
|
2021-12-11 11:25:32 +01:00
|
|
|
for (const FieldInput *field_input : field_inputs->nodes) {
|
|
|
|
if (!(*field_inputs_candidate)->nodes.contains(field_input)) {
|
|
|
|
inputs_not_in_candidate.append(field_input);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (inputs_not_in_candidate.is_empty()) {
|
|
|
|
/* The existing #FieldInputs can be reused, because no other field has additional inputs. */
|
|
|
|
return *field_inputs_candidate;
|
2021-09-11 13:05:20 +02:00
|
|
|
}
|
2021-12-11 11:25:32 +01:00
|
|
|
/* Create new #FieldInputs that contains all of the inputs that the fields depend on. */
|
|
|
|
std::shared_ptr<FieldInputs> new_field_inputs = std::make_shared<FieldInputs>(
|
|
|
|
**field_inputs_candidate);
|
|
|
|
for (const FieldInput *field_input : inputs_not_in_candidate) {
|
|
|
|
new_field_inputs->nodes.add(field_input);
|
|
|
|
new_field_inputs->deduplicated_nodes.add(*field_input);
|
|
|
|
}
|
|
|
|
return new_field_inputs;
|
2021-09-11 13:05:20 +02:00
|
|
|
}
|
|
|
|
|
2023-01-07 17:32:28 +01:00
|
|
|
FieldOperation::FieldOperation(const mf::MultiFunction &function, Vector<GField> inputs)
|
2022-01-02 14:27:16 +01:00
|
|
|
: FieldNode(FieldNodeType::Operation), function_(&function), inputs_(std::move(inputs))
|
2021-09-11 13:05:20 +02:00
|
|
|
{
|
2021-12-13 13:51:28 +01:00
|
|
|
field_inputs_ = combine_field_inputs(inputs_);
|
2021-09-11 13:05:20 +02:00
|
|
|
}
|
|
|
|
|
2022-09-15 07:27:21 +02:00
|
|
|
/** \} */
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
2022-09-16 10:13:19 +02:00
|
|
|
/** \name #FieldInput
|
2022-09-15 07:27:21 +02:00
|
|
|
* \{ */
|
2021-09-11 13:05:20 +02:00
|
|
|
|
|
|
|
FieldInput::FieldInput(const CPPType &type, std::string debug_name)
|
2022-01-02 14:27:16 +01:00
|
|
|
: FieldNode(FieldNodeType::Input), type_(&type), debug_name_(std::move(debug_name))
|
2021-09-11 13:05:20 +02:00
|
|
|
{
|
2021-12-11 11:25:32 +01:00
|
|
|
std::shared_ptr<FieldInputs> field_inputs = std::make_shared<FieldInputs>();
|
|
|
|
field_inputs->nodes.add_new(this);
|
|
|
|
field_inputs->deduplicated_nodes.add_new(*this);
|
|
|
|
field_inputs_ = std::move(field_inputs);
|
2021-09-11 13:05:20 +02:00
|
|
|
}
|
|
|
|
|
2022-02-04 17:18:41 +01:00
|
|
|
/* Avoid generating the destructor in every translation unit. */
|
|
|
|
FieldInput::~FieldInput() = default;
|
|
|
|
|
2022-09-15 07:27:21 +02:00
|
|
|
/** \} */
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
2022-09-16 10:13:19 +02:00
|
|
|
/** \name #FieldConstant
|
2022-09-15 07:27:21 +02:00
|
|
|
* \{ */
|
2022-01-02 14:27:16 +01:00
|
|
|
|
|
|
|
FieldConstant::FieldConstant(const CPPType &type, const void *value)
|
|
|
|
: FieldNode(FieldNodeType::Constant), type_(type)
|
|
|
|
{
|
|
|
|
value_ = MEM_mallocN_aligned(type.size(), type.alignment(), __func__);
|
|
|
|
type.copy_construct(value, value_);
|
|
|
|
}
|
|
|
|
|
|
|
|
FieldConstant::~FieldConstant()
|
|
|
|
{
|
|
|
|
type_.destruct(value_);
|
|
|
|
MEM_freeN(value_);
|
|
|
|
}
|
|
|
|
|
|
|
|
const CPPType &FieldConstant::output_cpp_type(int output_index) const
|
|
|
|
{
|
|
|
|
BLI_assert(output_index == 0);
|
|
|
|
UNUSED_VARS_NDEBUG(output_index);
|
|
|
|
return type_;
|
|
|
|
}
|
|
|
|
|
|
|
|
const CPPType &FieldConstant::type() const
|
|
|
|
{
|
|
|
|
return type_;
|
|
|
|
}
|
|
|
|
|
2022-01-03 20:52:39 +01:00
|
|
|
GPointer FieldConstant::value() const
|
2022-01-02 14:27:16 +01:00
|
|
|
{
|
|
|
|
return {type_, value_};
|
|
|
|
}
|
|
|
|
|
2022-09-15 07:27:21 +02:00
|
|
|
/** \} */
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
2022-09-16 10:13:19 +02:00
|
|
|
/** \name #FieldEvaluator
|
2022-09-15 07:27:21 +02:00
|
|
|
* \{ */
|
2021-09-09 12:54:20 +02:00
|
|
|
|
2022-03-25 00:47:48 +01:00
|
|
|
static IndexMask index_mask_from_selection(const IndexMask full_mask,
|
2022-06-23 18:50:53 +02:00
|
|
|
const VArray<bool> &selection,
|
2022-03-25 00:47:48 +01:00
|
|
|
ResourceScope &scope)
|
2021-09-09 12:54:20 +02:00
|
|
|
{
|
BLI: refactor IndexMask for better performance and memory usage
Goals of this refactor:
* Reduce memory consumption of `IndexMask`. The old `IndexMask` uses an
`int64_t` for each index which is more than necessary in pretty much all
practical cases currently. Using `int32_t` might still become limiting
in the future in case we use this to index e.g. byte buffers larger than
a few gigabytes. We also don't want to template `IndexMask`, because
that would cause a split in the "ecosystem", or everything would have to
be implemented twice or templated.
* Allow for more multi-threading. The old `IndexMask` contains a single
array. This is generally good but has the problem that it is hard to fill
from multiple-threads when the final size is not known from the beginning.
This is commonly the case when e.g. converting an array of bool to an
index mask. Currently, this kind of code only runs on a single thread.
* Allow for efficient set operations like join, intersect and difference.
It should be possible to multi-thread those operations.
* It should be possible to iterate over an `IndexMask` very efficiently.
The most important part of that is to avoid all memory access when iterating
over continuous ranges. For some core nodes (e.g. math nodes), we generate
optimized code for the cases of irregular index masks and simple index ranges.
To achieve these goals, a few compromises had to made:
* Slicing of the mask (at specific indices) and random element access is
`O(log #indices)` now, but with a low constant factor. It should be possible
to split a mask into n approximately equally sized parts in `O(n)` though,
making the time per split `O(1)`.
* Using range-based for loops does not work well when iterating over a nested
data structure like the new `IndexMask`. Therefor, `foreach_*` functions with
callbacks have to be used. To avoid extra code complexity at the call site,
the `foreach_*` methods support multi-threading out of the box.
The new data structure splits an `IndexMask` into an arbitrary number of ordered
`IndexMaskSegment`. Each segment can contain at most `2^14 = 16384` indices. The
indices within a segment are stored as `int16_t`. Each segment has an additional
`int64_t` offset which allows storing arbitrary `int64_t` indices. This approach
has the main benefits that segments can be processed/constructed individually on
multiple threads without a serial bottleneck. Also it reduces the memory
requirements significantly.
For more details see comments in `BLI_index_mask.hh`.
I did a few tests to verify that the data structure generally improves
performance and does not cause regressions:
* Our field evaluation benchmarks take about as much as before. This is to be
expected because we already made sure that e.g. add node evaluation is
vectorized. The important thing here is to check that changes to the way we
iterate over the indices still allows for auto-vectorization.
* Memory usage by a mask is about 1/4 of what it was before in the average case.
That's mainly caused by the switch from `int64_t` to `int16_t` for indices.
In the worst case, the memory requirements can be larger when there are many
indices that are very far away. However, when they are far away from each other,
that indicates that there aren't many indices in total. In common cases, memory
usage can be way lower than 1/4 of before, because sub-ranges use static memory.
* For some more specific numbers I benchmarked `IndexMask::from_bools` in
`index_mask_from_selection` on 10.000.000 elements at various probabilities for
`true` at every index:
```
Probability Old New
0 4.6 ms 0.8 ms
0.001 5.1 ms 1.3 ms
0.2 8.4 ms 1.8 ms
0.5 15.3 ms 3.0 ms
0.8 20.1 ms 3.0 ms
0.999 25.1 ms 1.7 ms
1 13.5 ms 1.1 ms
```
Pull Request: https://projects.blender.org/blender/blender/pulls/104629
2023-05-24 18:11:41 +02:00
|
|
|
return IndexMask::from_bools(full_mask, selection, scope.construct<IndexMaskMemory>());
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
int FieldEvaluator::add_with_destination(GField field, GVMutableArray dst)
|
2021-09-09 12:54:20 +02:00
|
|
|
{
|
|
|
|
const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
dst_varrays_.append(dst);
|
2021-09-09 12:54:20 +02:00
|
|
|
output_pointer_infos_.append({});
|
|
|
|
return field_index;
|
|
|
|
}
|
|
|
|
|
|
|
|
int FieldEvaluator::add_with_destination(GField field, GMutableSpan dst)
|
|
|
|
{
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
return this->add_with_destination(std::move(field), GVMutableArray::ForSpan(dst));
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
int FieldEvaluator::add(GField field, GVArray *varray_ptr)
|
2021-09-09 12:54:20 +02:00
|
|
|
{
|
|
|
|
const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
|
|
|
|
dst_varrays_.append(nullptr);
|
|
|
|
output_pointer_infos_.append(OutputPointerInfo{
|
2022-10-04 00:37:25 +02:00
|
|
|
varray_ptr, [](void *dst, const GVArray &varray, ResourceScope & /*scope*/) {
|
2022-09-25 17:39:45 +02:00
|
|
|
*static_cast<GVArray *>(dst) = varray;
|
2021-09-09 12:54:20 +02:00
|
|
|
}});
|
|
|
|
return field_index;
|
|
|
|
}
|
|
|
|
|
|
|
|
int FieldEvaluator::add(GField field)
|
|
|
|
{
|
|
|
|
const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
|
|
|
|
dst_varrays_.append(nullptr);
|
|
|
|
output_pointer_infos_.append({});
|
|
|
|
return field_index;
|
|
|
|
}
|
|
|
|
|
2021-12-14 15:40:16 +01:00
|
|
|
static IndexMask evaluate_selection(const Field<bool> &selection_field,
|
|
|
|
const FieldContext &context,
|
|
|
|
IndexMask full_mask,
|
|
|
|
ResourceScope &scope)
|
|
|
|
{
|
|
|
|
if (selection_field) {
|
|
|
|
VArray<bool> selection =
|
|
|
|
evaluate_fields(scope, {selection_field}, full_mask, context)[0].typed<bool>();
|
2022-03-25 00:47:48 +01:00
|
|
|
return index_mask_from_selection(full_mask, selection, scope);
|
2021-12-14 15:40:16 +01:00
|
|
|
}
|
|
|
|
return full_mask;
|
|
|
|
}
|
|
|
|
|
2021-09-09 12:54:20 +02:00
|
|
|
void FieldEvaluator::evaluate()
|
|
|
|
{
|
|
|
|
BLI_assert_msg(!is_evaluated_, "Cannot evaluate fields twice.");
|
2021-12-14 15:40:16 +01:00
|
|
|
|
|
|
|
selection_mask_ = evaluate_selection(selection_field_, context_, mask_, scope_);
|
|
|
|
|
2021-09-09 12:54:20 +02:00
|
|
|
Array<GFieldRef> fields(fields_to_evaluate_.size());
|
|
|
|
for (const int i : fields_to_evaluate_.index_range()) {
|
|
|
|
fields[i] = fields_to_evaluate_[i];
|
|
|
|
}
|
2021-12-14 15:40:16 +01:00
|
|
|
evaluated_varrays_ = evaluate_fields(scope_, fields, selection_mask_, context_, dst_varrays_);
|
2021-09-09 12:54:20 +02:00
|
|
|
BLI_assert(fields_to_evaluate_.size() == evaluated_varrays_.size());
|
|
|
|
for (const int i : fields_to_evaluate_.index_range()) {
|
|
|
|
OutputPointerInfo &info = output_pointer_infos_[i];
|
|
|
|
if (info.dst != nullptr) {
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
info.set(info.dst, evaluated_varrays_[i], scope_);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
is_evaluated_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
IndexMask FieldEvaluator::get_evaluated_as_mask(const int field_index)
|
|
|
|
{
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
VArray<bool> varray = this->get_evaluated(field_index).typed<bool>();
|
2021-09-09 12:54:20 +02:00
|
|
|
|
Geometry Nodes: refactor virtual array system
Goals of this refactor:
* Simplify creating virtual arrays.
* Simplify passing virtual arrays around.
* Simplify converting between typed and generic virtual arrays.
* Reduce memory allocations.
As a quick reminder, a virtual arrays is a data structure that behaves like an
array (i.e. it can be accessed using an index). However, it may not actually
be stored as array internally. The two most important implementations
of virtual arrays are those that correspond to an actual plain array and those
that have the same value for every index. However, many more
implementations exist for various reasons (interfacing with legacy attributes,
unified iterator over all points in multiple splines, ...).
With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and
`GVMutableArray`) can be used like "normal values". They typically live
on the stack. Before, they were usually inside a `std::unique_ptr`. This makes
passing them around much easier. Creation of new virtual arrays is also
much simpler now due to some constructors. Memory allocations are
reduced by making use of small object optimization inside the core types.
Previously, `VArray` was a class with virtual methods that had to be overridden
to change the behavior of a the virtual array. Now,`VArray` has a fixed size
and has no virtual methods. Instead it contains a `VArrayImpl` that is
similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly,
unless a new virtual array implementation is added.
To support the small object optimization for many `VArrayImpl` classes,
a new `blender::Any` type is added. It is similar to `std::any` with two
additional features. It has an adjustable inline buffer size and alignment.
The inline buffer size of `std::any` can't be relied on and is usually too
small for our use case here. Furthermore, `blender::Any` can store
additional user-defined type information without increasing the
stack size.
Differential Revision: https://developer.blender.org/D12986
2021-11-16 10:15:51 +01:00
|
|
|
if (varray.is_single()) {
|
|
|
|
if (varray.get_internal_single()) {
|
|
|
|
return IndexRange(varray.size());
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
return IndexRange(0);
|
|
|
|
}
|
2022-03-25 00:47:48 +01:00
|
|
|
return index_mask_from_selection(mask_, varray, scope_);
|
2021-12-14 15:40:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
IndexMask FieldEvaluator::get_evaluated_selection_as_mask()
|
|
|
|
{
|
|
|
|
BLI_assert(is_evaluated_);
|
|
|
|
return selection_mask_;
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
|
2022-09-15 07:27:21 +02:00
|
|
|
/** \} */
|
|
|
|
|
2021-09-09 12:54:20 +02:00
|
|
|
} // namespace blender::fn
|