Nodes: Support disabling outputs in built-in nodes

This patch adds support for disabling outputs in built-in nodes based on
menu inputs using a custom usage_inference callback. This just
essentially calls usage_inference_fn for outputs as well, while it was
only being called for inputs. Additionally, the usage_by_menu methods
were adjusted to only consider outputs if it is being called on inputs.

Some types were renamed to be more general, and not just for inputs.

Pull Request: https://projects.blender.org/blender/blender/pulls/148132
This commit is contained in:
Omar Emara
2025-10-15 15:57:56 +02:00
committed by Omar Emara
parent df4c7a6da2
commit 3aa674966f
6 changed files with 72 additions and 53 deletions

View File

@@ -197,8 +197,8 @@ struct CustomSocketDrawParams {
};
using CustomSocketDrawFn = std::function<void(CustomSocketDrawParams &params)>;
using InputSocketUsageInferenceFn = std::function<std::optional<bool>(
const socket_usage_inference::InputSocketUsageParams &params)>;
using SocketUsageInferenceFn =
std::function<std::optional<bool>(const socket_usage_inference::SocketUsageParams &params)>;
/**
* Describes a single input or output socket. This is subclassed for different socket types.
@@ -267,10 +267,10 @@ class SocketDeclaration : public ItemDeclaration {
*/
std::unique_ptr<CustomSocketDrawFn> custom_draw_fn;
/**
* Determines whether this input socket is used based on other input values and based on which
* outputs are used.
* Determines whether this socket is used based on other input values and based on which outputs
* are used.
*/
std::unique_ptr<InputSocketUsageInferenceFn> usage_inference_fn;
std::unique_ptr<SocketUsageInferenceFn> usage_inference_fn;
friend NodeDeclarationBuilder;
friend class BaseSocketDeclarationBuilder;
@@ -427,10 +427,10 @@ class BaseSocketDeclarationBuilder {
BaseSocketDeclarationBuilder &custom_draw(CustomSocketDrawFn fn);
/**
* Provide a function that determines whether this input socket is used based on other input
* values and based on which outputs are used.
* Provide a function that determines whether this socket is used based on other input values and
* based on which outputs are used.
*/
BaseSocketDeclarationBuilder &usage_inference(InputSocketUsageInferenceFn fn);
BaseSocketDeclarationBuilder &usage_inference(SocketUsageInferenceFn fn);
/**
* Utility method for the case when the node has a single menu input and this socket is only used

View File

@@ -30,7 +30,7 @@ class SocketUsageInferencer {
private:
SocketUsageInferencerImpl &impl_;
friend class InputSocketUsageParams;
friend class SocketUsageParams;
public:
SocketUsageInferencer(const bNodeTree &tree,
@@ -51,7 +51,7 @@ class SocketUsageInferencer {
void mark_top_level_node_outputs_as_used();
};
class InputSocketUsageParams {
class SocketUsageParams {
private:
SocketUsageInferencer &inferencer_;
const ComputeContext *compute_context_ = nullptr;
@@ -61,11 +61,11 @@ class InputSocketUsageParams {
const bNode &node;
const bNodeSocket &socket;
InputSocketUsageParams(SocketUsageInferencer &inferencer,
const ComputeContext *compute_context,
const bNodeTree &tree,
const bNode &node,
const bNodeSocket &socket);
SocketUsageParams(SocketUsageInferencer &inferencer,
const ComputeContext *compute_context,
const bNodeTree &tree,
const bNode &node,
const bNodeSocket &socket);
/**
* Get an the statically known input value for the given socket identifier. The value may be

View File

@@ -6,7 +6,7 @@
namespace blender::nodes::socket_usage_inference {
class InputSocketUsageParams;
class SocketUsageParams;
struct SocketUsage {
bool is_used = true;

View File

@@ -181,7 +181,7 @@ static void cmp_node_channel_matte_declare(NodeDeclarationBuilder &b)
CMP_NODE_CHANNEL_MATTE_CS_RGB;
})
.usage_inference(
[](const socket_usage_inference::InputSocketUsageParams &params) -> std::optional<bool> {
[](const socket_usage_inference::SocketUsageParams &params) -> std::optional<bool> {
return params.menu_input_may_be("Limit Method",
CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE) &&
params.menu_input_may_be("Color Space", CMP_NODE_CHANNEL_MATTE_CS_RGB);
@@ -203,7 +203,7 @@ static void cmp_node_channel_matte_declare(NodeDeclarationBuilder &b)
CMP_NODE_CHANNEL_MATTE_CS_HSV;
})
.usage_inference(
[](const socket_usage_inference::InputSocketUsageParams &params) -> std::optional<bool> {
[](const socket_usage_inference::SocketUsageParams &params) -> std::optional<bool> {
return params.menu_input_may_be("Limit Method",
CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE) &&
params.menu_input_may_be("Color Space", CMP_NODE_CHANNEL_MATTE_CS_HSV);
@@ -225,7 +225,7 @@ static void cmp_node_channel_matte_declare(NodeDeclarationBuilder &b)
CMP_NODE_CHANNEL_MATTE_CS_YUV;
})
.usage_inference(
[](const socket_usage_inference::InputSocketUsageParams &params) -> std::optional<bool> {
[](const socket_usage_inference::SocketUsageParams &params) -> std::optional<bool> {
return params.menu_input_may_be("Limit Method",
CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE) &&
params.menu_input_may_be("Color Space", CMP_NODE_CHANNEL_MATTE_CS_YUV);
@@ -247,7 +247,7 @@ static void cmp_node_channel_matte_declare(NodeDeclarationBuilder &b)
CMP_NODE_CHANNEL_MATTE_CS_YCC;
})
.usage_inference(
[](const socket_usage_inference::InputSocketUsageParams &params) -> std::optional<bool> {
[](const socket_usage_inference::SocketUsageParams &params) -> std::optional<bool> {
return params.menu_input_may_be("Limit Method",
CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE) &&
params.menu_input_may_be("Color Space", CMP_NODE_CHANNEL_MATTE_CS_YCC);

View File

@@ -769,9 +769,9 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::custom_draw(CustomSo
}
BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::usage_inference(
InputSocketUsageInferenceFn fn)
SocketUsageInferenceFn fn)
{
decl_base_->usage_inference_fn = std::make_unique<InputSocketUsageInferenceFn>(std::move(fn));
decl_base_->usage_inference_fn = std::make_unique<SocketUsageInferenceFn>(std::move(fn));
return *this;
}
@@ -810,19 +810,21 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::usage_by_single_menu
bNodeSocketValueMenu *value = socket.default_value_typed<bNodeSocketValueMenu>();
value->value = menu_value;
});
this->usage_inference([menu_value](const socket_usage_inference::InputSocketUsageParams &params)
this->usage_inference([menu_value](const socket_usage_inference::SocketUsageParams &params)
-> std::optional<bool> {
const bNodeSocket &socket = find_single_menu_input(params.node);
if (const std::optional<bool> any_output_used = params.any_output_is_used()) {
if (!*any_output_used) {
/* If no output is used, none of the inputs is used either. */
return false;
if (params.socket.is_input()) {
if (const std::optional<bool> any_output_used = params.any_output_is_used()) {
if (!*any_output_used) {
/* If no output is used, none of the inputs is used either. */
return false;
}
}
else {
/* It's not known if any output is used yet. This function will be called again once new
* information about output usages is available. */
return std::nullopt;
}
}
else {
/* It's not known if any output is used yet. This function will be called again once new
* information about output usages is available. */
return std::nullopt;
}
return params.menu_input_may_be(socket.identifier, menu_value);
});
@@ -850,17 +852,19 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::usage_by_menu(
});
this->usage_inference(
[menu_input_identifier, menu_values](
const socket_usage_inference::InputSocketUsageParams &params) -> std::optional<bool> {
if (const std::optional<bool> any_output_used = params.any_output_is_used()) {
if (!*any_output_used) {
/* If no output is used, none of the inputs is used either. */
return false;
const socket_usage_inference::SocketUsageParams &params) -> std::optional<bool> {
if (params.socket.is_input()) {
if (const std::optional<bool> any_output_used = params.any_output_is_used()) {
if (!*any_output_used) {
/* If no output is used, none of the inputs is used either. */
return false;
}
}
else {
/* It's not known if any output is used yet. This function will be called again once
* new information about output usages is available. */
return std::nullopt;
}
}
else {
/* It's not known if any output is used yet. This function will be called again once new
* information about output usages is available. */
return std::nullopt;
}
/* Check if the menu might be any of the given values. */

View File

@@ -33,7 +33,7 @@ namespace blender::nodes::socket_usage_inference {
/** Utility class to simplify passing global state into all the functions during inferencing. */
class SocketUsageInferencerImpl {
private:
friend InputSocketUsageParams;
friend SocketUsageParams;
bke::ComputeContextCache &compute_context_cache_;
@@ -486,7 +486,7 @@ class SocketUsageInferencerImpl {
socket, socket->owner_node().output_sockets(), {}, socket.context);
return;
}
InputSocketUsageParams params{
SocketUsageParams params{
*owner_, socket.context, socket->owner_tree(), socket->owner_node(), *socket};
const std::optional<bool> is_used = (*socket_decl->usage_inference_fn)(params);
if (!is_used.has_value()) {
@@ -656,6 +656,22 @@ class SocketUsageInferencerImpl {
this->disabled_output_task__output__enable_output_node(socket);
break;
}
const SocketDeclaration *socket_declaration = socket->runtime->declaration;
if (socket_declaration && socket_declaration->usage_inference_fn) {
SocketUsageParams params{
*owner_, socket.context, socket->owner_tree(), socket->owner_node(), *socket};
const std::optional<bool> is_used = (*socket_declaration->usage_inference_fn)(params);
if (!is_used.has_value()) {
/* Some value was requested, come back later when that value is available. */
return;
}
if (!*is_used) {
all_socket_disable_states_.add_new(socket, true);
break;
}
}
/* By default, all output sockets are enabled unless they are explicitly disabled by some
* rule above. */
all_socket_disable_states_.add_new(socket, false);
@@ -969,11 +985,11 @@ void infer_group_interface_usage(const bNodeTree &group,
group, group_input_values, r_input_usages, r_output_usages);
}
InputSocketUsageParams::InputSocketUsageParams(SocketUsageInferencer &inferencer,
const ComputeContext *compute_context,
const bNodeTree &tree,
const bNode &node,
const bNodeSocket &socket)
SocketUsageParams::SocketUsageParams(SocketUsageInferencer &inferencer,
const ComputeContext *compute_context,
const bNodeTree &tree,
const bNode &node,
const bNodeSocket &socket)
: inferencer_(inferencer),
compute_context_(compute_context),
tree(tree),
@@ -982,13 +998,13 @@ InputSocketUsageParams::InputSocketUsageParams(SocketUsageInferencer &inferencer
{
}
InferenceValue InputSocketUsageParams::get_input(const StringRef identifier) const
InferenceValue SocketUsageParams::get_input(const StringRef identifier) const
{
const SocketInContext input_socket{compute_context_, this->node.input_by_identifier(identifier)};
return inferencer_.impl_.get_socket_value(input_socket);
}
std::optional<bool> InputSocketUsageParams::any_output_is_used() const
std::optional<bool> SocketUsageParams::any_output_is_used() const
{
const bNodeSocket *first_missing = nullptr;
for (const bNodeSocket *output_socket : this->node.output_sockets()) {
@@ -1010,8 +1026,7 @@ std::optional<bool> InputSocketUsageParams::any_output_is_used() const
return false;
}
bool InputSocketUsageParams::menu_input_may_be(const StringRef identifier,
const int enum_value) const
bool SocketUsageParams::menu_input_may_be(const StringRef identifier, const int enum_value) const
{
BLI_assert(this->node.input_by_identifier(identifier)->type == SOCK_MENU);
const InferenceValue value = this->get_input(identifier);