WIP: Geometry Nodes: For Each Group / For Elements #112456

Closed
Iliya Katushenock wants to merge 5 commits from mod_moder:tmp_test_feg into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
13 changed files with 700 additions and 3 deletions

View File

@ -209,6 +209,16 @@ class NODE_OT_add_repeat_zone(NodeAddZoneOperator, Operator):
output_node_type = "GeometryNodeRepeatOutput"
class NODE_OT_add_for_each_group_zone(NodeAddZoneOperator, Operator):
"""Add a repeat zone that allows executing nodes a dynamic number of times"""
bl_idname = "node.add_for_each_group_zone"
bl_label = "Add For Each Group Zone"
bl_options = {'REGISTER', 'UNDO'}
input_node_type = "GeometryNodeForEachGroupInput"
output_node_type = "GeometryNodeForEachGroupOutput"
class NODE_OT_collapse_hide_unused_toggle(Operator):
"""Toggle collapsed nodes and hide unused sockets"""
bl_idname = "node.collapse_hide_unused_toggle"
@ -372,6 +382,7 @@ classes = (
NODE_OT_add_node,
NODE_OT_add_simulation_zone,
NODE_OT_add_repeat_zone,
NODE_OT_add_for_each_group_zone,
NODE_OT_collapse_hide_unused_toggle,
NODE_OT_interface_item_new,
NODE_OT_interface_item_duplicate,

View File

@ -75,6 +75,11 @@ def add_repeat_zone(layout, label):
return props
def add_for_each_group_zone(layout, label):
props = layout.operator("node.add_for_each_group_zone", text=label, text_ctxt=i18n_contexts.default)
props.use_transform = True
return props
class NODE_MT_category_layout(Menu):
bl_idname = "NODE_MT_category_layout"
bl_label = "Layout"

View File

@ -169,6 +169,7 @@ class NODE_MT_geometry_node_GEO_GEOMETRY(Menu):
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeGeometryToInstance")
node_add_menu.add_node_type(layout, "GeometryNodeJoinGeometry")
node_add_menu.add_for_each_group_zone(layout, label="For Each Group Zone")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)

View File

@ -1306,6 +1306,8 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_TOOL_SET_FACE_SET 2113
#define GEO_NODE_POINTS_TO_CURVES 2114
#define GEO_NODE_INPUT_EDGE_SMOOTH 2115
#define GEO_NODE_FOR_EACH_GROUP_INPUT 2116
#define GEO_NODE_FOR_EACH_GROUP_OUTPUT 2117
/** \} */

View File

@ -606,6 +606,13 @@ class NodeTreeMainUpdater {
}
}
}
if (node.type == GEO_NODE_FOR_EACH_GROUP_INPUT) {
if (const bNode *output_node = ntree.node_by_id(reinterpret_cast<const int &>(node.custom3))) {
if (output_node->runtime->changed_flag & NTREE_CHANGED_NODE_PROPERTY) {
return true;
}
}
}
return false;
}

View File

@ -36,6 +36,7 @@ static Vector<std::unique_ptr<bNodeTreeZone>> find_zone_nodes(
Vector<const bNode *> zone_output_nodes;
zone_output_nodes.extend(tree.nodes_by_type("GeometryNodeSimulationOutput"));
zone_output_nodes.extend(tree.nodes_by_type("GeometryNodeRepeatOutput"));
zone_output_nodes.extend(tree.nodes_by_type("GeometryNodeForEachGroupOutput"));
for (const bNode *node : zone_output_nodes) {
auto zone = std::make_unique<bNodeTreeZone>();
zone->owner = &owner;
@ -62,6 +63,15 @@ static Vector<std::unique_ptr<bNodeTreeZone>> find_zone_nodes(
}
}
}
for (const bNode *node : tree.nodes_by_type("GeometryNodeForEachGroupInput")) {
printf(">> %d;\n", reinterpret_cast<const int &>(node->custom3));
if (const bNode *feg_output_node = tree.node_by_id(reinterpret_cast<const int &>(node->custom3))) {
if (bNodeTreeZone *zone = r_zone_by_inout_node.lookup_default(feg_output_node, nullptr)) {
zone->input_node = node;
r_zone_by_inout_node.add(node, zone);
}
}
}
return zones;
}
@ -257,13 +267,13 @@ static std::unique_ptr<bNodeTreeZones> discover_tree_zones(const bNodeTree &tree
depend_on_output_flags |= depend_on_output_flag_array[from_node_i];
}
}
if (ELEM(node->type, GEO_NODE_SIMULATION_INPUT, GEO_NODE_REPEAT_INPUT)) {
if (ELEM(node->type, GEO_NODE_SIMULATION_INPUT, GEO_NODE_REPEAT_INPUT, GEO_NODE_FOR_EACH_GROUP_INPUT)) {
if (const bNodeTreeZone *zone = zone_by_inout_node.lookup_default(node, nullptr)) {
/* Now entering a zone, so set the corresponding bit. */
depend_on_input_flags[zone->index].set();
}
}
else if (ELEM(node->type, GEO_NODE_SIMULATION_OUTPUT, GEO_NODE_REPEAT_OUTPUT)) {
else if (ELEM(node->type, GEO_NODE_SIMULATION_OUTPUT, GEO_NODE_REPEAT_OUTPUT, GEO_NODE_FOR_EACH_GROUP_OUTPUT)) {
if (const bNodeTreeZone *zone = zone_by_inout_node.lookup_default(node, nullptr)) {
/* The output is implicitly linked to the input, so also propagate the bits from there. */
if (const bNode *zone_input_node = zone->input_node) {
@ -349,6 +359,17 @@ static std::unique_ptr<bNodeTreeZones> discover_tree_zones(const bNodeTree &tree
tree_zones->zones[zone_i]->child_nodes.append(node);
}
std::cout << std::endl;
for (std::unique_ptr<bNodeTreeZone> &zone : tree_zones->zones) {
if (const bNode *zone_input_node = zone->input_node) {
std::cout << zone.get()->index << ": " << zone_input_node->name << std::endl;
}
if (const bNode *zone_input_node = zone->output_node) {
std::cout << zone.get()->index << ": " << zone_input_node->name << std::endl;
}
}
update_zone_border_links(tree, *tree_zones);
return tree_zones;

View File

@ -3237,6 +3237,36 @@ static PointerRNA rna_NodeGeometrySimulationInput_paired_output_get(PointerRNA *
return r_ptr;
}
static PointerRNA rna_NodeGeometryForEachGroupInput_paired_output_get(PointerRNA *ptr)
{
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode *node = static_cast<bNode *>(ptr->data);
bNode *output_node = ntree->node_by_id(reinterpret_cast<const int &>(node->custom3));
return RNA_pointer_create(&ntree->id, &RNA_Node, output_node);
}
static bool rna_GeometryNodeForEachGroupInput_pair_with_output(ID *id, bNode *node, bContext *C, ReportList *reports, bNode *output_node)
{
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(id);
if (!NOD_geometry_for_each_group_input_pair_with_output(ntree, node, output_node)) {
BKE_reportf(reports,
RPT_ERROR,
"Failed to pair for each group input node %s with output node %s",
node->name,
output_node->name);
std::cout << __func__ << __LINE__ << std::endl;
return false;
}
std::cout << ">> >> >> >> " << reinterpret_cast<const int &>(node->custom3) << std::endl;
BKE_ntree_update_tag_node_property(ntree, node);
ED_node_tree_propagate_change(C, CTX_data_main(C), ntree);
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
return true;
}
static PointerRNA rna_NodeGeometryRepeatInput_paired_output_get(PointerRNA *ptr)
{
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(ptr->owner_id);
@ -8876,6 +8906,49 @@ static void def_geo_simulation_input(StructRNA *srna)
RNA_def_function_return(func, parm);
}
static void def_geo_for_each_group_input(StructRNA *srna)
{
PropertyRNA *prop;
FunctionRNA *func;
PropertyRNA *parm;
static const EnumPropertyItem mode_items[] = {
{GEO_NODE_DELETE_GEOMETRY_MODE_ALL, "ALL", 0, "All", ""},
{GEO_NODE_DELETE_GEOMETRY_MODE_EDGE_FACE, "EDGE_FACE", 0, "Only Edges & Faces", ""},
{GEO_NODE_DELETE_GEOMETRY_MODE_ONLY_FACE, "ONLY_FACE", 0, "Only Faces", ""},
{0, nullptr, 0, nullptr, nullptr},
};
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "custom1");
RNA_def_property_enum_items(prop, mode_items);
RNA_def_property_enum_default(prop, GEO_NODE_DELETE_GEOMETRY_MODE_ALL);
RNA_def_property_ui_text(prop, "Mode", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "custom2");
RNA_def_property_enum_items(prop, rna_enum_attribute_domain_without_corner_items);
RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT);
RNA_def_property_ui_text(prop, "Domain", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "paired_output", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "Node");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_pointer_funcs(prop, "rna_NodeGeometryForEachGroupInput_paired_output_get", nullptr, nullptr, nullptr);
RNA_def_property_ui_text(prop, "Paired Output", "Repeat output node that this input node is paired with");
func = RNA_def_function(srna, "pair_with_output", "rna_GeometryNodeForEachGroupInput_pair_with_output");
RNA_def_function_ui_description(func, "Pair a repeat input node with an output node.");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_REPORTS | FUNC_USE_CONTEXT);
parm = RNA_def_pointer(func, "output_node", "GeometryNode", "Output Node", "Repeat output node to pair with");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
/* return value */
parm = RNA_def_boolean(func, "result", false, "Result", "True if pairing the node was successful");
RNA_def_function_return(func, parm);
}
static void def_geo_repeat_input(StructRNA *srna)
{
PropertyRNA *prop;

View File

@ -28,7 +28,9 @@ bool NOD_geometry_simulation_input_pair_with_output(const bNodeTree *node_tree,
bool NOD_geometry_repeat_input_pair_with_output(const bNodeTree *node_tree,
bNode *repeat_input_node,
const bNode *repeat_output_node);
bool NOD_geometry_for_each_group_input_pair_with_output(const bNodeTree *node_tree,
bNode *repeat_input_node,
const bNode *repeat_output_node);
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -331,6 +331,8 @@ DefNode(GeometryNode, GEO_NODE_EXTRUDE_MESH, 0, "EXTRUDE_MESH", ExtrudeMesh, "Ex
DefNode(GeometryNode, GEO_NODE_FILL_CURVE, 0, "FILL_CURVE", FillCurve, "Fill Curve", "Generate a mesh on the XY plane with faces on the inside of input curves")
DefNode(GeometryNode, GEO_NODE_FILLET_CURVE, 0, "FILLET_CURVE", FilletCurve, "Fillet Curve", "Round corners by generating circular arcs on each control point")
DefNode(GeometryNode, GEO_NODE_FLIP_FACES, 0, "FLIP_FACES", FlipFaces, "Flip Faces", "Reverse the order of the vertices and edges of selected faces, flipping their normal direction")
DefNode(GeometryNode, GEO_NODE_FOR_EACH_GROUP_INPUT, def_geo_for_each_group_input, "FOR_EACH_GROUP_INPUT", ForEachGroupInput, "For Each Group Input", "")
DefNode(GeometryNode, GEO_NODE_FOR_EACH_GROUP_OUTPUT, 0, "FOR_EACH_GROUP_OUTPUT", ForEachGroupOutput, "For Each Group Output", "")
DefNode(GeometryNode, GEO_NODE_GEOMETRY_TO_INSTANCE, 0, "GEOMETRY_TO_INSTANCE", GeometryToInstance, "Geometry to Instance", "Convert each input geometry into an instance, which can be much faster than the Join Geometry node when the inputs are large")
DefNode(GeometryNode, GEO_NODE_IMAGE_INFO, 0, "IMAGE_INFO", ImageInfo, "Image Info", "Retrieve information about an image")
DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "Sample values from an image texture")

View File

@ -76,6 +76,8 @@ set(SRC
nodes/node_geo_evaluate_on_domain.cc
nodes/node_geo_extrude_mesh.cc
nodes/node_geo_flip_faces.cc
nodes/node_geo_for_each_group_input.cc
nodes/node_geo_for_each_group_output.cc
nodes/node_geo_geometry_to_instance.cc
nodes/node_geo_image.cc
nodes/node_geo_image_info.cc

View File

@ -0,0 +1,85 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_compute_contexts.hh"
#include "BKE_scene.h"
#include "DEG_depsgraph_query.h"
#include "RNA_enum_types.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "NOD_geometry.hh"
#include "NOD_socket.hh"
#include "NOD_rna_define.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_for_each_group_input_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
b.add_input<decl::Int>("Group ID").hide_value().field_on_all();
b.add_output<decl::Geometry>("Group Part").propagate_all();
b.add_output<decl::Int>("Group Value");
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
const bNode *node = static_cast<bNode *>(ptr->data);
printf(">>++ %d;\n", reinterpret_cast<const int &>(node->custom3));
uiItemR(layout, ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
if (ELEM(eAttrDomain(node->custom2), ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE, ATTR_DOMAIN_FACE)) {
uiItemR(layout, ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
}
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = GEO_NODE_DELETE_GEOMETRY_MODE_ALL;
node->custom2 = ATTR_DOMAIN_POINT;
reinterpret_cast<int &>(node->custom3) = 0;
}
static void node_register()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_FOR_EACH_GROUP_INPUT, "For Each Group Input", NODE_CLASS_INTERFACE);
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.gather_link_search_ops = nullptr;
nodeRegisterType(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::
bool NOD_geometry_for_each_group_input_pair_with_output(const bNodeTree *node_tree,
bNode *feg_input_node,
const bNode *feg_output_node)
{
namespace file_ns = blender::nodes::node_geo_for_each_group_input_cc;
BLI_assert(feg_input_node->type == GEO_NODE_FOR_EACH_GROUP_INPUT);
if (feg_output_node->type != GEO_NODE_FOR_EACH_GROUP_OUTPUT) {
return false;
}
/* Allow only one input paired to an output. */
for (const bNode *other_input_node : node_tree->nodes_by_type("GeometryNodeForEachGroupInput")) {
if (other_input_node != feg_input_node) {
if (reinterpret_cast<const int &>(other_input_node->custom3) == feg_output_node->identifier) {
return false;
}
}
}
reinterpret_cast<int &>(feg_input_node->custom3) = feg_output_node->identifier;
return true;
}

View File

@ -0,0 +1,130 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_compute_contexts.hh"
#include "BKE_scene.h"
#include "DEG_depsgraph_query.h"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "NOD_geometry.hh"
#include "NOD_socket.hh"
#include "BLI_string_utils.h"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_for_each_group_output_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Group Part");
b.add_output<decl::Geometry>("Geometry").propagate_all();
}
static void node_register()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_FOR_EACH_GROUP_OUTPUT, "For Each Group Output", NODE_CLASS_INTERFACE);
ntype.declare = node_declare;
nodeRegisterType(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::
/*
blender::Span<NodeRepeatItem> NodeGeometryForEachGroupOutput::items_span() const
{
return blender::Span<NodeRepeatItem>(items, items_num);
}
blender::MutableSpan<NodeRepeatItem> NodeGeometryRepeatOutput::items_span()
{
return blender::MutableSpan<NodeRepeatItem>(items, items_num);
}
bool NodeRepeatItem::supports_type(const eNodeSocketDatatype type)
{
return ELEM(type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_INT,
SOCK_STRING,
SOCK_GEOMETRY,
SOCK_OBJECT,
SOCK_MATERIAL,
SOCK_IMAGE,
SOCK_COLLECTION);
}
std::string NodeRepeatItem::identifier_str() const
{
return "Item_" + std::to_string(this->identifier);
}
NodeRepeatItem *NodeGeometryRepeatOutput::add_item(const char *name,
const eNodeSocketDatatype type)
{
if (!NodeRepeatItem::supports_type(type)) {
return nullptr;
}
const int insert_index = this->items_num;
NodeRepeatItem *old_items = this->items;
this->items = MEM_cnew_array<NodeRepeatItem>(this->items_num + 1, __func__);
std::copy_n(old_items, insert_index, this->items);
NodeRepeatItem &new_item = this->items[insert_index];
std::copy_n(old_items + insert_index + 1,
this->items_num - insert_index,
this->items + insert_index + 1);
new_item.identifier = this->next_identifier++;
this->set_item_name(new_item, name);
new_item.socket_type = type;
this->items_num++;
MEM_SAFE_FREE(old_items);
return &new_item;
}
void NodeGeometryRepeatOutput::set_item_name(NodeRepeatItem &item, const char *name)
{
char unique_name[MAX_NAME + 4];
STRNCPY(unique_name, name);
struct Args {
NodeGeometryRepeatOutput *storage;
const NodeRepeatItem *item;
} args = {this, &item};
const char *default_name = nodeStaticSocketLabel(item.socket_type, 0);
BLI_uniquename_cb(
[](void *arg, const char *name) {
const Args &args = *static_cast<Args *>(arg);
for (const NodeRepeatItem &item : args.storage->items_span()) {
if (&item != args.item) {
if (STREQ(item.name, name)) {
return true;
}
}
}
return false;
},
&args,
default_name,
'.',
unique_name,
ARRAY_SIZE(unique_name));
MEM_SAFE_FREE(item.name);
item.name = BLI_strdup(unique_name);
}
*/

View File

@ -1765,6 +1765,238 @@ class LazyFunctionForRepeatZone : public LazyFunction {
}
};
class LazyFunctionForFEGZone : public LazyFunction {
private:
const bNodeTreeZone &zone_;
const bNode &repeat_output_bnode_;
const ZoneBuildInfo &zone_info_;
const LazyFunction &body_fn_;
const RepeatBodyIndices &body_indices_;
public:
LazyFunctionForFEGZone(const bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const LazyFunction &body_fn,
const RepeatBodyIndices &body_indices)
: zone_(zone),
repeat_output_bnode_(*zone.output_node),
zone_info_(zone_info),
body_fn_(body_fn),
body_indices_(body_indices)
{
debug_name_ = "For Each Group Zone";
for (const bNodeSocket *socket : zone.input_node->input_sockets()) {
inputs_.append_as(socket->name, *socket->typeinfo->geometry_nodes_cpp_type);
}
zone_info.main_input_indices = inputs_.index_range();
for (const bNodeLink *link : zone.border_links) {
inputs_.append_as(link->fromsock->name, *link->tosock->typeinfo->geometry_nodes_cpp_type);
}
zone_info.border_link_input_indices = inputs_.index_range().take_back(zone.border_links.size());
for (const bNodeSocket *socket : zone.output_node->output_sockets()) {
inputs_.append_as("Usage", CPPType::get<bool>());
outputs_.append_as(socket->name, *socket->typeinfo->geometry_nodes_cpp_type);
}
zone_info.main_output_usage_indices = inputs_.index_range().take_back(zone.output_node->output_sockets().size());
zone_info.main_output_indices = outputs_.index_range();
for ([[maybe_unused]] const bNodeSocket *socket : zone.input_node->input_sockets()) {
outputs_.append_as("Usage", CPPType::get<bool>());
}
zone_info.main_input_usage_indices = outputs_.index_range().take_back(zone.input_node->input_sockets().size());
for ([[maybe_unused]] const bNodeLink *link : zone.border_links) {
outputs_.append_as("Border Link Usage", CPPType::get<bool>());
}
zone_info.border_link_input_usage_indices = outputs_.index_range().take_back(zone.border_links.size());
for (const auto item : body_indices.attribute_set_input_by_field_source_index.items()) {
const int index = inputs_.append_and_get_index_as("Attribute Set", CPPType::get<bke::AnonymousAttributeSet>());
zone_info.attribute_set_input_by_field_source_index.add_new(item.key, index);
}
for (const auto item : body_indices.attribute_set_input_by_caller_propagation_index.items()) {
const int index = inputs_.append_and_get_index_as("Attribute Set", CPPType::get<bke::AnonymousAttributeSet>());
zone_info.attribute_set_input_by_caller_propagation_index.add_new(item.key, index);
}
}
void execute_impl(lf::Params &/*params*/, const lf::Context &/*context*/) const override
{
#if (0)
GeoNodesLFUserData &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
const NodeGeometryRepeatOutput &node_storage = *static_cast<const NodeGeometryRepeatOutput *>(repeat_output_bnode_.storage);
/* Number of iterations to evaluate. */
const GeometrySet geometry = params.get_input<GeometrySet>(zone_info_.main_input_indices[0]);
const Field<int> group_id = params.get_input<ValueOrField<int>>(zone_info_.main_input_indices[1]).as_field();
LinearAllocator<> allocator;
/* Load border link values. */
const int border_links_num = zone_info_.border_link_input_indices.size();
Array<GMutablePointer> border_link_inputs(border_links_num);
for (const int i : IndexRange(border_links_num)) {
const int input_index = zone_info_.border_link_input_indices[i];
const CPPType &type = *body_fn_.inputs()[input_index].type;
void *value = params.try_get_input_data_ptr(input_index);
BLI_assert(value != nullptr);
border_link_inputs[i] = {type, value};
}
/* Load attribute sets that are needed to propagate attributes correctly in the zone. */
Map<int, bke::AnonymousAttributeSet *> attribute_set_by_field_source_index;
Map<int, bke::AnonymousAttributeSet *> attribute_set_by_caller_propagation_index;
for (const auto item : zone_info_.attribute_set_input_by_field_source_index.items()) {
bke::AnonymousAttributeSet &attribute_set = params.get_input<bke::AnonymousAttributeSet>(item.value);
attribute_set_by_field_source_index.add_new(item.key, &attribute_set);
}
for (const auto item : zone_info_.attribute_set_input_by_caller_propagation_index.items()) {
bke::AnonymousAttributeSet &attribute_set = params.get_input<bke::AnonymousAttributeSet>(item.value);
attribute_set_by_caller_propagation_index.add_new(item.key, &attribute_set);
}
const int body_inputs_num = body_fn_.inputs().size();
const int body_outputs_num = body_fn_.outputs().size();
/* Evaluate the repeat zone eagerly, one iteration at a time.
* This can be made more lazy as a separate step. */
for (const int iteration : IndexRange(10)) {
/* Prepare all data that has to be passed into the evaluation of the repeat zone body. */
Array<GMutablePointer> inputs(body_inputs_num);
Array<GMutablePointer> outputs(body_outputs_num);
Array<std::optional<lf::ValueUsage>> input_usages(body_inputs_num);
Array<lf::ValueUsage> output_usages(body_outputs_num, lf::ValueUsage::Used);
Array<bool> set_outputs(body_outputs_num, false);
/* Prepare pointers to the main input and output values of the repeat zone,
* as well as their usages. */
Array<bool> tmp_main_input_usages(10);
for (const int i : IndexRange(10)) {
const CPPType &type = *repeat_item_types[i];
void *prev_value = repeat_item_values[iteration * 10 + i];
void *next_value = repeat_item_values[(iteration + 1) * 10 + i];
inputs[body_indices_.main_inputs[i]] = {type, prev_value};
outputs[body_indices_.main_outputs[i]] = {type, next_value};
outputs[body_indices_.main_input_usages[i]] = &tmp_main_input_usages[i];
}
static bool static_true = true;
for (const int input_index : body_indices_.main_output_usages) {
/* All main outputs are used currently. */
inputs[input_index] = &static_true;
}
/* Prepare border link values for the repeat body. */
Array<bool> tmp_border_link_usages(border_links_num);
for (const int i : IndexRange(border_links_num)) {
const int input_index = body_indices_.border_link_inputs[i];
const int usage_index = body_indices_.border_link_usages[i];
const CPPType &type = *body_fn_.inputs()[input_index].type;
/* Need to copy because a lazy function is allowed to modify the input (e.g. move from
* it). */
void *value_copy = allocator.allocate(type.size(), type.alignment());
type.copy_construct(border_link_input_values[i], value_copy);
inputs[input_index] = {type, value_copy};
outputs[usage_index] = &tmp_border_link_usages[i];
}
/* Prepare attribute sets that are passed into the repeat body. */
for (const auto item : body_indices_.attribute_set_input_by_field_source_index.items()) {
bke::AnonymousAttributeSet &attribute_set =
*allocator
.construct<bke::AnonymousAttributeSet>(
*attribute_set_by_field_source_index.lookup(item.key))
.release();
inputs[item.value] = &attribute_set;
}
for (const auto item : body_indices_.attribute_set_input_by_caller_propagation_index.items())
{
bke::AnonymousAttributeSet &attribute_set =
*allocator
.construct<bke::AnonymousAttributeSet>(
*attribute_set_by_caller_propagation_index.lookup(item.key))
.release();
inputs[item.value] = &attribute_set;
}
/* Prepare evaluation context for the repeat body. */
bke::RepeatZoneComputeContext body_compute_context{
user_data.compute_context, repeat_output_bnode_, iteration};
GeoNodesLFUserData body_user_data = user_data;
body_user_data.compute_context = &body_compute_context;
if (user_data.modifier_data && user_data.modifier_data->socket_log_contexts) {
body_user_data.log_socket_values = user_data.modifier_data->socket_log_contexts->contains(
body_compute_context.hash());
}
GeoNodesLFLocalUserData body_local_user_data{body_user_data};
void *body_storage = body_fn_.init_storage(allocator);
lf::Context body_context{body_storage, &body_user_data, &body_local_user_data};
lf::BasicParams body_params{
body_fn_, inputs, outputs, input_usages, output_usages, set_outputs};
/* Actually evaluate the repeat body. */
body_fn_.execute(body_params, body_context);
/* Destruct values that are not needed after the evaluation anymore. */
body_fn_.destruct_storage(body_storage);
for (const int i : body_indices_.border_link_inputs) {
inputs[i].destruct();
}
for (const int i : body_indices_.attribute_set_input_by_field_source_index.values()) {
inputs[i].destruct();
}
for (const int i : body_indices_.attribute_set_input_by_caller_propagation_index.values()) {
inputs[i].destruct();
}
}
/* Set outputs of the repeat zone. */
for (const int i : IndexRange(repeat_items_num)) {
void *computed_value = repeat_item_values[iterations * repeat_items_num + i];
const int output_index = zone_info_.main_output_indices[i];
void *r_value = params.get_output_data_ptr(output_index);
const CPPType &type = *repeat_item_types[i];
type.move_construct(computed_value, r_value);
params.output_set(output_index);
}
for (const int i : zone_info_.main_input_usage_indices) {
params.set_output(i, true);
}
for (const int i : IndexRange(border_links_num)) {
params.set_output(zone_info_.border_link_input_usage_indices[i], true);
}
/* Destruct remaining values. */
for (const int iteration : IndexRange(iterations)) {
MutableSpan<void *> item_values = repeat_item_values.as_mutable_span().slice(
(iteration + 1) * repeat_items_num, repeat_items_num);
for (const int item_i : IndexRange(repeat_items_num)) {
const CPPType &type = *repeat_item_types[item_i];
type.destruct(item_values[item_i]);
}
}
#endif
}
std::string input_name(const int i) const override
{
if (zone_info_.main_output_usage_indices.contains(i)) {
const bNodeSocket &bsocket = zone_.output_node->output_socket(i - zone_info_.main_output_usage_indices.first());
return "Usage: " + StringRef(bsocket.name);
}
return inputs_[i].debug_name;
}
std::string output_name(const int i) const override
{
if (zone_info_.main_input_usage_indices.contains(i)) {
const bNodeSocket &bsocket = zone_.input_node->input_socket(i - zone_info_.main_input_usage_indices.first());
return "Usage: " + StringRef(bsocket.name);
}
return outputs_[i].debug_name;
}
};
/**
* Utility class to build a lazy-function graph based on a geometry nodes tree.
* This is mainly a separate class because it makes it easier to have variables that can be
@ -1853,6 +2085,10 @@ struct GeometryNodesLazyFunctionGraphBuilder {
this->build_repeat_zone_function(zone);
break;
}
case GEO_NODE_FOR_EACH_GROUP_OUTPUT: {
this->build_for_each_group_zone_function(zone);
break;
}
default: {
BLI_assert_unreachable();
break;
@ -2158,6 +2394,126 @@ struct GeometryNodesLazyFunctionGraphBuilder {
zone_info.lazy_function = &fn;
}
void build_for_each_group_zone_function(const bNodeTreeZone &zone)
{
ZoneBuildInfo &zone_info = zone_build_infos_[zone.index];
lf::Graph &lf_body_graph = scope_.construct<lf::Graph>();
BuildGraphParams graph_params{lf_body_graph};
Vector<const lf::OutputSocket *, 16> lf_body_inputs;
Vector<const lf::InputSocket *, 16> lf_body_outputs;
RepeatBodyIndices &body_indices = scope_.construct<RepeatBodyIndices>();
lf::DummyNode &lf_main_input_node = this->build_dummy_node_for_sockets(
"Repeat Input", {}, zone.input_node->output_sockets(), lf_body_graph);
for (const int i : zone.input_node->output_sockets().index_range()) {
const bNodeSocket &bsocket = zone.input_node->output_socket(i);
lf::OutputSocket &lf_socket = lf_main_input_node.output(i);
graph_params.lf_output_by_bsocket.add_new(&bsocket, &lf_socket);
}
lf_body_inputs.extend(lf_main_input_node.outputs());
body_indices.main_inputs = lf_body_inputs.index_range();
lf::DummyNode &lf_main_output_node = this->build_dummy_node_for_sockets(
"Repeat Output", zone.output_node->input_sockets(), {}, lf_body_graph);
lf_body_outputs.extend(lf_main_output_node.inputs());
body_indices.main_outputs = lf_body_outputs.index_range();
lf::Node &lf_main_input_usage_node = this->build_dummy_node_for_socket_usages(
"Input Usages", zone.input_node->output_sockets(), {}, lf_body_graph);
lf_body_outputs.extend(lf_main_input_usage_node.inputs());
body_indices.main_input_usages = lf_body_outputs.index_range().take_back(
lf_main_input_usage_node.inputs().size());
lf::Node &lf_main_output_usage_node = this->build_dummy_node_for_socket_usages(
"Output Usages", {}, zone.output_node->input_sockets(), lf_body_graph);
lf_body_inputs.extend(lf_main_output_usage_node.outputs());
body_indices.main_output_usages = lf_body_inputs.index_range().take_back(
lf_main_output_usage_node.outputs().size());
for (const int i : zone.output_node->input_sockets().index_range()) {
const bNodeSocket &bsocket = zone.output_node->input_socket(i);
lf::InputSocket &lf_socket = lf_main_output_node.input(i);
lf::OutputSocket &lf_usage = lf_main_output_usage_node.output(i);
graph_params.lf_inputs_by_bsocket.add(&bsocket, &lf_socket);
graph_params.usage_by_bsocket.add(&bsocket, &lf_usage);
}
lf::Node &lf_border_link_input_node = this->build_zone_border_links_input_node(zone, lf_body_graph);
lf_body_inputs.extend(lf_border_link_input_node.outputs());
body_indices.border_link_inputs = lf_body_inputs.index_range().take_back(lf_border_link_input_node.outputs().size());
lf::Node &lf_border_link_usage_node = this->build_border_link_input_usage_node(zone, lf_body_graph);
lf_body_outputs.extend(lf_border_link_usage_node.inputs());
body_indices.border_link_usages = lf_body_outputs.index_range().take_back(lf_border_link_usage_node.inputs().size());
this->insert_nodes_and_zones(zone.child_nodes, zone.child_zones, graph_params);
this->build_output_socket_usages(*zone.input_node, graph_params);
for (const int i : zone.input_node->output_sockets().index_range()) {
const bNodeSocket &bsocket = zone.input_node->output_socket(i);
lf::OutputSocket *lf_usage = graph_params.usage_by_bsocket.lookup_default(&bsocket, nullptr);
lf::InputSocket &lf_usage_output = lf_main_input_usage_node.input(i);
if (lf_usage) {
lf_body_graph.add_link(*lf_usage, lf_usage_output);
}
else {
static const bool static_false = false;
lf_usage_output.set_default_value(&static_false);
}
}
for (const auto item : graph_params.lf_output_by_bsocket.items()) {
this->insert_links_from_socket(*item.key, *item.value, graph_params);
}
this->link_border_link_inputs_and_usages(
zone, lf_border_link_input_node, lf_border_link_usage_node, graph_params);
this->add_default_inputs(graph_params);
Map<int, lf::OutputSocket *> lf_attribute_set_by_field_source_index;
Map<int, lf::OutputSocket *> lf_attribute_set_by_caller_propagation_index;
this->build_attribute_set_inputs_for_zone(graph_params,
lf_attribute_set_by_field_source_index,
lf_attribute_set_by_caller_propagation_index);
for (const auto item : lf_attribute_set_by_field_source_index.items()) {
lf::OutputSocket &lf_attribute_set_socket = *item.value;
if (lf_attribute_set_socket.node().is_dummy()) {
const int body_input_index = lf_body_inputs.append_and_get_index(&lf_attribute_set_socket);
body_indices.attribute_set_input_by_field_source_index.add_new(item.key, body_input_index);
}
}
for (const auto item : lf_attribute_set_by_caller_propagation_index.items()) {
lf::OutputSocket &lf_attribute_set_socket = *item.value;
if (lf_attribute_set_socket.node().is_dummy()) {
const int body_input_index = lf_body_inputs.append_and_get_index(&lf_attribute_set_socket);
body_indices.attribute_set_input_by_caller_propagation_index.add_new(item.key,
body_input_index);
}
}
this->link_attribute_set_inputs(lf_body_graph,
graph_params,
lf_attribute_set_by_field_source_index,
lf_attribute_set_by_caller_propagation_index);
this->fix_link_cycles(lf_body_graph, graph_params.socket_usage_inputs);
lf_body_graph.update_node_indices();
auto &logger = scope_.construct<GeometryNodesLazyFunctionLogger>(*lf_graph_info_);
auto &side_effect_provider = scope_.construct<GeometryNodesLazyFunctionSideEffectProvider>();
LazyFunction &body_graph_fn = scope_.construct<lf::GraphExecutor>(
lf_body_graph, lf_body_inputs, lf_body_outputs, &logger, &side_effect_provider);
// std::cout << "\n\n" << lf_body_graph.to_dot() << "\n\n";
auto &fn = scope_.construct<LazyFunctionForFEGZone>(
zone, zone_info, body_graph_fn, body_indices);
zone_info.lazy_function = &fn;
}
lf::DummyNode &build_zone_border_links_input_node(const bNodeTreeZone &zone, lf::Graph &lf_graph)
{
auto &debug_info = scope_.construct<lf::SimpleDummyDebugInfo>();