Refactor: Geometry Nodes: extract value tracing to separate file

This moves code used for tracing bundles and closures to a separate file. This
code is used to e.g. detect which Separate Bundle node a Combine Bundle node is
linked to. This allows providing automatic socket update operators for these
nodes. Similarly for closures.

Pull Request: https://projects.blender.org/blender/blender/pulls/143734
This commit is contained in:
Jacques Lucke
2025-07-31 20:35:09 +02:00
parent b7194ad693
commit 4c3724180b
6 changed files with 587 additions and 546 deletions

View File

@@ -121,34 +121,6 @@ bool node_editor_is_for_geometry_nodes_modifier(const SpaceNode &snode,
[[nodiscard]] const ComputeContext *compute_context_for_edittree_node(
const SpaceNode &snode, bke::ComputeContextCache &compute_context_cache, const bNode &node);
/**
* Attempts to find a compute context that the closure is evaluated in. If none is found, null is
* returned. If multiple are found, it currently picks the first one it finds which is somewhat
* arbitrary.
*/
[[nodiscard]] const ComputeContext *compute_context_for_closure_evaluation(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache,
const std::optional<nodes::ClosureSourceLocation> &source_location);
Vector<nodes::BundleSignature> gather_linked_target_bundle_signatures(
const ComputeContext *bundle_socket_context,
const bNodeSocket &bundle_socket,
bke::ComputeContextCache &compute_context_cache);
Vector<nodes::BundleSignature> gather_linked_origin_bundle_signatures(
const ComputeContext *bundle_socket_context,
const bNodeSocket &bundle_socket,
bke::ComputeContextCache &compute_context_cache);
Vector<nodes::ClosureSignature> gather_linked_target_closure_signatures(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache);
Vector<nodes::ClosureSignature> gather_linked_origin_closure_signatures(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache);
/**
* Creates a compute context for the given zone. It takes e.g. the current inspection index into
* account.
@@ -157,6 +129,10 @@ Vector<nodes::ClosureSignature> gather_linked_origin_closure_signatures(
const bke::bNodeTreeZone &zone,
bke::ComputeContextCache &compute_context_cache,
const ComputeContext *parent_compute_context);
[[nodiscard]] const ComputeContext *compute_context_for_zones(
const Span<const bke::bNodeTreeZone *> zones,
bke::ComputeContextCache &compute_context_cache,
const ComputeContext *parent_compute_context);
void ui_template_node_asset_menu_items(uiLayout &layout,
const bContext &C,

View File

@@ -64,7 +64,7 @@
#include "WM_api.hh"
#include "WM_types.hh"
#include "NOD_node_in_compute_context.hh"
#include "NOD_trace_values.hh"
#include "io_utils.hh"
@@ -421,10 +421,9 @@ const ComputeContext *compute_context_for_zone(const bke::bNodeTreeZone &zone,
return nullptr;
}
static const ComputeContext *compute_context_for_zones(
const Span<const bke::bNodeTreeZone *> zones,
bke::ComputeContextCache &compute_context_cache,
const ComputeContext *parent_compute_context)
const ComputeContext *compute_context_for_zones(const Span<const bke::bNodeTreeZone *> zones,
bke::ComputeContextCache &compute_context_cache,
const ComputeContext *parent_compute_context)
{
const ComputeContext *current = parent_compute_context;
for (const bke::bNodeTreeZone *zone : zones) {
@@ -473,511 +472,6 @@ static std::optional<const ComputeContext *> compute_context_for_tree_path(
return current;
}
static bool is_evaluate_closure_node_input(const nodes::SocketInContext &socket)
{
return socket->is_input() && socket->index() == 0 &&
socket.owner_node()->is_type("GeometryNodeEvaluateClosure");
}
static bool is_closure_zone_output_socket(const nodes::SocketInContext &socket)
{
return socket->owner_node().is_type("GeometryNodeClosureOutput") && socket->is_output();
}
static Vector<nodes::SocketInContext> find_origin_sockets_through_contexts(
nodes::SocketInContext start_socket,
bke::ComputeContextCache &compute_context_cache,
FunctionRef<bool(const nodes::SocketInContext &)> handle_possible_origin_socket_fn,
bool find_all);
static Vector<nodes::SocketInContext> find_target_sockets_through_contexts(
const nodes::SocketInContext start_socket,
bke::ComputeContextCache &compute_context_cache,
const FunctionRef<bool(const nodes::SocketInContext &)> handle_possible_target_socket_fn,
const bool find_all)
{
using BundlePath = Vector<std::string, 0>;
struct SocketToCheck {
nodes::SocketInContext socket;
BundlePath bundle_path;
};
Stack<SocketToCheck> sockets_to_check;
Set<nodes::SocketInContext> added_sockets;
auto add_if_new = [&](const nodes::SocketInContext &socket, BundlePath bundle_path) {
if (added_sockets.add(socket)) {
sockets_to_check.push({socket, std::move(bundle_path)});
}
};
add_if_new(start_socket, {});
VectorSet<nodes::SocketInContext> found_targets;
while (!sockets_to_check.is_empty()) {
const SocketToCheck socket_to_check = sockets_to_check.pop();
const nodes::SocketInContext socket = socket_to_check.socket;
const BundlePath &bundle_path = socket_to_check.bundle_path;
const nodes::NodeInContext &node = socket.owner_node();
if (socket->is_input()) {
if (node->is_muted()) {
for (const bNodeLink &link : node->internal_links()) {
if (link.fromsock == socket.socket) {
add_if_new({socket.context, link.tosock}, bundle_path);
}
}
continue;
}
if (bundle_path.is_empty() && handle_possible_target_socket_fn(socket)) {
found_targets.add(socket);
if (!find_all) {
break;
}
continue;
}
if (node->is_reroute()) {
add_if_new(node.output_socket(0), bundle_path);
continue;
}
if (node->is_group()) {
if (const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node->id)) {
group->ensure_topology_cache();
const ComputeContext &group_compute_context = compute_context_cache.for_group_node(
socket.context, node->identifier, &node->owner_tree());
for (const bNode *input_node : group->group_input_nodes()) {
const bNodeSocket &group_input_socket = input_node->output_socket(socket->index());
if (group_input_socket.is_directly_linked()) {
add_if_new({&group_compute_context, &group_input_socket}, bundle_path);
}
}
}
continue;
}
if (node->is_group_output()) {
if (const auto *group_context = dynamic_cast<const bke::GroupNodeComputeContext *>(
socket.context))
{
const bNodeTree *caller_group = group_context->tree();
const bNode *caller_group_node = group_context->node();
if (caller_group && caller_group_node) {
caller_group->ensure_topology_cache();
const bNodeSocket &output_socket = caller_group_node->output_socket(socket->index());
add_if_new({group_context->parent(), &output_socket}, bundle_path);
}
}
continue;
}
if (node->is_type("GeometryNodeCombineBundle")) {
const auto &storage = *static_cast<const NodeGeometryCombineBundle *>(node->storage);
BundlePath new_bundle_path = bundle_path;
new_bundle_path.append(storage.items[socket->index()].name);
add_if_new(node.output_socket(0), std::move(new_bundle_path));
continue;
}
if (node->is_type("GeometryNodeSeparateBundle")) {
if (bundle_path.is_empty()) {
continue;
}
const StringRef last_key = bundle_path.last();
const auto &storage = *static_cast<const NodeGeometrySeparateBundle *>(node->storage);
for (const int output_i : IndexRange(storage.items_num)) {
if (last_key == storage.items[output_i].name) {
add_if_new(node.output_socket(output_i), bundle_path.as_span().drop_back(1));
}
}
continue;
}
if (node->is_type("GeometryNodeClosureOutput")) {
const auto &closure_storage = *static_cast<const NodeGeometryClosureOutput *>(
node->storage);
const StringRef key = closure_storage.output_items.items[socket->index()].name;
const Vector<nodes::SocketInContext> target_sockets = find_target_sockets_through_contexts(
node.output_socket(0), compute_context_cache, is_evaluate_closure_node_input, true);
for (const auto &target_socket : target_sockets) {
const nodes::NodeInContext evaluate_node = target_socket.owner_node();
const auto &evaluate_storage = *static_cast<const NodeGeometryEvaluateClosure *>(
evaluate_node->storage);
for (const int i : IndexRange(evaluate_storage.output_items.items_num)) {
const NodeGeometryEvaluateClosureOutputItem &item =
evaluate_storage.output_items.items[i];
if (key == item.name) {
add_if_new(evaluate_node.output_socket(i), bundle_path);
}
}
}
continue;
}
if (node->is_type("GeometryNodeEvaluateClosure")) {
if (socket->index() == 0) {
continue;
}
const auto &evaluate_storage = *static_cast<const NodeGeometryEvaluateClosure *>(
node->storage);
const StringRef key = evaluate_storage.input_items.items[socket->index() - 1].name;
const Vector<nodes::SocketInContext> origin_sockets = find_origin_sockets_through_contexts(
node.input_socket(0), compute_context_cache, is_closure_zone_output_socket, true);
for (const nodes::SocketInContext origin_socket : origin_sockets) {
const bNodeTree &closure_tree = origin_socket->owner_tree();
const bke::bNodeTreeZones *closure_tree_zones = closure_tree.zones();
if (!closure_tree_zones) {
continue;
}
const auto &closure_output_node = origin_socket.owner_node();
const bke::bNodeTreeZone *closure_zone = closure_tree_zones->get_zone_by_node(
closure_output_node->identifier);
if (!closure_zone) {
continue;
}
const bNode *closure_input_node = closure_zone->input_node();
if (!closure_input_node) {
continue;
}
const ComputeContext &closure_context = compute_context_cache.for_evaluate_closure(
node.context,
node->identifier,
&node->owner_tree(),
nodes::ClosureSourceLocation{
&closure_tree, closure_output_node->identifier, origin_socket.context_hash()});
const auto &closure_output_storage = *static_cast<const NodeGeometryClosureOutput *>(
closure_output_node->storage);
for (const int i : IndexRange(closure_output_storage.input_items.items_num)) {
const NodeGeometryClosureInputItem &item = closure_output_storage.input_items.items[i];
if (key == item.name) {
add_if_new({&closure_context, &closure_input_node->output_socket(i)}, bundle_path);
}
}
}
continue;
}
}
else {
const bke::bNodeTreeZones *zones = node->owner_tree().zones();
if (!zones) {
continue;
}
const bke::bNodeTreeZone *from_zone = zones->get_zone_by_socket(*socket.socket);
for (const bNodeLink *link : socket->directly_linked_links()) {
if (!link->is_used()) {
continue;
}
bNodeSocket *to_socket = link->tosock;
const bke::bNodeTreeZone *to_zone = zones->get_zone_by_socket(*to_socket);
if (!zones->link_between_zones_is_allowed(from_zone, to_zone)) {
continue;
}
const Vector<const bke::bNodeTreeZone *> zones_to_enter = zones->get_zones_to_enter(
from_zone, to_zone);
const ComputeContext *compute_context = compute_context_for_zones(
zones_to_enter, compute_context_cache, socket.context);
if (!compute_context) {
continue;
}
add_if_new({compute_context, to_socket}, bundle_path);
}
}
}
return found_targets.extract_vector();
}
[[nodiscard]] const ComputeContext *compute_context_for_closure_evaluation(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache,
const std::optional<nodes::ClosureSourceLocation> &source_location)
{
const Vector<nodes::SocketInContext> target_sockets = find_target_sockets_through_contexts(
{closure_socket_context, &closure_socket},
compute_context_cache,
is_evaluate_closure_node_input,
false);
if (target_sockets.is_empty()) {
return nullptr;
}
const nodes::SocketInContext target_socket = target_sockets[0];
const nodes::NodeInContext target_node = target_socket.owner_node();
return &compute_context_cache.for_evaluate_closure(target_socket.context,
target_node->identifier,
&target_socket->owner_tree(),
source_location);
}
static Vector<nodes::SocketInContext> find_origin_sockets_through_contexts(
const nodes::SocketInContext start_socket,
bke::ComputeContextCache &compute_context_cache,
const FunctionRef<bool(const nodes::SocketInContext &)> handle_possible_origin_socket_fn,
const bool find_all)
{
using BundlePath = Vector<std::string, 0>;
struct SocketToCheck {
nodes::SocketInContext socket;
BundlePath bundle_path;
};
Stack<SocketToCheck> sockets_to_check;
Set<nodes::SocketInContext> added_sockets;
auto add_if_new = [&](const nodes::SocketInContext &socket, BundlePath bundle_path) {
if (added_sockets.add(socket)) {
sockets_to_check.push({socket, std::move(bundle_path)});
}
};
add_if_new(start_socket, {});
VectorSet<nodes::SocketInContext> found_origins;
while (!sockets_to_check.is_empty()) {
const SocketToCheck socket_to_check = sockets_to_check.pop();
const nodes::SocketInContext socket = socket_to_check.socket;
const BundlePath &bundle_path = socket_to_check.bundle_path;
const nodes::NodeInContext &node = socket.owner_node();
if (socket->is_input()) {
if (bundle_path.is_empty() && handle_possible_origin_socket_fn(socket)) {
found_origins.add(socket);
if (!find_all) {
break;
}
continue;
}
const bke::bNodeTreeZones *zones = node->owner_tree().zones();
if (!zones) {
continue;
}
const bke::bNodeTreeZone *to_zone = zones->get_zone_by_socket(*socket.socket);
for (const bNodeLink *link : socket->directly_linked_links()) {
if (!link->is_used()) {
continue;
}
const bNodeSocket *from_socket = link->fromsock;
const bke::bNodeTreeZone *from_zone = zones->get_zone_by_socket(*from_socket);
if (!zones->link_between_zones_is_allowed(from_zone, to_zone)) {
continue;
}
const Vector<const bke::bNodeTreeZone *> zones_to_enter = zones->get_zones_to_enter(
from_zone, to_zone);
const ComputeContext *compute_context = socket.context;
for (int i = zones_to_enter.size() - 1; i >= 0; i--) {
if (!compute_context) {
/* There must be a compute context when we are in a zone. */
BLI_assert_unreachable();
return found_origins.extract_vector();
}
/* Each zone corresponds to one compute context level. */
compute_context = compute_context->parent();
}
add_if_new({compute_context, from_socket}, bundle_path);
}
}
else {
if (node->is_muted()) {
for (const bNodeLink &link : node->internal_links()) {
if (link.tosock == socket.socket) {
add_if_new({socket.context, link.fromsock}, bundle_path);
}
}
continue;
}
if (bundle_path.is_empty() && handle_possible_origin_socket_fn(socket)) {
found_origins.add(socket);
if (!find_all) {
break;
}
continue;
}
if (node->is_reroute()) {
add_if_new(node.input_socket(0), bundle_path);
continue;
}
if (node->is_group()) {
if (const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node->id)) {
group->ensure_topology_cache();
if (const bNode *group_output_node = group->group_output_node()) {
const ComputeContext &group_compute_context = compute_context_cache.for_group_node(
socket.context, node->identifier, &node->owner_tree());
add_if_new({&group_compute_context, &group_output_node->input_socket(socket->index())},
bundle_path);
}
}
continue;
}
if (node->is_group_input()) {
if (const auto *group_context = dynamic_cast<const bke::GroupNodeComputeContext *>(
socket.context))
{
const bNodeTree *caller_group = group_context->tree();
const bNode *caller_group_node = group_context->node();
if (caller_group && caller_group_node) {
caller_group->ensure_topology_cache();
const bNodeSocket &input_socket = caller_group_node->input_socket(socket->index());
add_if_new({group_context->parent(), &input_socket}, bundle_path);
}
}
continue;
}
if (node->is_type("GeometryNodeEvaluateClosure")) {
const auto &evaluate_storage = *static_cast<const NodeGeometryEvaluateClosure *>(
node->storage);
const StringRef key = evaluate_storage.output_items.items[socket->index()].name;
const Vector<nodes::SocketInContext> origin_sockets = find_origin_sockets_through_contexts(
node.input_socket(0), compute_context_cache, is_closure_zone_output_socket, true);
for (const nodes::SocketInContext origin_socket : origin_sockets) {
const bNodeTree &closure_tree = origin_socket->owner_tree();
const nodes::NodeInContext closure_output_node = origin_socket.owner_node();
const auto &closure_storage = *static_cast<const NodeGeometryClosureOutput *>(
closure_output_node->storage);
const ComputeContext &closure_context = compute_context_cache.for_evaluate_closure(
node.context,
node->identifier,
&node->owner_tree(),
nodes::ClosureSourceLocation{
&closure_tree, closure_output_node->identifier, origin_socket.context_hash()});
for (const int i : IndexRange(closure_storage.output_items.items_num)) {
const NodeGeometryClosureOutputItem &item = closure_storage.output_items.items[i];
if (key == item.name) {
add_if_new({&closure_context, &closure_output_node->input_socket(i)}, bundle_path);
}
}
}
continue;
}
if (node->is_type("GeometryNodeClosureInput")) {
const auto &input_storage = *static_cast<const NodeGeometryClosureInput *>(node->storage);
const bNode *closure_output_node = node->owner_tree().node_by_id(
input_storage.output_node_id);
if (!closure_output_node) {
continue;
}
const auto &output_storage = *static_cast<const NodeGeometryClosureOutput *>(
closure_output_node->storage);
const StringRef key = output_storage.input_items.items[socket->index()].name;
const bNodeSocket &closure_output_socket = closure_output_node->output_socket(0);
const Vector<nodes::SocketInContext> target_sockets = find_target_sockets_through_contexts(
{socket.context, &closure_output_socket},
compute_context_cache,
is_evaluate_closure_node_input,
true);
for (const nodes::SocketInContext &target_socket : target_sockets) {
const nodes::NodeInContext target_node = target_socket.owner_node();
const auto &evaluate_storage = *static_cast<const NodeGeometryEvaluateClosure *>(
target_node.node->storage);
for (const int i : IndexRange(evaluate_storage.input_items.items_num)) {
const NodeGeometryEvaluateClosureInputItem &item =
evaluate_storage.input_items.items[i];
if (key == item.name) {
add_if_new(target_node.input_socket(i + 1), bundle_path);
}
}
}
continue;
}
if (node->is_type("GeometryNodeCombineBundle")) {
if (bundle_path.is_empty()) {
continue;
}
const StringRef last_key = bundle_path.last();
const auto &storage = *static_cast<const NodeGeometryCombineBundle *>(node->storage);
for (const int input_i : IndexRange(storage.items_num)) {
if (last_key == storage.items[input_i].name) {
add_if_new(node.input_socket(input_i), bundle_path.as_span().drop_back(1));
}
}
continue;
}
if (node->is_type("GeometryNodeSeparateBundle")) {
const auto &storage = *static_cast<const NodeGeometrySeparateBundle *>(node->storage);
BundlePath new_bundle_path = bundle_path;
new_bundle_path.append(storage.items[socket->index()].name);
add_if_new(node.input_socket(0), std::move(new_bundle_path));
continue;
}
}
}
return found_origins.extract_vector();
}
Vector<nodes::BundleSignature> gather_linked_target_bundle_signatures(
const ComputeContext *bundle_socket_context,
const bNodeSocket &bundle_socket,
bke::ComputeContextCache &compute_context_cache)
{
const Vector<nodes::SocketInContext> target_sockets = find_target_sockets_through_contexts(
{bundle_socket_context, &bundle_socket},
compute_context_cache,
[](const nodes::SocketInContext &socket) {
return socket->owner_node().is_type("GeometryNodeSeparateBundle");
},
true);
Vector<nodes::BundleSignature> signatures;
for (const nodes::SocketInContext &target_socket : target_sockets) {
const nodes::NodeInContext &target_node = target_socket.owner_node();
signatures.append(nodes::BundleSignature::from_separate_bundle_node(*target_node.node));
}
return signatures;
}
Vector<nodes::BundleSignature> gather_linked_origin_bundle_signatures(
const ComputeContext *bundle_socket_context,
const bNodeSocket &bundle_socket,
bke::ComputeContextCache &compute_context_cache)
{
const Vector<nodes::SocketInContext> origin_sockets = find_origin_sockets_through_contexts(
{bundle_socket_context, &bundle_socket},
compute_context_cache,
[](const nodes::SocketInContext &socket) {
return socket->owner_node().is_type("GeometryNodeCombineBundle");
},
true);
Vector<nodes::BundleSignature> signatures;
for (const nodes::SocketInContext &origin_socket : origin_sockets) {
const nodes::NodeInContext &origin_node = origin_socket.owner_node();
signatures.append(nodes::BundleSignature::from_combine_bundle_node(*origin_node.node));
}
return signatures;
}
Vector<nodes::ClosureSignature> gather_linked_target_closure_signatures(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache)
{
const Vector<nodes::SocketInContext> target_sockets = find_target_sockets_through_contexts(
{closure_socket_context, &closure_socket},
compute_context_cache,
is_evaluate_closure_node_input,
true);
Vector<nodes::ClosureSignature> signatures;
for (const nodes::SocketInContext &target_socket : target_sockets) {
const nodes::NodeInContext &target_node = target_socket.owner_node();
signatures.append(nodes::ClosureSignature::from_evaluate_closure_node(*target_node.node));
}
return signatures;
}
Vector<nodes::ClosureSignature> gather_linked_origin_closure_signatures(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache)
{
Vector<nodes::ClosureSignature> signatures;
find_origin_sockets_through_contexts(
{closure_socket_context, &closure_socket},
compute_context_cache,
[&](const nodes::SocketInContext &socket) {
if (is_closure_zone_output_socket(socket)) {
signatures.append(
nodes::ClosureSignature::from_closure_output_node(socket->owner_node()));
return true;
}
return false;
},
true);
return signatures;
}
static const ComputeContext *get_node_editor_root_compute_context(
const SpaceNode &snode, bke::ComputeContextCache &compute_context_cache)
{

View File

@@ -99,6 +99,7 @@ set(SRC
intern/socket_search_link.cc
intern/socket_usage_inference.cc
intern/sync_sockets.cc
intern/trace_values.cc
intern/value_elem.cc
intern/volume_grid_function_eval.cc
@@ -148,6 +149,7 @@ set(SRC
NOD_socket_usage_inference.hh
NOD_socket_usage_inference_fwd.hh
NOD_sync_sockets.hh
NOD_trace_values.hh
NOD_texture.h
NOD_value_elem.hh
NOD_value_elem_eval.hh

View File

@@ -0,0 +1,47 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BKE_compute_context_cache_fwd.hh"
#include "BLI_compute_context.hh"
#include "DNA_node_types.h"
#include "NOD_geometry_nodes_bundle_signature.hh"
#include "NOD_geometry_nodes_closure_location.hh"
#include "NOD_geometry_nodes_closure_signature.hh"
namespace blender::nodes {
/**
* Attempts to find a compute context that the closure is evaluated in. If none is found, null is
* returned. If multiple are found, it currently picks the first one it finds which is somewhat
* arbitrary.
*/
[[nodiscard]] const ComputeContext *compute_context_for_closure_evaluation(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache,
const std::optional<ClosureSourceLocation> &source_location);
Vector<BundleSignature> gather_linked_target_bundle_signatures(
const ComputeContext *bundle_socket_context,
const bNodeSocket &bundle_socket,
bke::ComputeContextCache &compute_context_cache);
Vector<BundleSignature> gather_linked_origin_bundle_signatures(
const ComputeContext *bundle_socket_context,
const bNodeSocket &bundle_socket,
bke::ComputeContextCache &compute_context_cache);
Vector<ClosureSignature> gather_linked_target_closure_signatures(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache);
Vector<ClosureSignature> gather_linked_origin_closure_signatures(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache);
} // namespace blender::nodes

View File

@@ -32,6 +32,7 @@
#include "NOD_geo_closure.hh"
#include "NOD_socket_items.hh"
#include "NOD_sync_sockets.hh"
#include "NOD_trace_values.hh"
namespace blender::nodes {
@@ -62,9 +63,8 @@ static BundleSyncState get_sync_state_separate_bundle(const SpaceNode &snode,
bke::ComputeContextCache compute_context_cache;
const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket(
snode, compute_context_cache, bundle_socket);
const Vector<nodes::BundleSignature> source_signatures =
ed::space_node::gather_linked_origin_bundle_signatures(
current_context, bundle_socket, compute_context_cache);
const Vector<nodes::BundleSignature> source_signatures = gather_linked_origin_bundle_signatures(
current_context, bundle_socket, compute_context_cache);
if (source_signatures.is_empty()) {
return {NodeSyncState::NoSyncSource};
}
@@ -90,9 +90,8 @@ static BundleSyncState get_sync_state_combine_bundle(const SpaceNode &snode,
bke::ComputeContextCache compute_context_cache;
const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket(
snode, compute_context_cache, bundle_socket);
const Vector<nodes::BundleSignature> source_signatures =
ed::space_node::gather_linked_target_bundle_signatures(
current_context, bundle_socket, compute_context_cache);
const Vector<nodes::BundleSignature> source_signatures = gather_linked_target_bundle_signatures(
current_context, bundle_socket, compute_context_cache);
if (source_signatures.is_empty()) {
return {NodeSyncState::NoSyncSource};
}
@@ -118,7 +117,7 @@ static ClosureSyncState get_sync_state_closure_output(const SpaceNode &snode,
const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket(
snode, compute_context_cache, closure_socket);
const Vector<nodes::ClosureSignature> source_signatures =
ed::space_node::gather_linked_target_closure_signatures(
gather_linked_target_closure_signatures(
current_context, closure_socket, compute_context_cache);
if (source_signatures.is_empty()) {
return {NodeSyncState::NoSyncSource};
@@ -145,7 +144,7 @@ static ClosureSyncState get_sync_state_evaluate_closure(const SpaceNode &snode,
const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket(
snode, compute_context_cache, closure_socket);
const Vector<nodes::ClosureSignature> source_signatures =
ed::space_node::gather_linked_origin_closure_signatures(
gather_linked_origin_closure_signatures(
current_context, closure_socket, compute_context_cache);
if (source_signatures.is_empty()) {
return {NodeSyncState::NoSyncSource};

View File

@@ -0,0 +1,523 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_stack.hh"
#include "NOD_geometry_nodes_bundle_signature.hh"
#include "NOD_geometry_nodes_closure_location.hh"
#include "NOD_geometry_nodes_closure_signature.hh"
#include "NOD_node_in_compute_context.hh"
#include "BKE_compute_context_cache.hh"
#include "BKE_node_tree_zones.hh"
#include "ED_node.hh"
namespace blender::nodes {
static bool is_evaluate_closure_node_input(const SocketInContext &socket)
{
return socket->is_input() && socket->index() == 0 &&
socket.owner_node()->is_type("GeometryNodeEvaluateClosure");
}
static bool is_closure_zone_output_socket(const SocketInContext &socket)
{
return socket->owner_node().is_type("GeometryNodeClosureOutput") && socket->is_output();
}
static Vector<SocketInContext> find_origin_sockets_through_contexts(
SocketInContext start_socket,
bke::ComputeContextCache &compute_context_cache,
FunctionRef<bool(const SocketInContext &)> handle_possible_origin_socket_fn,
bool find_all);
static Vector<SocketInContext> find_target_sockets_through_contexts(
const SocketInContext start_socket,
bke::ComputeContextCache &compute_context_cache,
const FunctionRef<bool(const SocketInContext &)> handle_possible_target_socket_fn,
const bool find_all)
{
using BundlePath = Vector<std::string, 0>;
struct SocketToCheck {
SocketInContext socket;
BundlePath bundle_path;
};
Stack<SocketToCheck> sockets_to_check;
Set<SocketInContext> added_sockets;
auto add_if_new = [&](const SocketInContext &socket, BundlePath bundle_path) {
if (added_sockets.add(socket)) {
sockets_to_check.push({socket, std::move(bundle_path)});
}
};
add_if_new(start_socket, {});
VectorSet<SocketInContext> found_targets;
while (!sockets_to_check.is_empty()) {
const SocketToCheck socket_to_check = sockets_to_check.pop();
const SocketInContext socket = socket_to_check.socket;
const BundlePath &bundle_path = socket_to_check.bundle_path;
const NodeInContext &node = socket.owner_node();
if (socket->is_input()) {
if (node->is_muted()) {
for (const bNodeLink &link : node->internal_links()) {
if (link.fromsock == socket.socket) {
add_if_new({socket.context, link.tosock}, bundle_path);
}
}
continue;
}
if (bundle_path.is_empty() && handle_possible_target_socket_fn(socket)) {
found_targets.add(socket);
if (!find_all) {
break;
}
continue;
}
if (node->is_reroute()) {
add_if_new(node.output_socket(0), bundle_path);
continue;
}
if (node->is_group()) {
if (const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node->id)) {
group->ensure_topology_cache();
const ComputeContext &group_compute_context = compute_context_cache.for_group_node(
socket.context, node->identifier, &node->owner_tree());
for (const bNode *input_node : group->group_input_nodes()) {
const bNodeSocket &group_input_socket = input_node->output_socket(socket->index());
if (group_input_socket.is_directly_linked()) {
add_if_new({&group_compute_context, &group_input_socket}, bundle_path);
}
}
}
continue;
}
if (node->is_group_output()) {
if (const auto *group_context = dynamic_cast<const bke::GroupNodeComputeContext *>(
socket.context))
{
const bNodeTree *caller_group = group_context->tree();
const bNode *caller_group_node = group_context->node();
if (caller_group && caller_group_node) {
caller_group->ensure_topology_cache();
const bNodeSocket &output_socket = caller_group_node->output_socket(socket->index());
add_if_new({group_context->parent(), &output_socket}, bundle_path);
}
}
continue;
}
if (node->is_type("GeometryNodeCombineBundle")) {
const auto &storage = *static_cast<const NodeGeometryCombineBundle *>(node->storage);
BundlePath new_bundle_path = bundle_path;
new_bundle_path.append(storage.items[socket->index()].name);
add_if_new(node.output_socket(0), std::move(new_bundle_path));
continue;
}
if (node->is_type("GeometryNodeSeparateBundle")) {
if (bundle_path.is_empty()) {
continue;
}
const StringRef last_key = bundle_path.last();
const auto &storage = *static_cast<const NodeGeometrySeparateBundle *>(node->storage);
for (const int output_i : IndexRange(storage.items_num)) {
if (last_key == storage.items[output_i].name) {
add_if_new(node.output_socket(output_i), bundle_path.as_span().drop_back(1));
}
}
continue;
}
if (node->is_type("GeometryNodeClosureOutput")) {
const auto &closure_storage = *static_cast<const NodeGeometryClosureOutput *>(
node->storage);
const StringRef key = closure_storage.output_items.items[socket->index()].name;
const Vector<SocketInContext> target_sockets = find_target_sockets_through_contexts(
node.output_socket(0), compute_context_cache, is_evaluate_closure_node_input, true);
for (const auto &target_socket : target_sockets) {
const NodeInContext evaluate_node = target_socket.owner_node();
const auto &evaluate_storage = *static_cast<const NodeGeometryEvaluateClosure *>(
evaluate_node->storage);
for (const int i : IndexRange(evaluate_storage.output_items.items_num)) {
const NodeGeometryEvaluateClosureOutputItem &item =
evaluate_storage.output_items.items[i];
if (key == item.name) {
add_if_new(evaluate_node.output_socket(i), bundle_path);
}
}
}
continue;
}
if (node->is_type("GeometryNodeEvaluateClosure")) {
if (socket->index() == 0) {
continue;
}
const auto &evaluate_storage = *static_cast<const NodeGeometryEvaluateClosure *>(
node->storage);
const StringRef key = evaluate_storage.input_items.items[socket->index() - 1].name;
const Vector<SocketInContext> origin_sockets = find_origin_sockets_through_contexts(
node.input_socket(0), compute_context_cache, is_closure_zone_output_socket, true);
for (const SocketInContext origin_socket : origin_sockets) {
const bNodeTree &closure_tree = origin_socket->owner_tree();
const bke::bNodeTreeZones *closure_tree_zones = closure_tree.zones();
if (!closure_tree_zones) {
continue;
}
const auto &closure_output_node = origin_socket.owner_node();
const bke::bNodeTreeZone *closure_zone = closure_tree_zones->get_zone_by_node(
closure_output_node->identifier);
if (!closure_zone) {
continue;
}
const bNode *closure_input_node = closure_zone->input_node();
if (!closure_input_node) {
continue;
}
const ComputeContext &closure_context = compute_context_cache.for_evaluate_closure(
node.context,
node->identifier,
&node->owner_tree(),
ClosureSourceLocation{
&closure_tree, closure_output_node->identifier, origin_socket.context_hash()});
const auto &closure_output_storage = *static_cast<const NodeGeometryClosureOutput *>(
closure_output_node->storage);
for (const int i : IndexRange(closure_output_storage.input_items.items_num)) {
const NodeGeometryClosureInputItem &item = closure_output_storage.input_items.items[i];
if (key == item.name) {
add_if_new({&closure_context, &closure_input_node->output_socket(i)}, bundle_path);
}
}
}
continue;
}
}
else {
const bke::bNodeTreeZones *zones = node->owner_tree().zones();
if (!zones) {
continue;
}
const bke::bNodeTreeZone *from_zone = zones->get_zone_by_socket(*socket.socket);
for (const bNodeLink *link : socket->directly_linked_links()) {
if (!link->is_used()) {
continue;
}
bNodeSocket *to_socket = link->tosock;
const bke::bNodeTreeZone *to_zone = zones->get_zone_by_socket(*to_socket);
if (!zones->link_between_zones_is_allowed(from_zone, to_zone)) {
continue;
}
const Vector<const bke::bNodeTreeZone *> zones_to_enter = zones->get_zones_to_enter(
from_zone, to_zone);
const ComputeContext *compute_context = ed::space_node::compute_context_for_zones(
zones_to_enter, compute_context_cache, socket.context);
if (!compute_context) {
continue;
}
add_if_new({compute_context, to_socket}, bundle_path);
}
}
}
return found_targets.extract_vector();
}
[[nodiscard]] const ComputeContext *compute_context_for_closure_evaluation(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache,
const std::optional<ClosureSourceLocation> &source_location)
{
const Vector<SocketInContext> target_sockets = find_target_sockets_through_contexts(
{closure_socket_context, &closure_socket},
compute_context_cache,
is_evaluate_closure_node_input,
false);
if (target_sockets.is_empty()) {
return nullptr;
}
const SocketInContext target_socket = target_sockets[0];
const NodeInContext target_node = target_socket.owner_node();
return &compute_context_cache.for_evaluate_closure(target_socket.context,
target_node->identifier,
&target_socket->owner_tree(),
source_location);
}
static Vector<SocketInContext> find_origin_sockets_through_contexts(
const SocketInContext start_socket,
bke::ComputeContextCache &compute_context_cache,
const FunctionRef<bool(const SocketInContext &)> handle_possible_origin_socket_fn,
const bool find_all)
{
using BundlePath = Vector<std::string, 0>;
struct SocketToCheck {
SocketInContext socket;
BundlePath bundle_path;
};
Stack<SocketToCheck> sockets_to_check;
Set<SocketInContext> added_sockets;
auto add_if_new = [&](const SocketInContext &socket, BundlePath bundle_path) {
if (added_sockets.add(socket)) {
sockets_to_check.push({socket, std::move(bundle_path)});
}
};
add_if_new(start_socket, {});
VectorSet<SocketInContext> found_origins;
while (!sockets_to_check.is_empty()) {
const SocketToCheck socket_to_check = sockets_to_check.pop();
const SocketInContext socket = socket_to_check.socket;
const BundlePath &bundle_path = socket_to_check.bundle_path;
const NodeInContext &node = socket.owner_node();
if (socket->is_input()) {
if (bundle_path.is_empty() && handle_possible_origin_socket_fn(socket)) {
found_origins.add(socket);
if (!find_all) {
break;
}
continue;
}
const bke::bNodeTreeZones *zones = node->owner_tree().zones();
if (!zones) {
continue;
}
const bke::bNodeTreeZone *to_zone = zones->get_zone_by_socket(*socket.socket);
for (const bNodeLink *link : socket->directly_linked_links()) {
if (!link->is_used()) {
continue;
}
const bNodeSocket *from_socket = link->fromsock;
const bke::bNodeTreeZone *from_zone = zones->get_zone_by_socket(*from_socket);
if (!zones->link_between_zones_is_allowed(from_zone, to_zone)) {
continue;
}
const Vector<const bke::bNodeTreeZone *> zones_to_enter = zones->get_zones_to_enter(
from_zone, to_zone);
const ComputeContext *compute_context = socket.context;
for (int i = zones_to_enter.size() - 1; i >= 0; i--) {
if (!compute_context) {
/* There must be a compute context when we are in a zone. */
BLI_assert_unreachable();
return found_origins.extract_vector();
}
/* Each zone corresponds to one compute context level. */
compute_context = compute_context->parent();
}
add_if_new({compute_context, from_socket}, bundle_path);
}
}
else {
if (node->is_muted()) {
for (const bNodeLink &link : node->internal_links()) {
if (link.tosock == socket.socket) {
add_if_new({socket.context, link.fromsock}, bundle_path);
}
}
continue;
}
if (bundle_path.is_empty() && handle_possible_origin_socket_fn(socket)) {
found_origins.add(socket);
if (!find_all) {
break;
}
continue;
}
if (node->is_reroute()) {
add_if_new(node.input_socket(0), bundle_path);
continue;
}
if (node->is_group()) {
if (const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node->id)) {
group->ensure_topology_cache();
if (const bNode *group_output_node = group->group_output_node()) {
const ComputeContext &group_compute_context = compute_context_cache.for_group_node(
socket.context, node->identifier, &node->owner_tree());
add_if_new({&group_compute_context, &group_output_node->input_socket(socket->index())},
bundle_path);
}
}
continue;
}
if (node->is_group_input()) {
if (const auto *group_context = dynamic_cast<const bke::GroupNodeComputeContext *>(
socket.context))
{
const bNodeTree *caller_group = group_context->tree();
const bNode *caller_group_node = group_context->node();
if (caller_group && caller_group_node) {
caller_group->ensure_topology_cache();
const bNodeSocket &input_socket = caller_group_node->input_socket(socket->index());
add_if_new({group_context->parent(), &input_socket}, bundle_path);
}
}
continue;
}
if (node->is_type("GeometryNodeEvaluateClosure")) {
const auto &evaluate_storage = *static_cast<const NodeGeometryEvaluateClosure *>(
node->storage);
const StringRef key = evaluate_storage.output_items.items[socket->index()].name;
const Vector<SocketInContext> origin_sockets = find_origin_sockets_through_contexts(
node.input_socket(0), compute_context_cache, is_closure_zone_output_socket, true);
for (const SocketInContext origin_socket : origin_sockets) {
const bNodeTree &closure_tree = origin_socket->owner_tree();
const NodeInContext closure_output_node = origin_socket.owner_node();
const auto &closure_storage = *static_cast<const NodeGeometryClosureOutput *>(
closure_output_node->storage);
const ComputeContext &closure_context = compute_context_cache.for_evaluate_closure(
node.context,
node->identifier,
&node->owner_tree(),
ClosureSourceLocation{
&closure_tree, closure_output_node->identifier, origin_socket.context_hash()});
for (const int i : IndexRange(closure_storage.output_items.items_num)) {
const NodeGeometryClosureOutputItem &item = closure_storage.output_items.items[i];
if (key == item.name) {
add_if_new({&closure_context, &closure_output_node->input_socket(i)}, bundle_path);
}
}
}
continue;
}
if (node->is_type("GeometryNodeClosureInput")) {
const auto &input_storage = *static_cast<const NodeGeometryClosureInput *>(node->storage);
const bNode *closure_output_node = node->owner_tree().node_by_id(
input_storage.output_node_id);
if (!closure_output_node) {
continue;
}
const auto &output_storage = *static_cast<const NodeGeometryClosureOutput *>(
closure_output_node->storage);
const StringRef key = output_storage.input_items.items[socket->index()].name;
const bNodeSocket &closure_output_socket = closure_output_node->output_socket(0);
const Vector<SocketInContext> target_sockets = find_target_sockets_through_contexts(
{socket.context, &closure_output_socket},
compute_context_cache,
is_evaluate_closure_node_input,
true);
for (const SocketInContext &target_socket : target_sockets) {
const NodeInContext target_node = target_socket.owner_node();
const auto &evaluate_storage = *static_cast<const NodeGeometryEvaluateClosure *>(
target_node.node->storage);
for (const int i : IndexRange(evaluate_storage.input_items.items_num)) {
const NodeGeometryEvaluateClosureInputItem &item =
evaluate_storage.input_items.items[i];
if (key == item.name) {
add_if_new(target_node.input_socket(i + 1), bundle_path);
}
}
}
continue;
}
if (node->is_type("GeometryNodeCombineBundle")) {
if (bundle_path.is_empty()) {
continue;
}
const StringRef last_key = bundle_path.last();
const auto &storage = *static_cast<const NodeGeometryCombineBundle *>(node->storage);
for (const int input_i : IndexRange(storage.items_num)) {
if (last_key == storage.items[input_i].name) {
add_if_new(node.input_socket(input_i), bundle_path.as_span().drop_back(1));
}
}
continue;
}
if (node->is_type("GeometryNodeSeparateBundle")) {
const auto &storage = *static_cast<const NodeGeometrySeparateBundle *>(node->storage);
BundlePath new_bundle_path = bundle_path;
new_bundle_path.append(storage.items[socket->index()].name);
add_if_new(node.input_socket(0), std::move(new_bundle_path));
continue;
}
}
}
return found_origins.extract_vector();
}
Vector<BundleSignature> gather_linked_target_bundle_signatures(
const ComputeContext *bundle_socket_context,
const bNodeSocket &bundle_socket,
bke::ComputeContextCache &compute_context_cache)
{
const Vector<SocketInContext> target_sockets = find_target_sockets_through_contexts(
{bundle_socket_context, &bundle_socket},
compute_context_cache,
[](const SocketInContext &socket) {
return socket->owner_node().is_type("GeometryNodeSeparateBundle");
},
true);
Vector<BundleSignature> signatures;
for (const SocketInContext &target_socket : target_sockets) {
const NodeInContext &target_node = target_socket.owner_node();
signatures.append(BundleSignature::from_separate_bundle_node(*target_node.node));
}
return signatures;
}
Vector<BundleSignature> gather_linked_origin_bundle_signatures(
const ComputeContext *bundle_socket_context,
const bNodeSocket &bundle_socket,
bke::ComputeContextCache &compute_context_cache)
{
const Vector<SocketInContext> origin_sockets = find_origin_sockets_through_contexts(
{bundle_socket_context, &bundle_socket},
compute_context_cache,
[](const SocketInContext &socket) {
return socket->owner_node().is_type("GeometryNodeCombineBundle");
},
true);
Vector<BundleSignature> signatures;
for (const SocketInContext &origin_socket : origin_sockets) {
const NodeInContext &origin_node = origin_socket.owner_node();
signatures.append(BundleSignature::from_combine_bundle_node(*origin_node.node));
}
return signatures;
}
Vector<ClosureSignature> gather_linked_target_closure_signatures(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache)
{
const Vector<SocketInContext> target_sockets = find_target_sockets_through_contexts(
{closure_socket_context, &closure_socket},
compute_context_cache,
is_evaluate_closure_node_input,
true);
Vector<ClosureSignature> signatures;
for (const SocketInContext &target_socket : target_sockets) {
const NodeInContext &target_node = target_socket.owner_node();
signatures.append(ClosureSignature::from_evaluate_closure_node(*target_node.node));
}
return signatures;
}
Vector<ClosureSignature> gather_linked_origin_closure_signatures(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache)
{
Vector<ClosureSignature> signatures;
find_origin_sockets_through_contexts(
{closure_socket_context, &closure_socket},
compute_context_cache,
[&](const SocketInContext &socket) {
if (is_closure_zone_output_socket(socket)) {
signatures.append(ClosureSignature::from_closure_output_node(socket->owner_node()));
return true;
}
return false;
},
true);
return signatures;
}
} // namespace blender::nodes