From 39c2f01b5131fdaf7d7f25e837a8b5ce52b1db74 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Mon, 19 May 2025 18:03:05 +0200 Subject: [PATCH] Nodes: add Bit Math node This adds a new Bit Math node which supports the following operations: `and`, `or`, `xor`, `not`, `shift` and `rotate`. For the `shift` and `rotate` operations, a posititive shift is a left shift and a negative shift is a right shift. Currently, the node always works on 32-bit integers which is what Geometry Nodes uses internally for integers. If required, this can be extended to work on other bit widths in the future. The need for this came up every now and then. It can be useful when encoding specific bits in integer attributes (for efficiency or because the geometry is exported to other software that expects a certain format). Also, this node is useful for some people doing crazy but fun things with Geometry Nodes like emulating hardware. Even if the use-cases are not common, if they arise, it's hard to work around and the cost of having this node is quite low for us. Co-authored-by: Charlie Jolly Pull Request: https://projects.blender.org/blender/blender/pulls/138290 --- .../startup/bl_ui/node_add_menu_geometry.py | 2 + .../blender/makesrna/intern/rna_nodetree.cc | 1 + source/blender/nodes/function/CMakeLists.txt | 1 + .../nodes/function/nodes/node_fn_bit_math.cc | 217 ++++++++++++++++++ 4 files changed, 221 insertions(+) create mode 100644 source/blender/nodes/function/nodes/node_fn_bit_math.cc diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 3aacc5004b4..1e908d22997 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -729,6 +729,8 @@ class NODE_MT_category_GEO_UTILITIES_MATH(Menu): def draw(self, context): layout = self.layout + node_add_menu.add_node_type_with_searchable_enum( + context, layout, "FunctionNodeBitMath", "operation", search_weight=-1.0) node_add_menu.add_node_type_with_searchable_enum(context, layout, "FunctionNodeBooleanMath", "operation") node_add_menu.add_node_type_with_searchable_enum(context, layout, "FunctionNodeIntegerMath", "operation") node_add_menu.add_node_type(layout, "ShaderNodeClamp") diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 05c8fec5f30..ffc123fa717 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -13829,6 +13829,7 @@ static void rna_def_nodes(BlenderRNA *brna) define("FunctionNode", "FunctionNodeAlignRotationToVector"); define("FunctionNode", "FunctionNodeAxesToRotation"); define("FunctionNode", "FunctionNodeAxisAngleToRotation"); + define("FunctionNode", "FunctionNodeBitMath"); define("FunctionNode", "FunctionNodeBooleanMath"); define("FunctionNode", "FunctionNodeCombineColor"); define("FunctionNode", "FunctionNodeCombineMatrix"); diff --git a/source/blender/nodes/function/CMakeLists.txt b/source/blender/nodes/function/CMakeLists.txt index 192807db19c..9c93b549f75 100644 --- a/source/blender/nodes/function/CMakeLists.txt +++ b/source/blender/nodes/function/CMakeLists.txt @@ -18,6 +18,7 @@ set(SRC nodes/node_fn_align_rotation_to_vector.cc nodes/node_fn_axes_to_rotation.cc nodes/node_fn_axis_angle_to_rotation.cc + nodes/node_fn_bit_math.cc nodes/node_fn_boolean_math.cc nodes/node_fn_combine_color.cc nodes/node_fn_combine_matrix.cc diff --git a/source/blender/nodes/function/nodes/node_fn_bit_math.cc b/source/blender/nodes/function/nodes/node_fn_bit_math.cc new file mode 100644 index 00000000000..3b36682d31c --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_bit_math.cc @@ -0,0 +1,217 @@ +/* SPDX-FileCopyrightText: 2025 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_string.h" + +#include "RNA_enum_types.hh" + +#include "UI_interface.hh" + +#include "NOD_rna_define.hh" +#include "NOD_socket_search_link.hh" + +#include "node_function_util.hh" + +static_assert(-1 == ~0, "Two's complement must be used for bitwise operations."); + +namespace blender::nodes::node_fn_bit_math_cc { + +enum BitMathOperation : int16_t { + And = 0, + Or = 1, + Xor = 2, + Not = 3, + Shift = 4, + Rotate = 5, +}; + +const std::array bit_math_operation_items = {{ + {BitMathOperation::And, + "AND", + 0, + "And", + "Returns a value where the bits of A and B are both set"}, + {BitMathOperation::Or, + "OR", + 0, + "Or", + "Returns a value where the bits of either A or B are set"}, + {BitMathOperation::Xor, + "XOR", + 0, + "Exclusive Or", + "Returns a value where only one bit from A and B is set"}, + {BitMathOperation::Not, + "NOT", + 0, + "Not", + "Returns the opposite bit value of A, in decimal it is equivalent of A = -A - 1"}, + {BitMathOperation::Shift, + "SHIFT", + 0, + "Shift", + "Shifts the bit values of A by the specified Shift amount. Positive values shift left, " + "negative values shift right."}, + {BitMathOperation::Rotate, + "ROTATE", + 0, + "Rotate", + "Rotates the bit values of A by the specified Shift amount. Positive values rotate left, " + "negative values rotate right."}, + {0, nullptr, 0, nullptr, nullptr}, +}}; + +constexpr static int32_t max_shift = sizeof(int32_t) * CHAR_BIT - 1; +constexpr static int32_t min_shift = -max_shift; + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.is_function_node(); + b.add_input("A"); + auto &b_socket = b.add_input("B"); + auto &shift = b.add_input("Shift").min(min_shift).max(max_shift); + b.add_output("Value"); + + if (const bNode *node = b.node_or_null()) { + const BitMathOperation operation = BitMathOperation(node->custom1); + b_socket.available(!ELEM( + operation, BitMathOperation::Not, BitMathOperation::Shift, BitMathOperation::Rotate)); + shift.available(ELEM(operation, BitMathOperation::Shift, BitMathOperation::Rotate)); + } +}; + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + layout->prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE); +} + +class SocketSearchOp { + public: + std::string socket_name; + BitMathOperation operation; + void operator()(LinkSearchOpParams ¶ms) + { + bNode &node = params.add_node("FunctionNodeBitMath"); + node.custom1 = int16_t(operation); + params.update_and_connect_available_socket(node, socket_name); + } +}; + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + if (!params.node_tree().typeinfo->validate_link(eNodeSocketDatatype(params.other_socket().type), + SOCK_INT)) + { + return; + } + + const bool is_integer = params.other_socket().type == SOCK_INT; + const int weight = is_integer ? 0 : -1; + + for (const auto &item : bit_math_operation_items) { + if (item.name != nullptr && item.identifier[0] != '\0') { + params.add_item( + IFACE_(item.name), SocketSearchOp{"A", BitMathOperation(item.value)}, weight); + } + } +} + +static void node_label(const bNodeTree * /*ntree*/, const bNode *node, char *label, int maxlen) +{ + char name[64] = {0}; + const char *operation_name = IFACE_("Unknown"); + RNA_enum_name(bit_math_operation_items.data(), node->custom1, &operation_name); + SNPRINTF(name, IFACE_("Bitwise %s"), operation_name); + BLI_strncpy(label, name, maxlen); +} + +static const mf::MultiFunction *get_multi_function(const bNode &bnode) +{ + const BitMathOperation operation = BitMathOperation(bnode.custom1); + static auto exec_preset = mf::build::exec_presets::AllSpanOrSingle(); + static auto and_fn = mf::build::SI2_SO( + "And", [](int a, int b) { return a & b; }, exec_preset); + static auto or_fn = mf::build::SI2_SO( + "Or", [](int a, int b) { return a | b; }, exec_preset); + static auto xor_fn = mf::build::SI2_SO( + "Xor", [](int a, int b) { return a ^ b; }, exec_preset); + static auto not_fn = mf::build::SI1_SO( + "Not", [](int a) { return ~a; }, exec_preset); + static auto shift_fn = mf::build::SI2_SO( + "Shift", + [](int a, int b) { + const uint32_t value = a; + const int shift = math::clamp(b, -32, 32); + const uint64_t wide_value = uint64_t(value) << 16; + const uint64_t wide_result = shift > 0 ? wide_value << shift : wide_value >> -shift; + return uint32_t(wide_result >> 16); + }, + exec_preset); + static auto rotate_fn = mf::build::SI2_SO( + "Rotate", + [](int a, int b) { + const uint32_t value = a; + const int shift = math::mod_periodic(b, 32); + const uint64_t wide_value = uint64_t(value) | (uint64_t(value) << 32); + const uint64_t double_result = (wide_value << shift); + return uint32_t((double_result | (double_result >> 32)) & ((uint64_t(1) << 33) - 1)); + }, + exec_preset); + + switch (operation) { + case BitMathOperation::And: + return &and_fn; + case BitMathOperation::Or: + return &or_fn; + case BitMathOperation::Xor: + return &xor_fn; + case BitMathOperation::Not: + return ¬_fn; + case BitMathOperation::Shift: + return &shift_fn; + case BitMathOperation::Rotate: + return &rotate_fn; + } + BLI_assert_unreachable(); + return nullptr; +} + +static void node_build_multi_function(NodeMultiFunctionBuilder &builder) +{ + const mf::MultiFunction *fn = get_multi_function(builder.node()); + builder.set_matching_fn(fn); +} + +static void node_rna(StructRNA *srna) +{ + PropertyRNA *prop = RNA_def_node_enum(srna, + "operation", + "Operation", + "", + bit_math_operation_items.data(), + NOD_inline_enum_accessors(custom1), + BitMathOperation::And); + RNA_def_property_update_runtime(prop, rna_Node_socket_update); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + + fn_node_type_base(&ntype, "FunctionNodeBitMath"); + ntype.ui_name = "Bit Math"; + ntype.nclass = NODE_CLASS_CONVERTER; + ntype.declare = node_declare; + ntype.labelfunc = node_label; + ntype.build_multi_function = node_build_multi_function; + ntype.draw_buttons = node_layout; + ntype.gather_link_search_ops = node_gather_link_searches; + ntype.ui_description = "Perform bitwise operations on 32-bit integers"; + + blender::bke::node_register_type(ntype); + node_rna(ntype.rna_ext.srna); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_fn_bit_math_cc