Add Chromium-only Blender WebEngine parity work
This commit is contained in:
267
blender-5.2.0/source/blender/nodes/CMakeLists.txt
Normal file
267
blender-5.2.0/source/blender/nodes/CMakeLists.txt
Normal file
@@ -0,0 +1,267 @@
|
||||
# SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(DISCOVER_NODES_PATH ${CMAKE_CURRENT_SOURCE_DIR}/intern/discover_nodes.py)
|
||||
|
||||
# Creates a library with the given name that exposes one function which calls
|
||||
# the register functions of all nodes in the provided source files.
|
||||
function(add_node_discovery
|
||||
lib_name
|
||||
sources
|
||||
output_file
|
||||
output_function
|
||||
)
|
||||
|
||||
set(extra_args "")
|
||||
|
||||
# See `add_definitions(-DUSE_MAKEFILE_WORKAROUND)` comment for `makesrna`.
|
||||
if(CMAKE_GENERATOR MATCHES ".*Unix Makefiles")
|
||||
set(extra_args "--use-makefile-workaround")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${output_file}
|
||||
COMMAND
|
||||
${PYTHON_EXECUTABLE}
|
||||
${DISCOVER_NODES_PATH}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${output_file}
|
||||
${output_function}
|
||||
${sources}
|
||||
${extra_args}
|
||||
DEPENDS
|
||||
${sources}
|
||||
${DISCOVER_NODES_PATH}
|
||||
)
|
||||
set_source_files_properties(${output_file} PROPERTIES GENERATED TRUE)
|
||||
add_library(${lib_name} ${output_file})
|
||||
endfunction()
|
||||
|
||||
add_subdirectory(composite)
|
||||
add_subdirectory(function)
|
||||
add_subdirectory(geometry)
|
||||
add_subdirectory(shader)
|
||||
add_subdirectory(texture)
|
||||
|
||||
set(INC
|
||||
PUBLIC .
|
||||
composite
|
||||
function
|
||||
geometry
|
||||
intern
|
||||
shader
|
||||
texture
|
||||
geometry/include
|
||||
../animrig
|
||||
../compositor
|
||||
../compositor/cached_resources
|
||||
../editors/include
|
||||
../depsgraph
|
||||
../makesrna
|
||||
../modifiers
|
||||
|
||||
# RNA_prototypes.hh
|
||||
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
)
|
||||
|
||||
set(SRC
|
||||
intern/bundle_type.cc
|
||||
intern/compositor_gizmos.cc
|
||||
intern/compositor_nodes_caller_ui.cc
|
||||
intern/compositor_nodes_srna.cc
|
||||
intern/dependencies.cc
|
||||
intern/eval_log.cc
|
||||
intern/geometry_nodes_bundle.cc
|
||||
intern/geometry_nodes_caller_ui.cc
|
||||
intern/geometry_nodes_closure.cc
|
||||
intern/geometry_nodes_closure_zone.cc
|
||||
intern/geometry_nodes_execute.cc
|
||||
intern/geometry_nodes_foreach_geometry_element_zone.cc
|
||||
intern/geometry_nodes_gizmos.cc
|
||||
intern/geometry_nodes_lazy_function.cc
|
||||
intern/geometry_nodes_list.cc
|
||||
intern/geometry_nodes_repeat_zone.cc
|
||||
intern/geometry_nodes_physics_bundles.cc
|
||||
intern/geometry_nodes_srna.cc
|
||||
intern/geometry_nodes_warning.cc
|
||||
intern/inverse_eval.cc
|
||||
intern/list_function_eval.cc
|
||||
intern/math_functions.cc
|
||||
intern/node_caller_ui.cc
|
||||
intern/node_common.cc
|
||||
intern/node_declaration.cc
|
||||
intern/node_exec.cc
|
||||
intern/node_geometry_exec.cc
|
||||
intern/node_multi_function.cc
|
||||
intern/node_register.cc
|
||||
intern/node_rna_define.cc
|
||||
intern/node_socket.cc
|
||||
intern/node_socket_declarations.cc
|
||||
intern/node_tree_defaults.cc
|
||||
intern/node_util.cc
|
||||
intern/partial_eval.cc
|
||||
intern/shader_nodes_inline.cc
|
||||
intern/socket_search_link.cc
|
||||
intern/socket_usage_inference.cc
|
||||
intern/socket_value_inference.cc
|
||||
intern/string_pattern.cc
|
||||
intern/sync_sockets.cc
|
||||
intern/trace_values.cc
|
||||
intern/value_elem.cc
|
||||
intern/volume_grid_function_eval.cc
|
||||
|
||||
NOD_bundle_type.hh
|
||||
NOD_caller_ui.hh
|
||||
NOD_common.hh
|
||||
NOD_composite.hh
|
||||
NOD_compositor_gizmos.hh
|
||||
NOD_compositor_nodes_caller_ui.hh
|
||||
NOD_compositor_nodes_srna.hh
|
||||
NOD_defaults.hh
|
||||
NOD_dependencies.hh
|
||||
NOD_eval_log.hh
|
||||
NOD_geometry.hh
|
||||
NOD_geometry_exec.hh
|
||||
NOD_geometry_nodes_bundle.hh
|
||||
NOD_geometry_nodes_bundle_fwd.hh
|
||||
NOD_geometry_nodes_bundle_signature.hh
|
||||
NOD_geometry_nodes_caller_ui.hh
|
||||
NOD_geometry_nodes_closure.hh
|
||||
NOD_geometry_nodes_closure_eval.hh
|
||||
NOD_geometry_nodes_closure_fwd.hh
|
||||
NOD_geometry_nodes_closure_location.hh
|
||||
NOD_geometry_nodes_closure_signature.hh
|
||||
NOD_geometry_nodes_execute.hh
|
||||
NOD_geometry_nodes_gizmos.hh
|
||||
NOD_geometry_nodes_lazy_function.hh
|
||||
NOD_geometry_nodes_list.hh
|
||||
NOD_geometry_nodes_list_fwd.hh
|
||||
NOD_geometry_nodes_physics_bundles.hh
|
||||
NOD_geometry_nodes_srna.hh
|
||||
NOD_geometry_nodes_values.hh
|
||||
NOD_geometry_nodes_warning.hh
|
||||
NOD_inverse_eval_params.hh
|
||||
NOD_inverse_eval_path.hh
|
||||
NOD_inverse_eval_run.hh
|
||||
NOD_math_functions.hh
|
||||
NOD_menu_value.hh
|
||||
NOD_multi_function.hh
|
||||
NOD_nested_node_id.hh
|
||||
NOD_node_declaration.hh
|
||||
NOD_node_extra_info.hh
|
||||
NOD_node_in_compute_context.hh
|
||||
NOD_nodes_srna.hh
|
||||
NOD_partial_eval.hh
|
||||
NOD_register.hh
|
||||
NOD_rna_define.hh
|
||||
NOD_shader.h
|
||||
NOD_shader_nodes_inline.hh
|
||||
NOD_socket.hh
|
||||
NOD_socket_declarations.hh
|
||||
NOD_socket_declarations_geometry.hh
|
||||
NOD_socket_items.hh
|
||||
NOD_socket_items_blend.hh
|
||||
NOD_socket_items_ops.hh
|
||||
NOD_socket_items_ui.hh
|
||||
NOD_socket_search_link.hh
|
||||
NOD_socket_usage_inference.hh
|
||||
NOD_socket_usage_inference_fwd.hh
|
||||
NOD_socket_value_inference.hh
|
||||
NOD_string_pattern.hh
|
||||
NOD_sync_sockets.hh
|
||||
NOD_texture.h
|
||||
NOD_trace_values.hh
|
||||
NOD_value_elem.hh
|
||||
NOD_value_elem_eval.hh
|
||||
intern/list_function_eval.hh
|
||||
intern/node_common.h
|
||||
intern/node_exec.hh
|
||||
intern/node_util.hh
|
||||
intern/volume_grid_function_eval.hh
|
||||
)
|
||||
|
||||
set(LIB
|
||||
PRIVATE bf::blenkernel
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::blenloader
|
||||
PRIVATE bf::blentranslation
|
||||
PRIVATE bf::bmesh
|
||||
PRIVATE bf::depsgraph
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::functions
|
||||
PRIVATE bf::geometry
|
||||
PRIVATE bf::gpu
|
||||
PRIVATE bf::imbuf
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
bf_nodes_composite
|
||||
bf_nodes_function
|
||||
bf_nodes_geometry
|
||||
bf_nodes_shader
|
||||
bf_nodes_texture
|
||||
PRIVATE bf::extern::xxhash
|
||||
PRIVATE bf::render
|
||||
PRIVATE bf::sequencer
|
||||
PRIVATE bf::windowmanager
|
||||
PRIVATE bf::intern::clog
|
||||
PRIVATE bf::dependencies::optional::gmp
|
||||
PRIVATE bf::dependencies::optional::bullet
|
||||
)
|
||||
|
||||
if(WITH_FREESTYLE)
|
||||
add_definitions(-DWITH_FREESTYLE)
|
||||
endif()
|
||||
|
||||
if(WITH_OPENSUBDIV)
|
||||
add_definitions(-DWITH_OPENSUBDIV)
|
||||
endif()
|
||||
|
||||
if(WITH_WEB)
|
||||
# Keep the node datablock and dependency-query implementation needed by
|
||||
# loading scenes. Geometry/compositor execution and editor integration stay
|
||||
# outside the headless Three.js-rendered engine.
|
||||
set(SRC
|
||||
intern/bundle_type.cc
|
||||
intern/dependencies.cc
|
||||
intern/eval_log.cc
|
||||
intern/geometry_nodes_bundle.cc
|
||||
intern/node_declaration.cc
|
||||
intern/node_socket.cc
|
||||
intern/node_socket_declarations.cc
|
||||
intern/node_util.cc
|
||||
|
||||
NOD_bundle_type.hh
|
||||
NOD_dependencies.hh
|
||||
NOD_eval_log.hh
|
||||
NOD_geometry_nodes_bundle.hh
|
||||
NOD_common.hh
|
||||
NOD_node_declaration.hh
|
||||
NOD_socket.hh
|
||||
NOD_socket_declarations.hh
|
||||
)
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_nodes "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
add_library(bf::nodes ALIAS bf_nodes)
|
||||
|
||||
# RNA_prototypes.hh
|
||||
add_dependencies(bf_nodes bf_rna)
|
||||
|
||||
if(WITH_GTESTS)
|
||||
set(TEST_INC
|
||||
)
|
||||
set(TEST_SRC
|
||||
intern/geometry_nodes_bundle_tests.cc
|
||||
intern/node_iterator_tests.cc
|
||||
intern/node_structure_type_inferencing_tests.cc
|
||||
)
|
||||
set(TEST_LIB
|
||||
${LIB}
|
||||
bf_nodes
|
||||
)
|
||||
blender_add_test_suite_lib(nodes "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${TEST_LIB}")
|
||||
endif()
|
||||
164
blender-5.2.0/source/blender/nodes/NOD_bundle_type.hh
Normal file
164
blender-5.2.0/source/blender/nodes/NOD_bundle_type.hh
Normal file
@@ -0,0 +1,164 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "NOD_bundle_type_fwd.hh"
|
||||
#include "NOD_geometry_nodes_bundle_signature.hh"
|
||||
#include "NOD_node_declaration.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
class FlatBundleType {
|
||||
public:
|
||||
struct Item {
|
||||
std::unique_ptr<SocketDeclaration> decl;
|
||||
|
||||
UString name() const;
|
||||
};
|
||||
|
||||
private:
|
||||
struct ItemNameGetter {
|
||||
UString operator()(const Item &item)
|
||||
{
|
||||
return item.decl->name;
|
||||
}
|
||||
};
|
||||
|
||||
std::string name_;
|
||||
CustomIDVectorSet<Item, ItemNameGetter> items_;
|
||||
|
||||
public:
|
||||
FlatBundleType(std::string name, Vector<std::unique_ptr<SocketDeclaration>> decls);
|
||||
|
||||
StringRefNull name() const;
|
||||
|
||||
Span<Item> items() const;
|
||||
const SocketDeclaration *find_decl(const UString name) const;
|
||||
|
||||
BundleSignature to_bundle_signature() const;
|
||||
};
|
||||
|
||||
class NestedBundleType {
|
||||
private:
|
||||
struct ItemNameGetter {
|
||||
std::string operator()(const FlatBundleTypePtr &item)
|
||||
{
|
||||
return item->name();
|
||||
}
|
||||
};
|
||||
|
||||
std::string name_;
|
||||
CustomIDVectorSet<FlatBundleTypePtr, ItemNameGetter> items_;
|
||||
|
||||
public:
|
||||
NestedBundleType(std::string name, Vector<FlatBundleTypePtr> bundle_types);
|
||||
|
||||
StringRefNull name() const;
|
||||
Span<FlatBundleTypePtr> items() const;
|
||||
};
|
||||
|
||||
class BundleType {
|
||||
public:
|
||||
std::variant<FlatBundleTypePtr, NestedBundleTypePtr> type;
|
||||
|
||||
BundleType(FlatBundleTypePtr type) : type(std::move(type)) {}
|
||||
BundleType(NestedBundleTypePtr type) : type(std::move(type)) {}
|
||||
|
||||
StringRefNull name() const;
|
||||
|
||||
uint64_t hash() const;
|
||||
|
||||
friend bool operator==(const BundleType &a, const BundleType &b) = default;
|
||||
};
|
||||
|
||||
class FlatBundleTypeBuilder {
|
||||
private:
|
||||
std::string name_;
|
||||
Vector<std::unique_ptr<BaseSocketDeclarationBuilder>> builders_;
|
||||
Vector<std::unique_ptr<SocketDeclaration>> decls_;
|
||||
|
||||
public:
|
||||
FlatBundleTypeBuilder(std::string name);
|
||||
|
||||
template<typename DeclType> typename DeclType::Builder &add(UString name);
|
||||
|
||||
FlatBundleTypePtr build();
|
||||
};
|
||||
|
||||
class BundleTypeRegistry {
|
||||
Map<std::string, Set<BundleType>> types_;
|
||||
|
||||
public:
|
||||
static void register_type(BundleType bundle_type);
|
||||
static FlatBundleTypePtr try_find_single_flat(StringRef name);
|
||||
static Vector<std::string> get_all_flat_type_names();
|
||||
};
|
||||
|
||||
template<typename DeclType>
|
||||
inline typename DeclType::Builder &FlatBundleTypeBuilder::add(UString name)
|
||||
{
|
||||
static_assert(std::is_base_of_v<SocketDeclaration, DeclType>);
|
||||
using SocketBuilder = typename DeclType::Builder;
|
||||
|
||||
auto decl_ptr = std::make_unique<DeclType>();
|
||||
DeclType &decl = *decl_ptr;
|
||||
|
||||
auto decl_builder_ptr = std::make_unique<SocketBuilder>();
|
||||
SocketBuilder &decl_builder = *decl_builder_ptr;
|
||||
|
||||
decl_builder.decl_ = &decl;
|
||||
decl_builder.decl_base_ = &decl;
|
||||
|
||||
decl.name = name;
|
||||
decl.identifier = name;
|
||||
decl.in_out = SOCK_IN;
|
||||
decl.socket_type = DeclType::static_socket_type;
|
||||
|
||||
decls_.append(std::move(decl_ptr));
|
||||
builders_.append(std::move(decl_builder_ptr));
|
||||
return decl_builder;
|
||||
}
|
||||
|
||||
inline UString FlatBundleType::Item::name() const
|
||||
{
|
||||
return this->decl->name;
|
||||
}
|
||||
|
||||
inline StringRefNull FlatBundleType::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
inline Span<FlatBundleType::Item> FlatBundleType::items() const
|
||||
{
|
||||
return items_;
|
||||
}
|
||||
|
||||
inline StringRefNull NestedBundleType::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
inline Span<FlatBundleTypePtr> NestedBundleType::items() const
|
||||
{
|
||||
return items_;
|
||||
}
|
||||
|
||||
inline StringRefNull BundleType::name() const
|
||||
{
|
||||
return std::visit([](auto &&type) { return type->name(); }, this->type);
|
||||
}
|
||||
|
||||
inline uint64_t BundleType::hash() const
|
||||
{
|
||||
return std::visit([](auto &&type) { return get_default_hash(type); }, this->type);
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
17
blender-5.2.0/source/blender/nodes/NOD_bundle_type_fwd.hh
Normal file
17
blender-5.2.0/source/blender/nodes/NOD_bundle_type_fwd.hh
Normal file
@@ -0,0 +1,17 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace blender::nodes {
|
||||
class FlatBundleType;
|
||||
class NestedBundleType;
|
||||
class BundleType;
|
||||
|
||||
using FlatBundleTypePtr = std::shared_ptr<const FlatBundleType>;
|
||||
using NestedBundleTypePtr = std::shared_ptr<const NestedBundleType>;
|
||||
|
||||
} // namespace blender::nodes
|
||||
51
blender-5.2.0/source/blender/nodes/NOD_caller_ui.hh
Normal file
51
blender-5.2.0/source/blender/nodes/NOD_caller_ui.hh
Normal file
@@ -0,0 +1,51 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bContext;
|
||||
struct PointerRNA;
|
||||
struct bNodeTreeInterfacePanel;
|
||||
struct bNodeTreeInterfaceSocket;
|
||||
|
||||
namespace ui {
|
||||
struct Layout;
|
||||
} // namespace ui
|
||||
|
||||
namespace nodes {
|
||||
|
||||
void draw_interface_panel_as_panel(
|
||||
const bContext &C,
|
||||
ui::Layout &layout,
|
||||
PointerRNA *properties_ptr,
|
||||
const bNodeTreeInterfacePanel &interface_panel,
|
||||
FunctionRef<bool(const bNodeTreeInterfaceSocket &)> fn_input_is_visible,
|
||||
FunctionRef<bool(const bNodeTreeInterfaceSocket &)> fn_input_is_active,
|
||||
FunctionRef<void(ui::Layout &,
|
||||
const bNodeTreeInterfaceSocket &,
|
||||
PointerRNA *,
|
||||
const std::optional<StringRef>)> fn_draw_property_for_socket);
|
||||
|
||||
void draw_interface_panel_content(
|
||||
const bContext &C,
|
||||
ui::Layout &layout,
|
||||
PointerRNA *properties_ptr,
|
||||
const bNodeTreeInterfacePanel &interface_panel,
|
||||
FunctionRef<bool(const bNodeTreeInterfaceSocket &)> fn_input_is_visible,
|
||||
FunctionRef<bool(const bNodeTreeInterfaceSocket &)> fn_input_is_active,
|
||||
FunctionRef<void(ui::Layout &,
|
||||
const bNodeTreeInterfaceSocket &,
|
||||
PointerRNA *,
|
||||
const std::optional<StringRef>)> fn_draw_property_for_socket,
|
||||
bool skip_first = false,
|
||||
std::optional<StringRef> parent_name = std::nullopt);
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
34
blender-5.2.0/source/blender/nodes/NOD_common.hh
Normal file
34
blender-5.2.0/source/blender/nodes/NOD_common.hh
Normal file
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2005 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup nodes
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bNode;
|
||||
struct bNodeSocket;
|
||||
namespace nodes {
|
||||
class NodeDeclarationBuilder;
|
||||
} // namespace nodes
|
||||
|
||||
bNodeSocket *node_group_find_input_socket(bNode *groupnode, StringRef identifier);
|
||||
bNodeSocket *node_group_find_output_socket(bNode *groupnode, StringRef identifier);
|
||||
|
||||
bNodeSocket *node_group_input_find_socket(bNode *node, StringRef identifier);
|
||||
bNodeSocket *node_group_output_find_socket(bNode *node, StringRef identifier);
|
||||
|
||||
int node_group_ui_class(const bNode *node);
|
||||
|
||||
namespace nodes {
|
||||
|
||||
void node_group_declare(NodeDeclarationBuilder &b);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
54
blender-5.2.0/source/blender/nodes/NOD_composite.hh
Normal file
54
blender-5.2.0/source/blender/nodes/NOD_composite.hh
Normal file
@@ -0,0 +1,54 @@
|
||||
/* SPDX-FileCopyrightText: 2005 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup nodes
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct CryptomatteSession;
|
||||
struct Scene;
|
||||
|
||||
extern bke::bNodeTreeType *ntreeType_Composite;
|
||||
|
||||
void register_node_tree_type_cmp();
|
||||
void register_node_type_cmp_custom_group(bke::bNodeType *ntype);
|
||||
|
||||
void node_cmp_rlayers_outputs(bNodeTree *ntree, bNode *node);
|
||||
|
||||
/**
|
||||
* Called from render pipeline, to tag render input and output.
|
||||
* need to do all scenes, to prevent errors when you re-render 1 scene.
|
||||
*/
|
||||
void ntreeCompositTagRender(Scene *scene);
|
||||
|
||||
/**
|
||||
* \note: Requires that viewlayers are in sync (call `BKE_main_view_layers_synced_ensure` or
|
||||
* similar before).
|
||||
*/
|
||||
void ntreeCompositCryptomatteSyncFromAdd(bNode *node);
|
||||
|
||||
void ntreeCompositCryptomatteSyncFromRemove(bNode *node);
|
||||
void ntreeCompositCryptomatteAddSocket(bNode *node);
|
||||
bool ntreeCompositCryptomatteRemoveSocket(bNode *node);
|
||||
void ntreeCompositCryptomatteLayerPrefix(const bNode *node, char *r_prefix, size_t prefix_maxncpy);
|
||||
|
||||
/**
|
||||
* Update the runtime layer names with the crypto-matte layer names of the references render layer
|
||||
* or image.
|
||||
*/
|
||||
void ntreeCompositCryptomatteUpdateLayerNames(bNode *node);
|
||||
|
||||
/**
|
||||
* \note: Requires that viewlayers are in sync (call `BKE_main_view_layers_synced_ensure` or
|
||||
* similar before).
|
||||
*/
|
||||
CryptomatteSession *ntreeCompositCryptomatteSession(bNode *node);
|
||||
|
||||
} // namespace blender
|
||||
107
blender-5.2.0/source/blender/nodes/NOD_compositor_gizmos.hh
Normal file
107
blender-5.2.0/source/blender/nodes/NOD_compositor_gizmos.hh
Normal file
@@ -0,0 +1,107 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup nodes
|
||||
*/
|
||||
|
||||
namespace blender {
|
||||
struct bContext;
|
||||
struct wmGizmoGroup;
|
||||
struct ARegion;
|
||||
struct wmGizmoGroupType;
|
||||
|
||||
namespace nodes::gizmos {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Common BBox Gizmos
|
||||
* \{ */
|
||||
|
||||
void bbox_draw_prepare_space_node(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
void box_mask_refresh(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
void bbox_draw_prepare_space_image(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Box Mask
|
||||
* \{ */
|
||||
|
||||
void box_mask_setup(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
bool box_mask_poll_space_node(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
bool box_mask_poll_space_image(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Crop
|
||||
* \{ */
|
||||
|
||||
void crop_refresh(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
void crop_setup(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
bool crop_poll_space_node(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
void crop_draw_prepare_space_node(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
bool crop_poll_space_image(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Glare
|
||||
* \{ */
|
||||
|
||||
void glare_setup(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
void glare_refresh(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
bool glare_poll_space_image(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
void glare_draw_prepare_space_image(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
bool glare_poll_space_node(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
void glare_draw_prepare_space_node(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Corner Pin
|
||||
* \{ */
|
||||
|
||||
void corner_pin_setup(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
void corner_pin_refresh(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
bool corner_pin_poll_space_image(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
bool corner_pin_poll_space_node(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
void corner_pin_draw_prepare_space_image(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
void corner_pin_draw_prepare_space_node(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Ellipse Mask
|
||||
* \{ */
|
||||
|
||||
void ellipse_mask_setup(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
bool ellipse_mask_poll_space_node(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
bool ellipse_mask_poll_space_image(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Split
|
||||
* \{ */
|
||||
|
||||
void split_refresh(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
void split_setup(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
bool split_poll_space_node(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
bool split_poll_space_image(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Backdrop Gizmo
|
||||
* \{ */
|
||||
|
||||
bool transform_poll(const bContext *C, wmGizmoGroupType *gzgt);
|
||||
void transform_refresh(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
void transform_setup(const bContext *C, wmGizmoGroup *gzgroup);
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace nodes::gizmos
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,23 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bContext;
|
||||
struct PointerRNA;
|
||||
|
||||
namespace ui {
|
||||
struct Layout;
|
||||
} // namespace ui
|
||||
|
||||
namespace nodes {
|
||||
|
||||
void draw_compositor_nodes_modifier_ui(const bContext &C,
|
||||
PointerRNA *modifier_ptr,
|
||||
ui::Layout &layout);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,28 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NOD_nodes_srna.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct StructRNA;
|
||||
struct bNodeTree;
|
||||
|
||||
namespace nodes {
|
||||
|
||||
enum class CompositorNodesInputType {
|
||||
Fallback = 0,
|
||||
Value = 1,
|
||||
};
|
||||
|
||||
extern const EnumPropertyItem compositor_nodes_input_type_items_fallback[];
|
||||
extern const EnumPropertyItem compositor_nodes_input_type_items_value[];
|
||||
|
||||
std::shared_ptr<GeneratedTreeSrnaData> create_compositor_nodes_rna_for_strip_modifier(
|
||||
const bNodeTree &tree);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
39
blender-5.2.0/source/blender/nodes/NOD_defaults.hh
Normal file
39
blender-5.2.0/source/blender/nodes/NOD_defaults.hh
Normal file
@@ -0,0 +1,39 @@
|
||||
/* SPDX-FileCopyrightText: 2005 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup nodes
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bContext;
|
||||
struct bNodeTree;
|
||||
struct ID;
|
||||
struct Main;
|
||||
struct Scene;
|
||||
|
||||
namespace nodes {
|
||||
|
||||
/**
|
||||
* Assumes nothing being done in ntree yet, sets the default in/out node.
|
||||
* Called from shading buttons or header.
|
||||
*/
|
||||
void node_tree_shader_default(const bContext *C, Main *bmain, ID *id);
|
||||
|
||||
/**
|
||||
* Assumes nothing being done in ntree yet, sets the default in/out node.
|
||||
* Called from compositing buttons or header.
|
||||
*/
|
||||
void node_tree_composit_default(const bContext *C, Scene *sce);
|
||||
|
||||
/**
|
||||
* Initializes an empty compositing node tree with default nodes.
|
||||
*/
|
||||
void node_tree_composit_default_init(const bContext *C, bNodeTree *ntree);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
89
blender-5.2.0/source/blender/nodes/NOD_dependencies.hh
Normal file
89
blender-5.2.0/source/blender/nodes/NOD_dependencies.hh
Normal file
@@ -0,0 +1,89 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_map.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct ID;
|
||||
struct Object;
|
||||
struct bNodeTree;
|
||||
|
||||
namespace nodes {
|
||||
|
||||
/**
|
||||
* Gathers dependencies that the node tree requires before it can be evaluated.
|
||||
*/
|
||||
struct EvalDependencies {
|
||||
/**
|
||||
* Stores additional dependency information for objects. It can be more efficient to only depend
|
||||
* on an object partially.
|
||||
*/
|
||||
struct ObjectDependencyInfo {
|
||||
bool transform = false;
|
||||
bool geometry = false;
|
||||
bool camera_parameters = false;
|
||||
bool pose = false;
|
||||
|
||||
friend bool operator==(const ObjectDependencyInfo &a, const ObjectDependencyInfo &b) = default;
|
||||
};
|
||||
static constexpr ObjectDependencyInfo all_object_deps{true, true, true, true};
|
||||
|
||||
/**
|
||||
* Maps `session_uid` to the corresponding data-block.
|
||||
* The data-block pointer is not used as key in this map, so that it can be modified in
|
||||
* #node_foreach_id.
|
||||
*/
|
||||
Map<uint32_t, ID *> ids;
|
||||
|
||||
/** Additional information for object dependencies. */
|
||||
Map<uint32_t, ObjectDependencyInfo> objects_info;
|
||||
|
||||
bool needs_own_transform = false;
|
||||
bool needs_active_camera = false;
|
||||
bool needs_scene_render_params = false;
|
||||
bool time_dependent = false;
|
||||
|
||||
/**
|
||||
* Adds a generic data-block dependency. Note that this does not add a dependency to e.g. the
|
||||
* transform or geometry of an object. If that is desired, use #add_object or
|
||||
* #add_generic_id_full instead.
|
||||
*/
|
||||
void add_generic_id(ID *id);
|
||||
|
||||
/**
|
||||
* Adds a data-block as dependency. For objects, it also adds a dependency to the transform and
|
||||
* geometry.
|
||||
*/
|
||||
void add_generic_id_full(ID *id);
|
||||
|
||||
/**
|
||||
* Add an object as dependency. It's customizable whether e.g. the transform and/or geometry is
|
||||
* required.
|
||||
*/
|
||||
void add_object(Object *object, const ObjectDependencyInfo &object_deps = all_object_deps);
|
||||
|
||||
/**
|
||||
* Add all the given dependencies to this one.
|
||||
*/
|
||||
void merge(const EvalDependencies &other);
|
||||
|
||||
friend bool operator==(const EvalDependencies &a, const EvalDependencies &b) = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds all evaluation dependencies for the given node. This does not include dependencies that
|
||||
* are passed into the node group. It also may not contain all data-blocks referenced by the node
|
||||
* tree if some of them can statically be detected to not be used by the evaluation.
|
||||
*/
|
||||
EvalDependencies gather_eval_dependencies_recursive(const bNodeTree &ntree);
|
||||
/**
|
||||
* Same as above, but assumes that dependencies are already cached on the referenced node groups.
|
||||
*/
|
||||
EvalDependencies gather_eval_dependencies_with_cache(const bNodeTree &ntree);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
559
blender-5.2.0/source/blender/nodes/NOD_eval_log.hh
Normal file
559
blender-5.2.0/source/blender/nodes/NOD_eval_log.hh
Normal file
@@ -0,0 +1,559 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup nodes
|
||||
*
|
||||
* Many geometry nodes related UI features need access to data produced during evaluation. Not only
|
||||
* is the final output required but also the intermediate results. Those features include attribute
|
||||
* search, node warnings, socket inspection and the viewer node.
|
||||
*
|
||||
* This file provides the system for logging data during evaluation and accessing the data after
|
||||
* evaluation. At the root of the logging data is a #NodesEvalLog which is created by the code that
|
||||
* invokes Geometry Nodes (e.g. the Geometry Nodes modifier).
|
||||
*
|
||||
* The system makes a distinction between "loggers" and the "log":
|
||||
* - Logger (#NodeTreeLogger): Is used during geometry nodes evaluation. Each thread logs data
|
||||
* independently to avoid communication between threads. Logging should generally be fast.
|
||||
* Generally, the logged data is just dumped into simple containers. Any processing of the data
|
||||
* happens later if necessary. This is important for performance, because in practice, most of
|
||||
* the logged data is never used again. So any processing of the data is likely to be a waste of
|
||||
* resources.
|
||||
* - Log (#NodeTreeLog, #NodeLog): Those are used when accessing logged data in UI code. They
|
||||
* contain and cache preprocessed data produced during logging. The log combines data from all
|
||||
* thread-local loggers to provide simple access. Importantly, the (preprocessed) log is only
|
||||
* created when it is actually used by UI code.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "BLI_cache_mutex.hh"
|
||||
#include "BLI_compute_context.hh"
|
||||
#include "BLI_enum_flags.hh"
|
||||
#include "BLI_enumerable_thread_specific.hh"
|
||||
#include "BLI_generic_pointer.hh"
|
||||
#include "BLI_linear_allocator_chunked_list.hh"
|
||||
#include "BLI_ustring.hh"
|
||||
|
||||
#include "BKE_compute_context_cache_fwd.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_socket_value.hh"
|
||||
#include "BKE_node_tree_zones.hh"
|
||||
#include "BKE_volume_grid_fwd.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_closure_location.hh"
|
||||
#include "NOD_geometry_nodes_list_fwd.hh"
|
||||
#include "NOD_geometry_nodes_warning.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct SpaceNode;
|
||||
struct NodesModifierData;
|
||||
struct Report;
|
||||
struct ImBuf;
|
||||
|
||||
namespace fn {
|
||||
class GField;
|
||||
}
|
||||
|
||||
namespace nodes::eval_log {
|
||||
|
||||
using fn::GField;
|
||||
|
||||
struct NodeWarning {
|
||||
NodeWarningType type;
|
||||
std::string message;
|
||||
|
||||
NodeWarning(NodeWarningType type, StringRef message) : type(type), message(message) {}
|
||||
NodeWarning(const Report &report);
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(this->type, this->message);
|
||||
}
|
||||
|
||||
friend bool operator==(const NodeWarning &a, const NodeWarning &b) = default;
|
||||
};
|
||||
|
||||
enum class NamedAttributeUsage {
|
||||
None = 0,
|
||||
Read = 1 << 0,
|
||||
Write = 1 << 1,
|
||||
Remove = 1 << 2,
|
||||
};
|
||||
ENUM_OPERATORS(NamedAttributeUsage);
|
||||
|
||||
/**
|
||||
* Values of different types are logged differently. This is necessary because some types are so
|
||||
* simple that we can log them entirely (e.g. `int`), while we don't want to log all intermediate
|
||||
* geometries in their entirety.
|
||||
*
|
||||
* #ValueLog is a base class for the different ways we log values.
|
||||
*/
|
||||
class ValueLog {
|
||||
public:
|
||||
virtual ~ValueLog() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Simplest logger. It just stores a copy of the entire value. This is used for most simple types
|
||||
* like `int`.
|
||||
*/
|
||||
class GenericValueLog : public ValueLog {
|
||||
public:
|
||||
/**
|
||||
* This is owning the value, but not the memory.
|
||||
*/
|
||||
GMutablePointer value;
|
||||
|
||||
GenericValueLog(const GMutablePointer value) : value(value) {}
|
||||
|
||||
~GenericValueLog() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fields are not logged entirely, because they might contain arbitrarily large data (e.g.
|
||||
* geometries that are sampled). Instead, only the data needed for UI features is logged.
|
||||
*/
|
||||
class FieldInfoLog : public ValueLog {
|
||||
public:
|
||||
const CPPType &type;
|
||||
Vector<std::string> input_tooltips;
|
||||
|
||||
FieldInfoLog(const GField &field);
|
||||
};
|
||||
|
||||
struct StringLog : public ValueLog {
|
||||
StringRef value;
|
||||
bool truncated;
|
||||
StringLog(StringRef string, LinearAllocator<> &allocator);
|
||||
};
|
||||
|
||||
struct GeometryAttributeInfo {
|
||||
std::string name;
|
||||
/** Can be empty when #name does not actually exist on a geometry yet. */
|
||||
std::optional<bke::AttrDomain> domain;
|
||||
std::optional<bke::AttrType> data_type;
|
||||
};
|
||||
|
||||
struct VolumeGridInfo {
|
||||
std::string name;
|
||||
VolumeGridType grid_type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Geometries are not logged entirely, because that would result in a lot of time and memory
|
||||
* overhead. Instead, only the data needed for UI features is logged.
|
||||
*/
|
||||
class GeometryInfoLog : public ValueLog {
|
||||
public:
|
||||
std::string name;
|
||||
Vector<GeometryAttributeInfo> attributes;
|
||||
Vector<bke::GeometryComponent::Type> component_types;
|
||||
|
||||
struct MeshInfo {
|
||||
int verts_num, edges_num, faces_num;
|
||||
};
|
||||
struct CurveInfo {
|
||||
int points_num;
|
||||
int splines_num;
|
||||
};
|
||||
struct PointCloudInfo {
|
||||
int points_num;
|
||||
};
|
||||
struct GreasePencilInfo {
|
||||
int layers_num;
|
||||
Vector<std::string> layer_names;
|
||||
};
|
||||
struct InstancesInfo {
|
||||
int instances_num;
|
||||
};
|
||||
struct EditDataInfo {
|
||||
bool has_deformed_positions = false;
|
||||
bool has_deform_matrices = false;
|
||||
int gizmo_transforms_num = 0;
|
||||
};
|
||||
struct VolumeInfo {
|
||||
Vector<VolumeGridInfo> grids;
|
||||
};
|
||||
|
||||
std::optional<MeshInfo> mesh_info;
|
||||
std::optional<CurveInfo> curve_info;
|
||||
std::optional<PointCloudInfo> pointcloud_info;
|
||||
std::optional<GreasePencilInfo> grease_pencil_info;
|
||||
std::optional<InstancesInfo> instances_info;
|
||||
std::optional<EditDataInfo> edit_data_info;
|
||||
std::optional<VolumeInfo> volume_info;
|
||||
|
||||
GeometryInfoLog(const bke::GeometrySet &geometry_set);
|
||||
};
|
||||
|
||||
class GridInfoLog : public ValueLog {
|
||||
public:
|
||||
bool is_empty = false;
|
||||
|
||||
GridInfoLog(const bke::GVolumeGrid &grid);
|
||||
};
|
||||
|
||||
class BundleValueLog : public ValueLog {
|
||||
public:
|
||||
struct Item {
|
||||
UString key;
|
||||
std::variant<const bke::bNodeSocketType *, StringRefNull> type;
|
||||
};
|
||||
|
||||
Vector<Item> items;
|
||||
|
||||
BundleValueLog(Vector<Item> items);
|
||||
};
|
||||
|
||||
class ClosureValueLog : public ValueLog {
|
||||
public:
|
||||
struct Item {
|
||||
std::string key;
|
||||
const bke::bNodeSocketType *type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Similar to #ClosureSourceLocation but does not keep pointer references to potentially
|
||||
* temporary data.
|
||||
*/
|
||||
struct Source {
|
||||
uint32_t orig_node_tree_session_uid;
|
||||
int closure_output_node_id;
|
||||
ComputeContextHash compute_context_hash;
|
||||
};
|
||||
|
||||
Vector<Item> inputs;
|
||||
Vector<Item> outputs;
|
||||
std::optional<Source> source;
|
||||
std::shared_ptr<ClosureEvalLog> eval_log;
|
||||
|
||||
ClosureValueLog(Vector<Item> inputs,
|
||||
Vector<Item> outputs,
|
||||
const std::optional<ClosureSourceLocation> &source_location,
|
||||
std::shared_ptr<ClosureEvalLog> eval_log);
|
||||
};
|
||||
|
||||
class ListInfoLog : public ValueLog {
|
||||
public:
|
||||
int64_t size;
|
||||
|
||||
ListInfoLog(const GListPtr &list);
|
||||
};
|
||||
|
||||
/**
|
||||
* Data logged by a viewer node when it is executed.
|
||||
*/
|
||||
class ViewerNodeLog {
|
||||
mutable CacheMutex main_geometry_cache_mutex_;
|
||||
mutable std::optional<bke::GeometrySet> main_geometry_cache_;
|
||||
|
||||
public:
|
||||
struct Item {
|
||||
int identifier;
|
||||
std::string name;
|
||||
bke::SocketValueVariant value;
|
||||
};
|
||||
|
||||
struct ItemIdentifierGetter {
|
||||
int operator()(const Item &item) const
|
||||
{
|
||||
return item.identifier;
|
||||
}
|
||||
};
|
||||
|
||||
CustomIDVectorSet<Item, ItemIdentifierGetter> items;
|
||||
|
||||
const bke::GeometrySet *main_geometry() const;
|
||||
};
|
||||
|
||||
/* Compositor image result. */
|
||||
class ImageInfoLog : public ValueLog {
|
||||
public:
|
||||
/* Stores compositor::Domain information. */
|
||||
const int2 data_size;
|
||||
const int2 display_size;
|
||||
const int2 data_offset;
|
||||
const float3x3 transformation;
|
||||
|
||||
/* Stores compositor::RealizationOptions information in a textual representation. */
|
||||
const StringRefNull interpolation;
|
||||
const StringRefNull extension_x;
|
||||
const StringRefNull extension_y;
|
||||
|
||||
/* Stores compositor::Result.precision in a textual representation. */
|
||||
const StringRefNull precision;
|
||||
|
||||
ImageInfoLog(int2 data_size,
|
||||
int2 display_size,
|
||||
int2 data_offset,
|
||||
float3x3 transformation,
|
||||
StringRefNull interpolation,
|
||||
StringRefNull extension_x,
|
||||
StringRefNull extension_y,
|
||||
StringRefNull precision);
|
||||
};
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using TimePoint = Clock::time_point;
|
||||
|
||||
/**
|
||||
* Logs all data for a specific geometry node tree in a specific context. When the same node group
|
||||
* is used in multiple times each instantiation will have a separate logger.
|
||||
*/
|
||||
class NodeTreeLogger {
|
||||
public:
|
||||
std::optional<ComputeContextHash> parent_hash;
|
||||
std::optional<int32_t> parent_node_id;
|
||||
Vector<ComputeContextHash> children_hashes;
|
||||
/**
|
||||
* The #ID.session_uid of the tree that this logger is for. It's an optional value because under
|
||||
* some circumstances it's not possible to know this exactly currently (e.g. for closures).
|
||||
*/
|
||||
std::optional<uint32_t> tree_orig_session_uid;
|
||||
/** The time spend in the compute context that this logger corresponds to. */
|
||||
std::chrono::nanoseconds execution_time{};
|
||||
|
||||
LinearAllocator<> *allocator = nullptr;
|
||||
|
||||
struct WarningWithNode {
|
||||
int32_t node_id;
|
||||
NodeWarning warning;
|
||||
};
|
||||
struct SocketValueLog {
|
||||
int32_t node_id;
|
||||
int socket_index;
|
||||
destruct_ptr<ValueLog> value;
|
||||
};
|
||||
struct NodeExecutionTime {
|
||||
int32_t node_id;
|
||||
TimePoint start;
|
||||
TimePoint end;
|
||||
};
|
||||
struct ViewerNodeLogWithNode {
|
||||
int32_t node_id;
|
||||
destruct_ptr<ViewerNodeLog> viewer_log;
|
||||
};
|
||||
struct AttributeUsageWithNode {
|
||||
int32_t node_id;
|
||||
StringRefNull attribute_name;
|
||||
NamedAttributeUsage usage;
|
||||
};
|
||||
struct DebugMessage {
|
||||
int32_t node_id;
|
||||
StringRefNull message;
|
||||
};
|
||||
struct EvaluatedGizmoNode {
|
||||
int32_t node_id;
|
||||
};
|
||||
struct NodeImagePreview {
|
||||
int32_t node_id;
|
||||
/** An image preview of the node. Owned by the logger and should be freed when destructed. */
|
||||
ImBuf *image_preview = nullptr;
|
||||
};
|
||||
|
||||
linear_allocator::ChunkedList<WarningWithNode> node_warnings;
|
||||
linear_allocator::ChunkedList<SocketValueLog, 16> input_socket_values;
|
||||
linear_allocator::ChunkedList<SocketValueLog, 16> output_socket_values;
|
||||
linear_allocator::ChunkedList<NodeExecutionTime, 16> node_execution_times;
|
||||
linear_allocator::ChunkedList<NodeImagePreview, 16> node_image_previews;
|
||||
linear_allocator::ChunkedList<ViewerNodeLogWithNode> viewer_node_logs;
|
||||
linear_allocator::ChunkedList<AttributeUsageWithNode> used_named_attributes;
|
||||
linear_allocator::ChunkedList<DebugMessage> debug_messages;
|
||||
/** Keeps track of which gizmo nodes have been tracked by this evaluation. */
|
||||
linear_allocator::ChunkedList<EvaluatedGizmoNode> evaluated_gizmo_nodes;
|
||||
|
||||
NodeTreeLogger();
|
||||
~NodeTreeLogger();
|
||||
|
||||
void log_value(const bNode &node, const bNodeSocket &socket, GPointer value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains data that has been logged for a specific node in a context. So when the node is in a
|
||||
* node group that is used multiple times, there will be a different #NodeLog for every
|
||||
* instance.
|
||||
*
|
||||
* By default, not all of the info below is valid. A #NodeTreeLog::ensure_* method has to be called
|
||||
* first.
|
||||
*/
|
||||
class NodeLog {
|
||||
public:
|
||||
/** Warnings generated for that node. */
|
||||
VectorSet<NodeWarning> warnings;
|
||||
/** Time spent in this node. */
|
||||
std::chrono::nanoseconds execution_time{0};
|
||||
/** Maps from socket indices to their values. */
|
||||
Map<int, ValueLog *> input_values_;
|
||||
Map<int, ValueLog *> output_values_;
|
||||
/** Maps from attribute name to their usage flags. */
|
||||
Map<StringRefNull, NamedAttributeUsage> used_named_attributes;
|
||||
/** Messages that are used for debugging purposes during development. */
|
||||
Vector<StringRefNull> debug_messages;
|
||||
/** An image preview of the node. Owned by the log and should be freed when destructed. */
|
||||
ImBuf *image_preview = nullptr;
|
||||
|
||||
NodeLog();
|
||||
~NodeLog();
|
||||
};
|
||||
|
||||
class NodesEvalLog;
|
||||
|
||||
/**
|
||||
* Contains data that has been logged for a specific node group in a context. If the same node
|
||||
* group is used multiple times, there will be a different #NodeTreeLog for every instance.
|
||||
*
|
||||
* This contains lazily evaluated data. Call the corresponding `ensure_*` methods before accessing
|
||||
* data.
|
||||
*/
|
||||
class NodeTreeLog {
|
||||
private:
|
||||
LinearAllocator<> allocator_;
|
||||
NodesEvalLog *root_log_;
|
||||
Vector<NodeTreeLogger *> tree_loggers_;
|
||||
VectorSet<ComputeContextHash> children_hashes_;
|
||||
bool reduced_node_warnings_ = false;
|
||||
bool reduced_execution_times_ = false;
|
||||
bool reduced_socket_values_ = false;
|
||||
bool reduced_viewer_node_logs_ = false;
|
||||
bool reduced_existing_attributes_ = false;
|
||||
bool reduced_used_named_attributes_ = false;
|
||||
bool reduced_debug_messages_ = false;
|
||||
bool reduced_evaluated_gizmo_nodes_ = false;
|
||||
bool reduced_layer_names_ = false;
|
||||
bool reduced_node_image_previews_ = false;
|
||||
|
||||
public:
|
||||
Map<int32_t, destruct_ptr<NodeLog>> nodes;
|
||||
Map<int32_t, ViewerNodeLog *, 0> viewer_node_logs;
|
||||
VectorSet<NodeWarning> all_warnings;
|
||||
std::chrono::nanoseconds execution_time{0};
|
||||
Vector<const GeometryAttributeInfo *> existing_attributes;
|
||||
Map<StringRefNull, NamedAttributeUsage> used_named_attributes;
|
||||
Set<int> evaluated_gizmo_nodes;
|
||||
Vector<std::string> all_layer_names;
|
||||
|
||||
NodeTreeLog(NodesEvalLog *root_log, Vector<NodeTreeLogger *> tree_loggers);
|
||||
~NodeTreeLog();
|
||||
|
||||
/**
|
||||
* Propagate node warnings. This needs access to the node group pointers, because propagation
|
||||
* settings are stored on the nodes. However, the log can only store weak pointers (in the form
|
||||
* of e.g. session ids) to original data to avoid dangling pointers.
|
||||
*/
|
||||
void ensure_node_warnings(const NodesModifierData &nmd);
|
||||
void ensure_node_warnings(const Main &bmain);
|
||||
void ensure_node_warnings(const Map<uint32_t, const bNodeTree *> &orig_tree_by_session_uid);
|
||||
|
||||
void ensure_execution_times();
|
||||
void ensure_socket_values();
|
||||
void ensure_viewer_node_logs();
|
||||
void ensure_existing_attributes();
|
||||
void ensure_used_named_attributes();
|
||||
void ensure_debug_messages();
|
||||
void ensure_evaluated_gizmo_nodes();
|
||||
void ensure_layer_names();
|
||||
void ensure_node_image_previews();
|
||||
|
||||
NodeLog *find_node_log(int32_t identifier) const;
|
||||
NodeLog &lookup_or_add_node_log(const int32_t identifier);
|
||||
|
||||
ValueLog *find_socket_value_log(const bNodeSocket &query_socket);
|
||||
[[nodiscard]] bool try_convert_primitive_socket_value(const GenericValueLog &value_log,
|
||||
const CPPType &dst_type,
|
||||
void *dst);
|
||||
|
||||
template<typename T>
|
||||
std::optional<T> find_primitive_socket_value(const bNodeSocket &query_socket)
|
||||
{
|
||||
if (auto *value_log = dynamic_cast<GenericValueLog *>(
|
||||
this->find_socket_value_log(query_socket)))
|
||||
{
|
||||
T value;
|
||||
if (this->try_convert_primitive_socket_value(*value_log, CPPType::get<T>(), &value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
class ContextualNodeTreeLogs {
|
||||
private:
|
||||
Map<const bke::bNodeTreeZone *, NodeTreeLog *> tree_logs_by_zone_;
|
||||
|
||||
public:
|
||||
ContextualNodeTreeLogs(Map<const bke::bNodeTreeZone *, NodeTreeLog *> tree_logs_by_zone = {});
|
||||
|
||||
/**
|
||||
* Get a tree log for the given zone/node/socket if available.
|
||||
*/
|
||||
NodeTreeLog *get_main_tree_log(const bke::bNodeTreeZone *zone) const;
|
||||
NodeTreeLog *get_main_tree_log(const bNode &node) const;
|
||||
NodeTreeLog *get_main_tree_log(const bNodeSocket &socket) const;
|
||||
|
||||
/**
|
||||
* Runs a callback for each tree log that may be returned above.
|
||||
*/
|
||||
void foreach_tree_log(FunctionRef<void(NodeTreeLog &)> callback) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* This contains all the loggers that are used during evaluation as well as the preprocessed logs
|
||||
* that are used by UI code.
|
||||
*/
|
||||
class NodesEvalLog {
|
||||
private:
|
||||
/** Data that is stored for each thread. */
|
||||
struct LocalData {
|
||||
/** Each thread has its own allocator. */
|
||||
LinearAllocator<> allocator;
|
||||
/**
|
||||
* Store a separate #NodeTreeLogger for each instance of the corresponding node group (e.g.
|
||||
* when the same node group is used multiple times).
|
||||
*/
|
||||
Map<ComputeContextHash, destruct_ptr<NodeTreeLogger>> tree_logger_by_context;
|
||||
};
|
||||
|
||||
/** Container for all thread-local data. */
|
||||
threading::EnumerableThreadSpecific<LocalData> data_per_thread_;
|
||||
/**
|
||||
* A #NodeTreeLog for every compute context. Those are created lazily when requested by UI code.
|
||||
*/
|
||||
Map<ComputeContextHash, std::unique_ptr<NodeTreeLog>> tree_logs_;
|
||||
|
||||
public:
|
||||
NodesEvalLog();
|
||||
~NodesEvalLog();
|
||||
|
||||
/**
|
||||
* Get a thread-local logger for the current node tree.
|
||||
*/
|
||||
NodeTreeLogger &get_local_tree_logger(const ComputeContext &compute_context);
|
||||
|
||||
/**
|
||||
* Get a log a specific node tree instance.
|
||||
*/
|
||||
NodeTreeLog &get_tree_log(const ComputeContextHash &compute_context_hash);
|
||||
|
||||
/**
|
||||
* Utility accessor to logged data.
|
||||
*/
|
||||
static Map<const bke::bNodeTreeZone *, ComputeContextHash>
|
||||
get_context_hash_by_zone_for_node_editor(const SpaceNode &snode,
|
||||
bke::ComputeContextCache &compute_context_cache);
|
||||
|
||||
static ContextualNodeTreeLogs get_contextual_tree_logs(const SpaceNode &snode);
|
||||
static const ViewerNodeLog *find_viewer_node_log_for_path(const ViewerPath &viewer_path);
|
||||
};
|
||||
|
||||
} // namespace nodes::eval_log
|
||||
|
||||
} // namespace blender
|
||||
21
blender-5.2.0/source/blender/nodes/NOD_geometry.hh
Normal file
21
blender-5.2.0/source/blender/nodes/NOD_geometry.hh
Normal file
@@ -0,0 +1,21 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
extern bke::bNodeTreeType *ntreeType_Geometry;
|
||||
|
||||
void register_node_tree_type_geo();
|
||||
void register_node_type_geo_custom_group(bke::bNodeType *ntype);
|
||||
|
||||
/**
|
||||
* Returns true if the socket is a Named Layer Selection field.
|
||||
*/
|
||||
bool is_layer_selection_field(const bNodeTreeInterfaceSocket &socket);
|
||||
|
||||
} // namespace blender
|
||||
378
blender-5.2.0/source/blender/nodes/NOD_geometry_exec.hh
Normal file
378
blender-5.2.0/source/blender/nodes/NOD_geometry_exec.hh
Normal file
@@ -0,0 +1,378 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_color_types.hh"
|
||||
#include "BLI_math_quaternion_types.hh"
|
||||
|
||||
#include "FN_field.hh"
|
||||
#include "FN_lazy_function.hh"
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "BKE_attribute_filter.hh"
|
||||
#include "BKE_geometry_fields.hh"
|
||||
#include "BKE_geometry_nodes_reference_set.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_node_socket_value.hh"
|
||||
#include "BKE_volume_grid_fwd.hh"
|
||||
#include "NOD_geometry_nodes_bundle_fwd.hh"
|
||||
#include "NOD_geometry_nodes_closure_fwd.hh"
|
||||
#include "NOD_geometry_nodes_list_fwd.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "NOD_geometry_nodes_lazy_function.hh"
|
||||
#include "NOD_geometry_nodes_values.hh"
|
||||
#include "NOD_menu_value.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
using bke::AttrDomain;
|
||||
using bke::AttributeAccessor;
|
||||
using bke::AttributeDomainAndType;
|
||||
using bke::AttributeFieldInput;
|
||||
using bke::AttributeFilter;
|
||||
using bke::AttributeIter;
|
||||
using bke::AttributeMetaData;
|
||||
using bke::AttributeReader;
|
||||
using bke::AttributeWriter;
|
||||
using bke::CurveComponent;
|
||||
using bke::GAttributeReader;
|
||||
using bke::GAttributeWriter;
|
||||
using bke::GeometryComponent;
|
||||
using bke::GeometryComponentEditData;
|
||||
using bke::GeometryNodesReferenceSet;
|
||||
using bke::GeometrySet;
|
||||
using bke::GreasePencilComponent;
|
||||
using bke::GSpanAttributeWriter;
|
||||
using bke::InstancesComponent;
|
||||
using bke::MeshComponent;
|
||||
using bke::MutableAttributeAccessor;
|
||||
using bke::PointCloudComponent;
|
||||
using bke::SocketValueVariant;
|
||||
using bke::SpanAttributeWriter;
|
||||
using bke::VolumeComponent;
|
||||
using eval_log::NamedAttributeUsage;
|
||||
using fn::Field;
|
||||
using fn::FieldContext;
|
||||
using fn::FieldEvaluator;
|
||||
using fn::FieldInput;
|
||||
using fn::FieldOperation;
|
||||
using fn::GField;
|
||||
|
||||
class NodeAttributeFilter : public AttributeFilter {
|
||||
private:
|
||||
const GeometryNodesReferenceSet &set_;
|
||||
|
||||
public:
|
||||
NodeAttributeFilter(const GeometryNodesReferenceSet &set) : set_(set) {}
|
||||
|
||||
Result filter(StringRef attribute_name) const override;
|
||||
};
|
||||
|
||||
class GeoNodeExecParams {
|
||||
private:
|
||||
const bNode &node_;
|
||||
lf::Params ¶ms_;
|
||||
const lf::Context &lf_context_;
|
||||
const Span<int> lf_input_for_output_bsocket_usage_;
|
||||
const Span<int> lf_input_for_attribute_propagation_to_output_;
|
||||
const FunctionRef<std::string(int)> get_output_attribute_id_;
|
||||
|
||||
public:
|
||||
GeoNodeExecParams(const bNode &node,
|
||||
lf::Params ¶ms,
|
||||
const lf::Context &lf_context,
|
||||
const Span<int> lf_input_for_output_bsocket_usage,
|
||||
const Span<int> lf_input_for_attribute_propagation_to_output,
|
||||
const FunctionRef<std::string(int)> get_output_attribute_id)
|
||||
: node_(node),
|
||||
params_(params),
|
||||
lf_context_(lf_context),
|
||||
lf_input_for_output_bsocket_usage_(lf_input_for_output_bsocket_usage),
|
||||
lf_input_for_attribute_propagation_to_output_(
|
||||
lf_input_for_attribute_propagation_to_output),
|
||||
get_output_attribute_id_(get_output_attribute_id)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input value for the input socket with the given identifier.
|
||||
*
|
||||
* This method can only be called once for each identifier.
|
||||
*/
|
||||
template<typename T> T extract_input(const UString identifier)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
this->check_input_access(identifier);
|
||||
#endif
|
||||
const int index = this->get_input_index(identifier);
|
||||
if constexpr (is_GeoNodesMultiInput_v<T>) {
|
||||
using ValueT = typename T::value_type;
|
||||
BLI_assert(node_.input_by_identifier(identifier)->is_multi_input());
|
||||
if constexpr (std::is_same_v<ValueT, SocketValueVariant>) {
|
||||
return params_.extract_input<T>(index);
|
||||
}
|
||||
else {
|
||||
auto values_variants = params_.extract_input<GeoNodesMultiInput<SocketValueVariant>>(
|
||||
index);
|
||||
GeoNodesMultiInput<ValueT> values;
|
||||
values.values.reserve(values_variants.values.size());
|
||||
for (const int i : values_variants.values.index_range()) {
|
||||
values.values.append(values_variants.values[i].extract<ValueT>());
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
else {
|
||||
SocketValueVariant value_variant = params_.extract_input<SocketValueVariant>(index);
|
||||
if constexpr (std::is_same_v<T, SocketValueVariant>) {
|
||||
return value_variant;
|
||||
}
|
||||
else if constexpr (std::is_enum_v<T>) {
|
||||
return T(value_variant.extract<MenuValue>().value);
|
||||
}
|
||||
else {
|
||||
T value = value_variant.extract<T>();
|
||||
if constexpr (std::is_same_v<T, GeometrySet>) {
|
||||
this->check_input_geometry_set(identifier, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void check_input_geometry_set(UString identifier, const GeometrySet &geometry_set) const;
|
||||
void check_output_geometry_set(const GeometrySet &geometry_set) const;
|
||||
|
||||
/**
|
||||
* Get the input value for the input socket with the given identifier.
|
||||
*/
|
||||
template<typename T> T get_input(const UString identifier) const
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
this->check_input_access(identifier);
|
||||
#endif
|
||||
const int index = this->get_input_index(identifier);
|
||||
if constexpr (is_GeoNodesMultiInput_v<T>) {
|
||||
using ValueT = typename T::value_type;
|
||||
BLI_assert(node_.input_by_identifier(identifier)->is_multi_input());
|
||||
if constexpr (std::is_same_v<ValueT, SocketValueVariant>) {
|
||||
return params_.get_input<T>(index);
|
||||
}
|
||||
else {
|
||||
auto values_variants = params_.get_input<GeoNodesMultiInput<SocketValueVariant>>(index);
|
||||
Vector<ValueT> values(values_variants.values.size());
|
||||
for (const int i : values_variants.values.index_range()) {
|
||||
values[i] = values_variants.values[i].extract<ValueT>();
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const SocketValueVariant &value_variant = params_.get_input<SocketValueVariant>(index);
|
||||
if constexpr (std::is_same_v<T, SocketValueVariant>) {
|
||||
return value_variant;
|
||||
}
|
||||
else if constexpr (std::is_enum_v<T>) {
|
||||
return T(value_variant.get<MenuValue>().value);
|
||||
}
|
||||
else {
|
||||
T value = value_variant.get<T>();
|
||||
if constexpr (std::is_same_v<T, GeometrySet>) {
|
||||
this->check_input_geometry_set(identifier, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Low level access to the parameters. Usually, it's better to use #get_input, #extract_input and
|
||||
* #set_output instead because they are easier to use and more safe. Sometimes it can be
|
||||
* beneficial to have more direct access to the raw values though and avoid the indirection.
|
||||
*/
|
||||
lf::Params &low_level_lazy_function_params()
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the output value for the given socket identifier.
|
||||
*/
|
||||
template<typename T> void set_output(const UString identifier, T &&value)
|
||||
{
|
||||
using StoredT = std::decay_t<T>;
|
||||
#ifndef NDEBUG
|
||||
this->check_output_access(identifier);
|
||||
#endif
|
||||
if constexpr (std::is_same_v<StoredT, GeometrySet>) {
|
||||
this->check_output_geometry_set(value);
|
||||
}
|
||||
const int index = this->get_output_index(identifier);
|
||||
if constexpr (std::is_same_v<StoredT, SocketValueVariant>) {
|
||||
params_.set_output(index, std::forward<T>(value));
|
||||
}
|
||||
else {
|
||||
params_.set_output(index, SocketValueVariant::From(std::forward<T>(value)));
|
||||
}
|
||||
}
|
||||
|
||||
eval_log::NodeTreeLogger *get_local_tree_logger() const
|
||||
{
|
||||
return this->local_user_data()->try_get_tree_logger(*this->user_data());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the evaluator that a specific input won't be used anymore.
|
||||
*/
|
||||
void set_input_unused(const UString identifier)
|
||||
{
|
||||
const int index = this->get_input_index(identifier);
|
||||
params_.set_input_unused(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the output has to be computed.
|
||||
*/
|
||||
bool output_is_required(const UString identifier) const
|
||||
{
|
||||
const int index = this->get_output_index(identifier);
|
||||
return params_.get_output_usage(index) != lf::ValueUsage::Unused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node that is currently being executed.
|
||||
*/
|
||||
const bNode &node() const
|
||||
{
|
||||
return node_;
|
||||
}
|
||||
|
||||
const Object *self_object() const
|
||||
{
|
||||
if (const auto *data = this->user_data()) {
|
||||
return data->call_data->self_object();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Depsgraph *depsgraph() const
|
||||
{
|
||||
if (const auto *data = this->user_data()) {
|
||||
if (data->call_data->modifier_data) {
|
||||
return data->call_data->modifier_data->depsgraph;
|
||||
}
|
||||
if (data->call_data->operator_data) {
|
||||
return data->call_data->operator_data->depsgraphs->active;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Main *bmain() const;
|
||||
|
||||
GeoNodesUserData *user_data() const
|
||||
{
|
||||
return static_cast<GeoNodesUserData *>(lf_context_.user_data);
|
||||
}
|
||||
|
||||
GeoNodesLocalUserData *local_user_data() const
|
||||
{
|
||||
return static_cast<GeoNodesLocalUserData *>(lf_context_.local_user_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an error message displayed at the top of the node when displaying the node tree,
|
||||
* and potentially elsewhere in Blender.
|
||||
*/
|
||||
void error_message_add(const NodeWarningType type, StringRef message) const;
|
||||
|
||||
void set_default_remaining_outputs();
|
||||
|
||||
void used_named_attribute(StringRef attribute_name, NamedAttributeUsage usage);
|
||||
|
||||
/**
|
||||
* Return true when the anonymous attribute referenced by the given output should be created.
|
||||
*/
|
||||
bool anonymous_attribute_output_is_required(const UString output_identifier)
|
||||
{
|
||||
const int lf_index =
|
||||
lf_input_for_output_bsocket_usage_[node_.output_by_identifier(UString(output_identifier))
|
||||
->index_in_all_outputs()];
|
||||
return params_.get_input<bool>(lf_index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new anonymous attribute id for the given output. None is returned if the anonymous
|
||||
* attribute is not needed.
|
||||
*/
|
||||
std::optional<std::string> get_output_anonymous_attribute_id_if_needed(
|
||||
const UString output_identifier, const bool force_create = false)
|
||||
{
|
||||
if (!this->anonymous_attribute_output_is_required(output_identifier) && !force_create) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const bNodeSocket &output_socket = *node_.output_by_identifier(UString(output_identifier));
|
||||
return get_output_attribute_id_(output_socket.index());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about which attributes should be propagated to the given output.
|
||||
*/
|
||||
NodeAttributeFilter get_attribute_filter(const UString output_identifier) const
|
||||
{
|
||||
const int lf_index = lf_input_for_attribute_propagation_to_output_
|
||||
[node_.output_by_identifier(UString(output_identifier))->index_in_all_outputs()];
|
||||
const GeometryNodesReferenceSet &set = params_.get_input<GeometryNodesReferenceSet>(lf_index);
|
||||
return NodeAttributeFilter(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the path is relative, attempt to make it absolute. If the current node tree is linked,
|
||||
* the path is relative to the linked file. Otherwise, the path is relative to the current file.
|
||||
*/
|
||||
std::optional<std::string> ensure_absolute_path(StringRefNull path) const;
|
||||
|
||||
private:
|
||||
/* Utilities for detecting common errors at when using this class. */
|
||||
void check_input_access(UString identifier) const;
|
||||
void check_output_access(UString identifier) const;
|
||||
|
||||
int get_input_index(const UString identifier) const
|
||||
{
|
||||
int counter = 0;
|
||||
for (const bNodeSocket *socket : node_.input_sockets()) {
|
||||
if (!socket->is_available()) {
|
||||
continue;
|
||||
}
|
||||
if (socket->identifier_ustr() == identifier) {
|
||||
return counter;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int get_output_index(const UString identifier) const
|
||||
{
|
||||
int counter = 0;
|
||||
for (const bNodeSocket *socket : node_.output_sockets()) {
|
||||
if (!socket->is_available()) {
|
||||
continue;
|
||||
}
|
||||
if (socket->identifier == identifier) {
|
||||
return counter;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::nodes
|
||||
506
blender-5.2.0/source/blender/nodes/NOD_geometry_nodes_bundle.hh
Normal file
506
blender-5.2.0/source/blender/nodes/NOD_geometry_nodes_bundle.hh
Normal file
@@ -0,0 +1,506 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_socket_value.hh"
|
||||
|
||||
#include "BLI_memory_counter_fwd.hh"
|
||||
#include "BLI_ustring.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_bundle_fwd.hh"
|
||||
#include "NOD_geometry_nodes_list.hh"
|
||||
#include "NOD_geometry_nodes_values.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
struct BundleItemSocketValue {
|
||||
/** The type of referenced data. */
|
||||
const bke::bNodeSocketType *type;
|
||||
bke::SocketValueVariant value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Other classes can derive from this to be able to store custom internal data in a bundle.
|
||||
*/
|
||||
class BundleItemInternalValueMixin : public ImplicitSharingMixin {
|
||||
public:
|
||||
/** UI name for the type. */
|
||||
virtual StringRefNull type_name() const = 0;
|
||||
};
|
||||
|
||||
struct BundleItemInternalValue {
|
||||
ImplicitSharingPtr<BundleItemInternalValueMixin> value;
|
||||
};
|
||||
|
||||
struct BundleItemValue {
|
||||
std::variant<BundleItemSocketValue, BundleItemInternalValue> value;
|
||||
|
||||
/**
|
||||
* Attempts to cast the stored value to the given type. This may do implicit conversions.
|
||||
*/
|
||||
std::optional<bke::SocketValueVariant> as_socket_value(
|
||||
const bke::bNodeSocketType &dst_socket_type) const;
|
||||
template<typename T>
|
||||
std::optional<T> as_socket_value(const bke::bNodeSocketType &socket_type) const;
|
||||
template<typename T> std::optional<T> as() const;
|
||||
|
||||
/**
|
||||
* Get a pointer to the underlying stored single value.
|
||||
*/
|
||||
template<typename T> T *as_pointer();
|
||||
template<typename T> const T *as_pointer() const;
|
||||
};
|
||||
|
||||
class BundleKey {
|
||||
private:
|
||||
UString key_;
|
||||
|
||||
BundleKey(UString key);
|
||||
|
||||
public:
|
||||
static std::optional<BundleKey> from_ustr(UString key);
|
||||
static std::optional<BundleKey> from_str(StringRef key);
|
||||
|
||||
uint64_t hash() const;
|
||||
bool operator==(const BundleKey &other) const = default;
|
||||
|
||||
UString ustr() const;
|
||||
|
||||
/* Disallow certain characters so that we can use them to e.g. build a bundle path or
|
||||
* expressions referencing multiple bundle items. We might not need all of them in the future,
|
||||
* but better reserve them now while we still can. */
|
||||
static constexpr StringRefNull forbidden_key_chars = "/*&|\"^~!,{}()+$#@[];:?<>.-%\\=";
|
||||
static bool is_valid_key(StringRef key);
|
||||
};
|
||||
|
||||
/**
|
||||
* A bundle is a map containing keys and their corresponding values.
|
||||
*
|
||||
* The API also supports working with paths in nested bundles like `root/child/data`.
|
||||
*/
|
||||
class Bundle : public ImplicitSharingMixin {
|
||||
public:
|
||||
using BundleItemMap = Map<BundleKey, BundleItemValue>;
|
||||
|
||||
private:
|
||||
BundleItemMap items_;
|
||||
|
||||
public:
|
||||
static inline BundleKey type_item_name = *BundleKey::from_str("Type");
|
||||
static BundlePtr create();
|
||||
|
||||
bool add(BundleKey key, const BundleItemValue &value);
|
||||
bool add(BundleKey key, BundleItemValue &&value);
|
||||
void add_new(BundleKey key, const BundleItemValue &value);
|
||||
void add_new(BundleKey key, BundleItemValue &&value);
|
||||
void add_override(BundleKey key, const BundleItemValue &value);
|
||||
bool add_path(StringRef path, const BundleItemValue &value);
|
||||
void add_path_new(StringRef path, const BundleItemValue &value);
|
||||
void add_path_override(StringRef path, const BundleItemValue &value);
|
||||
void add_path_override(Span<BundleKey> path, const BundleItemValue &value);
|
||||
|
||||
template<typename T>
|
||||
requires(!std::is_same_v<std::decay_t<T>, BundleItemValue>)
|
||||
void add(BundleKey key, T &&value);
|
||||
template<typename T>
|
||||
requires(!std::is_same_v<std::decay_t<T>, BundleItemValue>)
|
||||
void add_new(BundleKey key, T &&value);
|
||||
template<typename T>
|
||||
requires(!std::is_same_v<std::decay_t<T>, BundleItemValue>)
|
||||
void add_override(BundleKey key, T &&value);
|
||||
template<typename T>
|
||||
requires(!std::is_same_v<std::decay_t<T>, BundleItemValue>)
|
||||
void add_path(StringRef path, T &&value);
|
||||
template<typename T>
|
||||
requires(!std::is_same_v<std::decay_t<T>, BundleItemValue>)
|
||||
void add_path_override(Span<BundleKey> path, T &&value);
|
||||
template<typename T>
|
||||
requires(!std::is_same_v<std::decay_t<T>, BundleItemValue>)
|
||||
void add_path_override(StringRef path, T &&value);
|
||||
|
||||
bool remove(BundleKey key);
|
||||
bool remove_path(StringRef path);
|
||||
bool remove_path(Span<BundleKey> path);
|
||||
bool contains(BundleKey key) const;
|
||||
bool contains_path(StringRef path) const;
|
||||
bool contains_path(Span<BundleKey> path) const;
|
||||
|
||||
const BundleItemValue *lookup(BundleKey key) const;
|
||||
BundleItemValue *lookup(BundleKey key);
|
||||
const BundleItemValue *lookup_path(Span<BundleKey> path) const;
|
||||
const BundleItemValue *lookup_path(StringRef path) const;
|
||||
BundleItemValue *lookup_path_for_write(Span<BundleKey> path);
|
||||
BundleItemValue *lookup_path_for_write(StringRef path);
|
||||
template<typename T> std::optional<T> lookup(BundleKey key) const;
|
||||
template<typename T> std::optional<T> lookup_path(Span<BundleKey> path) const;
|
||||
template<typename T> std::optional<T> lookup_path(StringRef path) const;
|
||||
template<typename T> T *lookup_ptr(BundleKey key);
|
||||
template<typename T> const T *lookup_ptr(BundleKey key) const;
|
||||
template<typename T> const T *lookup_path_ptr(StringRef path) const;
|
||||
template<typename T> const T *lookup_path_ptr(Span<BundleKey> path) const;
|
||||
template<typename T> T *lookup_path_for_write_ptr(StringRef path);
|
||||
template<typename T> T *lookup_path_for_write_ptr(Span<BundleKey> path);
|
||||
|
||||
Bundle &ensure_nested_bundle(StringRef path);
|
||||
|
||||
void merge(const Bundle &other);
|
||||
void merge_override(const Bundle &other);
|
||||
|
||||
bool is_empty() const;
|
||||
int64_t size() const;
|
||||
|
||||
void clear();
|
||||
|
||||
std::optional<StringRef> type() const;
|
||||
|
||||
/** Also see #GeometrySet.ensure_owns_direct_data. */
|
||||
void ensure_owns_direct_data();
|
||||
bool owns_direct_data() const;
|
||||
|
||||
BundleItemMap::ItemIterator items() const;
|
||||
BundleItemMap::MutableItemIterator items();
|
||||
|
||||
BundlePtr copy() const;
|
||||
|
||||
void delete_self() override;
|
||||
|
||||
void count_memory(MemoryCounter &memory) const;
|
||||
|
||||
Vector<std::string> gather_paths(FunctionRef<bool(const Bundle &bundle)> fn) const;
|
||||
|
||||
/** Create the combined path by inserting '/' between each element. */
|
||||
static std::string combine_path(Span<StringRef> path);
|
||||
static std::string combine_path(Span<BundleKey> path);
|
||||
|
||||
static bool is_valid_path(StringRef path);
|
||||
static std::optional<Vector<BundleKey>> split_path(StringRef path);
|
||||
};
|
||||
|
||||
enum class BundlePathsGatherFilterResult {
|
||||
None,
|
||||
Recurse,
|
||||
Take,
|
||||
};
|
||||
|
||||
Vector<std::string> gather_bundle_paths_by_bundle_type(
|
||||
const Bundle &bundle, FunctionRef<bool(StringRef type)> type_filter_fn);
|
||||
Vector<std::string> gather_bundle_paths_by_data_type(const Bundle &bundle,
|
||||
eNodeSocketDatatype data_type);
|
||||
|
||||
void foreach_nested_bundle_item(
|
||||
const Bundle &bundle,
|
||||
FunctionRef<void(Span<BundleKey> path, const BundleItemValue &value)> fn);
|
||||
|
||||
template<typename T>
|
||||
inline std::optional<T> BundleItemValue::as_socket_value(
|
||||
const bke::bNodeSocketType &dst_socket_type) const
|
||||
{
|
||||
if (const std::optional<bke::SocketValueVariant> value = this->as_socket_value(dst_socket_type))
|
||||
{
|
||||
return value->get<T>();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename T> inline T *BundleItemValue::as_pointer()
|
||||
{
|
||||
return const_cast<T *>(std::as_const(*this).as_pointer<T>());
|
||||
}
|
||||
template<typename T> inline const T *BundleItemValue::as_pointer() const
|
||||
{
|
||||
const BundleItemSocketValue *socket_value = std::get_if<BundleItemSocketValue>(&this->value);
|
||||
if (!socket_value) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!socket_value->value.is_single()) {
|
||||
return nullptr;
|
||||
}
|
||||
const GPointer ptr = socket_value->value.get_single_ptr();
|
||||
if (!ptr.is_type<T>()) {
|
||||
return nullptr;
|
||||
}
|
||||
return ptr.get<T>();
|
||||
}
|
||||
|
||||
template<typename T> inline const bke::bNodeSocketType *socket_type_info_by_static_type()
|
||||
{
|
||||
if constexpr (fn::is_field_v<T>) {
|
||||
const std::optional<eNodeSocketDatatype> socket_type =
|
||||
bke::geo_nodes_base_cpp_type_to_socket_type(CPPType::get<typename T::base_type>());
|
||||
BLI_assert(socket_type);
|
||||
const bke::bNodeSocketType *socket_type_info = bke::node_socket_type_find_static(*socket_type);
|
||||
BLI_assert(socket_type_info);
|
||||
return socket_type_info;
|
||||
}
|
||||
else {
|
||||
const std::optional<eNodeSocketDatatype> socket_type =
|
||||
bke::geo_nodes_base_cpp_type_to_socket_type(CPPType::get<T>());
|
||||
if (!socket_type) {
|
||||
return nullptr;
|
||||
}
|
||||
return bke::node_socket_type_find_static(*socket_type);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> constexpr bool is_valid_internal_bundle_item_type()
|
||||
{
|
||||
if constexpr (is_ImplicitSharingPtr_strong_v<T>) {
|
||||
if constexpr (std::is_base_of_v<BundleItemInternalValueMixin, typename T::element_type>) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T> inline std::optional<T> BundleItemValue::as() const
|
||||
{
|
||||
if constexpr (is_valid_internal_bundle_item_type<T>()) {
|
||||
using SharingInfoT = typename T::element_type;
|
||||
const auto *internal_value = std::get_if<BundleItemInternalValue>(&this->value);
|
||||
if (!internal_value) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const BundleItemInternalValueMixin *sharing_info = internal_value->value.get();
|
||||
const SharingInfoT *converted_value = dynamic_cast<const SharingInfoT *>(sharing_info);
|
||||
if (!converted_value) {
|
||||
return std::nullopt;
|
||||
}
|
||||
sharing_info->add_user();
|
||||
return ImplicitSharingPtr<SharingInfoT>{converted_value};
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, bke::SocketValueVariant>) {
|
||||
if (const BundleItemSocketValue *socket_value = std::get_if<BundleItemSocketValue>(
|
||||
&this->value))
|
||||
{
|
||||
return socket_value->value;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, GListPtr>) {
|
||||
const BundleItemSocketValue *socket_value = std::get_if<BundleItemSocketValue>(&this->value);
|
||||
if (!socket_value) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (socket_value->value.is_list()) {
|
||||
return socket_value->value.get<GListPtr>();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (const bke::bNodeSocketType *dst_socket_type = socket_type_info_by_static_type<T>()) {
|
||||
return this->as_socket_value<T>(*dst_socket_type);
|
||||
}
|
||||
/* Can't lookup this type directly currently. */
|
||||
BLI_assert_unreachable();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename T> inline std::optional<T> Bundle::lookup(const BundleKey key) const
|
||||
{
|
||||
const BundleItemValue *item = this->lookup(key);
|
||||
if (!item) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return item->as<T>();
|
||||
}
|
||||
|
||||
template<typename T> inline T *Bundle::lookup_ptr(const BundleKey key)
|
||||
{
|
||||
BundleItemValue *item = this->lookup(key);
|
||||
return item ? item->as_pointer<T>() : nullptr;
|
||||
}
|
||||
|
||||
template<typename T> inline const T *Bundle::lookup_path_ptr(const StringRef path) const
|
||||
{
|
||||
const BundleItemValue *item = this->lookup_path(path);
|
||||
return item ? item->as_pointer<T>() : nullptr;
|
||||
}
|
||||
|
||||
template<typename T> inline const T *Bundle::lookup_path_ptr(const Span<BundleKey> path) const
|
||||
{
|
||||
const BundleItemValue *item = this->lookup_path(path);
|
||||
return item ? item->as_pointer<T>() : nullptr;
|
||||
}
|
||||
|
||||
template<typename T> inline const T *Bundle::lookup_ptr(const BundleKey key) const
|
||||
{
|
||||
const BundleItemValue *item = this->lookup(key);
|
||||
return item ? item->as_pointer<T>() : nullptr;
|
||||
}
|
||||
|
||||
template<typename T> inline T *Bundle::lookup_path_for_write_ptr(const Span<BundleKey> path)
|
||||
{
|
||||
BundleItemValue *item = this->lookup_path_for_write(path);
|
||||
return item ? item->as_pointer<T>() : nullptr;
|
||||
}
|
||||
|
||||
template<typename T> inline T *Bundle::lookup_path_for_write_ptr(const StringRef path)
|
||||
{
|
||||
BundleItemValue *item = this->lookup_path_for_write(path);
|
||||
return item ? item->as_pointer<T>() : nullptr;
|
||||
}
|
||||
|
||||
template<typename T> inline std::optional<T> Bundle::lookup_path(const Span<BundleKey> path) const
|
||||
{
|
||||
const BundleItemValue *item = this->lookup_path(path);
|
||||
if (!item) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return item->as<T>();
|
||||
}
|
||||
|
||||
template<typename T> inline std::optional<T> Bundle::lookup_path(const StringRef path) const
|
||||
{
|
||||
const BundleItemValue *item = this->lookup_path(path);
|
||||
if (!item) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return item->as<T>();
|
||||
}
|
||||
|
||||
template<typename T, typename Fn> inline void to_stored_type(T &&value, Fn &&fn)
|
||||
{
|
||||
using DecayT = std::decay_t<T>;
|
||||
if constexpr (std::is_same_v<DecayT, BundleItemValue>) {
|
||||
fn(std::forward<T>(value));
|
||||
}
|
||||
else if constexpr (std::is_same_v<DecayT, BundleItemSocketValue>) {
|
||||
fn(BundleItemValue{std::forward<T>(value)});
|
||||
}
|
||||
else if constexpr (std::is_same_v<DecayT, BundleItemInternalValue>) {
|
||||
fn(BundleItemValue{std::forward<T>(value)});
|
||||
}
|
||||
else if constexpr (std::is_same_v<DecayT, bke::SocketValueVariant>) {
|
||||
const eNodeSocketDatatype socket_type = value.socket_type();
|
||||
const bke::bNodeSocketType *socket_type_info = bke::node_socket_type_find_static(socket_type);
|
||||
fn(BundleItemValue{BundleItemSocketValue{socket_type_info, std::forward<T>(value)}});
|
||||
}
|
||||
else if constexpr (is_valid_internal_bundle_item_type<DecayT>()) {
|
||||
const BundleItemInternalValueMixin *sharing_info = value.get();
|
||||
if (sharing_info) {
|
||||
sharing_info->add_user();
|
||||
}
|
||||
fn(BundleItemValue{BundleItemInternalValue{ImplicitSharingPtr{sharing_info}}});
|
||||
}
|
||||
else if (const bke::bNodeSocketType *socket_type = socket_type_info_by_static_type<DecayT>()) {
|
||||
auto value_variant = bke::SocketValueVariant::From(std::forward<T>(value));
|
||||
fn(BundleItemValue{BundleItemSocketValue{socket_type, value_variant}});
|
||||
}
|
||||
else {
|
||||
/* All allowed types should be handled above already. */
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(!std::is_same_v<std::decay_t<T>, BundleItemValue>)
|
||||
inline void Bundle::add(const BundleKey key, T &&value)
|
||||
{
|
||||
to_stored_type(std::forward<T>(value),
|
||||
[&]<typename U>(U &&item_value) { this->add(key, std::forward<U>(item_value)); });
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(!std::is_same_v<std::decay_t<T>, BundleItemValue>)
|
||||
inline void Bundle::add_new(const BundleKey key, T &&value)
|
||||
{
|
||||
to_stored_type(std::forward<T>(value), [&]<typename U>(U &&item_value) {
|
||||
this->add_new(key, std::forward<U>(item_value));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(!std::is_same_v<std::decay_t<T>, BundleItemValue>)
|
||||
inline void Bundle::add_path(const StringRef path, T &&value)
|
||||
{
|
||||
to_stored_type(std::forward<T>(value), [&]<typename U>(U &&item_value) {
|
||||
this->add_path(path, std::forward<U>(item_value));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(!std::is_same_v<std::decay_t<T>, BundleItemValue>)
|
||||
inline void Bundle::add_override(const BundleKey key, T &&value)
|
||||
{
|
||||
to_stored_type(std::forward<T>(value), [&]<typename U>(U &&item_value) {
|
||||
this->add_override(key, std::forward<U>(item_value));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(!std::is_same_v<std::decay_t<T>, BundleItemValue>)
|
||||
inline void Bundle::add_path_override(const StringRef path, T &&value)
|
||||
{
|
||||
to_stored_type(std::forward<T>(value), [&]<typename U>(U &&item_value) {
|
||||
this->add_path_override(path, std::forward<U>(item_value));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(!std::is_same_v<std::decay_t<T>, BundleItemValue>)
|
||||
inline void Bundle::add_path_override(Span<BundleKey> path, T &&value)
|
||||
{
|
||||
to_stored_type(std::forward<T>(value), [&]<typename U>(U &&item_value) {
|
||||
this->add_path_override(path, std::forward<U>(item_value));
|
||||
});
|
||||
}
|
||||
|
||||
inline Bundle::BundleItemMap::ItemIterator Bundle::items() const
|
||||
{
|
||||
return items_.items();
|
||||
}
|
||||
|
||||
inline Bundle::BundleItemMap::MutableItemIterator Bundle::items()
|
||||
{
|
||||
return items_.items();
|
||||
}
|
||||
|
||||
inline bool Bundle::is_empty() const
|
||||
{
|
||||
return items_.is_empty();
|
||||
}
|
||||
|
||||
inline int64_t Bundle::size() const
|
||||
{
|
||||
return items_.size();
|
||||
}
|
||||
|
||||
inline BundleKey::BundleKey(const UString key) : key_(key) {}
|
||||
|
||||
inline uint64_t BundleKey::hash() const
|
||||
{
|
||||
return get_default_hash(key_);
|
||||
}
|
||||
|
||||
std::optional<BundleKey> inline BundleKey::from_ustr(const UString key)
|
||||
{
|
||||
if (is_valid_key(key.ref())) {
|
||||
return BundleKey(key);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<BundleKey> inline BundleKey::from_str(const StringRef key)
|
||||
{
|
||||
/* Check validity before converting to UString. */
|
||||
if (is_valid_key(key)) {
|
||||
return BundleKey(UString(key));
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
inline UString BundleKey::ustr() const
|
||||
{
|
||||
return key_;
|
||||
}
|
||||
|
||||
inline std::string_view format_as(const BundleKey key)
|
||||
{
|
||||
return key.ustr().ref();
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,15 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_implicit_sharing_ptr.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
class Bundle;
|
||||
using BundlePtr = ImplicitSharingPtr<Bundle>;
|
||||
struct BundleItemValue;
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,66 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
#include "NOD_node_in_compute_context.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
struct BundleSignature {
|
||||
struct Item {
|
||||
std::string key;
|
||||
const bke::bNodeSocketType *type = nullptr;
|
||||
NodeSocketInterfaceStructureType structure_type;
|
||||
|
||||
friend bool operator==(const Item &a, const Item &b) = default;
|
||||
};
|
||||
|
||||
struct ItemKeyGetter {
|
||||
StringRefNull operator()(const Item &item)
|
||||
{
|
||||
return item.key;
|
||||
}
|
||||
};
|
||||
|
||||
CustomIDVectorSet<Item, ItemKeyGetter> items;
|
||||
|
||||
friend bool operator==(const BundleSignature &a, const BundleSignature &b);
|
||||
friend bool operator!=(const BundleSignature &a, const BundleSignature &b);
|
||||
|
||||
void add(std::string key, const eNodeSocketDatatype socket_type);
|
||||
|
||||
static BundleSignature from_combine_bundle_node(const bNode &node,
|
||||
bool allow_auto_structure_type);
|
||||
static BundleSignature from_separate_bundle_node(const bNode &node,
|
||||
bool allow_auto_structure_type);
|
||||
|
||||
void set_auto_structure_types();
|
||||
};
|
||||
|
||||
/**
|
||||
* Multiple bundle signatures that may be linked to a single node.
|
||||
*/
|
||||
struct LinkedBundleSignatures {
|
||||
struct Item {
|
||||
BundleSignature signature;
|
||||
bool is_signature_definition = false;
|
||||
SocketInContext source_socket;
|
||||
};
|
||||
Vector<Item> items;
|
||||
bool has_type_definition() const;
|
||||
|
||||
std::optional<BundleSignature> get_merged_signature() const;
|
||||
};
|
||||
|
||||
NodeSocketInterfaceStructureType get_structure_type_for_bundle_signature(
|
||||
const bNodeSocket &socket,
|
||||
NodeSocketInterfaceStructureType stored_structure_type,
|
||||
bool allow_auto_structure_type);
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bContext;
|
||||
struct PointerRNA;
|
||||
struct wmOperator;
|
||||
struct bNodeTree;
|
||||
|
||||
namespace ui {
|
||||
struct Layout;
|
||||
} // namespace ui
|
||||
|
||||
namespace nodes {
|
||||
|
||||
namespace eval_log {
|
||||
class NodeTreeLog;
|
||||
}
|
||||
|
||||
void draw_geometry_nodes_modifier_ui(const bContext &C,
|
||||
PointerRNA *modifier_ptr,
|
||||
ui::Layout &layout);
|
||||
|
||||
void draw_geometry_nodes_operator_redo_ui(const bContext &C,
|
||||
wmOperator &op,
|
||||
bNodeTree &tree,
|
||||
eval_log::NodeTreeLog *tree_log);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
143
blender-5.2.0/source/blender/nodes/NOD_geometry_nodes_closure.hh
Normal file
143
blender-5.2.0/source/blender/nodes/NOD_geometry_nodes_closure.hh
Normal file
@@ -0,0 +1,143 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_node_socket_value.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_closure_fwd.hh"
|
||||
#include "NOD_geometry_nodes_closure_location.hh"
|
||||
#include "NOD_geometry_nodes_closure_signature.hh"
|
||||
|
||||
#include "BLI_resource_scope.hh"
|
||||
|
||||
#include "FN_lazy_function.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
/**
|
||||
* Describes the meaning of the various inputs and outputs of the lazy-function that's contained
|
||||
* in the closure.
|
||||
*/
|
||||
struct ClosureFunctionIndices {
|
||||
struct {
|
||||
IndexRange main;
|
||||
/** A boolean input for each output indicating whether that output is used. */
|
||||
IndexRange output_usages;
|
||||
/**
|
||||
* A #GeometryNodesReferenceSet input for a subset of the outputs. This is used to tell the
|
||||
* closure which attributes it has to propagate to the outputs.
|
||||
*
|
||||
* Main output index -> input `lf` socket index.
|
||||
*/
|
||||
Map<int, int> output_data_reference_sets;
|
||||
} inputs;
|
||||
struct {
|
||||
IndexRange main;
|
||||
/** A boolean output for each input indicating whether that input is used. */
|
||||
IndexRange input_usages;
|
||||
} outputs;
|
||||
};
|
||||
|
||||
/**
|
||||
* A closure is like a node group that is passed around as a value. It's typically evaluated using
|
||||
* the Evaluate Closure node.
|
||||
*
|
||||
* Internally, a closure is a lazy-function. So the inputs that are passed to the closure are
|
||||
* requested lazily. It's *not* yet supported to request the potentially captured values from the
|
||||
* Closure Zone lazily.
|
||||
*/
|
||||
class Closure : public ImplicitSharingMixin {
|
||||
private:
|
||||
std::shared_ptr<ClosureSignature> signature_;
|
||||
std::optional<ClosureSourceLocation> source_location_;
|
||||
std::shared_ptr<ClosureEvalLog> eval_log_;
|
||||
/**
|
||||
* When building complex lazy-functions, e.g. from Geometry Nodes, one often has to allocate
|
||||
* various additional resources (e.g. the lazy-functions for the individual nodes). Using
|
||||
* #ResourceScope provides a simple way to pass ownership of all these additional resources to
|
||||
* the Closure.
|
||||
*/
|
||||
std::unique_ptr<ResourceScope> scope_;
|
||||
const fn::lazy_function::LazyFunction &function_;
|
||||
ClosureFunctionIndices indices_;
|
||||
Vector<bke::SocketValueVariant> default_input_values_;
|
||||
Vector<const bke::SocketValueVariant *> captured_values_;
|
||||
|
||||
public:
|
||||
Closure(std::shared_ptr<ClosureSignature> signature,
|
||||
std::unique_ptr<ResourceScope> scope,
|
||||
const fn::lazy_function::LazyFunction &function,
|
||||
ClosureFunctionIndices indices,
|
||||
Vector<bke::SocketValueVariant> default_input_values,
|
||||
std::optional<ClosureSourceLocation> source_location,
|
||||
std::shared_ptr<ClosureEvalLog> eval_log,
|
||||
Vector<const bke::SocketValueVariant *> captured_values)
|
||||
: signature_(signature),
|
||||
source_location_(source_location),
|
||||
eval_log_(eval_log),
|
||||
scope_(std::move(scope)),
|
||||
function_(function),
|
||||
indices_(indices),
|
||||
default_input_values_(std::move(default_input_values)),
|
||||
captured_values_(std::move(captured_values))
|
||||
{
|
||||
}
|
||||
|
||||
const ClosureSignature &signature() const
|
||||
{
|
||||
return *signature_;
|
||||
}
|
||||
|
||||
const ClosureFunctionIndices &indices() const
|
||||
{
|
||||
return indices_;
|
||||
}
|
||||
|
||||
const fn::lazy_function::LazyFunction &function() const
|
||||
{
|
||||
return function_;
|
||||
}
|
||||
|
||||
const std::optional<ClosureSourceLocation> &source_location() const
|
||||
{
|
||||
return source_location_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<ClosureEvalLog> &eval_log_ptr() const
|
||||
{
|
||||
return eval_log_;
|
||||
}
|
||||
|
||||
const bke::SocketValueVariant &default_input_value(const int index) const
|
||||
{
|
||||
return default_input_values_[index];
|
||||
}
|
||||
|
||||
Span<bke::SocketValueVariant> default_input_values() const
|
||||
{
|
||||
return default_input_values_;
|
||||
}
|
||||
|
||||
Span<const bke::SocketValueVariant *> captured_values() const
|
||||
{
|
||||
return captured_values_;
|
||||
}
|
||||
|
||||
void delete_self() override
|
||||
{
|
||||
MEM_delete(this);
|
||||
}
|
||||
|
||||
void log_evaluation(const ClosureEvalLocation &location) const
|
||||
{
|
||||
if (!eval_log_) {
|
||||
return;
|
||||
}
|
||||
std::lock_guard lock{eval_log_->mutex};
|
||||
eval_log_->evaluations.append(location);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,40 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NOD_geometry_nodes_closure.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_lazy_function.hh"
|
||||
|
||||
#include "BKE_node_socket_value.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
struct ClosureEagerEvalParams {
|
||||
struct InputItem {
|
||||
std::string key;
|
||||
const bke::bNodeSocketType *type = nullptr;
|
||||
/** This may be moved from. */
|
||||
bke::SocketValueVariant value;
|
||||
};
|
||||
|
||||
struct OutputItem {
|
||||
std::string key;
|
||||
const bke::bNodeSocketType *type = nullptr;
|
||||
/**
|
||||
* Where the output value should be stored. This is expected to point to uninitialized memory
|
||||
* when it's passed into #evaluate_closure_eagerly which will then construct the value inplace.
|
||||
*/
|
||||
bke::SocketValueVariant *value = nullptr;
|
||||
};
|
||||
|
||||
Vector<InputItem> inputs;
|
||||
Vector<OutputItem> outputs;
|
||||
GeoNodesUserData *user_data = nullptr;
|
||||
};
|
||||
|
||||
void evaluate_closure_eagerly(const Closure &closure, ClosureEagerEvalParams ¶ms);
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,14 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_implicit_sharing_ptr.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
class Closure;
|
||||
using ClosurePtr = ImplicitSharingPtr<Closure>;
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,44 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "BLI_compute_context.hh"
|
||||
#include "BLI_mutex.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bNodeTree;
|
||||
struct bNode;
|
||||
|
||||
namespace nodes {
|
||||
|
||||
struct ClosureEvalLocation {
|
||||
uint32_t orig_node_tree_session_uid;
|
||||
int evaluate_closure_node_id;
|
||||
ComputeContextHash compute_context_hash;
|
||||
};
|
||||
|
||||
struct ClosureSourceLocation {
|
||||
/**
|
||||
* Tree where the closure is created. Note that this may be an original or evaluated tree,
|
||||
* depending on where it is used.
|
||||
*/
|
||||
const bNodeTree *tree;
|
||||
int closure_output_node_id;
|
||||
ComputeContextHash compute_context_hash;
|
||||
/** Optional actual compute context. If it is set, its hash should be the same as above. */
|
||||
const ComputeContext *compute_context = nullptr;
|
||||
};
|
||||
|
||||
struct ClosureEvalLog {
|
||||
Mutex mutex;
|
||||
Vector<ClosureEvalLocation> evaluations;
|
||||
};
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,66 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "NOD_node_in_compute_context.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
/** Describes the names and types of the inputs and outputs of a closure. */
|
||||
class ClosureSignature {
|
||||
public:
|
||||
struct Item {
|
||||
std::string key;
|
||||
const bke::bNodeSocketType *type = nullptr;
|
||||
NodeSocketInterfaceStructureType structure_type;
|
||||
|
||||
friend bool operator==(const Item &a, const Item &b) = default;
|
||||
};
|
||||
|
||||
struct ItemKeyGetter {
|
||||
std::string operator()(const Item &item)
|
||||
{
|
||||
return item.key;
|
||||
}
|
||||
};
|
||||
|
||||
CustomIDVectorSet<Item, ItemKeyGetter> inputs;
|
||||
CustomIDVectorSet<Item, ItemKeyGetter> outputs;
|
||||
|
||||
std::optional<int> find_input_index(StringRef key) const;
|
||||
std::optional<int> find_output_index(StringRef key) const;
|
||||
|
||||
friend bool operator==(const ClosureSignature &a, const ClosureSignature &b);
|
||||
friend bool operator!=(const ClosureSignature &a, const ClosureSignature &b);
|
||||
|
||||
static ClosureSignature from_closure_output_node(const bNode &node,
|
||||
bool allow_auto_structure_type);
|
||||
static ClosureSignature from_evaluate_closure_node(const bNode &node,
|
||||
bool allow_auto_structure_type);
|
||||
static ClosureSignature from_closure_to_list_node(const bNode &node);
|
||||
|
||||
void set_auto_structure_types();
|
||||
};
|
||||
|
||||
/**
|
||||
* Multiple closure signatures that may be linked to a single node.
|
||||
*/
|
||||
struct LinkedClosureSignatures {
|
||||
struct Item {
|
||||
ClosureSignature signature;
|
||||
bool define_signature = false;
|
||||
SocketInContext socket;
|
||||
};
|
||||
Vector<Item> items;
|
||||
bool has_type_definition() const;
|
||||
|
||||
std::optional<ClosureSignature> get_merged_signature() const;
|
||||
};
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,73 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_compute_context.hh"
|
||||
#include "BLI_generic_pointer.hh"
|
||||
#include "BLI_resource_scope.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_idprop.hh"
|
||||
|
||||
#include "NOD_socket_value_inference.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bNodeTree;
|
||||
struct bNodeTreeInterfaceSocket;
|
||||
struct PointerRNA;
|
||||
namespace bke {
|
||||
struct GeometrySet;
|
||||
}
|
||||
struct IDProperty;
|
||||
namespace nodes {
|
||||
struct GeoNodesCallData;
|
||||
namespace eval_log {
|
||||
class NodesEvalLog;
|
||||
} // namespace eval_log
|
||||
} // namespace nodes
|
||||
|
||||
namespace nodes {
|
||||
|
||||
constexpr StringRef input_use_attribute_suffix = "_use_attribute";
|
||||
constexpr StringRef input_attribute_name_suffix = "_attribute_name";
|
||||
|
||||
struct IDPropNameGetter {
|
||||
StringRef operator()(const IDProperty *value) const
|
||||
{
|
||||
return StringRef(value->name);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \return Whether using an attribute to input values of this type is supported.
|
||||
*/
|
||||
bool socket_type_has_attribute_toggle(eNodeSocketDatatype type);
|
||||
|
||||
/**
|
||||
* \return Whether using an attribute to input values of this type is supported, and the node
|
||||
* group's input for this socket accepts a field rather than just single values.
|
||||
*/
|
||||
bool input_has_attribute_toggle(const bNodeTree &node_tree, const int socket_index);
|
||||
|
||||
bke::GeometrySet execute_geometry_nodes_on_geometry(const bNodeTree &btree,
|
||||
const PointerRNA &properties_ptr,
|
||||
const ComputeContext &base_compute_context,
|
||||
GeoNodesCallData &call_data,
|
||||
bke::GeometrySet input_geometry);
|
||||
|
||||
/**
|
||||
* Get input values for the node tree for static value/usage inferencing. Inferencing does not
|
||||
* fully evaluate the node tree (would be way to slow), and does not support all socket types. So
|
||||
* this function may return #InferenceValue::Unknown for some sockets.
|
||||
*/
|
||||
Vector<InferenceValue> get_geometry_nodes_input_inference_values(const bNodeTree &btree,
|
||||
const PointerRNA &properties_ptr,
|
||||
ResourceScope &scope);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
142
blender-5.2.0/source/blender/nodes/NOD_geometry_nodes_gizmos.hh
Normal file
142
blender-5.2.0/source/blender/nodes/NOD_geometry_nodes_gizmos.hh
Normal file
@@ -0,0 +1,142 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_compute_context.hh"
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_multi_value_map.hh"
|
||||
|
||||
#include "NOD_inverse_eval_path.hh"
|
||||
#include "NOD_inverse_eval_run.hh"
|
||||
|
||||
#include "BKE_compute_context_cache_fwd.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Object;
|
||||
struct NodesModifierData;
|
||||
struct wmWindowManager;
|
||||
|
||||
namespace nodes::gizmos {
|
||||
|
||||
namespace ie = inverse_eval;
|
||||
|
||||
/**
|
||||
* Cached on node groups after each update to make looking up and evaluating gizmos more efficient.
|
||||
*/
|
||||
struct TreeGizmoPropagation {
|
||||
Vector<const bNode *> gizmo_nodes;
|
||||
/**
|
||||
* Sockets that are special from a gizmo perspective because their value is controlled by a
|
||||
* gizmo or because they are a group input that has a gizmo.
|
||||
*/
|
||||
Set<const bNodeSocket *> gizmo_endpoint_sockets;
|
||||
|
||||
/** Supports quickly finding the gizmo sockets that are controlled by certain gizmo targets. */
|
||||
MultiValueMap<ie::ValueNodeElem, ie::SocketElem> gizmo_inputs_by_value_nodes;
|
||||
MultiValueMap<ie::SocketElem, ie::SocketElem> gizmo_inputs_by_node_inputs;
|
||||
MultiValueMap<ie::GroupInputElem, ie::SocketElem> gizmo_inputs_by_group_inputs;
|
||||
|
||||
friend bool operator==(const TreeGizmoPropagation &a, const TreeGizmoPropagation &b) = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the #TreeGizmoPropagation cached on the node-tree.
|
||||
*
|
||||
* \return False, if the propagation changed.
|
||||
*/
|
||||
bool update_tree_gizmo_propagation(bNodeTree &tree);
|
||||
|
||||
bool is_builtin_gizmo_node(const bNode &node);
|
||||
|
||||
using ForeachGizmoFn = FunctionRef<void(const Object &object,
|
||||
const NodesModifierData &nmd,
|
||||
const ComputeContext &compute_context,
|
||||
const bNode &gizmo_node,
|
||||
const bNodeSocket &gizmo_socket)>;
|
||||
|
||||
/**
|
||||
* Calls the given function for each gizmo that is active. It scans open node editors for selected
|
||||
* or pinned gizmos and also finds the gizmos for the active object.
|
||||
*/
|
||||
void foreach_active_gizmo(const bContext &C,
|
||||
bke::ComputeContextCache &compute_context_cache,
|
||||
ForeachGizmoFn fn);
|
||||
|
||||
using ForeachGizmoInModifierFn = FunctionRef<void(const ComputeContext &compute_context,
|
||||
const bNode &gizmo_node,
|
||||
const bNodeSocket &gizmo_socket)>;
|
||||
|
||||
/**
|
||||
* Similar to #foreach_active_gizmo but filters the list of gizmos to those that are relevant for a
|
||||
* specific modifier evaluation.
|
||||
*/
|
||||
void foreach_active_gizmo_in_modifier(const Object &object,
|
||||
const NodesModifierData &nmd,
|
||||
const wmWindowManager &wm,
|
||||
bke::ComputeContextCache &compute_context_cache,
|
||||
ForeachGizmoInModifierFn fn);
|
||||
|
||||
/**
|
||||
* Iterates over all compute contexts that are touched by a specific gizmo back-propagation path.
|
||||
* This is used to make sure that all sockets on the path are logged.
|
||||
*/
|
||||
void foreach_compute_context_on_gizmo_path(const ComputeContext &gizmo_context,
|
||||
const bNode &gizmo_node,
|
||||
const bNodeSocket &gizmo_socket,
|
||||
FunctionRef<void(const ComputeContext &context)> fn);
|
||||
|
||||
/**
|
||||
* Iterates over all sockets that propagate values modified by gizmos backwards. This is used to
|
||||
* draw the links between those sockets in a special way.
|
||||
*/
|
||||
void foreach_socket_on_gizmo_path(const ComputeContext &gizmo_context,
|
||||
const bNode &gizmo_node,
|
||||
const bNodeSocket &gizmo_socket,
|
||||
FunctionRef<void(const ComputeContext &context,
|
||||
const bNodeSocket &socket,
|
||||
const ie::ElemVariant &elem)> fn);
|
||||
|
||||
/**
|
||||
* Get the value element of a gizmo socket that can be affected by a gizmo. E.g. for the Transform
|
||||
* Gizmo node this may report that only the rotation component can be controlled with the gizmo.
|
||||
*/
|
||||
ie::ElemVariant get_editable_gizmo_elem(const ComputeContext &gizmo_context,
|
||||
const bNode &gizmo_node,
|
||||
const bNodeSocket &gizmo_socket);
|
||||
|
||||
/**
|
||||
* Should be called when a gizmo is moved and the change should be propagated back to the right
|
||||
* place.
|
||||
*
|
||||
* \param C: The context the gizmo is changed in. Required for propagating updates correctly.
|
||||
* \param object: The object with the modifier that the gizmo belongs to.
|
||||
* \param nmd: The modifier that the gizmo belongs to.
|
||||
* \param eval_log: The logged evaluation data that should be used to compute the backpropagated
|
||||
* value. This may not be the most recent logged data, because generally the same logged data is
|
||||
* used while interacting with a gizmo.
|
||||
* \param gizmo_context: The compute context of the gizmo node whose gizmo is modified.
|
||||
* \param gizmo_socket: The gizmo socket whose gizmo is modified.
|
||||
* \param apply_on_gizmo_value_fn: Applies the change done to the gizmo to the value in the gizmo
|
||||
* node. For example, if an arrow gizmo is moved, the distance it's moved is added to the socket
|
||||
* value. This is a callback because gizmo sockets are multi-inputs and thus multiple values
|
||||
* need to change.
|
||||
*
|
||||
*/
|
||||
void apply_gizmo_change(bContext &C,
|
||||
Object &object,
|
||||
NodesModifierData &nmd,
|
||||
eval_log::NodesEvalLog &eval_log,
|
||||
const ComputeContext &gizmo_context,
|
||||
const bNodeSocket &gizmo_socket,
|
||||
FunctionRef<void(bke::SocketValueVariant &value)> apply_on_gizmo_value_fn);
|
||||
|
||||
/**
|
||||
* Returns true if the value if the given node is controlled by a gizmo.
|
||||
*/
|
||||
bool value_node_has_gizmo(const bNodeTree &tree, const bNode &node);
|
||||
|
||||
} // namespace nodes::gizmos
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,685 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* For evaluation, geometry node groups are converted to a lazy-function graph. The generated graph
|
||||
* is cached per node group, so it only has to be generated once after a change.
|
||||
*
|
||||
* Node groups are *not* inlined into the lazy-function graph. This could be added in the future as
|
||||
* it might improve performance in some cases, but generally does not seem necessary. Inlining node
|
||||
* groups also has disadvantages like making per-node-group caches less useful, resulting in more
|
||||
* overhead.
|
||||
*
|
||||
* Instead, group nodes are just like all other nodes in the lazy-function graph. What makes them
|
||||
* special is that they reference the lazy-function graph of the group they reference.
|
||||
*
|
||||
* During lazy-function graph generation, a mapping between the #bNodeTree and
|
||||
* #lazy_function::Graph is build that can be used when evaluating the graph (e.g. for logging).
|
||||
*/
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "FN_lazy_function_graph.hh"
|
||||
#include "FN_lazy_function_graph_executor.hh"
|
||||
|
||||
#include "NOD_eval_log.hh"
|
||||
#include "NOD_multi_function.hh"
|
||||
#include "NOD_nested_node_id.hh"
|
||||
|
||||
#include "BLI_compute_context.hh"
|
||||
#include "BLI_math_quaternion_types.hh"
|
||||
#include "BLI_multi_value_map.hh"
|
||||
|
||||
#include "BKE_bake_values.hh"
|
||||
#include "BKE_node_tree_zones.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Depsgraph;
|
||||
struct Object;
|
||||
struct Scene;
|
||||
|
||||
namespace nodes {
|
||||
|
||||
using lf::LazyFunction;
|
||||
using mf::MultiFunction;
|
||||
using ReferenceSetIndex = int;
|
||||
|
||||
/** The structs in here describe the different possible behaviors of a simulation input node. */
|
||||
namespace sim_input {
|
||||
|
||||
/**
|
||||
* The data is just passed through the node. Data that is incompatible with simulations (like
|
||||
* anonymous attributes), is removed though.
|
||||
*/
|
||||
struct PassThrough {};
|
||||
|
||||
/**
|
||||
* The input is not evaluated, instead the values provided here are output by the node.
|
||||
*/
|
||||
struct UseCache {
|
||||
float delta_time;
|
||||
bke::bake::BakeValues values;
|
||||
};
|
||||
|
||||
using Behavior = std::variant<PassThrough, UseCache>;
|
||||
|
||||
} // namespace sim_input
|
||||
|
||||
/** The structs in here describe the different possible behaviors of a simulation output node. */
|
||||
namespace sim_output {
|
||||
|
||||
/**
|
||||
* Output the data that comes from the corresponding simulation input node, ignoring the nodes in
|
||||
* the zone.
|
||||
*/
|
||||
struct PassThrough {};
|
||||
|
||||
/**
|
||||
* Computes the simulation step and calls the given function to cache the new simulation state.
|
||||
* The new simulation state is the output of the node.
|
||||
*/
|
||||
struct StoreNewState {
|
||||
std::function<void(bke::bake::BakeValues values)> store_fn;
|
||||
};
|
||||
|
||||
/**
|
||||
* The inputs are not evaluated, instead the given cached items are output directly.
|
||||
*/
|
||||
struct ReadSingle {
|
||||
bke::bake::BakeValues values;
|
||||
};
|
||||
|
||||
/**
|
||||
* The inputs are not evaluated, instead of a mix of the two given states is output.
|
||||
*/
|
||||
struct ReadInterpolated {
|
||||
/** Factor between 0 and 1 that determines the influence of the two simulation states. */
|
||||
float mix_factor;
|
||||
bke::bake::BakeValues prev_values;
|
||||
bke::bake::BakeValues next_values;
|
||||
};
|
||||
|
||||
/**
|
||||
* Used when there was some issue loading the baked data from disk.
|
||||
*/
|
||||
struct ReadError {
|
||||
std::string message;
|
||||
};
|
||||
|
||||
using Behavior = std::variant<PassThrough, StoreNewState, ReadSingle, ReadInterpolated, ReadError>;
|
||||
|
||||
} // namespace sim_output
|
||||
|
||||
/** Controls the behavior of one simulation zone. */
|
||||
struct SimulationZoneBehavior {
|
||||
sim_input::Behavior input;
|
||||
sim_output::Behavior output;
|
||||
bke::bake::BakeDataBlockMap *data_block_map = nullptr;
|
||||
};
|
||||
|
||||
class GeoNodesSimulationParams {
|
||||
public:
|
||||
/**
|
||||
* Get the expected behavior for the simulation zone with the given id (see #bNestedNodeRef).
|
||||
* It's possible that this method called multiple times for the same id. In this case, the same
|
||||
* pointer should be returned in each call.
|
||||
*/
|
||||
virtual SimulationZoneBehavior *get(const int zone_id) const = 0;
|
||||
};
|
||||
|
||||
struct BakeNodeBehavior {
|
||||
/** The set of possible behaviors are the same for both of these nodes currently. */
|
||||
sim_output::Behavior behavior;
|
||||
bke::bake::BakeDataBlockMap *data_block_map = nullptr;
|
||||
};
|
||||
|
||||
class GeoNodesBakeParams {
|
||||
public:
|
||||
virtual BakeNodeBehavior *get(const int id) const = 0;
|
||||
};
|
||||
|
||||
struct GeoNodesSideEffectNodes {
|
||||
MultiValueMap<ComputeContextHash, const lf::FunctionNode *> nodes_by_context;
|
||||
/**
|
||||
* The repeat/foreach zone is identified by the compute context of the parent and the identifier
|
||||
* of the repeat output node.
|
||||
*/
|
||||
MultiValueMap<std::pair<ComputeContextHash, int32_t>, int> iterations_by_iteration_zone;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data that is passed into geometry nodes evaluation from the modifier.
|
||||
*/
|
||||
struct GeoNodesModifierData {
|
||||
/** Object that is currently evaluated. */
|
||||
const Object *self_object = nullptr;
|
||||
/** Depsgraph that is evaluating the modifier. */
|
||||
Depsgraph *depsgraph = nullptr;
|
||||
};
|
||||
|
||||
struct GeoNodesOperatorDepsgraphs {
|
||||
/** Current evaluated depsgraph from the viewport. Shouldn't be null. */
|
||||
const Depsgraph *active = nullptr;
|
||||
/**
|
||||
* Depsgraph containing IDs referenced by the node tree and the node tree itself and from node
|
||||
* group inputs (the redo panel).
|
||||
*/
|
||||
Depsgraph *extra = nullptr;
|
||||
|
||||
~GeoNodesOperatorDepsgraphs();
|
||||
|
||||
/**
|
||||
* The evaluated data-block might be in the scene's active depsgraph, in that case we should use
|
||||
* it directly. Otherwise retrieve it from the extra depsgraph that was built for all other
|
||||
* data-blocks. Return null if it isn't found, generally geometry nodes can handle null ID
|
||||
* pointers.
|
||||
*/
|
||||
const ID *get_evaluated_id(const ID &id_orig) const;
|
||||
};
|
||||
|
||||
struct GeoNodesOperatorData {
|
||||
eObjectMode mode;
|
||||
/** The object currently effected by the operator. */
|
||||
const Object *self_object_orig = nullptr;
|
||||
const GeoNodesOperatorDepsgraphs *depsgraphs = nullptr;
|
||||
/**
|
||||
* Map from names of data-block node group input RNA properties to original data-blocks.
|
||||
*/
|
||||
const Map<std::string, ID *> *input_ids = {};
|
||||
Scene *scene_orig = nullptr;
|
||||
int2 mouse_position;
|
||||
int2 region_size;
|
||||
|
||||
float3 cursor_position;
|
||||
math::Quaternion cursor_rotation;
|
||||
|
||||
float4x4 viewport_winmat;
|
||||
float4x4 viewport_viewmat;
|
||||
bool viewport_is_perspective;
|
||||
|
||||
int active_point_index = -1;
|
||||
int active_edge_index = -1;
|
||||
int active_face_index = -1;
|
||||
int active_layer_index = -1;
|
||||
};
|
||||
|
||||
struct GeoNodesCallData {
|
||||
/**
|
||||
* Top-level node tree of the current evaluation.
|
||||
*/
|
||||
const bNodeTree *root_ntree = nullptr;
|
||||
/**
|
||||
* Optional logger that keeps track of data generated during evaluation to allow for better
|
||||
* debugging afterwards.
|
||||
*/
|
||||
eval_log::NodesEvalLog *eval_log = nullptr;
|
||||
/**
|
||||
* Optional injected behavior for simulations.
|
||||
*/
|
||||
GeoNodesSimulationParams *simulation_params = nullptr;
|
||||
/**
|
||||
* Optional injected behavior for bake nodes.
|
||||
*/
|
||||
GeoNodesBakeParams *bake_params = nullptr;
|
||||
/**
|
||||
* Some nodes should be executed even when their output is not used (e.g. active viewer nodes and
|
||||
* the node groups they are contained in).
|
||||
*/
|
||||
const GeoNodesSideEffectNodes *side_effect_nodes = nullptr;
|
||||
/**
|
||||
* Controls in which compute contexts we want to log information like socket values. Logging them
|
||||
* in all contexts has significant overhead. In the majority of cases, the logged values are
|
||||
* freed without being looked at anyway.
|
||||
*
|
||||
* If this is null, it is assumed that all compute contexts should be logged.
|
||||
*/
|
||||
const Set<ComputeContextHash> *verbose_log_contexts = nullptr;
|
||||
|
||||
/**
|
||||
* Data from the modifier that is being evaluated.
|
||||
*/
|
||||
GeoNodesModifierData *modifier_data = nullptr;
|
||||
/**
|
||||
* Data from execution as operator in 3D viewport.
|
||||
*/
|
||||
GeoNodesOperatorData *operator_data = nullptr;
|
||||
|
||||
/**
|
||||
* Stack limit at which Geometry Nodes should stop the evaluation. This is a preventative measure
|
||||
* to avoid crashes caused by running out of stack space.
|
||||
*/
|
||||
int call_depth_limit = 100;
|
||||
|
||||
/**
|
||||
* Self object has slightly different semantics depending on how geometry nodes is called.
|
||||
* Therefor, it is not stored directly in the global data.
|
||||
*/
|
||||
const Object *self_object() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom user data that can be passed to every geometry nodes related evaluation.
|
||||
*/
|
||||
struct GeoNodesUserData : public fn::UserData {
|
||||
/**
|
||||
* Data provided by the root caller of geometry nodes.
|
||||
*/
|
||||
const GeoNodesCallData *call_data = nullptr;
|
||||
/**
|
||||
* Current compute context. This is different depending in the (nested) node group that is being
|
||||
* evaluated.
|
||||
*/
|
||||
const ComputeContext *compute_context = nullptr;
|
||||
/**
|
||||
* Log "more" data in the current compute context. If true, the user is likely looking at nodes
|
||||
* in this context and expects detailed inspection information. If false, only necessary data
|
||||
* like warnings should be logged but not e.g. socket values.
|
||||
*
|
||||
* Child contexts might use logging again even if the current compute context does not.
|
||||
*/
|
||||
bool verbose_log = true;
|
||||
|
||||
destruct_ptr<fn::LocalUserData> get_local(LinearAllocator<> &allocator) override;
|
||||
|
||||
bool is_stack_limit_reached() const
|
||||
{
|
||||
return this->compute_context->parents_num() >= this->call_data->call_depth_limit;
|
||||
}
|
||||
};
|
||||
|
||||
struct GeoNodesLocalUserData : public fn::LocalUserData {
|
||||
private:
|
||||
/**
|
||||
* Thread-local logger for the current node tree in the current compute context. It is only
|
||||
* instantiated when it is actually used and then cached for the current thread.
|
||||
*/
|
||||
mutable std::optional<eval_log::NodeTreeLogger *> tree_logger_;
|
||||
|
||||
public:
|
||||
GeoNodesLocalUserData(GeoNodesUserData & /*user_data*/) {}
|
||||
|
||||
/**
|
||||
* Get the current tree logger. This method is not thread-safe, each thread is supposed to have
|
||||
* a separate logger.
|
||||
*/
|
||||
eval_log::NodeTreeLogger *try_get_tree_logger(const GeoNodesUserData &user_data) const
|
||||
{
|
||||
if (!tree_logger_.has_value()) {
|
||||
this->ensure_tree_logger(user_data);
|
||||
}
|
||||
return *tree_logger_;
|
||||
}
|
||||
|
||||
private:
|
||||
void ensure_tree_logger(const GeoNodesUserData &user_data) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* In the general case, this is #DynamicSocket. That means that to determine if a node group will
|
||||
* use a particular input, it has to be partially executed.
|
||||
*
|
||||
* In other cases, it's not necessary to look into the node group to determine if an input is
|
||||
* necessary.
|
||||
*/
|
||||
enum class InputUsageHintType {
|
||||
/** The input socket is never used. */
|
||||
Never,
|
||||
/** The input socket is used when a subset of the outputs is used. */
|
||||
DependsOnOutput,
|
||||
/** Can't determine statically if the input is used, check the corresponding output socket. */
|
||||
DynamicSocket,
|
||||
};
|
||||
|
||||
struct InputUsageHint {
|
||||
InputUsageHintType type = InputUsageHintType::DependsOnOutput;
|
||||
/** Used in depends-on-output mode. */
|
||||
Vector<int> output_dependencies;
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the mapping between the #bNodeTree and the corresponding lazy-function graph.
|
||||
* This is *not* a one-to-one mapping.
|
||||
*/
|
||||
struct GeometryNodeLazyFunctionGraphMapping {
|
||||
/**
|
||||
* This is an optimization to avoid partially evaluating a node group just to figure out which
|
||||
* inputs are needed.
|
||||
*/
|
||||
Vector<InputUsageHint> group_input_usage_hints;
|
||||
/**
|
||||
* A mapping used for logging intermediate values.
|
||||
*/
|
||||
MultiValueMap<const lf::Socket *, const bNodeSocket *> bsockets_by_lf_socket_map;
|
||||
/**
|
||||
* Mappings for some special node types. Generally, this mapping does not exist for all node
|
||||
* types, so better have more specialized mappings for now. The key is the identifier if a node.
|
||||
*/
|
||||
Map<int, const lf::FunctionNode *> group_node_map;
|
||||
Map<int, const lf::FunctionNode *> possible_side_effect_node_map;
|
||||
/** Key is the identifier of the zone output node. */
|
||||
Map<int, const lf::FunctionNode *> zone_node_map;
|
||||
|
||||
/* Indexed by #bNodeSocket::index_in_all_outputs. */
|
||||
Array<int> lf_input_index_for_output_bsocket_usage;
|
||||
/* Indexed by #bNodeSocket::index_in_all_outputs. */
|
||||
Array<int> lf_input_index_for_reference_set_for_output;
|
||||
/* Indexed by #bNodeSocket::index_in_tree. */
|
||||
Array<int> lf_index_by_bsocket;
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the information that is necessary to execute a geometry node tree.
|
||||
*/
|
||||
struct GeometryNodesGroupFunction {
|
||||
/**
|
||||
* The lazy-function that does what the node group does. Its inputs and outputs are described
|
||||
* below.
|
||||
*/
|
||||
const LazyFunction *function = nullptr;
|
||||
|
||||
struct {
|
||||
/**
|
||||
* Main input values that come out of the Group Input node.
|
||||
*/
|
||||
IndexRange main;
|
||||
/**
|
||||
* A boolean for every group output that indicates whether that output is needed. It's ok if
|
||||
* those are set to true even when an output is not used, but the other way around will lead to
|
||||
* bugs. The node group uses those values to compute the lifetimes of anonymous attributes.
|
||||
*/
|
||||
IndexRange output_usages;
|
||||
/**
|
||||
* Some node groups can propagate attributes from a geometry input to a geometry output. In
|
||||
* those cases, the caller of the node group has to decide which anonymous attributes have to
|
||||
* be kept alive on the geometry because the caller requires them.
|
||||
*/
|
||||
struct {
|
||||
IndexRange range;
|
||||
Vector<int> geometry_outputs;
|
||||
} references_to_propagate;
|
||||
} inputs;
|
||||
|
||||
struct {
|
||||
/**
|
||||
* Main output values that are passed into the Group Output node.
|
||||
*/
|
||||
IndexRange main;
|
||||
/**
|
||||
* A boolean for every group input that indicates whether this input will be used. Oftentimes
|
||||
* this can be determined without actually computing much. This is used to compute anonymous
|
||||
* attribute lifetimes.
|
||||
*/
|
||||
IndexRange input_usages;
|
||||
} outputs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data that is cached for every #bNodeTree.
|
||||
*/
|
||||
struct GeometryNodesLazyFunctionGraphInfo {
|
||||
/**
|
||||
* Contains resources that need to be freed when the graph is not needed anymore.
|
||||
*/
|
||||
ResourceScope scope;
|
||||
/**
|
||||
* Owned copy of the tree to evaluate. This allows the original tree to be freed without making
|
||||
* the execution graph invalid. This is very common because many changes to a node tree trigger a
|
||||
* new depsgraph-copy but not a new evaluation (since the output has not changed).
|
||||
*
|
||||
* Without an extra copy, one could not output fields/closures to other objects without having
|
||||
* those become dangling on trivial tree changes.
|
||||
*/
|
||||
std::shared_ptr<const bNodeTree> tree;
|
||||
/**
|
||||
* Session UID of the original tree in Main that this is based on. Used to map evaluated data
|
||||
* back to the original tree later on.
|
||||
*/
|
||||
uint32_t original_tree_session_uid;
|
||||
|
||||
GeometryNodesGroupFunction function;
|
||||
/**
|
||||
* The actual lazy-function graph.
|
||||
*/
|
||||
lf::Graph graph;
|
||||
Map<int, const lf::Graph *> debug_zone_body_graphs;
|
||||
/**
|
||||
* Mappings between the lazy-function graph and the #bNodeTree.
|
||||
*/
|
||||
GeometryNodeLazyFunctionGraphMapping mapping;
|
||||
/**
|
||||
* Approximate number of nodes in the graph if all sub-graphs were inlined.
|
||||
* This can be used as a simple heuristic for the complexity of the node group.
|
||||
*/
|
||||
int num_inline_nodes_approximate = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<LazyFunction> get_simulation_output_lazy_function(
|
||||
const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info);
|
||||
std::unique_ptr<LazyFunction> get_simulation_input_lazy_function(
|
||||
const bNodeTree &node_tree,
|
||||
const bNode &node,
|
||||
GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info);
|
||||
std::unique_ptr<LazyFunction> get_switch_node_lazy_function(const bNode &node);
|
||||
std::unique_ptr<LazyFunction> get_index_switch_node_lazy_function(
|
||||
const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info);
|
||||
std::unique_ptr<LazyFunction> get_bake_lazy_function(
|
||||
const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info);
|
||||
std::unique_ptr<LazyFunction> get_menu_switch_node_lazy_function(
|
||||
const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info);
|
||||
std::unique_ptr<LazyFunction> get_menu_switch_node_boolean_outputs_lazy_function(
|
||||
const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info);
|
||||
std::unique_ptr<LazyFunction> get_menu_switch_node_socket_usage_lazy_function(const bNode &node);
|
||||
std::unique_ptr<LazyFunction> get_warning_node_lazy_function(const bNode &node);
|
||||
std::unique_ptr<LazyFunction> get_enable_output_node_lazy_function(
|
||||
const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info);
|
||||
|
||||
/**
|
||||
* Outputs the default value of each output socket that has not been output yet. This needs the
|
||||
* #bNode because otherwise the default values for the outputs are not known. The lazy-function
|
||||
* parameters do not differentiate between e.g. float and vector sockets. The #SocketValueVariant
|
||||
* type is used for both.
|
||||
*/
|
||||
void set_default_remaining_node_outputs(lf::Params ¶ms, const bNode &node);
|
||||
void set_default_value_for_output_socket(lf::Params ¶ms,
|
||||
const int lf_index,
|
||||
const bNodeSocket &bsocket);
|
||||
void construct_socket_default_value(const bke::bNodeSocketType &stype, void *r_value);
|
||||
|
||||
std::string make_anonymous_attribute_socket_inspection_string(const bNodeSocket &socket);
|
||||
std::string make_anonymous_attribute_socket_inspection_string(StringRef node_name,
|
||||
StringRef socket_name);
|
||||
|
||||
std::optional<FoundNestedNodeID> find_nested_node_id(const GeoNodesUserData &user_data,
|
||||
const int node_id);
|
||||
|
||||
/**
|
||||
* Main function that converts a #bNodeTree into a lazy-function graph. If the graph has been
|
||||
* generated already, nothing is done. Under some circumstances a valid graph cannot be created. In
|
||||
* those cases null is returned.
|
||||
*/
|
||||
const std::shared_ptr<const GeometryNodesLazyFunctionGraphInfo> &
|
||||
ensure_geometry_nodes_lazy_function_graph(const bNodeTree &btree);
|
||||
|
||||
/**
|
||||
* In compute contexts that should not be logged verbosely, still log slow nodes.
|
||||
*/
|
||||
constexpr auto node_timer_log_threshold = std::chrono::microseconds(100);
|
||||
|
||||
/**
|
||||
* Utility to measure the time that is spend in a specific compute context during geometry nodes
|
||||
* evaluation.
|
||||
*/
|
||||
class ScopedComputeContextTimer {
|
||||
private:
|
||||
lf::Context &context_;
|
||||
eval_log::TimePoint start_;
|
||||
|
||||
public:
|
||||
ScopedComputeContextTimer(lf::Context &entered_context) : context_(entered_context)
|
||||
{
|
||||
start_ = eval_log::Clock::now();
|
||||
}
|
||||
|
||||
~ScopedComputeContextTimer()
|
||||
{
|
||||
const eval_log::TimePoint end = eval_log::Clock::now();
|
||||
auto &user_data = static_cast<GeoNodesUserData &>(*context_.user_data);
|
||||
auto &local_user_data = static_cast<GeoNodesLocalUserData &>(*context_.local_user_data);
|
||||
const std::chrono::duration duration = end - start_;
|
||||
if (user_data.verbose_log || duration > node_timer_log_threshold) {
|
||||
if (eval_log::NodeTreeLogger *tree_logger = local_user_data.try_get_tree_logger(user_data)) {
|
||||
tree_logger->execution_time += duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility to measure the time that is spend in a specific node during geometry nodes evaluation.
|
||||
*/
|
||||
class ScopedNodeTimer {
|
||||
private:
|
||||
const lf::Context &context_;
|
||||
const bNode &node_;
|
||||
eval_log::TimePoint start_;
|
||||
|
||||
public:
|
||||
ScopedNodeTimer(const lf::Context &context, const bNode &node) : context_(context), node_(node)
|
||||
{
|
||||
start_ = eval_log::Clock::now();
|
||||
}
|
||||
|
||||
~ScopedNodeTimer()
|
||||
{
|
||||
const eval_log::TimePoint end = eval_log::Clock::now();
|
||||
auto &user_data = static_cast<GeoNodesUserData &>(*context_.user_data);
|
||||
auto &local_user_data = static_cast<GeoNodesLocalUserData &>(*context_.local_user_data);
|
||||
const std::chrono::duration duration = end - start_;
|
||||
if (user_data.verbose_log || duration > node_timer_log_threshold) {
|
||||
if (eval_log::NodeTreeLogger *tree_logger = local_user_data.try_get_tree_logger(user_data)) {
|
||||
tree_logger->node_execution_times.append(*tree_logger->allocator,
|
||||
{node_.identifier, start_, end});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool should_log_verbose_in_context(const GeoNodesUserData &user_data,
|
||||
const ComputeContextHash hash);
|
||||
|
||||
/**
|
||||
* Computes the logical or of the inputs and supports short-circuit evaluation (i.e. if the first
|
||||
* input is true already, the other inputs are not checked).
|
||||
*/
|
||||
class LazyFunctionForLogicalOr : public lf::LazyFunction {
|
||||
public:
|
||||
LazyFunctionForLogicalOr(const int inputs_num);
|
||||
|
||||
void execute_impl(lf::Params ¶ms, const lf::Context &context) const override;
|
||||
};
|
||||
|
||||
struct ZoneFunctionIndices {
|
||||
struct {
|
||||
Vector<int> main;
|
||||
Vector<int> border_links;
|
||||
Vector<int> output_usages;
|
||||
Map<ReferenceSetIndex, int> reference_sets;
|
||||
} inputs;
|
||||
struct {
|
||||
Vector<int> main;
|
||||
Vector<int> border_link_usages;
|
||||
Vector<int> input_usages;
|
||||
} outputs;
|
||||
};
|
||||
|
||||
struct ZoneBuildInfo {
|
||||
/** The lazy function that contains the zone. */
|
||||
const LazyFunction *lazy_function = nullptr;
|
||||
|
||||
/** Information about what the various inputs and outputs of the lazy-function are. */
|
||||
ZoneFunctionIndices indices;
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the lazy-function for the "body" of a zone. It contains all the nodes inside of the
|
||||
* zone. The "body" function is wrapped by another lazy-function which represents the zone as a
|
||||
* hole. The wrapper function might invoke the zone body multiple times (like for repeat zones).
|
||||
*/
|
||||
struct ZoneBodyFunction {
|
||||
const LazyFunction *function = nullptr;
|
||||
ZoneFunctionIndices indices;
|
||||
};
|
||||
|
||||
LazyFunction &build_repeat_zone_lazy_function(ResourceScope &scope,
|
||||
const bNodeTree &btree,
|
||||
const bke::bNodeTreeZone &zone,
|
||||
ZoneBuildInfo &zone_info,
|
||||
const ZoneBodyFunction &body_fn);
|
||||
|
||||
LazyFunction &build_foreach_geometry_element_zone_lazy_function(ResourceScope &scope,
|
||||
const bNodeTree &btree,
|
||||
const bke::bNodeTreeZone &zone,
|
||||
ZoneBuildInfo &zone_info,
|
||||
const ZoneBodyFunction &body_fn);
|
||||
|
||||
LazyFunction &build_closure_zone_lazy_function(
|
||||
ResourceScope &scope,
|
||||
const bNodeTree &btree,
|
||||
const bke::bNodeTreeZone &zone,
|
||||
ZoneBuildInfo &zone_info,
|
||||
const ZoneBodyFunction &body_fn,
|
||||
std::shared_ptr<GeometryNodesLazyFunctionGraphInfo> &lf_graph_info);
|
||||
|
||||
struct EvaluateClosureFunctionIndices {
|
||||
struct {
|
||||
Vector<int> main;
|
||||
Vector<int> output_usages;
|
||||
Map<int, int> reference_set_by_output;
|
||||
} inputs;
|
||||
struct {
|
||||
Vector<int> main;
|
||||
Vector<int> input_usages;
|
||||
} outputs;
|
||||
};
|
||||
|
||||
struct EvaluateClosureFunction {
|
||||
const LazyFunction *lazy_function = nullptr;
|
||||
EvaluateClosureFunctionIndices indices;
|
||||
};
|
||||
|
||||
EvaluateClosureFunction build_evaluate_closure_node_lazy_function(ResourceScope &scope,
|
||||
const bNode &bnode);
|
||||
|
||||
void initialize_zone_wrapper(const bke::bNodeTreeZone &zone,
|
||||
ZoneBuildInfo &zone_info,
|
||||
const ZoneBodyFunction &body_fn,
|
||||
bool expose_all_reference_sets,
|
||||
Vector<lf::Input> &r_inputs,
|
||||
Vector<lf::Output> &r_outputs);
|
||||
|
||||
std::string zone_wrapper_input_name(const ZoneBuildInfo &zone_info,
|
||||
const bke::bNodeTreeZone &zone,
|
||||
const Span<lf::Input> inputs,
|
||||
const int lf_socket_i);
|
||||
|
||||
std::string zone_wrapper_output_name(const ZoneBuildInfo &zone_info,
|
||||
const bke::bNodeTreeZone &zone,
|
||||
const Span<lf::Output> outputs,
|
||||
const int lf_socket_i);
|
||||
|
||||
/**
|
||||
* Report an error from a multi-function evaluation within a Geometry Nodes evaluation.
|
||||
*
|
||||
* NOTE: Currently, this the error is only actually reported under limited circumstances. It's
|
||||
* still safe to call this function from any multi-function though.
|
||||
*/
|
||||
void report_from_multi_function(const mf::Context &context,
|
||||
NodeWarningType type,
|
||||
std::string message);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
359
blender-5.2.0/source/blender/nodes/NOD_geometry_nodes_list.hh
Normal file
359
blender-5.2.0/source/blender/nodes/NOD_geometry_nodes_list.hh
Normal file
@@ -0,0 +1,359 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "BLI_generic_pointer.hh"
|
||||
#include "BLI_generic_virtual_array.hh"
|
||||
#include "BLI_implicit_sharing_ptr.hh"
|
||||
#include "BLI_memory_counter_fwd.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_list_fwd.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
class GList : public ImplicitSharingMixin {
|
||||
public:
|
||||
class ArrayData {
|
||||
public:
|
||||
/**
|
||||
* This is const because it uses implicit sharing. In some contexts the const can be cast away
|
||||
* when it's clear that the data is not shared.
|
||||
*/
|
||||
const void *data;
|
||||
ImplicitSharingPtr<> sharing_info;
|
||||
static ArrayData ForValue(const GPointer &value, int64_t size);
|
||||
static ArrayData ForDefaultValue(const CPPType &type, int64_t size);
|
||||
static ArrayData ForConstructed(const CPPType &type, int64_t size);
|
||||
static ArrayData ForUninitialized(const CPPType &type, int64_t size);
|
||||
|
||||
void count_memory(MemoryCounter &memory, const CPPType &type, const int64_t size) const;
|
||||
|
||||
GMutableSpan span_for_write(const CPPType &type, int64_t size);
|
||||
};
|
||||
|
||||
class SingleData {
|
||||
public:
|
||||
/**
|
||||
* This is const because it uses implicit sharing. In some contexts the const can be cast away
|
||||
* when it's clear that the data is not shared.
|
||||
*/
|
||||
const void *value;
|
||||
ImplicitSharingPtr<> sharing_info;
|
||||
static SingleData ForValue(const GPointer &value);
|
||||
static SingleData ForDefaultValue(const CPPType &type);
|
||||
|
||||
void count_memory(MemoryCounter &memory, const CPPType &type) const;
|
||||
|
||||
GMutablePointer value_for_write(const CPPType &type);
|
||||
};
|
||||
|
||||
using DataVariant = std::variant<ArrayData, SingleData>;
|
||||
|
||||
private:
|
||||
const CPPType &cpp_type_;
|
||||
DataVariant data_;
|
||||
int64_t size_ = 0;
|
||||
|
||||
public:
|
||||
/**
|
||||
* #GList is expected to always have a valid #CPPType. Therefore, it can't be default
|
||||
* constructed.
|
||||
*/
|
||||
GList() = delete;
|
||||
GList(const CPPType &type) : GList(type, DataVariant{}, 0) {}
|
||||
GList(const CPPType &type, DataVariant data, const int64_t size);
|
||||
static GListPtr create(const CPPType &type, DataVariant data, const int64_t size);
|
||||
template<typename ContainerT> static GListPtr from_container(ContainerT &&container);
|
||||
static GListPtr from_garray(GArray<> array);
|
||||
|
||||
DataVariant &data();
|
||||
const DataVariant &data() const;
|
||||
const CPPType &cpp_type() const;
|
||||
int64_t size() const;
|
||||
|
||||
/** Access values stored in the list. This is a variant because lists support different storage
|
||||
* backends and more may be added in the future. */
|
||||
std::variant<GSpan, GPointer> values() const;
|
||||
std::variant<GMutableSpan, GMutablePointer> values_for_write();
|
||||
|
||||
void delete_self() override;
|
||||
GListPtr copy() const;
|
||||
|
||||
/** Access the list as virtual array. */
|
||||
GVArray varray() const;
|
||||
|
||||
void count_memory(MemoryCounter &memory) const;
|
||||
|
||||
void ensure_owns_direct_data();
|
||||
bool owns_direct_data() const;
|
||||
|
||||
/**
|
||||
* Get a typed reference to this field. Note that #Field<T> happens to be identical to #GField on
|
||||
* a bit-level. So this is just a cast.
|
||||
*/
|
||||
template<typename T> const List<T> &typed() const;
|
||||
template<typename T> List<T> &typed();
|
||||
};
|
||||
|
||||
template<typename T> class List {
|
||||
public:
|
||||
using base_type = T;
|
||||
using generic_type = GList;
|
||||
|
||||
private:
|
||||
/**
|
||||
* #List<T> just stores a #GList. This makes converting between the two types easy.
|
||||
*/
|
||||
GList list_;
|
||||
|
||||
friend GList;
|
||||
|
||||
public:
|
||||
List();
|
||||
|
||||
template<typename ContainerT>
|
||||
requires std::is_same_v<typename ContainerT::value_type, T>
|
||||
static ListPtr<T> from_container(ContainerT &&container);
|
||||
|
||||
/** This is implicitly cast to #GField which is always valid. */
|
||||
operator const GList &() const;
|
||||
|
||||
/** Access the list as virtual array. */
|
||||
VArray<T> varray() const;
|
||||
|
||||
std::variant<Span<T>, const T *> values() const;
|
||||
std::variant<MutableSpan<T>, T *> values_for_write();
|
||||
|
||||
template<typename Fn> void foreach(Fn &&fn) const;
|
||||
template<typename Fn> void foreach_for_write(Fn &&fn);
|
||||
};
|
||||
|
||||
class GListPtr {
|
||||
private:
|
||||
ImplicitSharingPtr<GList> data_;
|
||||
|
||||
public:
|
||||
GListPtr() = default;
|
||||
explicit GListPtr(const GList *data) : data_(data) {}
|
||||
explicit GListPtr(const CPPType &type) : GListPtr(MEM_new<GList>(__func__, type)) {}
|
||||
|
||||
operator bool() const;
|
||||
const GList *operator->() const;
|
||||
const GList &operator*() const;
|
||||
|
||||
const GList *get() const;
|
||||
GList &get_for_write();
|
||||
|
||||
template<typename T> const ListPtr<T> &typed() const;
|
||||
};
|
||||
|
||||
template<typename T> class ListPtr {
|
||||
public:
|
||||
using base_type = T;
|
||||
using generic_type = GListPtr;
|
||||
|
||||
private:
|
||||
GListPtr data_;
|
||||
|
||||
public:
|
||||
operator bool() const;
|
||||
operator const GListPtr &() const;
|
||||
const List<T> *operator->() const;
|
||||
const List<T> &operator*() const;
|
||||
|
||||
const List<T> &get() const;
|
||||
List<T> &get_for_write();
|
||||
};
|
||||
|
||||
template<typename T> constexpr bool is_ListPtr_v = false;
|
||||
template<typename T> constexpr bool is_ListPtr_v<ListPtr<T>> = true;
|
||||
|
||||
template<typename ContainerT> inline GListPtr GList::from_container(ContainerT &&container)
|
||||
{
|
||||
using T = typename std::decay_t<ContainerT>::value_type;
|
||||
static_assert(std::is_convertible_v<ContainerT, MutableSpan<T>>);
|
||||
auto *sharable_data = new ImplicitSharedValue<std::decay_t<ContainerT>>(
|
||||
std::forward<ContainerT>(container));
|
||||
ArrayData array_data;
|
||||
array_data.data = sharable_data->data.data();
|
||||
array_data.sharing_info = ImplicitSharingPtr<>(sharable_data);
|
||||
return GList::create(CPPType::get<T>(), std::move(array_data), sharable_data->data.size());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename ContainerT>
|
||||
requires std::is_same_v<typename ContainerT::value_type, T>
|
||||
inline ListPtr<T> List<T>::from_container(ContainerT &&container)
|
||||
{
|
||||
return GList::from_container(std::forward<ContainerT>(container)).template typed<T>();
|
||||
}
|
||||
|
||||
inline GList::DataVariant &GList::data()
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
inline const GList::DataVariant &GList::data() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
inline const CPPType &GList::cpp_type() const
|
||||
{
|
||||
return cpp_type_;
|
||||
}
|
||||
|
||||
inline int64_t GList::size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
template<typename T> inline const List<T> &GList::typed() const
|
||||
{
|
||||
static_assert(sizeof(GList) == sizeof(List<T>));
|
||||
BLI_assert(this->cpp_type().is<T>());
|
||||
return reinterpret_cast<const List<T> &>(*this);
|
||||
}
|
||||
|
||||
template<typename T> inline List<T> &GList::typed()
|
||||
{
|
||||
static_assert(sizeof(GList) == sizeof(List<T>));
|
||||
BLI_assert(this->cpp_type().is<T>());
|
||||
return reinterpret_cast<List<T> &>(*this);
|
||||
}
|
||||
|
||||
template<typename T> inline List<T>::List() : list_(CPPType::get<T>()) {}
|
||||
|
||||
template<typename T> inline VArray<T> List<T>::varray() const
|
||||
{
|
||||
return list_.varray().template typed<T>();
|
||||
}
|
||||
|
||||
template<typename T> inline std::variant<Span<T>, const T *> List<T>::values() const
|
||||
{
|
||||
const std::variant<GSpan, GPointer> values = list_.values();
|
||||
if (const auto *span_values = std::get_if<GSpan>(&values)) {
|
||||
return span_values->typed<T>();
|
||||
}
|
||||
if (const auto *single_value = std::get_if<GPointer>(&values)) {
|
||||
return single_value->get<T>();
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T> inline std::variant<MutableSpan<T>, T *> List<T>::values_for_write()
|
||||
{
|
||||
const std::variant<GMutableSpan, GMutablePointer> values = list_.values_for_write();
|
||||
if (const auto *span_values = std::get_if<GMutableSpan>(&values)) {
|
||||
return span_values->typed<T>();
|
||||
}
|
||||
if (const auto *single_value = std::get_if<GMutablePointer>(&values)) {
|
||||
return single_value->get<T>();
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T> template<typename Fn> inline void List<T>::foreach(Fn &&fn) const
|
||||
{
|
||||
const std::variant<Span<T>, const T *> values = this->values();
|
||||
if (const auto *span_values = std::get_if<Span<T>>(&values)) {
|
||||
for (const T &value : *span_values) {
|
||||
fn(value);
|
||||
}
|
||||
}
|
||||
else if (const auto *single_value = std::get_if<const T *>(&values)) {
|
||||
fn(**single_value);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> template<typename Fn> inline void List<T>::foreach_for_write(Fn &&fn)
|
||||
{
|
||||
const std::variant<MutableSpan<T>, T *> values = this->values_for_write();
|
||||
if (auto *span_values = std::get_if<MutableSpan<T>>(&values)) {
|
||||
for (T &value : *span_values) {
|
||||
fn(value);
|
||||
}
|
||||
}
|
||||
else if (auto *single_value = std::get_if<T *>(&values)) {
|
||||
fn(**single_value);
|
||||
}
|
||||
}
|
||||
|
||||
inline GListPtr::operator bool() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
inline const GList *GListPtr::operator->() const
|
||||
{
|
||||
return data_.get();
|
||||
}
|
||||
|
||||
inline const GList &GListPtr::operator*() const
|
||||
{
|
||||
return *data_;
|
||||
}
|
||||
|
||||
inline const GList *GListPtr::get() const
|
||||
{
|
||||
return data_.get();
|
||||
}
|
||||
|
||||
inline GList &GListPtr::get_for_write()
|
||||
{
|
||||
BLI_assert(data_);
|
||||
if (!data_->is_mutable()) {
|
||||
*this = data_->copy();
|
||||
}
|
||||
BLI_assert(data_->is_mutable());
|
||||
data_->tag_ensured_mutable();
|
||||
return const_cast<GList &>(*data_);
|
||||
}
|
||||
|
||||
template<typename T> inline const ListPtr<T> &GListPtr::typed() const
|
||||
{
|
||||
static_assert(sizeof(GList) == sizeof(List<T>));
|
||||
BLI_assert(!data_ || data_->cpp_type().is<T>());
|
||||
return reinterpret_cast<const ListPtr<T> &>(*this);
|
||||
}
|
||||
|
||||
template<typename T> inline ListPtr<T>::operator bool() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
template<typename T> inline ListPtr<T>::operator const GListPtr &() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
template<typename T> inline const List<T> *ListPtr<T>::operator->() const
|
||||
{
|
||||
if (!data_) {
|
||||
return nullptr;
|
||||
}
|
||||
return &data_->typed<T>();
|
||||
}
|
||||
|
||||
template<typename T> inline const List<T> &ListPtr<T>::operator*() const
|
||||
{
|
||||
return data_->typed<T>();
|
||||
}
|
||||
|
||||
template<typename T> inline const List<T> &ListPtr<T>::get() const
|
||||
{
|
||||
return data_->typed<T>();
|
||||
}
|
||||
|
||||
template<typename T> inline List<T> &ListPtr<T>::get_for_write()
|
||||
{
|
||||
return data_.get_for_write().template typed<T>();
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,15 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
class GList;
|
||||
class GListPtr;
|
||||
|
||||
template<typename T> class List;
|
||||
template<typename T> class ListPtr;
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,88 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "NOD_bundle_type_fwd.hh"
|
||||
|
||||
namespace blender::nodes::physics_bundles {
|
||||
|
||||
class MeshColliderBundle {
|
||||
public:
|
||||
static constexpr StringRefNull name = "Blender.Collider.Mesh";
|
||||
static const FlatBundleTypePtr &get_bundle_type();
|
||||
};
|
||||
|
||||
class CollisionContactsBundle {
|
||||
public:
|
||||
static constexpr StringRefNull name = "Blender.CollisionContacts";
|
||||
static const FlatBundleTypePtr &get_bundle_type();
|
||||
};
|
||||
|
||||
class DampingBundle {
|
||||
public:
|
||||
static constexpr StringRefNull name = "Blender.Damping";
|
||||
static const FlatBundleTypePtr &get_bundle_type();
|
||||
};
|
||||
|
||||
class PinPositionBundle {
|
||||
public:
|
||||
static constexpr StringRefNull name = "Blender.Constraint.PinPosition";
|
||||
static const FlatBundleTypePtr &get_bundle_type();
|
||||
};
|
||||
|
||||
class PinRotationBundle {
|
||||
public:
|
||||
static constexpr StringRefNull name = "Blender.Constraint.PinRotation";
|
||||
static const FlatBundleTypePtr &get_bundle_type();
|
||||
};
|
||||
|
||||
class RodStretchShearBundle {
|
||||
public:
|
||||
static constexpr StringRefNull name = "Blender.Constraint.RodStretchShear";
|
||||
static const FlatBundleTypePtr &get_bundle_type();
|
||||
};
|
||||
|
||||
class RodBendTwistBundle {
|
||||
public:
|
||||
static constexpr StringRefNull name = "Blender.Constraint.RodBendTwist";
|
||||
static const FlatBundleTypePtr &get_bundle_type();
|
||||
};
|
||||
|
||||
class EdgeLengthConstraintBundle {
|
||||
public:
|
||||
static constexpr StringRefNull name = "Blender.Constraint.EdgeLength";
|
||||
static const FlatBundleTypePtr &get_bundle_type();
|
||||
};
|
||||
|
||||
class CrossEdgeLengthConstraintBundle {
|
||||
public:
|
||||
static constexpr StringRefNull name = "Blender.Constraint.CrossEdgeLength";
|
||||
static const FlatBundleTypePtr &get_bundle_type();
|
||||
};
|
||||
|
||||
/** This is currently not used by built-in nodes but is used in essential assets. */
|
||||
class ForceBundle {
|
||||
public:
|
||||
static constexpr StringRefNull name = "Blender.Force";
|
||||
static const FlatBundleTypePtr &get_bundle_type();
|
||||
};
|
||||
|
||||
/** This is currently not used by built-in nodes but is used in essential assets. */
|
||||
class CustomGeometryEffector {
|
||||
public:
|
||||
static constexpr StringRefNull name = "Blender.CustomEffector.Geometry";
|
||||
static const FlatBundleTypePtr &get_bundle_type();
|
||||
};
|
||||
|
||||
/** This is currently not used by built-in nodes but is used in essential assets. */
|
||||
class CustomWorldEffector {
|
||||
public:
|
||||
static constexpr StringRefNull name = "Blender.CustomEffector.World";
|
||||
static const FlatBundleTypePtr &get_bundle_type();
|
||||
};
|
||||
|
||||
} // namespace blender::nodes::physics_bundles
|
||||
@@ -0,0 +1,35 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NOD_nodes_srna.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct StructRNA;
|
||||
struct bNodeTree;
|
||||
|
||||
namespace nodes {
|
||||
|
||||
/**
|
||||
* Shared across all socket types, though some entries don't make sense for some types.
|
||||
*/
|
||||
enum class GeometryNodesInputType {
|
||||
Fallback = 0,
|
||||
Value = 1,
|
||||
Attribute = 2,
|
||||
Layer = 3,
|
||||
};
|
||||
|
||||
extern const EnumPropertyItem geometry_nodes_input_type_items_fallback[];
|
||||
extern const EnumPropertyItem geometry_nodes_input_type_items_value[];
|
||||
extern const EnumPropertyItem geometry_nodes_input_type_items_value_or_attribute[];
|
||||
extern const EnumPropertyItem geometry_nodes_input_type_items_value_or_attribute_or_layer[];
|
||||
|
||||
std::shared_ptr<GeneratedTreeSrnaData> create_geometry_nodes_rna_for_modifier(
|
||||
const bNodeTree &tree);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,97 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_socket_value.hh"
|
||||
#include "BKE_volume_grid_fwd.hh"
|
||||
|
||||
#include "BLI_color_types.hh"
|
||||
#include "BLI_math_quaternion_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_memory_utils.hh"
|
||||
|
||||
#include "FN_field.hh"
|
||||
#include "FN_lazy_function.hh"
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_bundle_fwd.hh"
|
||||
#include "NOD_geometry_nodes_closure_fwd.hh"
|
||||
#include "NOD_geometry_nodes_list_fwd.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
namespace bke {
|
||||
class SocketValueVariant;
|
||||
}
|
||||
namespace nodes {
|
||||
struct GeoNodesUserData;
|
||||
}
|
||||
|
||||
namespace nodes {
|
||||
|
||||
template<typename T> struct GeoNodesMultiInput {
|
||||
using value_type = T;
|
||||
Vector<T> values;
|
||||
};
|
||||
template<typename T> constexpr bool is_GeoNodesMultiInput_v = false;
|
||||
template<typename T> constexpr bool is_GeoNodesMultiInput_v<GeoNodesMultiInput<T>> = true;
|
||||
|
||||
/**
|
||||
* Executes a multi-function. If all inputs are single values, the results will also be single
|
||||
* values. If any input is a field, the outputs will also be fields.
|
||||
*/
|
||||
[[nodiscard]] bool execute_multi_function_on_value_variant(
|
||||
const mf::MultiFunction &fn,
|
||||
const std::shared_ptr<mf::MultiFunction> &owned_fn,
|
||||
Span<bke::SocketValueVariant *> input_values,
|
||||
Span<bke::SocketValueVariant *> output_values,
|
||||
GeoNodesUserData *user_data,
|
||||
std::string &r_error_message);
|
||||
|
||||
[[nodiscard]] inline bool execute_multi_function_on_value_variant(
|
||||
const std::shared_ptr<mf::MultiFunction> &owned_fn,
|
||||
const Span<bke::SocketValueVariant *> input_values,
|
||||
const Span<bke::SocketValueVariant *> output_values,
|
||||
GeoNodesUserData *user_data,
|
||||
std::string &r_error_message)
|
||||
{
|
||||
const mf::MultiFunction &fn = *owned_fn;
|
||||
return execute_multi_function_on_value_variant(
|
||||
fn, std::move(owned_fn), input_values, output_values, user_data, r_error_message);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool execute_multi_function_on_value_variant(
|
||||
const mf::MultiFunction &fn,
|
||||
const Span<bke::SocketValueVariant *> input_values,
|
||||
const Span<bke::SocketValueVariant *> output_values,
|
||||
GeoNodesUserData *user_data,
|
||||
std::string &r_error_message)
|
||||
{
|
||||
return execute_multi_function_on_value_variant(
|
||||
fn, {}, input_values, output_values, user_data, r_error_message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs implicit conversion between socket types. Returns false if the conversion is not
|
||||
* possible. In that case, r_to_value is left uninitialized.
|
||||
*/
|
||||
[[nodiscard]] std::optional<bke::SocketValueVariant> implicitly_convert_socket_value(
|
||||
const bke::bNodeSocketType &from_type,
|
||||
const bke::SocketValueVariant &from_value,
|
||||
const bke::bNodeSocketType &to_type);
|
||||
|
||||
/**
|
||||
* Builds a lazy-function that can convert between socket types. Returns null if the conversion is
|
||||
* never possible.
|
||||
*/
|
||||
const fn::lazy_function::LazyFunction *build_implicit_conversion_lazy_function(
|
||||
const bke::bNodeSocketType &from_type,
|
||||
const bke::bNodeSocketType &to_type,
|
||||
ResourceScope &scope);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,22 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
/** These values are also written to .blend files, so don't change them lightly. */
|
||||
enum class NodeWarningType {
|
||||
Error = 0,
|
||||
Warning = 1,
|
||||
Info = 2,
|
||||
};
|
||||
|
||||
int node_warning_type_icon(NodeWarningType type);
|
||||
int node_warning_type_severity(NodeWarningType type);
|
||||
StringRefNull node_warning_type_name(NodeWarningType type);
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,60 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_map.hh"
|
||||
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_node_socket_value.hh"
|
||||
|
||||
namespace blender::nodes::inverse_eval {
|
||||
|
||||
/**
|
||||
* Is passed to inverse node evaluation functions to figure out how the inputs have to change
|
||||
* exactly to get a specific output value. What is special here is that this gives access to
|
||||
* (logged) node inputs and node output values, instead of just either inputs or outputs.
|
||||
*
|
||||
* This is required because sometimes certain inputs are fixed and need to be known to be able to
|
||||
* figure out how another input changes. A typical example of this is the math node, where the
|
||||
* second input is fixed and only the first input changes.
|
||||
*/
|
||||
class InverseEvalParams {
|
||||
private:
|
||||
const Map<const bNodeSocket *, bke::SocketValueVariant> &socket_values_;
|
||||
Map<const bNodeSocket *, bke::SocketValueVariant> &updated_socket_values_;
|
||||
|
||||
public:
|
||||
const bNode &node;
|
||||
|
||||
InverseEvalParams(const bNode &node,
|
||||
const Map<const bNodeSocket *, bke::SocketValueVariant> &socket_values,
|
||||
Map<const bNodeSocket *, bke::SocketValueVariant> &updated_socket_values);
|
||||
|
||||
template<typename T> T get_output(const UString identifier) const
|
||||
{
|
||||
const bNodeSocket &socket = *node.output_by_identifier(identifier);
|
||||
if (const bke::SocketValueVariant *value = socket_values_.lookup_ptr(&socket)) {
|
||||
return value->get<T>();
|
||||
}
|
||||
return T();
|
||||
}
|
||||
|
||||
template<typename T> T get_input(const UString identifier) const
|
||||
{
|
||||
const bNodeSocket &socket = *node.input_by_identifier(identifier);
|
||||
if (const bke::SocketValueVariant *value = socket_values_.lookup_ptr(&socket)) {
|
||||
return value->get<T>();
|
||||
}
|
||||
return T();
|
||||
}
|
||||
|
||||
template<typename T> void set_input(const UString identifier, T value)
|
||||
{
|
||||
const bNodeSocket &socket = *node.input_by_identifier(identifier);
|
||||
updated_socket_values_.add(&socket, bke::SocketValueVariant(value));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::nodes::inverse_eval
|
||||
48
blender-5.2.0/source/blender/nodes/NOD_inverse_eval_path.hh
Normal file
48
blender-5.2.0/source/blender/nodes/NOD_inverse_eval_path.hh
Normal file
@@ -0,0 +1,48 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_compute_context.hh"
|
||||
|
||||
#include "NOD_value_elem.hh"
|
||||
|
||||
namespace blender::nodes::inverse_eval {
|
||||
|
||||
using namespace value_elem;
|
||||
|
||||
struct LocalInverseEvalTargets {
|
||||
Vector<SocketElem> input_sockets;
|
||||
Vector<GroupInputElem> group_inputs;
|
||||
Vector<ValueNodeElem> value_nodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Scans the node tree backwards from the given socket to figure out which values may need to
|
||||
* change to set the given socket to a specific value.
|
||||
*/
|
||||
LocalInverseEvalTargets find_local_inverse_eval_targets(const bNodeTree &tree,
|
||||
const SocketElem &initial_socket_elem);
|
||||
|
||||
/**
|
||||
* Traverses the inverse evaluation path that starts at the given socket in a specific compute
|
||||
* context.
|
||||
*
|
||||
* \param initial_context: Compute context where the inverse evaluation starts (e.g. may be deep in
|
||||
* some nested node group).
|
||||
* \param initial_socket_elem: Socket and value element that is propagated backwards.
|
||||
* \param foreach_context_fn: If provided, it is called for each compute context that is touched by
|
||||
* the inverse evaluation path.
|
||||
* \param foreach_socket_fn: If provided, it is called for each socket on the inverse evaluation
|
||||
* path.
|
||||
*/
|
||||
void foreach_element_on_inverse_eval_path(
|
||||
const ComputeContext &initial_context,
|
||||
const SocketElem &initial_socket_elem,
|
||||
FunctionRef<void(const ComputeContext &context)> foreach_context_fn,
|
||||
FunctionRef<void(const ComputeContext &context,
|
||||
const bNodeSocket &socket,
|
||||
const ElemVariant &elem)> foreach_socket_fn);
|
||||
|
||||
} // namespace blender::nodes::inverse_eval
|
||||
59
blender-5.2.0/source/blender/nodes/NOD_inverse_eval_run.hh
Normal file
59
blender-5.2.0/source/blender/nodes/NOD_inverse_eval_run.hh
Normal file
@@ -0,0 +1,59 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_node_socket_value.hh"
|
||||
|
||||
#include "BLI_compute_context.hh"
|
||||
|
||||
#include "NOD_eval_log.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct NodesModifierData;
|
||||
|
||||
namespace nodes::inverse_eval {
|
||||
|
||||
using bke::SocketValueVariant;
|
||||
|
||||
/**
|
||||
* Utility struct to pack information about a value that is propagated backwards through a node
|
||||
* tree.
|
||||
*/
|
||||
struct SocketToUpdate {
|
||||
const ComputeContext *context = nullptr;
|
||||
const bNodeSocket *socket = nullptr;
|
||||
/** Only needed if the socket is a multi-input socket. */
|
||||
const bNodeLink *multi_input_link = nullptr;
|
||||
/** The new value that the socket should have after the backpropagation. */
|
||||
SocketValueVariant new_value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to change socket/node/modifier values so that the given sockets will have a specific value.
|
||||
*/
|
||||
bool backpropagate_socket_values(bContext &C,
|
||||
Object &object,
|
||||
NodesModifierData &nmd,
|
||||
eval_log::NodesEvalLog &eval_log,
|
||||
Span<SocketToUpdate> sockets_to_update);
|
||||
|
||||
/**
|
||||
* Attempts to get the value for a specific socket from the log.
|
||||
*/
|
||||
std::optional<SocketValueVariant> get_logged_socket_value(eval_log::NodeTreeLog &tree_log,
|
||||
const bNodeSocket &socket);
|
||||
|
||||
/**
|
||||
* Performs implicit conversion from the old to the new socket on the given value, if possible.
|
||||
*/
|
||||
std::optional<bke::SocketValueVariant> convert_single_socket_value(
|
||||
const bNodeSocket &old_socket,
|
||||
const bNodeSocket &new_socket,
|
||||
const bke::SocketValueVariant &old_value);
|
||||
|
||||
} // namespace nodes::inverse_eval
|
||||
|
||||
} // namespace blender
|
||||
38
blender-5.2.0/source/blender/nodes/NOD_math_functions.hh
Normal file
38
blender-5.2.0/source/blender/nodes/NOD_math_functions.hh
Normal file
@@ -0,0 +1,38 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_ustring.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
void node_math_build_multi_function(NodeMultiFunctionBuilder &builder);
|
||||
|
||||
struct FloatMathOperationInfo {
|
||||
StringRefNull title_case_name;
|
||||
StringRefNull shader_name;
|
||||
UString multi_function_name;
|
||||
|
||||
FloatMathOperationInfo() = delete;
|
||||
FloatMathOperationInfo(StringRefNull title_case_name,
|
||||
StringRefNull shader_name,
|
||||
UString multi_function_name = {})
|
||||
: title_case_name(title_case_name),
|
||||
shader_name(shader_name),
|
||||
multi_function_name(multi_function_name)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
const FloatMathOperationInfo *get_float_math_operation_info(int operation);
|
||||
const FloatMathOperationInfo *get_float3_math_operation_info(int operation);
|
||||
const FloatMathOperationInfo *get_float_compare_operation_info(int operation);
|
||||
|
||||
} // namespace blender::nodes
|
||||
42
blender-5.2.0/source/blender/nodes/NOD_menu_value.hh
Normal file
42
blender-5.2.0/source/blender/nodes/NOD_menu_value.hh
Normal file
@@ -0,0 +1,42 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_unique_hash.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
/**
|
||||
* Don't use integer for menus directly, so that there is a each static single value type maps to
|
||||
* exactly one socket type. Also it avoids accidentally casting the menu value to other types.
|
||||
*/
|
||||
struct MenuValue {
|
||||
int value = 0;
|
||||
|
||||
MenuValue() = default;
|
||||
explicit MenuValue(const int value) : value(value) {}
|
||||
|
||||
template<typename EnumT>
|
||||
MenuValue(const EnumT value)
|
||||
requires(std::is_enum_v<EnumT>)
|
||||
: value(int(value))
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(this->value);
|
||||
}
|
||||
|
||||
void hash_unique(UniqueHashBytes &hash) const
|
||||
{
|
||||
hash_unique_default(this->value, hash);
|
||||
}
|
||||
|
||||
friend bool operator==(const MenuValue &a, const MenuValue &b) = default;
|
||||
};
|
||||
|
||||
} // namespace blender::nodes
|
||||
164
blender-5.2.0/source/blender/nodes/NOD_multi_function.hh
Normal file
164
blender-5.2.0/source/blender/nodes/NOD_multi_function.hh
Normal file
@@ -0,0 +1,164 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
class NodeMultiFunctions;
|
||||
|
||||
/**
|
||||
* Utility class to help nodes build a multi-function for themselves.
|
||||
*/
|
||||
class NodeMultiFunctionBuilder : NonCopyable, NonMovable {
|
||||
private:
|
||||
const bNode &node_;
|
||||
const bNodeTree &tree_;
|
||||
std::shared_ptr<mf::MultiFunction> owned_built_fn_;
|
||||
const mf::MultiFunction *built_fn_ = nullptr;
|
||||
/**
|
||||
* If set, the created multi-function can take shared ownership of the tree. This allows it to
|
||||
* safely reference data owned by the node tree without making additional copies.
|
||||
*/
|
||||
std::shared_ptr<const bNodeTree> shared_tree_;
|
||||
|
||||
friend NodeMultiFunctions;
|
||||
|
||||
public:
|
||||
NodeMultiFunctionBuilder(const bNode &node,
|
||||
const bNodeTree &tree,
|
||||
std::shared_ptr<const bNodeTree> shared_tree = nullptr);
|
||||
|
||||
/**
|
||||
* Assign a multi-function for the current node. The input and output parameters of the function
|
||||
* have to match the available sockets in the node.
|
||||
*/
|
||||
void set_matching_fn(const mf::MultiFunction *fn);
|
||||
void set_matching_fn(const mf::MultiFunction &fn);
|
||||
|
||||
/**
|
||||
* Utility method for creating and assigning a multi-function when it can't have a static
|
||||
* lifetime.
|
||||
*/
|
||||
template<typename T, typename... Args> void construct_and_set_matching_fn(Args &&...args);
|
||||
|
||||
/**
|
||||
* Similar to #construct_and_set_matching_fn, but can be used when the type name of the
|
||||
* multi-function is not known (e.g. when using `mf::build::SI1_SO`).
|
||||
*
|
||||
* \param create_multi_function: A function that returns the multi-function by value.
|
||||
*/
|
||||
template<typename Fn> void construct_and_set_matching_fn_cb(Fn &&create_multi_function);
|
||||
|
||||
const bNode &node();
|
||||
const bNodeTree &tree();
|
||||
const mf::MultiFunction &function();
|
||||
|
||||
/**
|
||||
* If the created multi-function references data from the node it was created for, it should also
|
||||
* take shared ownership of the tree if provided. This makes sure that the tree is not freed
|
||||
* while multi-functions referencing it are still in use.
|
||||
*/
|
||||
const std::shared_ptr<const bNodeTree> &shared_tree();
|
||||
};
|
||||
|
||||
/**
|
||||
* Gives access to multi-functions for all nodes in a node tree that support them.
|
||||
*/
|
||||
class NodeMultiFunctions {
|
||||
public:
|
||||
struct Item {
|
||||
const mf::MultiFunction *fn = nullptr;
|
||||
std::shared_ptr<mf::MultiFunction> owned_fn;
|
||||
};
|
||||
|
||||
private:
|
||||
Map<const bNode *, Item> map_;
|
||||
|
||||
public:
|
||||
NodeMultiFunctions(const bNodeTree &tree,
|
||||
const std::shared_ptr<const bNodeTree> &shared_tree = nullptr);
|
||||
|
||||
const Item &try_get(const bNode &node) const;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #NodeMultiFunctionBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline NodeMultiFunctionBuilder::NodeMultiFunctionBuilder(
|
||||
const bNode &node, const bNodeTree &tree, std::shared_ptr<const bNodeTree> shared_tree)
|
||||
: node_(node), tree_(tree), shared_tree_(std::move(shared_tree))
|
||||
{
|
||||
}
|
||||
|
||||
inline const bNode &NodeMultiFunctionBuilder::node()
|
||||
{
|
||||
return node_;
|
||||
}
|
||||
|
||||
inline const bNodeTree &NodeMultiFunctionBuilder::tree()
|
||||
{
|
||||
return tree_;
|
||||
}
|
||||
|
||||
inline const std::shared_ptr<const bNodeTree> &NodeMultiFunctionBuilder::shared_tree()
|
||||
{
|
||||
return shared_tree_;
|
||||
}
|
||||
|
||||
inline const mf::MultiFunction &NodeMultiFunctionBuilder::function()
|
||||
{
|
||||
return *built_fn_;
|
||||
}
|
||||
|
||||
inline void NodeMultiFunctionBuilder::set_matching_fn(const mf::MultiFunction *fn)
|
||||
{
|
||||
built_fn_ = fn;
|
||||
}
|
||||
|
||||
inline void NodeMultiFunctionBuilder::set_matching_fn(const mf::MultiFunction &fn)
|
||||
{
|
||||
built_fn_ = &fn;
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
inline void NodeMultiFunctionBuilder::construct_and_set_matching_fn(Args &&...args)
|
||||
{
|
||||
owned_built_fn_ = std::make_shared<T>(std::forward<Args>(args)...);
|
||||
built_fn_ = &*owned_built_fn_;
|
||||
}
|
||||
|
||||
template<typename Fn>
|
||||
inline void NodeMultiFunctionBuilder::construct_and_set_matching_fn_cb(Fn &&create_multi_function)
|
||||
{
|
||||
using T = decltype(create_multi_function());
|
||||
T *allocated_function = new T(create_multi_function());
|
||||
owned_built_fn_ = std::shared_ptr<T>(allocated_function);
|
||||
built_fn_ = &*owned_built_fn_;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #NodeMultiFunctions Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline const NodeMultiFunctions::Item &NodeMultiFunctions::try_get(const bNode &node) const
|
||||
{
|
||||
static Item empty_item;
|
||||
const Item *item = map_.lookup_ptr(&node);
|
||||
if (item == nullptr) {
|
||||
return empty_item;
|
||||
}
|
||||
return *item;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::nodes
|
||||
20
blender-5.2.0/source/blender/nodes/NOD_nested_node_id.hh
Normal file
20
blender-5.2.0/source/blender/nodes/NOD_nested_node_id.hh
Normal file
@@ -0,0 +1,20 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
/**
|
||||
* Utility struct to store information about a nested node id. Also see #bNestedNodeRef.
|
||||
* Sometimes these IDs can only be used when they are at the top level and not within zones.
|
||||
*/
|
||||
struct FoundNestedNodeID {
|
||||
int id;
|
||||
bool is_in_simulation = false;
|
||||
bool is_in_loop = false;
|
||||
bool is_in_closure = false;
|
||||
};
|
||||
|
||||
} // namespace blender::nodes
|
||||
812
blender-5.2.0/source/blender/nodes/NOD_node_declaration.hh
Normal file
812
blender-5.2.0/source/blender/nodes/NOD_node_declaration.hh
Normal file
@@ -0,0 +1,812 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_ustring.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BLT_translation.hh" /* IWYU pragma: export */
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "RNA_types.hh"
|
||||
|
||||
#include "NOD_socket_usage_inference_fwd.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bContext;
|
||||
struct bNode;
|
||||
|
||||
namespace ui {
|
||||
struct Layout;
|
||||
} // namespace ui
|
||||
|
||||
namespace nodes {
|
||||
|
||||
class NodeDeclarationBuilder;
|
||||
class PanelDeclaration;
|
||||
|
||||
/**
|
||||
* An enum that maps to the #compositor::InputRealizationMode.
|
||||
*/
|
||||
enum class CompositorInputRealizationMode : int8_t {
|
||||
None,
|
||||
Transforms,
|
||||
OperationDomain,
|
||||
};
|
||||
|
||||
struct StructureTypeInterface {
|
||||
struct OutputDependency {
|
||||
StructureType type;
|
||||
Array<int> linked_inputs;
|
||||
|
||||
friend bool operator==(const OutputDependency &a, const OutputDependency &b) = default;
|
||||
};
|
||||
|
||||
Array<StructureType> inputs;
|
||||
Array<OutputDependency> outputs;
|
||||
|
||||
friend bool operator==(const StructureTypeInterface &a,
|
||||
const StructureTypeInterface &b) = default;
|
||||
};
|
||||
|
||||
namespace reference_lifetimes {
|
||||
|
||||
/**
|
||||
* Attributes can be propagated from an input geometry to an output geometry.
|
||||
*/
|
||||
struct DataPropagation {
|
||||
int from_input;
|
||||
int to_output;
|
||||
|
||||
friend bool operator==(const DataPropagation &a, const DataPropagation &b) = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* References to attributes can be propagated from an input field to an output field.
|
||||
*/
|
||||
struct ReferencePropagation {
|
||||
int from_input;
|
||||
int to_output;
|
||||
|
||||
friend bool operator==(const ReferencePropagation &a, const ReferencePropagation &b) = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* An input field is evaluated on an input geometry.
|
||||
*/
|
||||
struct UseRelation {
|
||||
int reference_input;
|
||||
int data_input;
|
||||
|
||||
friend bool operator==(const UseRelation &a, const UseRelation &b) = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* An output field is available on an output geometry.
|
||||
*/
|
||||
struct AvailableRelation {
|
||||
int reference_output;
|
||||
int data_output;
|
||||
|
||||
friend bool operator==(const AvailableRelation &a, const AvailableRelation &b) = default;
|
||||
};
|
||||
|
||||
struct RelationsInNode {
|
||||
Vector<DataPropagation> data_propagations;
|
||||
Vector<ReferencePropagation> reference_propagations;
|
||||
Vector<UseRelation> use_relations;
|
||||
Vector<AvailableRelation> available_relations;
|
||||
Vector<int> available_on_none;
|
||||
|
||||
friend bool operator==(const RelationsInNode &a, const RelationsInNode &b) = default;
|
||||
};
|
||||
|
||||
std::ostream &operator<<(std::ostream &stream, const RelationsInNode &relations);
|
||||
|
||||
} // namespace reference_lifetimes
|
||||
|
||||
/* Common alias for this namespace. */
|
||||
namespace rl = reference_lifetimes;
|
||||
|
||||
/** Socket or panel declaration. */
|
||||
class ItemDeclaration {
|
||||
public:
|
||||
const PanelDeclaration *parent = nullptr;
|
||||
|
||||
virtual ~ItemDeclaration() = default;
|
||||
};
|
||||
|
||||
using ItemDeclarationPtr = std::unique_ptr<ItemDeclaration>;
|
||||
|
||||
struct SocketNameRNA {
|
||||
PointerRNA owner = PointerRNA_NULL;
|
||||
std::string property_name;
|
||||
};
|
||||
|
||||
struct CustomSocketDrawParams {
|
||||
const bContext &C;
|
||||
ui::Layout &layout;
|
||||
bNodeTree &tree;
|
||||
bNode &node;
|
||||
bNodeSocket &socket;
|
||||
PointerRNA node_ptr;
|
||||
PointerRNA socket_ptr;
|
||||
StringRefNull label;
|
||||
const Map<const bNode *, const bNode *> *menu_switch_source_by_index_switch = nullptr;
|
||||
|
||||
void draw_standard(ui::Layout &layout,
|
||||
std::optional<StringRefNull> label_override = std::nullopt);
|
||||
};
|
||||
|
||||
using CustomSocketDrawFn = std::function<void(CustomSocketDrawParams ¶ms)>;
|
||||
using CustomSocketLabelFn = std::function<StringRefNull(const bNode &node)>;
|
||||
using SocketUsageInferenceFn =
|
||||
std::function<std::optional<bool>(const socket_usage_inference::SocketUsageParams ¶ms)>;
|
||||
|
||||
struct OutputStructureTypeDependency {
|
||||
struct None {};
|
||||
struct All {};
|
||||
struct Partial {
|
||||
Array<int, 2> linked_inputs;
|
||||
};
|
||||
std::variant<None, All, Partial> variant = None();
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes a single input or output socket. This is subclassed for different socket types.
|
||||
*/
|
||||
class SocketDeclaration : public ItemDeclaration {
|
||||
public:
|
||||
UString name;
|
||||
std::string short_label;
|
||||
UString identifier;
|
||||
std::string description;
|
||||
std::optional<std::string> translation_context;
|
||||
/** Defined by whether the socket is part of the node's input or
|
||||
* output socket declaration list. Included here for convenience. */
|
||||
eNodeSocketInOut in_out;
|
||||
/** Socket type that corresponds to this socket declaration. */
|
||||
eNodeSocketDatatype socket_type;
|
||||
/**
|
||||
* Indicates that the meaning of the socket values is clear even if the label is not shown. This
|
||||
* can result in cleaner UIs in some cases. The drawing code will still draw the label sometimes.
|
||||
*/
|
||||
bool optional_label = false;
|
||||
bool hide_value = false;
|
||||
bool compact = false;
|
||||
bool is_multi_input = false;
|
||||
bool no_mute_links = false;
|
||||
bool is_available = true;
|
||||
bool is_attribute_name = false;
|
||||
bool is_default_link_socket = false;
|
||||
/** Puts this socket on the same line as the previous one in the UI. */
|
||||
bool align_with_previous_socket = false;
|
||||
/** This socket is used as a toggle for the parent panel. */
|
||||
bool is_panel_toggle = false;
|
||||
bool is_layer_name = false;
|
||||
bool is_volume_grid_name = false;
|
||||
bool is_anonymous_attribute_output = false;
|
||||
|
||||
/** Index in the list of inputs or outputs of the node. */
|
||||
int index = -1;
|
||||
|
||||
OutputStructureTypeDependency structure_type_output_dependency;
|
||||
|
||||
StructureType structure_type = StructureType::Single;
|
||||
|
||||
private:
|
||||
CompositorInputRealizationMode compositor_realization_mode_ =
|
||||
CompositorInputRealizationMode::OperationDomain;
|
||||
|
||||
/** The priority of the input for determining the domain of the node. If negative, then the
|
||||
* domain priority is not set and the index of the input is assumed to be the priority instead.
|
||||
* See compositor::InputDescriptor for more information. */
|
||||
int compositor_domain_priority_ = -1;
|
||||
|
||||
/** Utility method to make the socket available if there is a straightforward way to do so. */
|
||||
std::function<void(bNode &)> make_available_fn_;
|
||||
|
||||
public:
|
||||
/** Some input sockets can have non-trivial values in the case when they are unlinked. */
|
||||
NodeDefaultInputType default_input_type = NodeDefaultInputType::NODE_DEFAULT_INPUT_VALUE;
|
||||
/**
|
||||
* Property that stores the name of the socket so that it can be modified directly from the
|
||||
* node without going to the side-bar.
|
||||
*/
|
||||
std::unique_ptr<SocketNameRNA> socket_name_rna;
|
||||
/**
|
||||
* Draw function that overrides how the socket is drawn for a specific node.
|
||||
*/
|
||||
std::unique_ptr<CustomSocketDrawFn> custom_draw_fn;
|
||||
/**
|
||||
* Custom label function so a socket can display a different text depending on what it does.
|
||||
*/
|
||||
std::unique_ptr<CustomSocketLabelFn> label_fn;
|
||||
/**
|
||||
* Determines whether this socket is used based on other input values and based on which outputs
|
||||
* are used.
|
||||
*/
|
||||
std::unique_ptr<SocketUsageInferenceFn> usage_inference_fn;
|
||||
|
||||
friend NodeDeclarationBuilder;
|
||||
friend class FlatBundleTypeBuilder;
|
||||
friend class BaseSocketDeclarationBuilder;
|
||||
template<typename SocketDecl> friend class SocketDeclarationBuilder;
|
||||
|
||||
~SocketDeclaration() override = default;
|
||||
|
||||
virtual bNodeSocket &build(bNodeTree &ntree, bNode &node) const = 0;
|
||||
virtual bool matches(const bNodeSocket &socket) const = 0;
|
||||
virtual bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const;
|
||||
|
||||
/**
|
||||
* Determine if a new socket described by this declaration could have a valid connection
|
||||
* the other socket.
|
||||
*/
|
||||
virtual bool can_connect(const bNodeSocket &socket) const = 0;
|
||||
|
||||
/**
|
||||
* Change the node such that the socket will become visible. The node type's update method
|
||||
* should be called afterwards.
|
||||
* \note this is not necessarily implemented for all node types.
|
||||
*/
|
||||
void make_available(bNode &node) const;
|
||||
|
||||
const CompositorInputRealizationMode &compositor_realization_mode() const;
|
||||
int compositor_domain_priority() const;
|
||||
|
||||
protected:
|
||||
void set_common_flags(bNodeSocket &socket) const;
|
||||
bool matches_common_data(const bNodeSocket &socket) const;
|
||||
};
|
||||
|
||||
class NodeDeclarationBuilder;
|
||||
class DeclarationListBuilder;
|
||||
class PanelDeclarationBuilder;
|
||||
|
||||
class BaseSocketDeclarationBuilder {
|
||||
protected:
|
||||
NodeDeclarationBuilder *node_decl_builder_ = nullptr;
|
||||
SocketDeclaration *decl_base_ = nullptr;
|
||||
|
||||
/* Variables which are used to add additional data to the declaration after all sockets have been
|
||||
* added. */
|
||||
bool propagate_all_input_references_ = false;
|
||||
bool propagate_all_input_data_ = false;
|
||||
bool propagate_all_input_data_from_geometry_ = false;
|
||||
bool input_reference_used_on_all_data_ = false;
|
||||
bool output_reference_available_on_all_data_ = false;
|
||||
|
||||
friend class NodeDeclarationBuilder;
|
||||
friend class DeclarationListBuilder;
|
||||
friend class FlatBundleTypeBuilder;
|
||||
|
||||
public:
|
||||
virtual ~BaseSocketDeclarationBuilder() = default;
|
||||
|
||||
BaseSocketDeclarationBuilder &optional_label(bool value = true);
|
||||
|
||||
BaseSocketDeclarationBuilder &hide_value(bool value = true);
|
||||
|
||||
BaseSocketDeclarationBuilder &multi_input(bool value = true);
|
||||
|
||||
BaseSocketDeclarationBuilder &compact(bool value = true);
|
||||
|
||||
BaseSocketDeclarationBuilder &short_label(std::string value = "");
|
||||
|
||||
BaseSocketDeclarationBuilder &description(std::string value = "");
|
||||
|
||||
BaseSocketDeclarationBuilder &translation_context(
|
||||
std::optional<std::string> value = std::nullopt);
|
||||
|
||||
BaseSocketDeclarationBuilder &no_muted_links(bool value = true);
|
||||
|
||||
/**
|
||||
* Can be used to make a socket unavailable. It's still stored in DNA, but it's not shown in the
|
||||
* UI and also can't be unhidden.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &available(bool value = true);
|
||||
|
||||
BaseSocketDeclarationBuilder &is_attribute_name(bool value = true);
|
||||
|
||||
BaseSocketDeclarationBuilder &is_default_link_socket(bool value = true);
|
||||
|
||||
BaseSocketDeclarationBuilder &default_input_type(NodeDefaultInputType value);
|
||||
|
||||
/**
|
||||
* Declares that this input is or contains a field that is evaluated on some geometry in this
|
||||
* node.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &evaluated_geometry_field();
|
||||
BaseSocketDeclarationBuilder &evaluated_geometry_field(Span<int> geometry_input_indices);
|
||||
/**
|
||||
* Declares that this socket outputs an attribute field referencing an anonymous attribute that
|
||||
* exists on an output geometry of the node.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &anonymous_attribute_output();
|
||||
BaseSocketDeclarationBuilder &anonymous_attribute_output(Span<int> geometry_output_indices);
|
||||
|
||||
/**
|
||||
* The output might reference data (anonymous attributes) on other outputs that does not exist on
|
||||
* the inputs already. Use the more specific #anonymous_attribute_output() if applicable.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &references_other_outputs();
|
||||
BaseSocketDeclarationBuilder &references_other_outputs(Span<int> output_indices);
|
||||
|
||||
/** The output has a dynamic structure type which is automatically inferred from inputs. */
|
||||
BaseSocketDeclarationBuilder &inferred_structure_type();
|
||||
BaseSocketDeclarationBuilder &inferred_structure_type(Span<int> input_indices);
|
||||
|
||||
/** The node passes references to data (anonymous attributes) from inputs to this output. */
|
||||
BaseSocketDeclarationBuilder &propagate_references();
|
||||
BaseSocketDeclarationBuilder &propagate_references(Span<int> input_indices);
|
||||
|
||||
/**
|
||||
* The node passes data and references from inputs this output. Since data like a geometry can
|
||||
* also indirectly contain fields, those are simply implied here.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &propagate_all();
|
||||
BaseSocketDeclarationBuilder &propagate_all(Span<int> input_indices);
|
||||
/** Filters the set of inputs that is propagated from to only geometry sockets. */
|
||||
BaseSocketDeclarationBuilder &propagate_all_geometry();
|
||||
|
||||
BaseSocketDeclarationBuilder &compositor_realization_mode(CompositorInputRealizationMode value);
|
||||
|
||||
/**
|
||||
* The priority of the input for determining the domain of the node. Needs to be positive. See
|
||||
* compositor::InputDescriptor for more information.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &compositor_domain_priority(int priority);
|
||||
|
||||
/**
|
||||
* Pass a function that sets properties on the node required to make the corresponding socket
|
||||
* available, if it is not available on the default state of the node. The function is allowed to
|
||||
* make other sockets unavailable, since it is meant to be called when the node is first added.
|
||||
* The node type's update function is called afterwards.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &make_available(std::function<void(bNode &)> fn);
|
||||
|
||||
/**
|
||||
* Provide a fully custom draw function for the socket that overrides any default behavior.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &custom_draw(CustomSocketDrawFn fn);
|
||||
|
||||
/**
|
||||
* 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(SocketUsageInferenceFn fn);
|
||||
|
||||
/**
|
||||
* Provide a function that determines the UI label of this socket.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &label_fn(CustomSocketLabelFn fn);
|
||||
|
||||
/**
|
||||
* Utility method for the case when the node has a single menu input and this socket is only used
|
||||
* when the menu input has a specific value.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &usage_by_single_menu(const int menu_value);
|
||||
|
||||
/**
|
||||
* Utility method for the case when this socket is only used when the menu input of the given
|
||||
* identifier has a specific value.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &usage_by_menu(const UString menu_input_identifier,
|
||||
const int menu_value);
|
||||
|
||||
/**
|
||||
* Utility method for the case when this socket is only used when the menu input of the given
|
||||
* identifier has one of the specifies values.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &usage_by_menu(const UString menu_input_identifier,
|
||||
const Array<int> menu_values);
|
||||
|
||||
/**
|
||||
* Utility method for the case when this socket is only used when the given boolean input matches
|
||||
* the specified value.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &usage_by_bool(UString bool_input_identifier, bool value);
|
||||
|
||||
/**
|
||||
* The socket is only used if the parent panel toggle is checked.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &usage_by_panel_toggle();
|
||||
|
||||
/**
|
||||
* Puts this socket on the same row as the previous socket. This only works when one of them is
|
||||
* an input and the other is an output.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &align_with_previous(bool value = true);
|
||||
|
||||
/**
|
||||
* Set a function that retrieves an RNA pointer to the name of the socket. This can be used to be
|
||||
* able to rename the socket within the node.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &socket_name_ptr(PointerRNA ptr, StringRef property_name);
|
||||
BaseSocketDeclarationBuilder &socket_name_ptr(const ID *id,
|
||||
const StructRNA *srna,
|
||||
const void *data,
|
||||
StringRef property_name);
|
||||
/**
|
||||
* Use the socket as a toggle in its panel.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &panel_toggle(bool value = true);
|
||||
|
||||
BaseSocketDeclarationBuilder &structure_type(StructureType structure_type);
|
||||
|
||||
BaseSocketDeclarationBuilder &is_layer_name(bool value = true);
|
||||
BaseSocketDeclarationBuilder &is_volume_grid_name(bool value = true);
|
||||
|
||||
/** Index in the list of inputs or outputs. */
|
||||
int index() const;
|
||||
|
||||
bool is_input() const;
|
||||
bool is_output() const;
|
||||
|
||||
virtual BaseSocketDeclarationBuilder &try_copy_ui_data(const SocketDeclaration &other_decl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps a #SocketDeclaration and provides methods to set it up correctly.
|
||||
* This is separate from #SocketDeclaration, because it allows separating the API used by nodes to
|
||||
* declare themselves from how the declaration is stored internally.
|
||||
*/
|
||||
template<typename SocketDecl>
|
||||
class SocketDeclarationBuilder : public BaseSocketDeclarationBuilder {
|
||||
protected:
|
||||
using Self = typename SocketDecl::Builder;
|
||||
static_assert(std::is_base_of_v<SocketDeclaration, SocketDecl>);
|
||||
SocketDecl *decl_;
|
||||
|
||||
friend class NodeDeclarationBuilder;
|
||||
friend class DeclarationListBuilder;
|
||||
friend class FlatBundleTypeBuilder;
|
||||
};
|
||||
|
||||
using SocketDeclarationPtr = std::unique_ptr<SocketDeclaration>;
|
||||
|
||||
using DrawNodeLayoutFn = void(ui::Layout &, bContext *, PointerRNA *);
|
||||
|
||||
class SeparatorDeclaration : public ItemDeclaration {};
|
||||
|
||||
class LayoutDeclaration : public ItemDeclaration {
|
||||
public:
|
||||
std::function<DrawNodeLayoutFn> draw;
|
||||
/**
|
||||
* Sometimes the default layout has special handling (e.g. choose between #draw_buttons and
|
||||
* #draw_buttons_ex).
|
||||
*/
|
||||
bool is_default = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes a panel containing sockets or other panels.
|
||||
*/
|
||||
class PanelDeclaration : public ItemDeclaration {
|
||||
public:
|
||||
int identifier;
|
||||
UString name;
|
||||
std::string description;
|
||||
std::optional<std::string> translation_context;
|
||||
bool default_collapsed = false;
|
||||
Vector<ItemDeclaration *> items;
|
||||
/** Index in the list of panels on the node. */
|
||||
int index = -1;
|
||||
PanelDeclaration *parent_panel = nullptr;
|
||||
|
||||
private:
|
||||
friend NodeDeclarationBuilder;
|
||||
friend class PanelDeclarationBuilder;
|
||||
|
||||
public:
|
||||
~PanelDeclaration() override = default;
|
||||
|
||||
void build(bNodePanelState &panel) const;
|
||||
bool matches(const bNodePanelState &panel) const;
|
||||
void update_or_build(const bNodePanelState &old_panel, bNodePanelState &new_panel) const;
|
||||
|
||||
int depth() const;
|
||||
|
||||
/** Get the declaration for a child item that should be drawn as part of the panel header. */
|
||||
const SocketDeclaration *panel_input_decl() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a base class for #NodeDeclarationBuilder and #PanelDeclarationBuilder. It unifies the
|
||||
* behavior of adding sockets and other items to the root node and to panels.
|
||||
*/
|
||||
class DeclarationListBuilder {
|
||||
public:
|
||||
NodeDeclarationBuilder &node_decl_builder;
|
||||
Vector<ItemDeclaration *> &items;
|
||||
PanelDeclaration *parent_panel_decl = nullptr;
|
||||
|
||||
DeclarationListBuilder(NodeDeclarationBuilder &node_decl_builder,
|
||||
Vector<ItemDeclaration *> &items)
|
||||
: node_decl_builder(node_decl_builder), items(items)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename DeclType>
|
||||
typename DeclType::Builder &add_socket(UString name,
|
||||
UString identifier,
|
||||
eNodeSocketInOut in_out);
|
||||
|
||||
template<typename DeclType>
|
||||
typename DeclType::Builder &add_input(UString name, UString identifier = ""_ustr);
|
||||
template<typename DeclType>
|
||||
typename DeclType::Builder &add_output(UString name, UString identifier = ""_ustr);
|
||||
|
||||
BaseSocketDeclarationBuilder &add_input(eNodeSocketDatatype socket_type,
|
||||
UString name,
|
||||
UString identifier = ""_ustr);
|
||||
BaseSocketDeclarationBuilder &add_input(eCustomDataType data_type,
|
||||
UString name,
|
||||
UString identifier = ""_ustr);
|
||||
BaseSocketDeclarationBuilder &add_output(eNodeSocketDatatype socket_type,
|
||||
UString name,
|
||||
UString identifier = ""_ustr);
|
||||
BaseSocketDeclarationBuilder &add_output(eCustomDataType data_type,
|
||||
UString name,
|
||||
UString identifier = ""_ustr);
|
||||
|
||||
PanelDeclarationBuilder &add_panel(UString name, int identifier = -1);
|
||||
|
||||
void add_separator();
|
||||
void add_default_layout();
|
||||
void add_layout(std::function<void(ui::Layout &, bContext *, PointerRNA *)> draw);
|
||||
};
|
||||
|
||||
class PanelDeclarationBuilder : public DeclarationListBuilder {
|
||||
protected:
|
||||
using Self = PanelDeclarationBuilder;
|
||||
PanelDeclaration *decl_;
|
||||
|
||||
friend class NodeDeclarationBuilder;
|
||||
|
||||
public:
|
||||
PanelDeclarationBuilder(NodeDeclarationBuilder &node_builder, PanelDeclaration &decl)
|
||||
: DeclarationListBuilder(node_builder, decl.items), decl_(&decl)
|
||||
{
|
||||
this->parent_panel_decl = &decl;
|
||||
}
|
||||
|
||||
Self &description(std::string value = "");
|
||||
Self &translation_context(std::optional<std::string> value = std::nullopt);
|
||||
Self &default_closed(bool closed);
|
||||
};
|
||||
|
||||
using PanelDeclarationPtr = std::unique_ptr<PanelDeclaration>;
|
||||
|
||||
class NodeDeclaration {
|
||||
public:
|
||||
/** Contains all items including recursive children. */
|
||||
Vector<ItemDeclarationPtr> all_items;
|
||||
/** Contains only the items in the root. */
|
||||
Vector<ItemDeclaration *> root_items;
|
||||
/** All input and output socket declarations. */
|
||||
Vector<SocketDeclaration *> inputs;
|
||||
Vector<SocketDeclaration *> outputs;
|
||||
Vector<PanelDeclaration *> panels;
|
||||
std::unique_ptr<rl::RelationsInNode> reference_lifetime_relations_;
|
||||
|
||||
/** Leave the sockets in place, even if they don't match the declaration. Used for dynamic
|
||||
* declarations when the information used to build the declaration is missing, but might become
|
||||
* available again in the future. */
|
||||
bool skip_updating_sockets = false;
|
||||
|
||||
/** Use order of socket declarations for socket order instead of conventional
|
||||
* outputs | buttons | inputs order. Panels are only supported when using custom socket order. */
|
||||
bool use_custom_socket_order = false;
|
||||
|
||||
/** Usually output sockets come before input sockets currently. Only some specific nodes are
|
||||
* exempt from that rule for now. */
|
||||
bool allow_any_socket_order = false;
|
||||
|
||||
/**
|
||||
* True if any context was used to build this declaration.
|
||||
*/
|
||||
bool is_context_dependent = false;
|
||||
|
||||
friend NodeDeclarationBuilder;
|
||||
|
||||
/** Asserts that the declaration is considered valid. */
|
||||
void assert_valid() const;
|
||||
|
||||
bool matches(const bNode &node) const;
|
||||
Span<SocketDeclaration *> sockets(eNodeSocketInOut in_out) const;
|
||||
|
||||
const rl::RelationsInNode *reference_lifetime_relations() const
|
||||
{
|
||||
return reference_lifetime_relations_.get();
|
||||
}
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("NodeDeclaration")
|
||||
};
|
||||
|
||||
class NodeDeclarationBuilder : public DeclarationListBuilder {
|
||||
private:
|
||||
const bke::bNodeType &typeinfo_;
|
||||
NodeDeclaration &declaration_;
|
||||
const bNodeTree *ntree_ = nullptr;
|
||||
const bNode *node_ = nullptr;
|
||||
Vector<std::unique_ptr<BaseSocketDeclarationBuilder>> socket_builders_;
|
||||
Vector<BaseSocketDeclarationBuilder *> input_socket_builders_;
|
||||
Vector<BaseSocketDeclarationBuilder *> output_socket_builders_;
|
||||
Vector<std::unique_ptr<PanelDeclarationBuilder>> panel_builders_;
|
||||
bool is_function_node_ = false;
|
||||
|
||||
friend DeclarationListBuilder;
|
||||
|
||||
public:
|
||||
NodeDeclarationBuilder(const bke::bNodeType &typeinfo,
|
||||
NodeDeclaration &declaration,
|
||||
const bNodeTree *ntree = nullptr,
|
||||
const bNode *node = nullptr);
|
||||
|
||||
const bNode *node_or_null() const
|
||||
{
|
||||
declaration_.is_context_dependent = true;
|
||||
return node_;
|
||||
}
|
||||
|
||||
const bNodeTree *tree_or_null() const
|
||||
{
|
||||
declaration_.is_context_dependent = true;
|
||||
return ntree_;
|
||||
}
|
||||
|
||||
/**
|
||||
* All inputs support fields, and all outputs are fields if any of the inputs is a field.
|
||||
* Calling field status definitions on each socket is unnecessary.
|
||||
*/
|
||||
void is_function_node()
|
||||
{
|
||||
is_function_node_ = true;
|
||||
}
|
||||
|
||||
void finalize();
|
||||
|
||||
void use_custom_socket_order(bool enable = true);
|
||||
void allow_any_socket_order(bool enable = true);
|
||||
|
||||
rl::RelationsInNode &get_reference_lifetime_relations()
|
||||
{
|
||||
if (!declaration_.reference_lifetime_relations_) {
|
||||
declaration_.reference_lifetime_relations_ = std::make_unique<rl::RelationsInNode>();
|
||||
}
|
||||
return *declaration_.reference_lifetime_relations_;
|
||||
}
|
||||
|
||||
NodeDeclaration &declaration()
|
||||
{
|
||||
return declaration_;
|
||||
}
|
||||
|
||||
private:
|
||||
void build_remaining_anonymous_attribute_relations();
|
||||
};
|
||||
|
||||
using ImplicitInputValueFn = std::function<void(const bNode &node, void *r_value)>;
|
||||
std::optional<ImplicitInputValueFn> get_implicit_input_value_fn(NodeDefaultInputType type);
|
||||
bool socket_type_supports_default_input_type(const bke::bNodeSocketType &socket_type,
|
||||
NodeDefaultInputType input_type);
|
||||
bool node_tree_type_supports_default_input_type(eNodeTree_Type node_tree_type,
|
||||
NodeDefaultInputType input_type);
|
||||
bool default_input_type_is_field(NodeDefaultInputType input_type);
|
||||
|
||||
void build_node_declaration(const bke::bNodeType &typeinfo,
|
||||
NodeDeclaration &r_declaration,
|
||||
const bNodeTree *ntree,
|
||||
const bNode *node);
|
||||
|
||||
std::unique_ptr<SocketDeclaration> make_declaration_for_socket_type(
|
||||
eNodeSocketDatatype socket_type);
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #DeclarationListBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
template<typename DeclType>
|
||||
inline typename DeclType::Builder &DeclarationListBuilder::add_input(UString name,
|
||||
UString identifier)
|
||||
{
|
||||
return this->add_socket<DeclType>(name, identifier, SOCK_IN);
|
||||
}
|
||||
|
||||
template<typename DeclType>
|
||||
inline typename DeclType::Builder &DeclarationListBuilder::add_output(UString name,
|
||||
UString identifier)
|
||||
{
|
||||
return this->add_socket<DeclType>(name, identifier, SOCK_OUT);
|
||||
}
|
||||
|
||||
template<typename DeclType>
|
||||
inline typename DeclType::Builder &DeclarationListBuilder::add_socket(UString name,
|
||||
UString identifier,
|
||||
eNodeSocketInOut in_out)
|
||||
{
|
||||
static_assert(std::is_base_of_v<SocketDeclaration, DeclType>);
|
||||
using SocketBuilder = typename DeclType::Builder;
|
||||
|
||||
BLI_assert(ELEM(in_out, SOCK_IN, SOCK_OUT));
|
||||
|
||||
std::unique_ptr<SocketBuilder> socket_decl_builder_ptr = std::make_unique<SocketBuilder>();
|
||||
SocketBuilder &socket_decl_builder = *socket_decl_builder_ptr;
|
||||
this->node_decl_builder.socket_builders_.append(std::move(socket_decl_builder_ptr));
|
||||
|
||||
std::unique_ptr<DeclType> socket_decl_ptr = std::make_unique<DeclType>();
|
||||
DeclType &socket_decl = *socket_decl_ptr;
|
||||
this->node_decl_builder.declaration_.all_items.append(std::move(socket_decl_ptr));
|
||||
this->items.append(&socket_decl);
|
||||
|
||||
socket_decl.parent = this->parent_panel_decl;
|
||||
socket_decl_builder.node_decl_builder_ = &this->node_decl_builder;
|
||||
|
||||
socket_decl_builder.decl_ = &socket_decl;
|
||||
socket_decl_builder.decl_base_ = &socket_decl;
|
||||
socket_decl.name = name;
|
||||
socket_decl.identifier = identifier.is_empty() ? name : identifier;
|
||||
socket_decl.in_out = in_out;
|
||||
socket_decl.socket_type = DeclType::static_socket_type;
|
||||
|
||||
if (this->node_decl_builder.is_function_node_) {
|
||||
socket_decl.structure_type = StructureType::Dynamic;
|
||||
if (in_out == SOCK_OUT) {
|
||||
socket_decl_builder.propagate_references();
|
||||
socket_decl_builder.inferred_structure_type();
|
||||
}
|
||||
}
|
||||
|
||||
if (in_out == SOCK_IN) {
|
||||
this->node_decl_builder.input_socket_builders_.append(&socket_decl_builder);
|
||||
socket_decl.index = this->node_decl_builder.declaration_.inputs.append_and_get_index(
|
||||
&socket_decl);
|
||||
}
|
||||
else {
|
||||
this->node_decl_builder.output_socket_builders_.append(&socket_decl_builder);
|
||||
socket_decl.index = this->node_decl_builder.declaration_.outputs.append_and_get_index(
|
||||
&socket_decl);
|
||||
}
|
||||
return socket_decl_builder;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #BaseSocketDeclarationBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline int BaseSocketDeclarationBuilder::index() const
|
||||
{
|
||||
return decl_base_->index;
|
||||
}
|
||||
|
||||
inline bool BaseSocketDeclarationBuilder::is_input() const
|
||||
{
|
||||
return decl_base_->in_out == SOCK_IN;
|
||||
}
|
||||
|
||||
inline bool BaseSocketDeclarationBuilder::is_output() const
|
||||
{
|
||||
return decl_base_->in_out == SOCK_OUT;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
33
blender-5.2.0/source/blender/nodes/NOD_node_extra_info.hh
Normal file
33
blender-5.2.0/source/blender/nodes/NOD_node_extra_info.hh
Normal file
@@ -0,0 +1,33 @@
|
||||
/* SPDX-FileCopyrightText: 2005 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "UI_interface_c.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
struct NodeExtraInfoRow {
|
||||
std::string text;
|
||||
int icon = 0;
|
||||
StringRef tooltip;
|
||||
|
||||
ui::ButtonToolTipFunc tooltip_fn = nullptr;
|
||||
void *tooltip_fn_arg = nullptr;
|
||||
void (*tooltip_fn_free_arg)(void *) = nullptr;
|
||||
void *(*tooltip_fn_copy_arg)(void *) = nullptr;
|
||||
|
||||
std::function<void(ui::Button &)> set_execute_fn;
|
||||
};
|
||||
|
||||
struct NodeExtraInfoParams {
|
||||
Vector<NodeExtraInfoRow> &rows;
|
||||
const bNodeTree &tree;
|
||||
const bNode &node;
|
||||
const bContext &C;
|
||||
};
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,198 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_compute_context.hh"
|
||||
#include "BLI_hash.hh"
|
||||
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bNode;
|
||||
struct bNodeSocket;
|
||||
|
||||
namespace nodes {
|
||||
|
||||
struct NodeInContext;
|
||||
struct SocketInContext;
|
||||
|
||||
/**
|
||||
* Utility struct to pair a node with a compute context. This uniquely identifies a node in an
|
||||
* node-tree evaluation.
|
||||
*/
|
||||
struct NodeInContext {
|
||||
const ComputeContext *context = nullptr;
|
||||
const bNode *node = nullptr;
|
||||
|
||||
uint64_t hash() const;
|
||||
ComputeContextHash context_hash() const;
|
||||
const bNode *operator->() const;
|
||||
const bNode &operator*() const;
|
||||
operator bool() const;
|
||||
|
||||
SocketInContext input_socket(int index) const;
|
||||
SocketInContext output_socket(int index) const;
|
||||
|
||||
/**
|
||||
* Two nodes in context compare equal if their context hash is equal, not the pointer to the
|
||||
* context. This is important as the same compute context may be constructed multiple times.
|
||||
*/
|
||||
friend bool operator==(const NodeInContext &a, const NodeInContext &b)
|
||||
{
|
||||
return a.context_hash() == b.context_hash() && a.node == b.node;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility struct to pair a socket with a compute context. This uniquely identifies a socket in a
|
||||
* node-tree evaluation.
|
||||
*/
|
||||
struct SocketInContext {
|
||||
const ComputeContext *context = nullptr;
|
||||
const bNodeSocket *socket = nullptr;
|
||||
|
||||
uint64_t hash() const;
|
||||
ComputeContextHash context_hash() const;
|
||||
const bNodeSocket *operator->() const;
|
||||
const bNodeSocket &operator*() const;
|
||||
operator bool() const;
|
||||
|
||||
NodeInContext owner_node() const;
|
||||
|
||||
/**
|
||||
* Two sockets in context compare equal if their context hash is equal, not the pointer to the
|
||||
* context. This is important as the same compute context may be constructed multiple times.
|
||||
*/
|
||||
friend bool operator==(const SocketInContext &a, const SocketInContext &b)
|
||||
{
|
||||
return a.context_hash() == b.context_hash() && a.socket == b.socket;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility struct to pair a tree with a compute context.
|
||||
*/
|
||||
struct TreeInContext {
|
||||
const ComputeContext *context = nullptr;
|
||||
const bNodeTree *tree = nullptr;
|
||||
|
||||
uint64_t hash() const;
|
||||
ComputeContextHash context_hash() const;
|
||||
const bNodeTree *operator->() const;
|
||||
const bNodeTree &operator*() const;
|
||||
operator bool() const;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #NodeInContext Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline uint64_t NodeInContext::hash() const
|
||||
{
|
||||
return get_default_hash(this->context_hash(), this->node);
|
||||
}
|
||||
|
||||
inline ComputeContextHash NodeInContext::context_hash() const
|
||||
{
|
||||
return context ? context->hash() : ComputeContextHash{};
|
||||
}
|
||||
|
||||
inline const bNode *NodeInContext::operator->() const
|
||||
{
|
||||
return this->node;
|
||||
}
|
||||
|
||||
inline const bNode &NodeInContext::operator*() const
|
||||
{
|
||||
return *this->node;
|
||||
}
|
||||
|
||||
inline NodeInContext::operator bool() const
|
||||
{
|
||||
return this->node != nullptr;
|
||||
}
|
||||
|
||||
inline SocketInContext NodeInContext::input_socket(const int index) const
|
||||
{
|
||||
return {this->context, &this->node->input_socket(index)};
|
||||
}
|
||||
|
||||
inline SocketInContext NodeInContext::output_socket(const int index) const
|
||||
{
|
||||
return {this->context, &this->node->output_socket(index)};
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #SocketInContext Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline uint64_t SocketInContext::hash() const
|
||||
{
|
||||
return get_default_hash(this->context_hash(), this->socket);
|
||||
}
|
||||
|
||||
inline ComputeContextHash SocketInContext::context_hash() const
|
||||
{
|
||||
return context ? context->hash() : ComputeContextHash{};
|
||||
}
|
||||
|
||||
inline const bNodeSocket *SocketInContext::operator->() const
|
||||
{
|
||||
return this->socket;
|
||||
}
|
||||
|
||||
inline const bNodeSocket &SocketInContext::operator*() const
|
||||
{
|
||||
return *this->socket;
|
||||
}
|
||||
|
||||
inline SocketInContext::operator bool() const
|
||||
{
|
||||
return this->socket != nullptr;
|
||||
}
|
||||
|
||||
inline NodeInContext SocketInContext::owner_node() const
|
||||
{
|
||||
return {this->context, &this->socket->owner_node()};
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #TreeInContext Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline uint64_t TreeInContext::hash() const
|
||||
{
|
||||
return get_default_hash(this->context_hash(), this->tree);
|
||||
}
|
||||
|
||||
inline ComputeContextHash TreeInContext::context_hash() const
|
||||
{
|
||||
return context ? context->hash() : ComputeContextHash{};
|
||||
}
|
||||
|
||||
inline const bNodeTree *TreeInContext::operator->() const
|
||||
{
|
||||
return this->tree;
|
||||
}
|
||||
|
||||
inline const bNodeTree &TreeInContext::operator*() const
|
||||
{
|
||||
return *this->tree;
|
||||
}
|
||||
|
||||
inline TreeInContext::operator bool() const
|
||||
{
|
||||
return this->tree != nullptr;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
37
blender-5.2.0/source/blender/nodes/NOD_nodes_srna.hh
Normal file
37
blender-5.2.0/source/blender/nodes/NOD_nodes_srna.hh
Normal file
@@ -0,0 +1,37 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_resource_scope.hh"
|
||||
|
||||
#include "RNA_define.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct BlenderRNA;
|
||||
struct StructRNA;
|
||||
namespace nodes {
|
||||
|
||||
/**
|
||||
* Contains a runtime registration of RNA types generated based on the node group's interface for
|
||||
* a particular context. These RNA types are not registered in the global list, but owned by the
|
||||
* node group so that RNA access to interface properties works as long as the node group exists.
|
||||
*/
|
||||
struct GeneratedTreeSrnaData {
|
||||
ResourceScope scope;
|
||||
StructRNA *properties_struct;
|
||||
BlenderRNA *generated_rna;
|
||||
GeneratedTreeSrnaData()
|
||||
{
|
||||
generated_rna = RNA_create_runtime();
|
||||
}
|
||||
~GeneratedTreeSrnaData()
|
||||
{
|
||||
RNA_free(generated_rna);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
95
blender-5.2.0/source/blender/nodes/NOD_partial_eval.hh
Normal file
95
blender-5.2.0/source/blender/nodes/NOD_partial_eval.hh
Normal file
@@ -0,0 +1,95 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NOD_node_in_compute_context.hh"
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_set.hh"
|
||||
|
||||
#include "BKE_compute_context_cache.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
/**
|
||||
* This header provides functionality that makes it relatively straight forward to evaluate parts
|
||||
* of a node tree. The evaluator is designed to be flexible and simple to use in different
|
||||
* contexts. It's not designed to be highly efficient and parallel. However, it has a lower
|
||||
* start-up cost compared to e.g. the lazy-function evaluation for geometry nodes, which needs to
|
||||
* convert the entire node graph into a lazy-function graph first. So it can be more efficient when
|
||||
* only very few nodes of a larger graph have to be evaluated and those nodes are cheap.
|
||||
*
|
||||
* The evaluator does not use recursion, so it can be used on node graphs of every size and depth.
|
||||
*/
|
||||
namespace blender::nodes::partial_eval {
|
||||
|
||||
/**
|
||||
* Evaluating part of a node tree from left-to-right. The part that's evaluated starts at
|
||||
* the given sockets and is propagated downstream step-by-step. The caller is responsible for
|
||||
* storing the socket values (a value per #SocketInContext).
|
||||
*
|
||||
* \note This handles node groups transparently, but does not handle e.g. repeat zones yet.
|
||||
*
|
||||
* \param initial_sockets: Sockets where the evaluation should start.
|
||||
* \param compute_context_cache: Is used to construct compute contexts
|
||||
* which the caller may want to outlive the entire evaluation.
|
||||
* \param evaluate_node_fn: Is called when all (relevant) upstream nodes are already evaluated and
|
||||
* evaluates the given node. This should updated the values the caller stores for the output
|
||||
* sockets.
|
||||
* \param propagate_value_fn: Should copy the value stored for one socket to the other socket. This
|
||||
* may have to do type conversions. The return value indicates success. False indicates that the
|
||||
* value was not propagated and as such the target node also shouldn't be evaluated (unless there
|
||||
* are other reasons to evaluate it).
|
||||
*/
|
||||
void eval_downstream(
|
||||
const Span<SocketInContext> initial_sockets,
|
||||
bke::ComputeContextCache &compute_context_cache,
|
||||
FunctionRef<void(const NodeInContext &ctx_node,
|
||||
Vector<const bNodeSocket *> &r_outputs_to_propagate)> evaluate_node_fn,
|
||||
FunctionRef<bool(const SocketInContext &ctx_from, const SocketInContext &ctx_to)>
|
||||
propagate_value_fn);
|
||||
|
||||
struct UpstreamEvalTargets {
|
||||
Set<SocketInContext> sockets;
|
||||
Set<NodeInContext> value_nodes;
|
||||
Set<SocketInContext> group_inputs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluates part of a node tree from right-to-left (inverse direction). The caller is responsible
|
||||
* for storing the socket values (a value per #SocketInContext). Evaluation in the upstream
|
||||
* direction is not always well defined, because output sockets may be linked to multiple inputs
|
||||
* and nodes may not always have an inverse evaluation function. The caller is responsible for
|
||||
* handling these cases gracefully in the given callbacks.
|
||||
*
|
||||
* \note This handles node groups transparently, but does not handle e.g. repeat zones yet.
|
||||
*
|
||||
* \param initial_sockets: Sockets where the evaluation should start.
|
||||
* \param compute_context_cache: Is used to construct compute contexts
|
||||
* which the caller may want to outlive the entire evaluation.
|
||||
* \param evaluate_node_fn: Called to evaluate the node in reverse, i.e. it's outputs are computed
|
||||
* first, and the node evaluation computes the inputs.
|
||||
* \param propagate_value_fn: Should copy the value from one socket to another, while optionally
|
||||
* doing type conversions. This has to handle the case when multiple values are propagated to the
|
||||
* same socket. Returning false indicates that no value was propagated.
|
||||
* \param get_inputs_to_propagate_fn: Gathers a list of input sockets that should be propagated
|
||||
* further.
|
||||
*
|
||||
* \return Places in the node tree that have gotten new values that can't be propagated further in
|
||||
* the node tree.
|
||||
*/
|
||||
UpstreamEvalTargets eval_upstream(
|
||||
const Span<SocketInContext> initial_sockets,
|
||||
bke::ComputeContextCache &compute_context_cache,
|
||||
FunctionRef<void(const NodeInContext &ctx_node,
|
||||
Vector<const bNodeSocket *> &r_modified_inputs)> evaluate_node_fn,
|
||||
FunctionRef<bool(const SocketInContext &ctx_from, const SocketInContext &ctx_to)>
|
||||
propagate_value_fn,
|
||||
FunctionRef<void(const NodeInContext &ctx_node, Vector<const bNodeSocket *> &r_sockets)>
|
||||
get_inputs_to_propagate_fn);
|
||||
|
||||
bool is_supported_value_node(const bNode &node);
|
||||
|
||||
} // namespace blender::nodes::partial_eval
|
||||
44
blender-5.2.0/source/blender/nodes/NOD_register.hh
Normal file
44
blender-5.2.0/source/blender/nodes/NOD_register.hh
Normal file
@@ -0,0 +1,44 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
void register_nodes();
|
||||
|
||||
void register_node_type_frame();
|
||||
void register_node_type_reroute();
|
||||
void register_node_type_implicit_conversion();
|
||||
|
||||
void register_node_type_group_input();
|
||||
void register_node_type_group_output();
|
||||
|
||||
void register_compositor_nodes();
|
||||
void register_function_nodes();
|
||||
void register_geometry_nodes();
|
||||
void register_shader_nodes();
|
||||
void register_texture_nodes();
|
||||
|
||||
/**
|
||||
* This macro has three purposes:
|
||||
* - It serves as marker in source code that `discover_nodes.py` can search for to find nodes that
|
||||
* need to be registered. This script generates code that calls the register functions of all
|
||||
* nodes.
|
||||
* - It creates a non-static wrapper function for the registration function that is then called by
|
||||
* the generated code. This wrapper is necessary because the normal registration is static and
|
||||
* can't be called from somewhere else. It could be made non-static, but then it would require
|
||||
* a declaration to avoid warnings.
|
||||
* - It reduces the amount of "magic" with how node registration works. The script could also
|
||||
* search for `node_register` functions directly, but then it would not be apparent in the code
|
||||
* that anything unusual is going on.
|
||||
*/
|
||||
#define NOD_REGISTER_NODE(REGISTER_FUNC) \
|
||||
void REGISTER_FUNC##_discover(); \
|
||||
void REGISTER_FUNC##_discover() \
|
||||
{ \
|
||||
REGISTER_FUNC(); \
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
131
blender-5.2.0/source/blender/nodes/NOD_rna_define.hh
Normal file
131
blender-5.2.0/source/blender/nodes/NOD_rna_define.hh
Normal file
@@ -0,0 +1,131 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "RNA_define.hh"
|
||||
|
||||
#include "WM_types.hh" /* For notifier defines */
|
||||
|
||||
namespace blender {
|
||||
|
||||
void rna_Node_update(Main *bmain, Scene *scene, PointerRNA *ptr);
|
||||
void rna_Node_socket_update(Main *bmain, Scene *scene, PointerRNA *ptr);
|
||||
void rna_Node_update_relations(Main *bmain, Scene *scne, PointerRNA *ptr);
|
||||
void rna_Node_Viewer_shortcut_node_set(PointerRNA *ptr, PropertyRNA *prop, int value);
|
||||
const EnumPropertyItem *rna_NodeSocket_structure_type_item_filter(
|
||||
const bNodeTree *ntree, const eNodeSocketDatatype socket_type, bool *r_free);
|
||||
|
||||
namespace nodes {
|
||||
|
||||
struct EnumRNAAccessors {
|
||||
EnumPropertyGetFunc getter;
|
||||
EnumPropertySetFunc setter;
|
||||
|
||||
EnumRNAAccessors(EnumPropertyGetFunc getter, EnumPropertySetFunc setter)
|
||||
: getter(getter), setter(setter)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates accessor methods for a property stored directly in the `bNode`, typically
|
||||
* `bNode->custom1` or similar.
|
||||
*/
|
||||
#define NOD_inline_enum_accessors(member) \
|
||||
EnumRNAAccessors( \
|
||||
[](PointerRNA *ptr, PropertyRNA * /*prop*/) -> int { \
|
||||
const bNode &node = *static_cast<const bNode *>(ptr->data); \
|
||||
return node.member; \
|
||||
}, \
|
||||
[](PointerRNA *ptr, PropertyRNA * /*prop*/, const int value) { \
|
||||
bNode &node = *static_cast<bNode *>(ptr->data); \
|
||||
node.member = static_cast<std::remove_reference_t<decltype(node.member)>>(value); \
|
||||
})
|
||||
|
||||
/**
|
||||
* Generates accessor methods for a property stored in `bNode->storage`. This is expected to be
|
||||
* used in a node file that uses #NODE_STORAGE_FUNCS.
|
||||
*/
|
||||
#define NOD_storage_enum_accessors(member) \
|
||||
EnumRNAAccessors( \
|
||||
[](PointerRNA *ptr, PropertyRNA * /*prop*/) -> int { \
|
||||
const bNode &node = *static_cast<const bNode *>(ptr->data); \
|
||||
return int(node_storage(node).member); \
|
||||
}, \
|
||||
[](PointerRNA *ptr, PropertyRNA * /*prop*/, const int value) { \
|
||||
bNode &node = *static_cast<bNode *>(ptr->data); \
|
||||
node_storage(node).member = \
|
||||
static_cast<std::remove_reference_t<decltype(node_storage(node).member)>>(value); \
|
||||
})
|
||||
|
||||
struct BooleanRNAAccessors {
|
||||
BooleanPropertyGetFunc getter;
|
||||
BooleanPropertySetFunc setter;
|
||||
|
||||
BooleanRNAAccessors(BooleanPropertyGetFunc getter, BooleanPropertySetFunc setter)
|
||||
: getter(getter), setter(setter)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates accessor methods for a property stored directly in the `bNode`, typically
|
||||
* `bNode->custom1` or similar.
|
||||
*/
|
||||
#define NOD_inline_boolean_accessors(member, flag) \
|
||||
BooleanRNAAccessors( \
|
||||
[](PointerRNA *ptr, PropertyRNA * /*prop*/) -> bool { \
|
||||
const bNode &node = *static_cast<const bNode *>(ptr->data); \
|
||||
return node.member & (flag); \
|
||||
}, \
|
||||
[](PointerRNA *ptr, PropertyRNA * /*prop*/, const bool value) { \
|
||||
bNode &node = *static_cast<bNode *>(ptr->data); \
|
||||
SET_FLAG_FROM_TEST(node.member, value, (flag)); \
|
||||
})
|
||||
|
||||
/**
|
||||
* Generates accessor methods for a property stored in `bNode->storage`. This is expected to be
|
||||
* used in a node file that uses #NODE_STORAGE_FUNCS.
|
||||
*/
|
||||
#define NOD_storage_boolean_accessors(member, flag) \
|
||||
BooleanRNAAccessors( \
|
||||
[](PointerRNA *ptr, PropertyRNA * /*prop*/) -> bool { \
|
||||
const bNode &node = *static_cast<const bNode *>(ptr->data); \
|
||||
return node_storage(node).member & (flag); \
|
||||
}, \
|
||||
[](PointerRNA *ptr, PropertyRNA * /*prop*/, const bool value) { \
|
||||
bNode &node = *static_cast<bNode *>(ptr->data); \
|
||||
SET_FLAG_FROM_TEST(node_storage(node).member, value, (flag)); \
|
||||
})
|
||||
|
||||
const EnumPropertyItem *enum_items_filter(const EnumPropertyItem *original_item_array,
|
||||
FunctionRef<bool(const EnumPropertyItem &item)> fn);
|
||||
|
||||
PropertyRNA *RNA_def_node_enum(StructRNA *srna,
|
||||
const char *identifier,
|
||||
const char *ui_name,
|
||||
const char *ui_description,
|
||||
const EnumPropertyItem *static_items,
|
||||
const EnumRNAAccessors accessors,
|
||||
std::optional<int> default_value = std::nullopt,
|
||||
const EnumPropertyItemFunc item_func = nullptr,
|
||||
bool allow_animation = false);
|
||||
|
||||
PropertyRNA *RNA_def_node_boolean(StructRNA *srna,
|
||||
const char *identifier,
|
||||
const char *ui_name,
|
||||
const char *ui_description,
|
||||
const BooleanRNAAccessors accessors,
|
||||
std::optional<bool> default_value = std::nullopt,
|
||||
bool allow_animation = false);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
37
blender-5.2.0/source/blender/nodes/NOD_shader.h
Normal file
37
blender-5.2.0/source/blender/nodes/NOD_shader.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/* SPDX-FileCopyrightText: 2005 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup nodes
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
extern struct bke::bNodeTreeType *ntreeType_Shader;
|
||||
|
||||
void register_node_type_sh_custom_group(bke::bNodeType *ntype);
|
||||
|
||||
struct bNodeTreeExec *ntreeShaderBeginExecTree(struct bNodeTree *ntree);
|
||||
void ntreeShaderEndExecTree(struct bNodeTreeExec *exec);
|
||||
|
||||
/**
|
||||
* Find an output node of the shader tree.
|
||||
*
|
||||
* \note it will only return output which is NOT in the group, which isn't how
|
||||
* render engines works but it's how the GPU shader compilation works. This we
|
||||
* can change in the future and make it a generic function, but for now it stays
|
||||
* private here.
|
||||
*/
|
||||
struct bNode *ntreeShaderOutputNode(struct bNodeTree *ntree, int target);
|
||||
|
||||
/**
|
||||
* This one needs to work on a local tree.
|
||||
*/
|
||||
void ntreeGPUMaterialNodes(struct bNodeTree *localtree, struct GPUMaterial *mat);
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,45 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bNodeTree;
|
||||
struct bNode;
|
||||
|
||||
namespace nodes {
|
||||
|
||||
struct InlineShaderNodeTreeParams {
|
||||
/**
|
||||
* Disable loop unrolling and keep Repeat Zone nodes in the tree.
|
||||
* (For engines with native support for Repeat Zones)
|
||||
*
|
||||
* Some Repeat Zones may still be unrolled (eg. if they have Closure or Bundle Zone Items).
|
||||
*/
|
||||
bool allow_preserving_repeat_zones = false;
|
||||
|
||||
/* Allow processing only the outputs relevant to specific engines. */
|
||||
NodeShaderOutputTarget target_engine_ = SHD_OUTPUT_ALL;
|
||||
|
||||
struct ErrorMessage {
|
||||
/* In theory, more contextual information could be added here like the entire context path to
|
||||
* that node. In practice, we can't report errors with that level of detail in shader nodes
|
||||
* yet. */
|
||||
const bNode *node;
|
||||
std::string message;
|
||||
};
|
||||
Vector<ErrorMessage> r_error_messages;
|
||||
};
|
||||
|
||||
bool inline_shader_node_tree(const bNodeTree &src_tree,
|
||||
bNodeTree &dst_tree,
|
||||
InlineShaderNodeTreeParams ¶ms);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
45
blender-5.2.0/source/blender/nodes/NOD_socket.hh
Normal file
45
blender-5.2.0/source/blender/nodes/NOD_socket.hh
Normal file
@@ -0,0 +1,45 @@
|
||||
/* SPDX-FileCopyrightText: 2007 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup nodes
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bNode;
|
||||
struct bNodeTree;
|
||||
|
||||
bNodeSocket *node_add_socket_from_template(bNodeTree *ntree,
|
||||
bNode *node,
|
||||
bke::bNodeSocketTemplate *stemp,
|
||||
eNodeSocketInOut in_out);
|
||||
|
||||
void node_verify_sockets(Main *bmain, bNodeTree *ntree, bNode *node, bool do_id_user);
|
||||
|
||||
void node_socket_init_default_value_data(eNodeSocketDatatype datatype, int subtype, void **data);
|
||||
void node_socket_copy_default_value_data(eNodeSocketDatatype datatype, void *to, const void *from);
|
||||
void node_socket_init_default_value(bNodeSocket *sock);
|
||||
void node_socket_copy_default_value(bNodeSocket *to, const bNodeSocket *from);
|
||||
void register_standard_node_socket_types();
|
||||
|
||||
|
||||
namespace nodes {
|
||||
|
||||
/**
|
||||
* Change the sockets of the node so that it matches the declaration.
|
||||
*
|
||||
* \param bmain: Optional, necessary for updating animation data.
|
||||
*/
|
||||
void update_node_declaration_and_sockets(bNodeTree &ntree, bNode &node, Main *bmain = nullptr);
|
||||
bool socket_type_supports_fields(eNodeSocketDatatype socket_type);
|
||||
bool socket_type_supports_attributes(eNodeSocketDatatype socket_type);
|
||||
bool socket_type_supports_grids(eNodeSocketDatatype socket_type);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
823
blender-5.2.0/source/blender/nodes/NOD_socket_declarations.hh
Normal file
823
blender-5.2.0/source/blender/nodes/NOD_socket_declarations.hh
Normal file
@@ -0,0 +1,823 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cfloat>
|
||||
|
||||
#include "NOD_bundle_type.hh"
|
||||
#include "NOD_menu_value.hh"
|
||||
#include "NOD_node_declaration.hh"
|
||||
|
||||
#include "RNA_types.hh"
|
||||
|
||||
#include "BKE_node_enum.hh"
|
||||
|
||||
#include "BLI_color_types.hh"
|
||||
#include "BLI_implicit_sharing_ptr.hh"
|
||||
#include "BLI_math_euler_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
class ClosureSignature;
|
||||
}
|
||||
|
||||
namespace blender::nodes::decl {
|
||||
|
||||
class FloatBuilder;
|
||||
|
||||
class Float : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_FLOAT;
|
||||
|
||||
float default_value = 0.0f;
|
||||
float soft_min_value = -FLT_MAX;
|
||||
float soft_max_value = FLT_MAX;
|
||||
PropertySubType subtype = PROP_NONE;
|
||||
|
||||
friend FloatBuilder;
|
||||
|
||||
using Builder = FloatBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class FloatBuilder : public SocketDeclarationBuilder<Float> {
|
||||
public:
|
||||
FloatBuilder &min(float value);
|
||||
FloatBuilder &max(float value);
|
||||
FloatBuilder &default_value(float value);
|
||||
FloatBuilder &subtype(PropertySubType subtype);
|
||||
FloatBuilder &try_copy_ui_data(const SocketDeclaration &other_decl) override;
|
||||
};
|
||||
|
||||
class IntBuilder;
|
||||
|
||||
class Int : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_INT;
|
||||
|
||||
int default_value = 0;
|
||||
int soft_min_value = INT32_MIN;
|
||||
int soft_max_value = INT32_MAX;
|
||||
PropertySubType subtype = PROP_NONE;
|
||||
|
||||
friend IntBuilder;
|
||||
|
||||
using Builder = IntBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class IntBuilder : public SocketDeclarationBuilder<Int> {
|
||||
public:
|
||||
IntBuilder &min(int value);
|
||||
IntBuilder &max(int value);
|
||||
IntBuilder &default_value(int value);
|
||||
IntBuilder &subtype(PropertySubType subtype);
|
||||
IntBuilder &try_copy_ui_data(const SocketDeclaration &other_decl) override;
|
||||
};
|
||||
|
||||
class VectorBuilder;
|
||||
|
||||
class Vector : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_VECTOR;
|
||||
|
||||
float4 default_value = {0, 0, 0, 0};
|
||||
float soft_min_value = -FLT_MAX;
|
||||
float soft_max_value = FLT_MAX;
|
||||
int dimensions = 3;
|
||||
PropertySubType subtype = PROP_NONE;
|
||||
|
||||
friend VectorBuilder;
|
||||
|
||||
using Builder = VectorBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class VectorBuilder : public SocketDeclarationBuilder<Vector> {
|
||||
public:
|
||||
VectorBuilder &default_value(const float2 value);
|
||||
VectorBuilder &default_value(const float3 value);
|
||||
VectorBuilder &default_value(const float4 value);
|
||||
VectorBuilder &subtype(PropertySubType subtype);
|
||||
VectorBuilder &dimensions(int dimensions);
|
||||
VectorBuilder &min(float min);
|
||||
VectorBuilder &max(float max);
|
||||
VectorBuilder &compact();
|
||||
VectorBuilder &try_copy_ui_data(const SocketDeclaration &other_decl) override;
|
||||
};
|
||||
|
||||
class IntVectorBuilder;
|
||||
|
||||
class IntVector : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_INT_VECTOR;
|
||||
|
||||
int3 default_value = {0, 0, 0};
|
||||
int soft_min_value = INT_MIN;
|
||||
int soft_max_value = INT_MAX;
|
||||
int dimensions = 3;
|
||||
PropertySubType subtype = PROP_NONE;
|
||||
|
||||
friend IntVectorBuilder;
|
||||
|
||||
using Builder = IntVectorBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class IntVectorBuilder : public SocketDeclarationBuilder<IntVector> {
|
||||
public:
|
||||
IntVectorBuilder &default_value(const int2 value);
|
||||
IntVectorBuilder &default_value(const int3 value);
|
||||
IntVectorBuilder &subtype(PropertySubType subtype);
|
||||
IntVectorBuilder &dimensions(int dimensions);
|
||||
IntVectorBuilder &min(int min);
|
||||
IntVectorBuilder &max(int max);
|
||||
IntVectorBuilder &compact();
|
||||
};
|
||||
|
||||
class BoolBuilder;
|
||||
|
||||
class Bool : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_BOOLEAN;
|
||||
|
||||
bool default_value = false;
|
||||
friend BoolBuilder;
|
||||
|
||||
using Builder = BoolBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class BoolBuilder : public SocketDeclarationBuilder<Bool> {
|
||||
public:
|
||||
BoolBuilder &default_value(bool value);
|
||||
BoolBuilder &try_copy_ui_data(const SocketDeclaration &other_decl) override;
|
||||
};
|
||||
|
||||
class ColorBuilder;
|
||||
|
||||
class Color : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_RGBA;
|
||||
|
||||
ColorGeometry4f default_value{0.8f, 0.8f, 0.8f, 1.0f};
|
||||
|
||||
friend ColorBuilder;
|
||||
|
||||
using Builder = ColorBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class ColorBuilder : public SocketDeclarationBuilder<Color> {
|
||||
public:
|
||||
ColorBuilder &default_value(const ColorGeometry4f value);
|
||||
ColorBuilder &try_copy_ui_data(const SocketDeclaration &other_decl) override;
|
||||
};
|
||||
|
||||
class RotationBuilder;
|
||||
|
||||
class Rotation : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_ROTATION;
|
||||
|
||||
math::EulerXYZ default_value;
|
||||
|
||||
friend RotationBuilder;
|
||||
|
||||
using Builder = RotationBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class RotationBuilder : public SocketDeclarationBuilder<Rotation> {
|
||||
public:
|
||||
RotationBuilder &default_value(const math::EulerXYZ &value);
|
||||
RotationBuilder &try_copy_ui_data(const SocketDeclaration &other_decl) override;
|
||||
};
|
||||
|
||||
class MatrixBuilder;
|
||||
|
||||
class Matrix : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_MATRIX;
|
||||
|
||||
friend MatrixBuilder;
|
||||
|
||||
using Builder = MatrixBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class MatrixBuilder : public SocketDeclarationBuilder<Matrix> {};
|
||||
|
||||
class StringBuilder;
|
||||
|
||||
class String : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_STRING;
|
||||
|
||||
std::string default_value;
|
||||
PropertySubType subtype = PROP_NONE;
|
||||
std::optional<std::string> path_filter;
|
||||
|
||||
friend StringBuilder;
|
||||
|
||||
using Builder = StringBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class StringBuilder : public SocketDeclarationBuilder<String> {
|
||||
public:
|
||||
StringBuilder &default_value(const std::string value);
|
||||
StringBuilder &subtype(PropertySubType subtype);
|
||||
StringBuilder &path_filter(std::optional<std::string> filter);
|
||||
StringBuilder &try_copy_ui_data(const SocketDeclaration &other_decl) override;
|
||||
};
|
||||
|
||||
class MenuBuilder;
|
||||
|
||||
class Menu : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_MENU;
|
||||
|
||||
MenuValue default_value;
|
||||
bool is_expanded = false;
|
||||
ImplicitSharingPtr<bke::RuntimeNodeEnumItems> items;
|
||||
|
||||
friend MenuBuilder;
|
||||
|
||||
using Builder = MenuBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class MenuBuilder : public SocketDeclarationBuilder<Menu> {
|
||||
public:
|
||||
MenuBuilder &default_value(MenuValue value);
|
||||
|
||||
/** Draw the menu items next to each other instead of as a drop-down menu. */
|
||||
MenuBuilder &expanded(bool value = true);
|
||||
|
||||
using StaticItemFilterFn = bool (*)(const EnumPropertyItem &item);
|
||||
/** Set the available items in the menu. The items array must have static lifetime. */
|
||||
MenuBuilder &static_items(const EnumPropertyItem *items,
|
||||
const StaticItemFilterFn filter_fn = nullptr);
|
||||
};
|
||||
|
||||
class BundleBuilder;
|
||||
|
||||
class Bundle : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_BUNDLE;
|
||||
/**
|
||||
* Index of a corresponding input socket. If set, the output is assumed to have the same bundle
|
||||
* structure as the input.
|
||||
*/
|
||||
std::optional<int> pass_through_input_index;
|
||||
|
||||
std::optional<BundleType> bundle_type;
|
||||
|
||||
friend BundleBuilder;
|
||||
|
||||
using Builder = BundleBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class BundleBuilder : public SocketDeclarationBuilder<Bundle> {
|
||||
public:
|
||||
BundleBuilder &bundle_type(BundleType bundle_type);
|
||||
|
||||
/** On output sockets, indicate that the bundle structure is passed through from an input. */
|
||||
BundleBuilder &pass_through_input_index(std::optional<int> index);
|
||||
};
|
||||
|
||||
class ClosureBuilder;
|
||||
|
||||
class Closure : public SocketDeclaration {
|
||||
public:
|
||||
std::unique_ptr<std::function<ClosureSignature(const bNode &)>> create_signature;
|
||||
|
||||
Closure();
|
||||
~Closure() override;
|
||||
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_CLOSURE;
|
||||
|
||||
friend ClosureBuilder;
|
||||
|
||||
using Builder = ClosureBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class ClosureBuilder : public SocketDeclarationBuilder<Closure> {
|
||||
public:
|
||||
void create_signature(std::function<ClosureSignature(const bNode &)> create_signature);
|
||||
};
|
||||
|
||||
class IDSocketDeclaration : public SocketDeclaration {
|
||||
public:
|
||||
const char *idname;
|
||||
/**
|
||||
* Get the default ID pointer for this socket. This is a function to avoid dangling pointers,
|
||||
* since bNode::id pointers are remapped as ID pointers change, but pointers in socket
|
||||
* declarations are not managed the same way.
|
||||
*/
|
||||
std::function<ID *(const bNode &node)> default_value_fn;
|
||||
|
||||
IDSocketDeclaration(const char *idname);
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
template<typename T> class IDSocketDeclarationBuilder : public SocketDeclarationBuilder<T> {
|
||||
public:
|
||||
IDSocketDeclarationBuilder &default_value_fn(std::function<ID *(const bNode &node)> fn)
|
||||
{
|
||||
this->decl_->default_value_fn = std::move(fn);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class Object : public IDSocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_OBJECT;
|
||||
|
||||
using Builder = IDSocketDeclarationBuilder<Object>;
|
||||
|
||||
Object();
|
||||
};
|
||||
|
||||
class Material : public IDSocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_MATERIAL;
|
||||
|
||||
using Builder = IDSocketDeclarationBuilder<Material>;
|
||||
|
||||
Material();
|
||||
};
|
||||
|
||||
class Collection : public IDSocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_COLLECTION;
|
||||
|
||||
using Builder = IDSocketDeclarationBuilder<Collection>;
|
||||
|
||||
Collection();
|
||||
};
|
||||
|
||||
class Texture : public IDSocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_TEXTURE;
|
||||
|
||||
using Builder = IDSocketDeclarationBuilder<Texture>;
|
||||
|
||||
Texture();
|
||||
};
|
||||
|
||||
class Image : public IDSocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_IMAGE;
|
||||
|
||||
using Builder = IDSocketDeclarationBuilder<Image>;
|
||||
|
||||
Image();
|
||||
};
|
||||
|
||||
class Font : public IDSocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_FONT;
|
||||
|
||||
using Builder = IDSocketDeclarationBuilder<Font>;
|
||||
|
||||
Font();
|
||||
};
|
||||
|
||||
class Scene : public IDSocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_SCENE;
|
||||
|
||||
using Builder = IDSocketDeclarationBuilder<Scene>;
|
||||
|
||||
Scene();
|
||||
};
|
||||
|
||||
class Text : public IDSocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_TEXT_ID;
|
||||
|
||||
using Builder = IDSocketDeclarationBuilder<Text>;
|
||||
|
||||
Text();
|
||||
};
|
||||
|
||||
class Mask : public IDSocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_MASK;
|
||||
|
||||
using Builder = IDSocketDeclarationBuilder<Mask>;
|
||||
|
||||
Mask();
|
||||
};
|
||||
|
||||
class Sound : public IDSocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_SOUND;
|
||||
|
||||
using Builder = IDSocketDeclarationBuilder<Sound>;
|
||||
|
||||
Sound();
|
||||
};
|
||||
|
||||
class ShaderBuilder;
|
||||
|
||||
class Shader : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_SHADER;
|
||||
|
||||
friend ShaderBuilder;
|
||||
|
||||
using Builder = ShaderBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class ShaderBuilder : public SocketDeclarationBuilder<Shader> {};
|
||||
|
||||
class ExtendBuilder;
|
||||
|
||||
class Extend : public SocketDeclaration {
|
||||
private:
|
||||
friend ExtendBuilder;
|
||||
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_CUSTOM;
|
||||
|
||||
using Builder = ExtendBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class ExtendBuilder : public SocketDeclarationBuilder<Extend> {};
|
||||
|
||||
class CustomTypeBuilder;
|
||||
|
||||
class Custom : public SocketDeclaration {
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_CUSTOM;
|
||||
|
||||
friend CustomTypeBuilder;
|
||||
|
||||
using Builder = CustomTypeBuilder;
|
||||
|
||||
const char *idname_;
|
||||
std::function<void(bNode &node, bNodeSocket &socket, const char *data_path)> init_socket_fn;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
};
|
||||
|
||||
class CustomTypeBuilder : public SocketDeclarationBuilder<Custom> {
|
||||
public:
|
||||
CustomTypeBuilder &idname(const char *idname);
|
||||
|
||||
CustomTypeBuilder &init_socket_fn(
|
||||
std::function<void(bNode &node, bNodeSocket &socket, const char *data_path)> fn)
|
||||
{
|
||||
decl_->init_socket_fn = std::move(fn);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #FloatBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline FloatBuilder &FloatBuilder::min(const float value)
|
||||
{
|
||||
decl_->soft_min_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline FloatBuilder &FloatBuilder::max(const float value)
|
||||
{
|
||||
decl_->soft_max_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline FloatBuilder &FloatBuilder::default_value(const float value)
|
||||
{
|
||||
decl_->default_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline FloatBuilder &FloatBuilder::subtype(PropertySubType subtype)
|
||||
{
|
||||
decl_->subtype = subtype;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #IntBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline IntBuilder &IntBuilder::min(const int value)
|
||||
{
|
||||
decl_->soft_min_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IntBuilder &IntBuilder::max(const int value)
|
||||
{
|
||||
decl_->soft_max_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IntBuilder &IntBuilder::default_value(const int value)
|
||||
{
|
||||
decl_->default_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IntBuilder &IntBuilder::subtype(PropertySubType subtype)
|
||||
{
|
||||
decl_->subtype = subtype;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #VectorBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline VectorBuilder &VectorBuilder::default_value(const float2 value)
|
||||
{
|
||||
decl_->default_value = float4(value, 0.0f, 0.0f);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline VectorBuilder &VectorBuilder::default_value(const float3 value)
|
||||
{
|
||||
decl_->default_value = float4(value, 0.0f);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline VectorBuilder &VectorBuilder::default_value(const float4 value)
|
||||
{
|
||||
decl_->default_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline VectorBuilder &VectorBuilder::subtype(PropertySubType subtype)
|
||||
{
|
||||
decl_->subtype = subtype;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline VectorBuilder &VectorBuilder::dimensions(int dimensions)
|
||||
{
|
||||
BLI_assert(dimensions >= 2 && dimensions <= 4);
|
||||
decl_->dimensions = dimensions;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline VectorBuilder &VectorBuilder::min(const float min)
|
||||
{
|
||||
decl_->soft_min_value = min;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline VectorBuilder &VectorBuilder::max(const float max)
|
||||
{
|
||||
decl_->soft_max_value = max;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline VectorBuilder &VectorBuilder::compact()
|
||||
{
|
||||
decl_->compact = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #IntVectorBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline IntVectorBuilder &IntVectorBuilder::default_value(const int2 value)
|
||||
{
|
||||
decl_->default_value = int3(value, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IntVectorBuilder &IntVectorBuilder::default_value(const int3 value)
|
||||
{
|
||||
decl_->default_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IntVectorBuilder &IntVectorBuilder::subtype(PropertySubType subtype)
|
||||
{
|
||||
decl_->subtype = subtype;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IntVectorBuilder &IntVectorBuilder::dimensions(int dimensions)
|
||||
{
|
||||
BLI_assert(dimensions >= 2 && dimensions <= 3);
|
||||
decl_->dimensions = dimensions;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IntVectorBuilder &IntVectorBuilder::min(const int min)
|
||||
{
|
||||
decl_->soft_min_value = min;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IntVectorBuilder &IntVectorBuilder::max(const int max)
|
||||
{
|
||||
decl_->soft_max_value = max;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IntVectorBuilder &IntVectorBuilder::compact()
|
||||
{
|
||||
decl_->compact = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #BoolBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline BoolBuilder &BoolBuilder::default_value(const bool value)
|
||||
{
|
||||
decl_->default_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #ColorBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline ColorBuilder &ColorBuilder::default_value(const ColorGeometry4f value)
|
||||
{
|
||||
decl_->default_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #StringBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline StringBuilder &StringBuilder::default_value(std::string value)
|
||||
{
|
||||
decl_->default_value = std::move(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline StringBuilder &StringBuilder::subtype(PropertySubType subtype)
|
||||
{
|
||||
decl_->subtype = subtype;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #MenuBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline MenuBuilder &MenuBuilder::default_value(const MenuValue value)
|
||||
{
|
||||
decl_->default_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline MenuBuilder &MenuBuilder::expanded(const bool value)
|
||||
{
|
||||
decl_->is_expanded = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #RotationBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline RotationBuilder &RotationBuilder::default_value(const math::EulerXYZ &value)
|
||||
{
|
||||
decl_->default_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #IDSocketDeclaration and Children Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline IDSocketDeclaration::IDSocketDeclaration(const char *idname) : idname(idname) {}
|
||||
|
||||
inline Object::Object() : IDSocketDeclaration("NodeSocketObject") {}
|
||||
|
||||
inline Material::Material() : IDSocketDeclaration("NodeSocketMaterial") {}
|
||||
|
||||
inline Collection::Collection() : IDSocketDeclaration("NodeSocketCollection") {}
|
||||
|
||||
inline Texture::Texture() : IDSocketDeclaration("NodeSocketTexture") {}
|
||||
|
||||
inline Image::Image() : IDSocketDeclaration("NodeSocketImage") {}
|
||||
|
||||
inline Font::Font() : IDSocketDeclaration("NodeSocketFont") {}
|
||||
|
||||
inline Scene::Scene() : IDSocketDeclaration("NodeSocketScene") {}
|
||||
|
||||
inline Text::Text() : IDSocketDeclaration("NodeSocketText") {}
|
||||
|
||||
inline Mask::Mask() : IDSocketDeclaration("NodeSocketMask") {}
|
||||
|
||||
inline Sound::Sound() : IDSocketDeclaration("NodeSocketSound") {}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #CustomTypeBuilder Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline CustomTypeBuilder &CustomTypeBuilder::idname(const char *idname)
|
||||
{
|
||||
decl_->idname_ = idname;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::nodes::decl
|
||||
@@ -0,0 +1,47 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_geometry_set.hh"
|
||||
|
||||
#include "NOD_node_declaration.hh"
|
||||
|
||||
namespace blender::nodes::decl {
|
||||
|
||||
class GeometryBuilder;
|
||||
|
||||
class Geometry : public SocketDeclaration {
|
||||
private:
|
||||
blender::Vector<bke::GeometryComponent::Type> supported_types_;
|
||||
bool only_realized_data_ = false;
|
||||
bool only_instances_ = false;
|
||||
|
||||
friend GeometryBuilder;
|
||||
|
||||
public:
|
||||
static constexpr eNodeSocketDatatype static_socket_type = SOCK_GEOMETRY;
|
||||
|
||||
using Builder = GeometryBuilder;
|
||||
|
||||
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
|
||||
bool matches(const bNodeSocket &socket) const override;
|
||||
bool can_connect(const bNodeSocket &socket) const override;
|
||||
|
||||
Span<bke::GeometryComponent::Type> supported_types() const;
|
||||
bool only_realized_data() const;
|
||||
bool only_instances() const;
|
||||
};
|
||||
|
||||
class GeometryBuilder : public SocketDeclarationBuilder<Geometry> {
|
||||
public:
|
||||
GeometryBuilder &supported_type(bke::GeometryComponent::Type supported_type);
|
||||
GeometryBuilder &supported_type(blender::Vector<bke::GeometryComponent::Type> supported_types);
|
||||
GeometryBuilder &only_realized_data(bool value = true);
|
||||
GeometryBuilder &only_instances(bool value = true);
|
||||
};
|
||||
|
||||
} // namespace blender::nodes::decl
|
||||
415
blender-5.2.0/source/blender/nodes/NOD_socket_items.hh
Normal file
415
blender-5.2.0/source/blender/nodes/NOD_socket_items.hh
Normal file
@@ -0,0 +1,415 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Some nodes have a dynamic number of sockets (e.g. simulation input/output). These nodes store an
|
||||
* array of items in their `bNode->storage` (e.g. `NodeSimulationItem`). Different nodes have
|
||||
* slightly different storage requirements, but a lot of the logic is still the same between nodes.
|
||||
* This file implements various shared functionality that can be used by different nodes to deal
|
||||
* with these item arrays.
|
||||
*
|
||||
* In order to use the functions, one has to implement an "accessor" which tells the shared code
|
||||
* how to deal with specific item arrays. Different functions have different requirements for the
|
||||
* accessor. It's easiest to just look at existing accessors like #SimulationItemsAccessor and
|
||||
* #RepeatItemsAccessor and to implement the same methods.
|
||||
*/
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_node_tree_update.hh"
|
||||
|
||||
#include "DNA_array_utils.hh"
|
||||
|
||||
#include "NOD_socket.hh"
|
||||
|
||||
namespace blender::nodes::socket_items {
|
||||
|
||||
struct SocketItemsAccessorDefaults {
|
||||
static constexpr bool has_single_identifier_str = true;
|
||||
static constexpr bool has_name_validation = false;
|
||||
static constexpr bool has_custom_initial_name = false;
|
||||
static constexpr bool has_vector_dimensions = false;
|
||||
static constexpr bool can_have_empty_name = false;
|
||||
static constexpr char unique_name_separator = '.';
|
||||
};
|
||||
|
||||
/**
|
||||
* References a "C-Array" that is stored elsewhere. This is different from a MutableSpan, because
|
||||
* one can even resize the array through this reference.
|
||||
*/
|
||||
template<typename T> struct SocketItemsRef {
|
||||
T **items;
|
||||
int *items_num;
|
||||
int *active_index;
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterates over the node tree to find the node that this item belongs to.
|
||||
*/
|
||||
template<typename Accessor>
|
||||
inline bNode *find_node_by_item(bNodeTree &ntree, const typename Accessor::ItemT &item)
|
||||
{
|
||||
ntree.ensure_topology_cache();
|
||||
for (bNode *node : ntree.nodes_by_type(UString(Accessor::node_idname))) {
|
||||
SocketItemsRef array = Accessor::get_items_from_node(*node);
|
||||
if (&item >= *array.items && &item < *array.items + *array.items_num) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/** Find the item with the given identifier. */
|
||||
template<typename Accessor>
|
||||
inline typename Accessor::ItemT *find_item_by_identifier(bNode &node, const StringRef identifier)
|
||||
{
|
||||
|
||||
SocketItemsRef array = Accessor::get_items_from_node(node);
|
||||
for (const int i : IndexRange(*array.items_num)) {
|
||||
typename Accessor::ItemT &item = (*array.items)[i];
|
||||
if (Accessor::socket_identifier_for_item(item) == identifier) {
|
||||
return &item;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destruct all the items and the free the array itself.
|
||||
*/
|
||||
template<typename Accessor> inline void destruct_array(bNode &node)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
SocketItemsRef ref = Accessor::get_items_from_node(node);
|
||||
for (const int i : IndexRange(*ref.items_num)) {
|
||||
ItemT &item = (*ref.items)[i];
|
||||
Accessor::destruct_item(&item);
|
||||
}
|
||||
MEM_SAFE_DELETE(*ref.items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items from the node.
|
||||
*/
|
||||
template<typename Accessor> inline void clear(bNode &node)
|
||||
{
|
||||
destruct_array<Accessor>(node);
|
||||
SocketItemsRef ref = Accessor::get_items_from_node(node);
|
||||
*ref.items_num = 0;
|
||||
if (ref.active_index) {
|
||||
*ref.active_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the items from the storage of the source node to the storage of the destination node.
|
||||
*/
|
||||
template<typename Accessor> inline void copy_array(const bNode &src_node, bNode &dst_node)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
SocketItemsRef src_ref = Accessor::get_items_from_node(const_cast<bNode &>(src_node));
|
||||
SocketItemsRef dst_ref = Accessor::get_items_from_node(dst_node);
|
||||
const int items_num = *src_ref.items_num;
|
||||
*dst_ref.items = MEM_new_array<ItemT>(items_num, __func__);
|
||||
for (const int i : IndexRange(items_num)) {
|
||||
Accessor::copy_item((*src_ref.items)[i], (*dst_ref.items)[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce constraints on the name of the item.
|
||||
*/
|
||||
template<typename Accessor> inline std::string get_validated_name(const StringRef name)
|
||||
{
|
||||
if constexpr (Accessor::has_name_validation) {
|
||||
return Accessor::validate_name(name);
|
||||
}
|
||||
else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the name of an existing item and makes sure that the name is unique among other the
|
||||
* other items in the same array.
|
||||
*/
|
||||
template<typename Accessor>
|
||||
inline void set_item_name_and_make_unique(bNode &node,
|
||||
typename Accessor::ItemT &item,
|
||||
const char *value)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
SocketItemsRef array = Accessor::get_items_from_node(node);
|
||||
|
||||
std::string name = value;
|
||||
if constexpr (!Accessor::can_have_empty_name) {
|
||||
if (name.empty()) {
|
||||
if constexpr (Accessor::has_type) {
|
||||
name = *bke::node_static_socket_label(Accessor::get_socket_type(item), 0);
|
||||
}
|
||||
else {
|
||||
name = "Item";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::string validated_name = get_validated_name<Accessor>(name);
|
||||
|
||||
const std::string unique_name = BLI_uniquename_cb(
|
||||
[&](const StringRef name) {
|
||||
for (ItemT &item_iter : MutableSpan(*array.items, *array.items_num)) {
|
||||
if (&item_iter != &item) {
|
||||
if (*Accessor::get_name(item_iter) == name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
Accessor::unique_name_separator,
|
||||
validated_name);
|
||||
|
||||
/* The unique name should still be valid. */
|
||||
BLI_assert(unique_name == get_validated_name<Accessor>(unique_name));
|
||||
|
||||
char **item_name = Accessor::get_name(item);
|
||||
MEM_SAFE_DELETE(*item_name);
|
||||
*item_name = BLI_strdup(unique_name.c_str());
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<typename Accessor> inline typename Accessor::ItemT &add_item_to_array(bNode &node)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
SocketItemsRef array = Accessor::get_items_from_node(node);
|
||||
|
||||
ItemT *old_items = *array.items;
|
||||
const int old_items_num = *array.items_num;
|
||||
const int new_items_num = old_items_num + 1;
|
||||
|
||||
ItemT *new_items = MEM_new_array<ItemT>(new_items_num, __func__);
|
||||
std::copy_n(old_items, old_items_num, new_items);
|
||||
ItemT &new_item = new_items[old_items_num];
|
||||
|
||||
MEM_SAFE_DELETE(old_items);
|
||||
*array.items = new_items;
|
||||
*array.items_num = new_items_num;
|
||||
if (array.active_index) {
|
||||
*array.active_index = old_items_num;
|
||||
}
|
||||
|
||||
return new_item;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Add a new item at the end with the given socket type and name. The optional dimensions argument
|
||||
* can be provided for types that support multiple possible dimensions like Vector. It is expected
|
||||
* to be in the range [2, 4] and if not provided, 3 should be assumed.
|
||||
*/
|
||||
template<typename Accessor>
|
||||
inline typename Accessor::ItemT *add_item_with_socket_type_and_name(
|
||||
bNodeTree &ntree,
|
||||
bNode &node,
|
||||
const eNodeSocketDatatype socket_type,
|
||||
const char *name,
|
||||
std::optional<int> dimensions = std::nullopt)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
BLI_assert(Accessor::supports_socket_type(socket_type, ntree.type));
|
||||
BLI_assert(!(dimensions.has_value() && socket_type != SOCK_VECTOR));
|
||||
BLI_assert(ELEM(dimensions.value_or(3), 2, 3, 4));
|
||||
UNUSED_VARS_NDEBUG(ntree);
|
||||
ItemT &new_item = detail::add_item_to_array<Accessor>(node);
|
||||
if constexpr (Accessor::has_vector_dimensions) {
|
||||
Accessor::init_with_socket_type_and_name(node, new_item, socket_type, name, dimensions);
|
||||
}
|
||||
else {
|
||||
Accessor::init_with_socket_type_and_name(node, new_item, socket_type, name);
|
||||
}
|
||||
return &new_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new item at the end with the given name.
|
||||
*/
|
||||
template<typename Accessor>
|
||||
inline typename Accessor::ItemT *add_item_with_name(bNode &node, const char *name)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
ItemT &new_item = detail::add_item_to_array<Accessor>(node);
|
||||
Accessor::init_with_name(node, new_item, name);
|
||||
return &new_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new item at the end.
|
||||
*/
|
||||
template<typename Accessor> inline typename Accessor::ItemT *add_item(bNode &node)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
ItemT &new_item = detail::add_item_to_array<Accessor>(node);
|
||||
Accessor::init(node, new_item);
|
||||
return &new_item;
|
||||
}
|
||||
|
||||
template<typename Accessor>
|
||||
inline std::string get_socket_identifier(const typename Accessor::ItemT &item,
|
||||
const eNodeSocketInOut in_out)
|
||||
{
|
||||
if constexpr (Accessor::has_single_identifier_str) {
|
||||
return Accessor::socket_identifier_for_item(item);
|
||||
}
|
||||
else {
|
||||
if (in_out == SOCK_IN) {
|
||||
return Accessor::input_socket_identifier_for_item(item);
|
||||
}
|
||||
return Accessor::output_socket_identifier_for_item(item);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::optional<eNodeSocketDatatype> get_socket_item_type_to_add(
|
||||
const eNodeSocketDatatype linked_type,
|
||||
const FunctionRef<bool(eNodeSocketDatatype type)> is_supported)
|
||||
{
|
||||
if (is_supported(linked_type)) {
|
||||
return linked_type;
|
||||
}
|
||||
if (linked_type == SOCK_RGBA) {
|
||||
if (is_supported(SOCK_VECTOR)) {
|
||||
return SOCK_VECTOR;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the link connects to the `extend_socket`. If yes, create a new item for the linked
|
||||
* socket, update the node and then change the link to point to the new socket.
|
||||
* \return False if the link should be removed.
|
||||
*/
|
||||
template<typename Accessor>
|
||||
[[nodiscard]] inline bool try_add_item_via_extend_socket(
|
||||
bNodeTree &ntree,
|
||||
bNode &extend_node,
|
||||
bNodeSocket &extend_socket,
|
||||
bNode &storage_node,
|
||||
bNodeLink &link,
|
||||
typename Accessor::ItemT **r_new_item = nullptr)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
bNodeSocket *src_socket = nullptr;
|
||||
if (link.tosock == &extend_socket) {
|
||||
src_socket = link.fromsock;
|
||||
}
|
||||
else if (link.fromsock == &extend_socket) {
|
||||
src_socket = link.tosock;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemT *item = nullptr;
|
||||
if constexpr (Accessor::has_name && Accessor::has_type) {
|
||||
const eNodeSocketDatatype src_socket_type = src_socket->type;
|
||||
const std::optional<eNodeSocketDatatype> added_socket_type = get_socket_item_type_to_add(
|
||||
src_socket_type, [&](const eNodeSocketDatatype type) {
|
||||
return Accessor::supports_socket_type(type, ntree.type);
|
||||
});
|
||||
if (!added_socket_type) {
|
||||
return false;
|
||||
}
|
||||
/* Ensure the source node declaration is up-to-date before capturing the socket label.
|
||||
* This is necessary to correctly capture dynamic labels at creation time. */
|
||||
ntree.ensure_topology_cache();
|
||||
blender::bke::node_declaration_ensure(src_socket->owner_tree(), src_socket->owner_node());
|
||||
std::string name = blender::bke::node_socket_label(*src_socket);
|
||||
|
||||
std::optional<int> dimensions = std::nullopt;
|
||||
if (src_socket_type == SOCK_VECTOR && added_socket_type == SOCK_VECTOR) {
|
||||
dimensions = src_socket->default_value_typed<bNodeSocketValueVector>()->dimensions;
|
||||
}
|
||||
|
||||
if constexpr (Accessor::has_custom_initial_name) {
|
||||
name = Accessor::custom_initial_name(storage_node, name);
|
||||
}
|
||||
|
||||
item = add_item_with_socket_type_and_name<Accessor>(
|
||||
ntree, storage_node, *added_socket_type, name.c_str(), dimensions);
|
||||
}
|
||||
else if constexpr (Accessor::has_name && !Accessor::has_type) {
|
||||
item = add_item_with_name<Accessor>(storage_node, src_socket->name);
|
||||
}
|
||||
else {
|
||||
item = add_item<Accessor>(storage_node);
|
||||
}
|
||||
if (item == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (r_new_item) {
|
||||
*r_new_item = item;
|
||||
}
|
||||
|
||||
update_node_declaration_and_sockets(ntree, extend_node);
|
||||
if (extend_socket.is_input()) {
|
||||
const std::string item_identifier = get_socket_identifier<Accessor>(*item, SOCK_IN);
|
||||
bNodeSocket *new_socket = bke::node_find_socket(
|
||||
extend_node, SOCK_IN, UString(item_identifier));
|
||||
link.tosock = new_socket;
|
||||
}
|
||||
else {
|
||||
const std::string item_identifier = get_socket_identifier<Accessor>(*item, SOCK_OUT);
|
||||
bNodeSocket *new_socket = bke::node_find_socket(
|
||||
extend_node, SOCK_OUT, UString(item_identifier.c_str()));
|
||||
link.fromsock = new_socket;
|
||||
}
|
||||
BKE_ntree_update_tag_node_property(&ntree, &storage_node);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the item array to be extended from any extend-socket in the node.
|
||||
* \return False if the link should be removed.
|
||||
*/
|
||||
template<typename Accessor>
|
||||
[[nodiscard]] inline bool try_add_item_via_any_extend_socket(
|
||||
bNodeTree &ntree,
|
||||
bNode &extend_node,
|
||||
bNode &storage_node,
|
||||
bNodeLink &link,
|
||||
const std::optional<StringRef> socket_identifier = std::nullopt,
|
||||
typename Accessor::ItemT **r_new_item = nullptr)
|
||||
{
|
||||
bNodeSocket *possible_extend_socket = nullptr;
|
||||
if (link.fromnode == &extend_node) {
|
||||
possible_extend_socket = link.fromsock;
|
||||
}
|
||||
if (link.tonode == &extend_node) {
|
||||
possible_extend_socket = link.tosock;
|
||||
}
|
||||
if (possible_extend_socket == nullptr) {
|
||||
return true;
|
||||
}
|
||||
if (!STREQ(possible_extend_socket->idname, "NodeSocketVirtual")) {
|
||||
return true;
|
||||
}
|
||||
if (socket_identifier.has_value()) {
|
||||
if (possible_extend_socket->identifier != socket_identifier) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return try_add_item_via_extend_socket<Accessor>(
|
||||
ntree, extend_node, *possible_extend_socket, storage_node, link, r_new_item);
|
||||
}
|
||||
|
||||
} // namespace blender::nodes::socket_items
|
||||
34
blender-5.2.0/source/blender/nodes/NOD_socket_items_blend.hh
Normal file
34
blender-5.2.0/source/blender/nodes/NOD_socket_items_blend.hh
Normal file
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLO_read_write.hh"
|
||||
|
||||
#include "NOD_socket_items.hh"
|
||||
|
||||
namespace blender::nodes::socket_items {
|
||||
|
||||
template<typename Accessor> inline void blend_write(BlendWriter *writer, const bNode &node)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
const SocketItemsRef<ItemT> items = Accessor::get_items_from_node(const_cast<bNode &>(node));
|
||||
writer->write_struct_array_by_id(
|
||||
dna::sdna_struct_id_get<ItemT>(), *items.items_num, *items.items);
|
||||
for (const ItemT &item : Span(*items.items, *items.items_num)) {
|
||||
Accessor::blend_write_item(writer, item);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Accessor> inline void blend_read_data(BlendDataReader *reader, bNode &node)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
const SocketItemsRef<ItemT> items = Accessor::get_items_from_node(node);
|
||||
BLO_read_array_and_validate_size(reader, items.items, items.items_num);
|
||||
for (ItemT &item : MutableSpan(*items.items, *items.items_num)) {
|
||||
Accessor::blend_read_data_item(reader, item);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::nodes::socket_items
|
||||
452
blender-5.2.0/source/blender/nodes/NOD_socket_items_ops.hh
Normal file
452
blender-5.2.0/source/blender/nodes/NOD_socket_items_ops.hh
Normal file
@@ -0,0 +1,452 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
#include "NOD_socket_items.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_library.hh"
|
||||
#include "BKE_main_invariants.hh"
|
||||
#include "BKE_node_tree_update.hh"
|
||||
#include "BKE_node_tree_zones.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_define.hh"
|
||||
#include "RNA_enum_types.hh"
|
||||
#include "RNA_prototypes.hh"
|
||||
|
||||
#include "ED_node.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "DNA_space_types.h"
|
||||
|
||||
namespace blender::nodes::socket_items::ops {
|
||||
|
||||
inline PointerRNA get_active_node_to_operate_on(bContext *C,
|
||||
wmOperator *op,
|
||||
const StringRef node_idname)
|
||||
{
|
||||
SpaceNode *snode = CTX_wm_space_node(C);
|
||||
if (!snode) {
|
||||
return PointerRNA_NULL;
|
||||
}
|
||||
if (!snode->edittree) {
|
||||
return PointerRNA_NULL;
|
||||
}
|
||||
if (!ID_IS_EDITABLE(snode->edittree)) {
|
||||
return PointerRNA_NULL;
|
||||
}
|
||||
|
||||
bNode *node = nullptr;
|
||||
if (RNA_struct_property_is_set(op->ptr, "node_identifier")) {
|
||||
const int id = RNA_int_get(op->ptr, "node_identifier");
|
||||
node = snode->edittree->node_by_id(id);
|
||||
}
|
||||
else {
|
||||
node = bke::node_get_active(*snode->edittree);
|
||||
}
|
||||
if (!node) {
|
||||
return PointerRNA_NULL;
|
||||
}
|
||||
|
||||
if (bke::zone_type_by_node_type(node->type_legacy) != nullptr) {
|
||||
const bke::bNodeTreeZones *zones = snode->edittree->zones();
|
||||
if (!zones) {
|
||||
return PointerRNA_NULL;
|
||||
}
|
||||
if (const bke::bNodeTreeZone *zone = zones->get_zone_by_node(node->identifier)) {
|
||||
if (zone->input_node() == node) {
|
||||
/* Assume the data is generally stored on the output and not the input node. */
|
||||
node = const_cast<bNode *>(zone->output_node());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node->idname != node_idname) {
|
||||
return PointerRNA_NULL;
|
||||
}
|
||||
return RNA_pointer_create_discrete(&snode->edittree->id, RNA_Node, node);
|
||||
}
|
||||
|
||||
inline void update_after_node_change(bContext *C, const PointerRNA node_ptr)
|
||||
{
|
||||
bNode *node = static_cast<bNode *>(node_ptr.data);
|
||||
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(node_ptr.owner_id);
|
||||
|
||||
BKE_ntree_update_tag_node_property(ntree, node);
|
||||
BKE_main_ensure_invariants(*CTX_data_main(C), ntree->id);
|
||||
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
|
||||
}
|
||||
|
||||
template<typename Accessor> inline bool editable_node_active_poll(bContext *C)
|
||||
{
|
||||
SpaceNode *snode = CTX_wm_space_node(C);
|
||||
if (!snode) {
|
||||
return false;
|
||||
}
|
||||
if (!snode->edittree) {
|
||||
return false;
|
||||
}
|
||||
if (!ID_IS_EDITABLE(snode->edittree)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void add_node_identifier_property(wmOperatorType *ot)
|
||||
{
|
||||
RNA_def_int(ot->srna,
|
||||
"node_identifier",
|
||||
0,
|
||||
0,
|
||||
INT32_MAX,
|
||||
"Node Identifier",
|
||||
"Optional identifier of the node to operate on",
|
||||
0,
|
||||
INT32_MAX);
|
||||
}
|
||||
|
||||
template<typename Accessor>
|
||||
inline void remove_active_item(wmOperatorType *ot,
|
||||
const char *name,
|
||||
const char *idname,
|
||||
const char *description)
|
||||
{
|
||||
ot->name = name;
|
||||
ot->idname = idname;
|
||||
ot->description = description;
|
||||
ot->poll = editable_node_active_poll<Accessor>;
|
||||
ot->flag = OPTYPE_UNDO;
|
||||
|
||||
ot->exec = [](bContext *C, wmOperator *op) -> wmOperatorStatus {
|
||||
PointerRNA node_ptr = get_active_node_to_operate_on(C, op, Accessor::node_idname);
|
||||
if (!node_ptr.data) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
bNode &node = *static_cast<bNode *>(node_ptr.data);
|
||||
SocketItemsRef ref = Accessor::get_items_from_node(node);
|
||||
if (*ref.items_num > 0 && ref.active_index) {
|
||||
dna::array::remove_index(
|
||||
ref.items, ref.items_num, ref.active_index, *ref.active_index, Accessor::destruct_item);
|
||||
update_after_node_change(C, node_ptr);
|
||||
}
|
||||
return OPERATOR_FINISHED;
|
||||
};
|
||||
|
||||
add_node_identifier_property(ot);
|
||||
}
|
||||
|
||||
template<typename Accessor>
|
||||
inline void remove_item_by_index(wmOperatorType *ot,
|
||||
const char *name,
|
||||
const char *idname,
|
||||
const char *description)
|
||||
{
|
||||
ot->name = name;
|
||||
ot->idname = idname;
|
||||
ot->description = description;
|
||||
ot->poll = editable_node_active_poll<Accessor>;
|
||||
ot->flag = OPTYPE_UNDO;
|
||||
|
||||
ot->exec = [](bContext *C, wmOperator *op) -> wmOperatorStatus {
|
||||
PointerRNA node_ptr = get_active_node_to_operate_on(C, op, Accessor::node_idname);
|
||||
if (!node_ptr.data) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
bNode &node = *static_cast<bNode *>(node_ptr.data);
|
||||
const int index_to_remove = RNA_int_get(op->ptr, "index");
|
||||
SocketItemsRef ref = Accessor::get_items_from_node(node);
|
||||
dna::array::remove_index(
|
||||
ref.items, ref.items_num, ref.active_index, index_to_remove, Accessor::destruct_item);
|
||||
|
||||
update_after_node_change(C, node_ptr);
|
||||
return OPERATOR_FINISHED;
|
||||
};
|
||||
|
||||
RNA_def_int(ot->srna, "index", 0, 0, INT32_MAX, "Index", "Index to remove", 0, INT32_MAX);
|
||||
}
|
||||
|
||||
template<typename Accessor>
|
||||
inline void add_item(wmOperatorType *ot,
|
||||
const char *name,
|
||||
const char *idname,
|
||||
const char *description)
|
||||
{
|
||||
ot->name = name;
|
||||
ot->idname = idname;
|
||||
ot->description = description;
|
||||
ot->poll = editable_node_active_poll<Accessor>;
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
static const char *socket_type_id = "socket_type";
|
||||
static const char *item_name_id = "item_name";
|
||||
|
||||
ot->invoke = [](bContext *C, wmOperator *op, const wmEvent *event) {
|
||||
const bool show_dialog = RNA_boolean_get(op->ptr, "show_dialog");
|
||||
const bool needs_dialog = Accessor::has_type || Accessor::has_name;
|
||||
if (show_dialog && needs_dialog) {
|
||||
return WM_operator_props_popup_confirm_ex(C, op, event, IFACE_("Add Item"));
|
||||
}
|
||||
return op->type->exec(C, op);
|
||||
};
|
||||
|
||||
ot->ui = [](bContext * /*C*/, wmOperator *op) {
|
||||
ui::Layout &layout = *op->layout;
|
||||
if constexpr (Accessor::has_name) {
|
||||
PropertyRNA *prop = RNA_struct_find_property(op->ptr, item_name_id);
|
||||
ui::Layout &row = layout.row(true);
|
||||
row.activate_init_set(true);
|
||||
row.prop(op->ptr, prop, RNA_NO_INDEX, 0, UI_ITEM_NONE, "", ICON_NONE, IFACE_("Name"));
|
||||
}
|
||||
if constexpr (Accessor::has_type) {
|
||||
layout.prop(op->ptr, socket_type_id, UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
};
|
||||
|
||||
ot->exec = [](bContext *C, wmOperator *op) -> wmOperatorStatus {
|
||||
PointerRNA node_ptr = get_active_node_to_operate_on(C, op, Accessor::node_idname);
|
||||
if (node_ptr.data == nullptr) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(node_ptr.owner_id);
|
||||
bNode &node = *static_cast<bNode *>(node_ptr.data);
|
||||
SocketItemsRef ref = Accessor::get_items_from_node(node);
|
||||
const typename Accessor::ItemT *active_item = nullptr;
|
||||
int dst_index = *ref.items_num;
|
||||
if (ref.active_index) {
|
||||
const int old_active_index = *ref.active_index;
|
||||
if (old_active_index >= 0 && old_active_index < *ref.items_num) {
|
||||
active_item = &(*ref.items)[old_active_index];
|
||||
dst_index = active_item ? old_active_index + 1 : *ref.items_num;
|
||||
}
|
||||
}
|
||||
|
||||
/* Determine name if necessary. */
|
||||
const bool init_from_active = RNA_boolean_get(op->ptr, "init_from_active");
|
||||
std::optional<std::string> name;
|
||||
if constexpr (Accessor::has_name) {
|
||||
if (!init_from_active) {
|
||||
name = RNA_string_get(op->ptr, item_name_id);
|
||||
}
|
||||
else {
|
||||
name = active_item ? active_item->name : "";
|
||||
if constexpr (Accessor::has_custom_initial_name) {
|
||||
name = Accessor::custom_initial_name(node, *name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Determine socket type if necessary. */
|
||||
std::optional<eNodeSocketDatatype> socket_type;
|
||||
if constexpr (Accessor::has_type) {
|
||||
if (!init_from_active) {
|
||||
socket_type = eNodeSocketDatatype(RNA_enum_get(op->ptr, socket_type_id));
|
||||
}
|
||||
else if (active_item) {
|
||||
socket_type = Accessor::get_socket_type(*active_item);
|
||||
}
|
||||
else {
|
||||
socket_type = Accessor::supports_socket_type(SOCK_GEOMETRY, ntree->type) ? SOCK_GEOMETRY :
|
||||
SOCK_FLOAT;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (Accessor::has_type && Accessor::has_name) {
|
||||
socket_items::add_item_with_socket_type_and_name<Accessor>(
|
||||
*ntree, node, *socket_type, name->c_str());
|
||||
}
|
||||
else if constexpr (!Accessor::has_type && Accessor::has_name) {
|
||||
socket_items::add_item_with_name<Accessor>(node, name->c_str());
|
||||
}
|
||||
else if constexpr (!Accessor::has_type && !Accessor::has_name) {
|
||||
socket_items::add_item<Accessor>(node);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
dna::array::move_index(*ref.items, *ref.items_num, *ref.items_num - 1, dst_index);
|
||||
if (ref.active_index) {
|
||||
*ref.active_index = dst_index;
|
||||
}
|
||||
|
||||
update_after_node_change(C, node_ptr);
|
||||
return OPERATOR_FINISHED;
|
||||
};
|
||||
|
||||
add_node_identifier_property(ot);
|
||||
|
||||
PropertyRNA *prop;
|
||||
prop = RNA_def_boolean(ot->srna,
|
||||
"show_dialog",
|
||||
false,
|
||||
"Show Dialog",
|
||||
"Show a dialog to edit the initial properties");
|
||||
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
||||
|
||||
prop = RNA_def_boolean(
|
||||
ot->srna,
|
||||
"init_from_active",
|
||||
true,
|
||||
"Init from Active",
|
||||
"Instead of using the provided name or type, copy the state of the active item");
|
||||
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
||||
|
||||
if constexpr (Accessor::has_type) {
|
||||
prop = RNA_def_enum(ot->srna,
|
||||
socket_type_id,
|
||||
rna_enum_node_socket_data_type_items,
|
||||
SOCK_FLOAT,
|
||||
"Socket Type",
|
||||
"Type of the new socket item");
|
||||
RNA_def_property_enum_funcs_runtime(
|
||||
prop,
|
||||
nullptr,
|
||||
nullptr,
|
||||
[](bContext *C, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
|
||||
if (!C) {
|
||||
*r_free = false;
|
||||
return rna_enum_node_socket_data_type_items;
|
||||
}
|
||||
*r_free = true;
|
||||
SpaceNode *snode = CTX_wm_space_node(C);
|
||||
return enum_items_filter(rna_enum_node_socket_data_type_items,
|
||||
[&](const EnumPropertyItem &item) -> bool {
|
||||
return Accessor::supports_socket_type(
|
||||
eNodeSocketDatatype(item.value), snode->edittree->type);
|
||||
});
|
||||
},
|
||||
nullptr,
|
||||
nullptr);
|
||||
}
|
||||
if constexpr (Accessor::has_name) {
|
||||
RNA_def_string(ot->srna, item_name_id, nullptr, 0, "Item Name", "Name of the new socket item");
|
||||
}
|
||||
}
|
||||
|
||||
enum class MoveDirection {
|
||||
Up = 0,
|
||||
Down = 1,
|
||||
};
|
||||
|
||||
template<typename Accessor>
|
||||
inline void move_active_item(wmOperatorType *ot,
|
||||
const char *name,
|
||||
const char *idname,
|
||||
const char *description)
|
||||
{
|
||||
ot->name = name;
|
||||
ot->idname = idname;
|
||||
ot->description = description;
|
||||
ot->poll = editable_node_active_poll<Accessor>;
|
||||
ot->flag = OPTYPE_UNDO;
|
||||
|
||||
ot->exec = [](bContext *C, wmOperator *op) -> wmOperatorStatus {
|
||||
PointerRNA node_ptr = get_active_node_to_operate_on(C, op, Accessor::node_idname);
|
||||
if (!node_ptr.data) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
bNode &node = *static_cast<bNode *>(node_ptr.data);
|
||||
const MoveDirection direction = MoveDirection(RNA_enum_get(op->ptr, "direction"));
|
||||
|
||||
SocketItemsRef ref = Accessor::get_items_from_node(node);
|
||||
const int old_active_index = *ref.active_index;
|
||||
if (direction == MoveDirection::Up && old_active_index > 0) {
|
||||
dna::array::move_index(*ref.items, *ref.items_num, old_active_index, old_active_index - 1);
|
||||
*ref.active_index -= 1;
|
||||
}
|
||||
else if (direction == MoveDirection::Down && old_active_index < *ref.items_num - 1) {
|
||||
dna::array::move_index(*ref.items, *ref.items_num, old_active_index, old_active_index + 1);
|
||||
*ref.active_index += 1;
|
||||
}
|
||||
|
||||
update_after_node_change(C, node_ptr);
|
||||
return OPERATOR_FINISHED;
|
||||
};
|
||||
|
||||
static const EnumPropertyItem direction_items[] = {
|
||||
{int(MoveDirection::Up), "UP", 0, "Up", ""},
|
||||
{int(MoveDirection::Down), "DOWN", 0, "Down", ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
RNA_def_enum(ot->srna, "direction", direction_items, 0, "Direction", "Move direction");
|
||||
add_node_identifier_property(ot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates operator to add a node item.
|
||||
* The idname is passed in explicitly, so that it is more searchable compared to when it would be
|
||||
* computed automatically.
|
||||
*/
|
||||
template<typename Accessor> inline void make_add_item_operator()
|
||||
{
|
||||
WM_operatortype_append([](wmOperatorType *ot) {
|
||||
socket_items::ops::add_item<Accessor>(ot,
|
||||
"Add Item",
|
||||
Accessor::operator_idnames::add_item.c_str(),
|
||||
"Add item below active item");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates operator to remove the active item.
|
||||
* The idname is passed in explicitly, so that it is more searchable compared to when it would be
|
||||
* computed automatically.
|
||||
*/
|
||||
template<typename Accessor> inline void make_remove_active_item_operator()
|
||||
{
|
||||
WM_operatortype_append([](wmOperatorType *ot) {
|
||||
socket_items::ops::remove_active_item<Accessor>(
|
||||
ot, "Remove Item", Accessor::operator_idnames::remove_item.c_str(), "Remove active item");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates operator to remove an item by index.
|
||||
* The idname is passed in explicitly, so that it is more searchable compared to when it would be
|
||||
* computed automatically.
|
||||
*/
|
||||
template<typename Accessor> inline void make_remove_item_by_index_operator()
|
||||
{
|
||||
WM_operatortype_append([](wmOperatorType *ot) {
|
||||
socket_items::ops::remove_item_by_index<Accessor>(
|
||||
ot, "Remove Item", Accessor::operator_idnames::remove_item.c_str(), "Remove active item");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates operator to move a node item.
|
||||
* The idname is passed in explicitly, so that it is more searchable compared to when it would be
|
||||
* computed automatically.
|
||||
*/
|
||||
template<typename Accessor> inline void make_move_item_operator()
|
||||
{
|
||||
WM_operatortype_append([](wmOperatorType *ot) {
|
||||
socket_items::ops::move_active_item<Accessor>(
|
||||
ot, "Move Item", Accessor::operator_idnames::move_item.c_str(), "Move active item");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates simple operators for adding, removing and moving items.
|
||||
* The idnames are passed in explicitly, so that they are more searchable compared to when they
|
||||
* would be computed automatically.
|
||||
*/
|
||||
template<typename Accessor> inline void make_common_operators()
|
||||
{
|
||||
make_add_item_operator<Accessor>();
|
||||
make_remove_active_item_operator<Accessor>();
|
||||
make_move_item_operator<Accessor>();
|
||||
}
|
||||
|
||||
} // namespace blender::nodes::socket_items::ops
|
||||
136
blender-5.2.0/source/blender/nodes/NOD_socket_items_ui.hh
Normal file
136
blender-5.2.0/source/blender/nodes/NOD_socket_items_ui.hh
Normal file
@@ -0,0 +1,136 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_prototypes.hh"
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "BKE_screen.hh"
|
||||
|
||||
#include "NOD_node_declaration.hh"
|
||||
#include "NOD_socket_items.hh"
|
||||
|
||||
namespace blender::nodes::socket_items::ui {
|
||||
|
||||
template<typename Accessor>
|
||||
static void draw_item_in_list(uiList * /*ui_list*/,
|
||||
const bContext *C,
|
||||
blender::ui::Layout &layout,
|
||||
PointerRNA * /*idataptr*/,
|
||||
PointerRNA *itemptr,
|
||||
int /*icon*/,
|
||||
PointerRNA * /*active_dataptr*/,
|
||||
const char * /*active_propname*/,
|
||||
int /*index*/,
|
||||
int /*flt_flag*/)
|
||||
{
|
||||
blender::ui::Layout &row = layout.row(true);
|
||||
if constexpr (Accessor::has_type) {
|
||||
float4 color;
|
||||
RNA_float_get_array(itemptr, "color", color);
|
||||
template_node_socket(&row, const_cast<bContext *>(C), color);
|
||||
}
|
||||
row.emboss_set(blender::ui::EmbossType::None);
|
||||
row.prop(itemptr, "name", blender::UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a ui-list that contains the items. The list also has operators to add, remove and reorder
|
||||
* items.
|
||||
*/
|
||||
template<typename Accessor>
|
||||
static void draw_items_list_with_operators(const bContext *C,
|
||||
blender::ui::Layout *layout,
|
||||
const bNodeTree &tree,
|
||||
const bNode &node)
|
||||
{
|
||||
BLI_assert(Accessor::node_idname == node.idname);
|
||||
PointerRNA node_ptr = RNA_pointer_create_discrete(
|
||||
const_cast<ID *>(&tree.id), RNA_Node, const_cast<bNode *>(&node));
|
||||
|
||||
static const uiListType *items_list = []() {
|
||||
uiListType *list = MEM_new_zeroed<uiListType>(Accessor::ui_idnames::list.c_str());
|
||||
STRNCPY_UTF8(list->idname, Accessor::ui_idnames::list.c_str());
|
||||
list->draw_item = draw_item_in_list<Accessor>;
|
||||
WM_uilisttype_add(list);
|
||||
return list;
|
||||
}();
|
||||
|
||||
blender::ui::Layout *row = &layout->row(false);
|
||||
template_uilist(row,
|
||||
C,
|
||||
items_list->idname,
|
||||
"",
|
||||
&node_ptr,
|
||||
Accessor::rna_names::items,
|
||||
&node_ptr,
|
||||
Accessor::rna_names::active_index.c_str(),
|
||||
nullptr,
|
||||
3,
|
||||
5,
|
||||
UILST_LAYOUT_DEFAULT,
|
||||
blender::ui::TEMPLATE_LIST_FLAG_NONE);
|
||||
|
||||
blender::ui::Layout *ops_col = &row->column(false);
|
||||
{
|
||||
blender::ui::Layout *add_remove_col = &ops_col->column(true);
|
||||
add_remove_col->op(Accessor::operator_idnames::add_item, "", ICON_ADD);
|
||||
add_remove_col->op(Accessor::operator_idnames::remove_item, "", ICON_REMOVE);
|
||||
}
|
||||
{
|
||||
blender::ui::Layout *up_down_col = &ops_col->column(true);
|
||||
PointerRNA op_ptr = up_down_col->op(Accessor::operator_idnames::move_item, "", ICON_TRIA_UP);
|
||||
RNA_enum_set(&op_ptr, "direction", 0);
|
||||
op_ptr = up_down_col->op(Accessor::operator_idnames::move_item, "", ICON_TRIA_DOWN);
|
||||
RNA_enum_set(&op_ptr, "direction", 1);
|
||||
}
|
||||
}
|
||||
|
||||
/** Draw properties of the active item if there is any. */
|
||||
template<typename Accessor>
|
||||
static void draw_active_item_props(const bNodeTree &tree,
|
||||
const bNode &node,
|
||||
const FunctionRef<void(PointerRNA *item_ptr)> draw_item)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
BLI_assert(Accessor::node_idname == node.idname);
|
||||
|
||||
SocketItemsRef<ItemT> ref = Accessor::get_items_from_node(const_cast<bNode &>(node));
|
||||
if (*ref.active_index < 0) {
|
||||
return;
|
||||
}
|
||||
if (*ref.active_index >= *ref.items_num) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemT &item = (*ref.items)[*ref.active_index];
|
||||
PointerRNA item_ptr = RNA_pointer_create_discrete(
|
||||
const_cast<ID *>(&tree.id), *Accessor::item_srna, &item);
|
||||
draw_item(&item_ptr);
|
||||
}
|
||||
|
||||
template<typename Accessor> static auto draw_extend_socket_fn()
|
||||
{
|
||||
return [](CustomSocketDrawParams ¶ms) {
|
||||
::blender::ui::Layout &layout = params.layout;
|
||||
layout.emboss_set(::blender::ui::EmbossType::None);
|
||||
PointerRNA op_ptr = layout.op(Accessor::operator_idnames::add_item, "", ICON_ADD);
|
||||
RNA_int_set(&op_ptr, "node_identifier", params.node.identifier);
|
||||
RNA_boolean_set(&op_ptr, "show_dialog", true);
|
||||
RNA_boolean_set(&op_ptr, "init_from_active", false);
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace blender::nodes::socket_items::ui
|
||||
171
blender-5.2.0/source/blender/nodes/NOD_socket_search_link.hh
Normal file
171
blender-5.2.0/source/blender/nodes/NOD_socket_search_link.hh
Normal file
@@ -0,0 +1,171 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "DNA_node_types.h" /* Necessary for eNodeSocketInOut. */
|
||||
|
||||
#include "NOD_node_declaration.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bContext;
|
||||
struct SpaceNode;
|
||||
|
||||
namespace nodes {
|
||||
|
||||
/**
|
||||
* Parameters for the operation of adding a node after the link drag search menu closes.
|
||||
*/
|
||||
class LinkSearchOpParams {
|
||||
private:
|
||||
/**
|
||||
* Keep track of the nodes added by the callback, so they can be selected or moved afterwards.
|
||||
*/
|
||||
Vector<bNode *> &added_nodes_;
|
||||
|
||||
public:
|
||||
const bContext &C;
|
||||
bNodeTree &node_tree;
|
||||
/**
|
||||
* The node that contains the #socket.
|
||||
*/
|
||||
bNode &node;
|
||||
/**
|
||||
* The existing socket to connect any added nodes to. Might be an input or output socket.
|
||||
*/
|
||||
bNodeSocket &socket;
|
||||
|
||||
LinkSearchOpParams(const bContext &C,
|
||||
bNodeTree &node_tree,
|
||||
bNode &node,
|
||||
bNodeSocket &socket,
|
||||
Vector<bNode *> &added_nodes)
|
||||
: added_nodes_(added_nodes), C(C), node_tree(node_tree), node(node), socket(socket)
|
||||
{
|
||||
}
|
||||
|
||||
bNode &add_node(UString idname);
|
||||
bNode &add_node(const bke::bNodeType &node_type);
|
||||
/**
|
||||
* Find a socket with the given name (correctly checks for inputs and outputs)
|
||||
* and connect it to the socket the link drag started from (#socket).
|
||||
*/
|
||||
void connect_available_socket(bNode &new_node, UString socket_name);
|
||||
void connect_available_socket_by_identifier(bNode &new_node, UString socket_identifier);
|
||||
void connect_socket(bNode &new_node, bNodeSocket &new_socket);
|
||||
/**
|
||||
* Like #connect_available_socket, but also calls the node's update function.
|
||||
*/
|
||||
void update_and_connect_available_socket(bNode &new_node, UString socket_name);
|
||||
void update_and_connect_available_socket_by_identifier(bNode &new_node,
|
||||
UString socket_identifier);
|
||||
};
|
||||
|
||||
struct SocketLinkOperation {
|
||||
using LinkSocketFn = std::function<void(LinkSearchOpParams &link_params)>;
|
||||
|
||||
std::string name;
|
||||
LinkSocketFn fn;
|
||||
int weight = 0;
|
||||
};
|
||||
|
||||
class GatherLinkSearchOpParams {
|
||||
/** The current node type. */
|
||||
const bke::bNodeType &node_type_;
|
||||
|
||||
const SpaceNode &snode_;
|
||||
const bNodeTree &node_tree_;
|
||||
|
||||
const bNodeSocket &other_socket_;
|
||||
|
||||
/* The operations currently being built. Owned by the caller. */
|
||||
Vector<SocketLinkOperation> &items_;
|
||||
|
||||
public:
|
||||
GatherLinkSearchOpParams(const bke::bNodeType &node_type,
|
||||
const SpaceNode &snode,
|
||||
const bNodeTree &node_tree,
|
||||
const bNodeSocket &other_socket,
|
||||
Vector<SocketLinkOperation> &items)
|
||||
: node_type_(node_type),
|
||||
snode_(snode),
|
||||
node_tree_(node_tree),
|
||||
other_socket_(other_socket),
|
||||
items_(items)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* The node on the other side of the dragged link.
|
||||
*/
|
||||
const bNodeSocket &other_socket() const;
|
||||
|
||||
/**
|
||||
* The currently active node editor.
|
||||
*/
|
||||
const SpaceNode &space_node() const;
|
||||
|
||||
/**
|
||||
* The node tree the user is editing when the search menu is created.
|
||||
*/
|
||||
const bNodeTree &node_tree() const;
|
||||
|
||||
/**
|
||||
* The type of the node in the current callback.
|
||||
*/
|
||||
const bke::bNodeType &node_type() const;
|
||||
|
||||
/**
|
||||
* Whether to list the input or output sockets of the node.
|
||||
*/
|
||||
eNodeSocketInOut in_out() const;
|
||||
|
||||
/**
|
||||
* \param weight: Used to customize the order when multiple search items match.
|
||||
*
|
||||
* \warning When creating lambdas for the #fn argument, be careful not to capture this class
|
||||
* itself, since it is temporary. That is why we tend to use the same variable name for this
|
||||
* class (`params`) that we do for the argument to `LinkSocketFn`.
|
||||
*/
|
||||
void add_item(std::string socket_name, SocketLinkOperation::LinkSocketFn fn, int weight = 0);
|
||||
|
||||
/**
|
||||
* Same as #add_item, but the provide the full name of the menu entry instead of just the socket
|
||||
* name.
|
||||
*/
|
||||
void add_item_full_name(std::string name, SocketLinkOperation::LinkSocketFn fn, int weight = 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* This callback can be used for a node type when a few things are true about its inputs.
|
||||
* To avoid creating more boilerplate, it is the default callback for node types.
|
||||
* - Either all declared sockets are visible in the default state of the node, *OR* the node's
|
||||
* type's declaration has been extended with #make_available functions for those sockets.
|
||||
*
|
||||
* If a node type does not meet these criteria, the function will do nothing in a release build.
|
||||
* In a debug build, an assert will most likely be hit.
|
||||
*
|
||||
* \note For nodes with the deprecated #bke::bNodeSocketTemplate instead of a declaration,
|
||||
* these criteria do not apply and the function just tries its best without asserting.
|
||||
*/
|
||||
void search_link_ops_for_basic_node(GatherLinkSearchOpParams ¶ms);
|
||||
|
||||
/**
|
||||
* Same as search_link_ops_for_basic_node with additional filtering to exclude sockets.
|
||||
*/
|
||||
void search_filtered_link_ops_for_basic_node(GatherLinkSearchOpParams ¶ms,
|
||||
const Set<UString> &skip_socket_identifiers);
|
||||
|
||||
void search_link_ops_for_declarations(GatherLinkSearchOpParams ¶ms,
|
||||
Span<SocketDeclaration *> declarations);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
135
blender-5.2.0/source/blender/nodes/NOD_socket_usage_inference.hh
Normal file
135
blender-5.2.0/source/blender/nodes/NOD_socket_usage_inference.hh
Normal file
@@ -0,0 +1,135 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_generic_pointer.hh"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_execute.hh"
|
||||
#include "NOD_socket_usage_inference_fwd.hh"
|
||||
#include "NOD_socket_value_inference.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bNodeTree;
|
||||
struct bNodeSocket;
|
||||
struct IDProperty;
|
||||
|
||||
namespace nodes::socket_usage_inference {
|
||||
|
||||
class SocketUsageInferencerImpl;
|
||||
|
||||
/**
|
||||
* Can detect which sockets are used or disabled.
|
||||
*/
|
||||
class SocketUsageInferencer {
|
||||
private:
|
||||
SocketUsageInferencerImpl &impl_;
|
||||
|
||||
friend class SocketUsageParams;
|
||||
|
||||
public:
|
||||
SocketUsageInferencer(const bNodeTree &tree,
|
||||
ResourceScope &scope,
|
||||
SocketValueInferencer &value_inferencer,
|
||||
bke::ComputeContextCache &compute_context_cache,
|
||||
bool ignore_top_level_node_muting = false);
|
||||
|
||||
bool is_socket_used(const SocketInContext &socket);
|
||||
bool is_group_input_used(int input_i);
|
||||
|
||||
bool is_disabled_output(const SocketInContext &socket);
|
||||
bool is_disabled_group_output(int output_i);
|
||||
|
||||
/** This can be used when detecting the usage of all input sockets in a node tree, instead of
|
||||
* just the inputs of the group as a whole.
|
||||
*/
|
||||
void mark_top_level_node_outputs_as_used();
|
||||
};
|
||||
|
||||
class SocketUsageParams {
|
||||
private:
|
||||
SocketUsageInferencer &inferencer_;
|
||||
const ComputeContext *compute_context_ = nullptr;
|
||||
|
||||
public:
|
||||
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
|
||||
* unknown, in which case null is returned.
|
||||
*/
|
||||
InferenceValue get_input(UString identifier) const;
|
||||
|
||||
/**
|
||||
* Returns true if any output is known to be used or false if no output is used. std::nullopt is
|
||||
* returned if it's not known yet if any output is used. In this case, the caller should return
|
||||
* early, it will be checked again once new information about output usages becomes available.
|
||||
*/
|
||||
std::optional<bool> any_output_is_used() const;
|
||||
|
||||
/**
|
||||
* Utility for the case when the socket depends on a specific menu input to have a certain value.
|
||||
*/
|
||||
bool menu_input_may_be(UString identifier, int enum_value) const;
|
||||
bool bool_input_may_be(UString identifier, bool bool_value) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine which sockets in the tree are currently used and thus which should be grayed out or
|
||||
* made invisible.
|
||||
*/
|
||||
Array<SocketUsage> infer_all_sockets_usage(const bNodeTree &tree);
|
||||
|
||||
/**
|
||||
* Get usage of the inputs and outputs of the node group given the set of input values. The result
|
||||
* can be used to e.g. gray out or hide individual inputs that are unused.
|
||||
*
|
||||
* \param group: The node group that is called.
|
||||
* \param group_input_values: An optional input value for each node group input. The type is
|
||||
* expected to be `bNodeSocketType::base_cpp_type`. If the input value for a socket is not known
|
||||
* or can't be represented as base type, null has to be passed instead.
|
||||
* \param r_input_usages: The destination array where the inferred input usages are written.
|
||||
* \param r_output_usages: The destination array where the inferred output usages are written.
|
||||
*/
|
||||
void infer_group_interface_usage(
|
||||
const bNodeTree &group,
|
||||
Span<InferenceValue> group_input_values,
|
||||
MutableSpan<SocketUsage> r_input_usages,
|
||||
std::optional<MutableSpan<SocketUsage>> r_output_usages = std::nullopt);
|
||||
|
||||
/**
|
||||
* Same as above, but automatically retrieves the input values from the given sockets..
|
||||
* This is used for group nodes.
|
||||
*/
|
||||
void infer_group_interface_inputs_usage(const bNodeTree &group,
|
||||
Span<const bNodeSocket *> input_sockets,
|
||||
MutableSpan<SocketUsage> r_input_usages);
|
||||
|
||||
/**
|
||||
* Same as above, but automatically retrieves the input values from the given properties.
|
||||
* This is used with the geometry nodes modifier and node tools.
|
||||
*/
|
||||
void infer_group_interface_inputs_usage(
|
||||
const bNodeTree &group,
|
||||
const PointerRNA &properties_ptr,
|
||||
MutableSpan<SocketUsage> r_input_usages,
|
||||
std::optional<MutableSpan<SocketUsage>> r_output_usages = std::nullopt);
|
||||
|
||||
} // namespace nodes::socket_usage_inference
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,16 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender::nodes::socket_usage_inference {
|
||||
|
||||
class SocketUsageParams;
|
||||
|
||||
struct SocketUsage {
|
||||
bool is_used = true;
|
||||
bool is_visible = true;
|
||||
};
|
||||
|
||||
} // namespace blender::nodes::socket_usage_inference
|
||||
111
blender-5.2.0/source/blender/nodes/NOD_socket_value_inference.hh
Normal file
111
blender-5.2.0/source/blender/nodes/NOD_socket_value_inference.hh
Normal file
@@ -0,0 +1,111 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_generic_pointer.hh"
|
||||
#include "BLI_resource_scope.hh"
|
||||
|
||||
#include "BKE_compute_context_cache_fwd.hh"
|
||||
|
||||
#include "DNA_material_types.h"
|
||||
#include "NOD_node_in_compute_context.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bNodeTree;
|
||||
|
||||
namespace nodes {
|
||||
|
||||
/**
|
||||
* During socket usage inferencing, some socket values are computed. This class represents such a
|
||||
* computed value. Not all possible values can be presented here, only "basic" once (like int, but
|
||||
* not int-field). A value can also be unknown if it can't be determined statically.
|
||||
*/
|
||||
class InferenceValue {
|
||||
private:
|
||||
/**
|
||||
* Non-owning pointer to a value of type #bNodeSocketType.base_cpp_type of the corresponding
|
||||
* socket. If this is null, the value is assumed to be unknown (aka, it can't be determined
|
||||
* statically).
|
||||
*/
|
||||
const void *value_ = nullptr;
|
||||
|
||||
InferenceValue(const void *value) : value_(value) {}
|
||||
|
||||
public:
|
||||
static InferenceValue from_primitive(const void *value)
|
||||
{
|
||||
BLI_assert(value != nullptr);
|
||||
return InferenceValue(value);
|
||||
}
|
||||
|
||||
static InferenceValue Unknown()
|
||||
{
|
||||
return InferenceValue(nullptr);
|
||||
}
|
||||
|
||||
bool is_unknown() const
|
||||
{
|
||||
return value_ == nullptr;
|
||||
}
|
||||
|
||||
bool is_primitive_value() const
|
||||
{
|
||||
return !this->is_unknown();
|
||||
}
|
||||
|
||||
const void *get_primitive_ptr() const
|
||||
{
|
||||
BLI_assert(this->is_primitive_value());
|
||||
return value_;
|
||||
}
|
||||
|
||||
template<typename T> T get_primitive() const
|
||||
{
|
||||
BLI_assert(this->is_primitive_value());
|
||||
return *static_cast<const T *>(this->value_);
|
||||
}
|
||||
|
||||
template<typename T> std::optional<T> get_if_primitive() const
|
||||
{
|
||||
if (!this->is_primitive_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return this->get_primitive<T>();
|
||||
}
|
||||
};
|
||||
|
||||
class SocketValueInferencerImpl;
|
||||
|
||||
class SocketValueInferencer {
|
||||
private:
|
||||
SocketValueInferencerImpl &impl_;
|
||||
|
||||
public:
|
||||
SocketValueInferencer(
|
||||
const bNodeTree &tree,
|
||||
ResourceScope &scope,
|
||||
bke::ComputeContextCache &compute_context_cache,
|
||||
FunctionRef<InferenceValue(int group_input_i)> group_input_value_fn = nullptr,
|
||||
std::optional<Span<bool>> top_level_ignored_inputs = std::nullopt);
|
||||
|
||||
InferenceValue get_socket_value(const SocketInContext &socket);
|
||||
};
|
||||
|
||||
namespace switch_node_inference_utils {
|
||||
|
||||
bool is_socket_selected__switch(const SocketInContext &socket, const InferenceValue &condition);
|
||||
bool is_socket_selected__index_switch(const SocketInContext &socket,
|
||||
const InferenceValue &condition);
|
||||
bool is_socket_selected__menu_switch(const SocketInContext &socket,
|
||||
const InferenceValue &condition);
|
||||
bool is_socket_selected__mix_node(const SocketInContext &socket, const InferenceValue &condition);
|
||||
bool is_socket_selected__shader_mix_node(const SocketInContext &socket,
|
||||
const InferenceValue &condition);
|
||||
|
||||
} // namespace switch_node_inference_utils
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
85
blender-5.2.0/source/blender/nodes/NOD_string_pattern.hh
Normal file
85
blender-5.2.0/source/blender/nodes/NOD_string_pattern.hh
Normal file
@@ -0,0 +1,85 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "RNA_types.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
enum class StringPatternMode {
|
||||
/** Only exact matches are allowed. */
|
||||
Exact = 0,
|
||||
/** A single wildcard (*) is allowed. */
|
||||
Wildcard = 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* A predicate for strings. A pattern can be created and then other strings can be tested against
|
||||
* it.
|
||||
*/
|
||||
class StringPattern {
|
||||
private:
|
||||
struct Exact {
|
||||
StringRef pattern;
|
||||
|
||||
bool match(const StringRef query) const
|
||||
{
|
||||
return pattern == query;
|
||||
}
|
||||
};
|
||||
|
||||
struct Wildcard {
|
||||
StringRef prefix;
|
||||
StringRef suffix;
|
||||
|
||||
bool match(const StringRef query) const
|
||||
{
|
||||
return query.startswith(prefix) && query.endswith(suffix);
|
||||
}
|
||||
};
|
||||
|
||||
using PatternVariant = std::variant<Exact, Wildcard>;
|
||||
StringRef full_pattern_;
|
||||
PatternVariant variant_;
|
||||
|
||||
StringPattern(StringRef full_pattern, PatternVariant variant)
|
||||
: full_pattern_(full_pattern), variant_(std::move(variant))
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* Create a new pattern. If the pattern is invalid, nullopt is returned and the error message is
|
||||
* set.
|
||||
*/
|
||||
static std::optional<StringPattern> from_str(StringPatternMode mode,
|
||||
StringRef pattern,
|
||||
std::string &r_error);
|
||||
|
||||
/** Returns true if the string matches the pattern. */
|
||||
bool match(StringRef query) const;
|
||||
|
||||
/** Sometimes things can be implemented more efficiently when there is an exact pattern. */
|
||||
std::optional<StringRef> exact_pattern() const
|
||||
{
|
||||
if (const Exact *exact = std::get_if<Exact>(&variant_)) {
|
||||
return exact->pattern;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
StringRef full_pattern() const
|
||||
{
|
||||
return full_pattern_;
|
||||
}
|
||||
};
|
||||
|
||||
extern const EnumPropertyItem string_pattern_mode_items[];
|
||||
|
||||
} // namespace blender::nodes
|
||||
59
blender-5.2.0/source/blender/nodes/NOD_sync_sockets.hh
Normal file
59
blender-5.2.0/source/blender/nodes/NOD_sync_sockets.hh
Normal file
@@ -0,0 +1,59 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct SpaceNode;
|
||||
struct bNode;
|
||||
struct ReportList;
|
||||
struct bContext;
|
||||
struct bNodeTree;
|
||||
struct Main;
|
||||
struct bNodeSocket;
|
||||
|
||||
namespace nodes {
|
||||
|
||||
/**
|
||||
* Sync the sockets of that node if possible. For example, a Separate Bundle node will be updated
|
||||
* to match a linked Combine Bundle if one is found.
|
||||
*/
|
||||
void sync_node(bContext &C, bNode &node, ReportList *reports);
|
||||
|
||||
/**
|
||||
* Get a description of what syncing the node would do. This can be used as tooltip.
|
||||
*/
|
||||
std::string sync_node_description_get(const bContext &C, const bNode &node);
|
||||
|
||||
/** Access (cached) information of whether a specific node can be synced currently. */
|
||||
bool node_can_sync_sockets(const bContext &C, const bNodeTree &tree, const bNode &node);
|
||||
void node_can_sync_cache_clear(Main &bmain);
|
||||
|
||||
void sync_sockets_evaluate_closure(SpaceNode &snode,
|
||||
bNode &evaluate_closure_node,
|
||||
ReportList *reports,
|
||||
const bNodeSocket *src_closure_socket = nullptr);
|
||||
void sync_sockets_separate_bundle(SpaceNode &snode,
|
||||
bNode &separate_bundle_node,
|
||||
ReportList *reports,
|
||||
const bNodeSocket *src_bundle_socket = nullptr);
|
||||
void sync_sockets_combine_bundle(SpaceNode &snode,
|
||||
bNode &combine_bundle_node,
|
||||
ReportList *reports,
|
||||
const bNodeSocket *src_bundle_socket = nullptr);
|
||||
void sync_sockets_closure(SpaceNode &snode,
|
||||
bNode &closure_input_node,
|
||||
bNode &closure_output_node,
|
||||
ReportList *reports,
|
||||
const bNodeSocket *src_closure_socket = nullptr);
|
||||
void sync_sockets_closure_to_list(SpaceNode &snode,
|
||||
bNode &closure_to_list_node,
|
||||
ReportList *reports,
|
||||
const bNodeSocket *src_closure_socket = nullptr);
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
30
blender-5.2.0/source/blender/nodes/NOD_texture.h
Normal file
30
blender-5.2.0/source/blender/nodes/NOD_texture.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/* SPDX-FileCopyrightText: 2005 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup nodes
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
extern struct bke::bNodeTreeType *ntreeType_Texture;
|
||||
|
||||
void ntreeTexCheckCyclics(struct bNodeTree *ntree);
|
||||
struct bNodeTreeExec *ntreeTexBeginExecTree(struct bNodeTree *ntree);
|
||||
void ntreeTexEndExecTree(struct bNodeTreeExec *exec);
|
||||
int ntreeTexExecTree(struct bNodeTree *ntree,
|
||||
struct TexResult *target,
|
||||
const float co[3],
|
||||
short thread,
|
||||
const struct Tex *tex,
|
||||
short which_output,
|
||||
int cfra,
|
||||
int preview,
|
||||
struct MTex *mtex);
|
||||
|
||||
} // namespace blender
|
||||
51
blender-5.2.0/source/blender/nodes/NOD_trace_values.hh
Normal file
51
blender-5.2.0/source/blender/nodes/NOD_trace_values.hh
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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"
|
||||
#include "NOD_node_in_compute_context.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);
|
||||
|
||||
LinkedBundleSignatures gather_linked_target_bundle_signatures(
|
||||
const ComputeContext *bundle_socket_context,
|
||||
const bNodeSocket &bundle_socket,
|
||||
bke::ComputeContextCache &compute_context_cache);
|
||||
LinkedBundleSignatures gather_linked_origin_bundle_signatures(
|
||||
const ComputeContext *bundle_socket_context,
|
||||
const bNodeSocket &bundle_socket,
|
||||
bke::ComputeContextCache &compute_context_cache);
|
||||
LinkedClosureSignatures gather_linked_target_closure_signatures(
|
||||
const ComputeContext *closure_socket_context,
|
||||
const bNodeSocket &closure_socket,
|
||||
bke::ComputeContextCache &compute_context_cache);
|
||||
LinkedClosureSignatures gather_linked_origin_closure_signatures(
|
||||
const ComputeContext *closure_socket_context,
|
||||
const bNodeSocket &closure_socket,
|
||||
bke::ComputeContextCache &compute_context_cache);
|
||||
|
||||
std::optional<NodeInContext> find_origin_index_menu_switch(
|
||||
const SocketInContext &socket, bke::ComputeContextCache &compute_context_cache);
|
||||
|
||||
} // namespace blender::nodes
|
||||
311
blender-5.2.0/source/blender/nodes/NOD_value_elem.hh
Normal file
311
blender-5.2.0/source/blender/nodes/NOD_value_elem.hh
Normal file
@@ -0,0 +1,311 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* A #ValueElem is an abstract element or part of a value. It does not store the actual value of
|
||||
* the type but which parts of it are affected. For example, #VectorElem does not store the actual
|
||||
* vector values but just a boolean for each component.
|
||||
*
|
||||
* Some nodes implement special #node_eval_elem and #node_eval_inverse_elem methods which allow
|
||||
* analyzing the potential impact of changing part of a value in one place of a node tree.
|
||||
*
|
||||
* The types are generally quite small and trivially copyable and destructible.
|
||||
* They just contain some booleans.
|
||||
*/
|
||||
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
#include "BLI_hash.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
namespace blender::nodes::value_elem {
|
||||
|
||||
/**
|
||||
* Common base type value primitive types that can't be subdivided further.
|
||||
*/
|
||||
struct PrimitiveValueElem {
|
||||
bool affected = false;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->affected;
|
||||
}
|
||||
|
||||
friend bool operator==(const PrimitiveValueElem &a, const PrimitiveValueElem &b) = default;
|
||||
|
||||
void merge(const PrimitiveValueElem &other)
|
||||
{
|
||||
this->affected |= other.affected;
|
||||
}
|
||||
|
||||
void intersect(const PrimitiveValueElem &other)
|
||||
{
|
||||
this->affected &= other.affected;
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(this->affected);
|
||||
}
|
||||
};
|
||||
|
||||
struct BoolElem : public PrimitiveValueElem {
|
||||
static BoolElem all()
|
||||
{
|
||||
return {true};
|
||||
}
|
||||
};
|
||||
|
||||
struct FloatElem : public PrimitiveValueElem {
|
||||
static FloatElem all()
|
||||
{
|
||||
return {true};
|
||||
}
|
||||
};
|
||||
|
||||
struct IntElem : public PrimitiveValueElem {
|
||||
static IntElem all()
|
||||
{
|
||||
return {true};
|
||||
}
|
||||
};
|
||||
|
||||
struct VectorElem {
|
||||
/** Members indicate which components of the vector are affected. */
|
||||
FloatElem x;
|
||||
FloatElem y;
|
||||
FloatElem z;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->x || this->y || this->z;
|
||||
}
|
||||
|
||||
friend bool operator==(const VectorElem &a, const VectorElem &b) = default;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(this->x, this->y, this->z);
|
||||
}
|
||||
|
||||
void merge(const VectorElem &other)
|
||||
{
|
||||
this->x.merge(other.x);
|
||||
this->y.merge(other.y);
|
||||
this->z.merge(other.z);
|
||||
}
|
||||
|
||||
void intersect(const VectorElem &other)
|
||||
{
|
||||
this->x.intersect(other.x);
|
||||
this->y.intersect(other.y);
|
||||
this->z.intersect(other.z);
|
||||
}
|
||||
|
||||
static VectorElem all()
|
||||
{
|
||||
return {{true}, {true}, {true}};
|
||||
}
|
||||
};
|
||||
|
||||
struct RotationElem {
|
||||
/**
|
||||
* The euler and axis-angle components have overlap. All components that can be affected need to
|
||||
* be tagged. For example if a node affects the euler angles, it indirectly also affects the
|
||||
* axis-angle.
|
||||
*/
|
||||
VectorElem euler;
|
||||
VectorElem axis;
|
||||
FloatElem angle;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->euler || this->axis || this->angle;
|
||||
}
|
||||
|
||||
friend bool operator==(const RotationElem &a, const RotationElem &b) = default;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(this->euler, this->axis, this->angle);
|
||||
}
|
||||
|
||||
void merge(const RotationElem &other)
|
||||
{
|
||||
this->euler.merge(other.euler);
|
||||
this->axis.merge(other.axis);
|
||||
this->angle.merge(other.angle);
|
||||
}
|
||||
|
||||
void intersect(const RotationElem &other)
|
||||
{
|
||||
this->euler.intersect(other.euler);
|
||||
this->axis.intersect(other.axis);
|
||||
this->angle.intersect(other.angle);
|
||||
}
|
||||
|
||||
static RotationElem all()
|
||||
{
|
||||
return {VectorElem::all(), VectorElem::all(), {true}};
|
||||
}
|
||||
};
|
||||
|
||||
struct MatrixElem {
|
||||
VectorElem translation;
|
||||
RotationElem rotation;
|
||||
VectorElem scale;
|
||||
/** For 4x4 matrices this describes whether any entry of the last row is affected. */
|
||||
FloatElem any_non_transform;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->translation || this->rotation || this->scale || this->any_non_transform;
|
||||
}
|
||||
|
||||
friend bool operator==(const MatrixElem &a, const MatrixElem &b) = default;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(
|
||||
this->translation, this->rotation, this->scale, this->any_non_transform);
|
||||
}
|
||||
|
||||
void merge(const MatrixElem &other)
|
||||
{
|
||||
this->translation.merge(other.translation);
|
||||
this->rotation.merge(other.rotation);
|
||||
this->scale.merge(other.scale);
|
||||
this->any_non_transform.merge(other.any_non_transform);
|
||||
}
|
||||
|
||||
void intersect(const MatrixElem &other)
|
||||
{
|
||||
this->translation.intersect(other.translation);
|
||||
this->rotation.intersect(other.rotation);
|
||||
this->scale.intersect(other.scale);
|
||||
this->any_non_transform.intersect(other.any_non_transform);
|
||||
}
|
||||
|
||||
static MatrixElem all()
|
||||
{
|
||||
return {VectorElem::all(), RotationElem::all(), VectorElem::all(), FloatElem::all()};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A generic type that can hold the value element for any of the above types and has the same
|
||||
* interface. This should be used when the data type is not known at compile time.
|
||||
*/
|
||||
struct ElemVariant {
|
||||
std::variant<BoolElem, FloatElem, IntElem, VectorElem, RotationElem, MatrixElem> elem;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return std::visit([](const auto &value) { return bool(value); }, this->elem);
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return std::visit([](auto &value) { return value.hash(); }, this->elem);
|
||||
}
|
||||
|
||||
void merge(const ElemVariant &other)
|
||||
{
|
||||
BLI_assert(this->elem.index() == other.elem.index());
|
||||
std::visit(
|
||||
[&](auto &value) {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
value.merge(std::get<T>(other.elem));
|
||||
},
|
||||
this->elem);
|
||||
}
|
||||
|
||||
void intersect(const ElemVariant &other)
|
||||
{
|
||||
BLI_assert(this->elem.index() == other.elem.index());
|
||||
std::visit(
|
||||
[&](auto &value) {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
value.intersect(std::get<T>(other.elem));
|
||||
},
|
||||
this->elem);
|
||||
}
|
||||
|
||||
void set_all()
|
||||
{
|
||||
std::visit(
|
||||
[](auto &value) {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
value = T::all();
|
||||
},
|
||||
this->elem);
|
||||
}
|
||||
|
||||
void clear_all()
|
||||
{
|
||||
std::visit(
|
||||
[](auto &value) {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
value = T();
|
||||
},
|
||||
this->elem);
|
||||
}
|
||||
|
||||
friend bool operator==(const ElemVariant &a, const ElemVariant &b) = default;
|
||||
};
|
||||
|
||||
/** Utility struct to pair a socket with a value element. */
|
||||
struct SocketElem {
|
||||
const bNodeSocket *socket = nullptr;
|
||||
ElemVariant elem;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(this->socket, this->elem);
|
||||
}
|
||||
|
||||
friend bool operator==(const SocketElem &a, const SocketElem &b) = default;
|
||||
};
|
||||
|
||||
/** Utility struct to pair a group input index with a value element. */
|
||||
struct GroupInputElem {
|
||||
int group_input_index = 0;
|
||||
ElemVariant elem;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(this->group_input_index, this->elem);
|
||||
}
|
||||
|
||||
friend bool operator==(const GroupInputElem &a, const GroupInputElem &b) = default;
|
||||
};
|
||||
|
||||
/** Utility struct to pair a value node with a value element. */
|
||||
struct ValueNodeElem {
|
||||
const bNode *node = nullptr;
|
||||
ElemVariant elem;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(this->node, this->elem);
|
||||
}
|
||||
|
||||
friend bool operator==(const ValueNodeElem &a, const ValueNodeElem &b) = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the default value element for the given socket type if it exists.
|
||||
*/
|
||||
std::optional<ElemVariant> get_elem_variant_for_socket_type(eNodeSocketDatatype type);
|
||||
|
||||
/** Converts the type of a value element if possible. */
|
||||
std::optional<ElemVariant> convert_socket_elem(const bNodeSocket &old_socket,
|
||||
const bNodeSocket &new_socket,
|
||||
const ElemVariant &old_elem);
|
||||
|
||||
} // namespace blender::nodes::value_elem
|
||||
77
blender-5.2.0/source/blender/nodes/NOD_value_elem_eval.hh
Normal file
77
blender-5.2.0/source/blender/nodes/NOD_value_elem_eval.hh
Normal file
@@ -0,0 +1,77 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NOD_value_elem.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
|
||||
namespace blender::nodes::value_elem {
|
||||
|
||||
/**
|
||||
* Is passed to the node evaluation function to figure which outputs change when parts of the
|
||||
* inputs change.
|
||||
*/
|
||||
class ElemEvalParams {
|
||||
private:
|
||||
const Map<const bNodeSocket *, ElemVariant> &elem_by_socket_;
|
||||
Vector<SocketElem> &output_elems_;
|
||||
|
||||
public:
|
||||
const bNode &node;
|
||||
|
||||
ElemEvalParams(const bNode &node,
|
||||
const Map<const bNodeSocket *, ElemVariant> &elem_by_socket,
|
||||
Vector<SocketElem> &output_elems);
|
||||
|
||||
template<typename T> T get_input_elem(const UString identifier) const
|
||||
{
|
||||
const bNodeSocket &socket = *node.input_by_identifier(identifier);
|
||||
if (const ElemVariant *elem = this->elem_by_socket_.lookup_ptr(&socket)) {
|
||||
return std::get<T>(elem->elem);
|
||||
}
|
||||
return T();
|
||||
}
|
||||
|
||||
template<typename T> void set_output_elem(const UString identifier, T elem)
|
||||
{
|
||||
const bNodeSocket &socket = *node.output_by_identifier(identifier);
|
||||
output_elems_.append({&socket, ElemVariant{elem}});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as above but for inverse evaluation, i.e. to figure out which inputs need to change when
|
||||
* specific parts of the output change.
|
||||
*/
|
||||
class InverseElemEvalParams {
|
||||
private:
|
||||
const Map<const bNodeSocket *, ElemVariant> &elem_by_socket_;
|
||||
Vector<SocketElem> &input_elems_;
|
||||
|
||||
public:
|
||||
const bNode &node;
|
||||
|
||||
InverseElemEvalParams(const bNode &node,
|
||||
const Map<const bNodeSocket *, ElemVariant> &elem_by_socket,
|
||||
Vector<SocketElem> &input_elems);
|
||||
|
||||
template<typename T> T get_output_elem(const UString identifier) const
|
||||
{
|
||||
const bNodeSocket &socket = *node.output_by_identifier(identifier);
|
||||
if (const ElemVariant *elem = this->elem_by_socket_.lookup_ptr(&socket)) {
|
||||
return std::get<T>(elem->elem);
|
||||
}
|
||||
return T();
|
||||
}
|
||||
|
||||
template<typename T> void set_input_elem(const UString identifier, T elem)
|
||||
{
|
||||
const bNodeSocket &socket = *node.input_by_identifier(identifier);
|
||||
input_elems_.append({&socket, ElemVariant{elem}});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::nodes::value_elem
|
||||
160
blender-5.2.0/source/blender/nodes/composite/CMakeLists.txt
Normal file
160
blender-5.2.0/source/blender/nodes/composite/CMakeLists.txt
Normal file
@@ -0,0 +1,160 @@
|
||||
# SPDX-FileCopyrightText: 2021 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(INC
|
||||
.
|
||||
..
|
||||
include
|
||||
../intern
|
||||
../../editors/include
|
||||
../../compositor
|
||||
../../makesrna
|
||||
../../compositor/algorithms
|
||||
../../compositor/cached_resources
|
||||
../../compositor/derived_resources
|
||||
../../compositor/utilities
|
||||
../../sequencer
|
||||
|
||||
# RNA_prototypes.hh
|
||||
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
)
|
||||
|
||||
set(SRC
|
||||
nodes/node_composite_alpha_convert.cc
|
||||
nodes/node_composite_alpha_over.cc
|
||||
nodes/node_composite_anti_aliasing.cc
|
||||
nodes/node_composite_bilateral_blur.cc
|
||||
nodes/node_composite_blank_image.cc
|
||||
nodes/node_composite_blur.cc
|
||||
nodes/node_composite_bokeh_blur.cc
|
||||
nodes/node_composite_bokeh_image.cc
|
||||
nodes/node_composite_box_mask.cc
|
||||
nodes/node_composite_brightness_contrast.cc
|
||||
nodes/node_composite_channel_key.cc
|
||||
nodes/node_composite_chroma_key.cc
|
||||
nodes/node_composite_color.cc
|
||||
nodes/node_composite_color_balance.cc
|
||||
nodes/node_composite_color_correction.cc
|
||||
nodes/node_composite_color_key.cc
|
||||
nodes/node_composite_color_spill.cc
|
||||
nodes/node_composite_combine_color.cc
|
||||
nodes/node_composite_convert_color_space.cc
|
||||
nodes/node_composite_convert_to_display.cc
|
||||
nodes/node_composite_convolve.cc
|
||||
nodes/node_composite_corner_pin.cc
|
||||
nodes/node_composite_crop.cc
|
||||
nodes/node_composite_cryptomatte.cc
|
||||
nodes/node_composite_defocus.cc
|
||||
nodes/node_composite_denoise.cc
|
||||
nodes/node_composite_depth_combine.cc
|
||||
nodes/node_composite_despeckle.cc
|
||||
nodes/node_composite_difference_key.cc
|
||||
nodes/node_composite_dilate_erode.cc
|
||||
nodes/node_composite_directional_blur.cc
|
||||
nodes/node_composite_displace.cc
|
||||
nodes/node_composite_distance_key.cc
|
||||
nodes/node_composite_double_edge_mask.cc
|
||||
nodes/node_composite_ellipse_mask.cc
|
||||
nodes/node_composite_exposure.cc
|
||||
nodes/node_composite_file_output.cc
|
||||
nodes/node_composite_filter.cc
|
||||
nodes/node_composite_flip.cc
|
||||
nodes/node_composite_glare.cc
|
||||
nodes/node_composite_group.cc
|
||||
nodes/node_composite_hue_correct.cc
|
||||
nodes/node_composite_hue_saturation_value.cc
|
||||
nodes/node_composite_id_mask.cc
|
||||
nodes/node_composite_image.cc
|
||||
nodes/node_composite_image_coordinates.cc
|
||||
nodes/node_composite_image_info.cc
|
||||
nodes/node_composite_inpaint.cc
|
||||
nodes/node_composite_invert.cc
|
||||
nodes/node_composite_keying.cc
|
||||
nodes/node_composite_keying_screen.cc
|
||||
nodes/node_composite_kuwahara.cc
|
||||
nodes/node_composite_lens_distortion.cc
|
||||
nodes/node_composite_levels.cc
|
||||
nodes/node_composite_luminance_key.cc
|
||||
nodes/node_composite_map_uv.cc
|
||||
nodes/node_composite_mask.cc
|
||||
nodes/node_composite_mask_to_sdf.cc
|
||||
nodes/node_composite_movie_clip.cc
|
||||
nodes/node_composite_movie_distortion.cc
|
||||
nodes/node_composite_normal.cc
|
||||
nodes/node_composite_normalize.cc
|
||||
nodes/node_composite_pixelate.cc
|
||||
nodes/node_composite_plane_track_deform.cc
|
||||
nodes/node_composite_posterize.cc
|
||||
nodes/node_composite_relative_to_pixel.cc
|
||||
nodes/node_composite_render_layers.cc
|
||||
nodes/node_composite_rgb_curves.cc
|
||||
nodes/node_composite_rgb_to_bw.cc
|
||||
nodes/node_composite_rotate.cc
|
||||
nodes/node_composite_scale.cc
|
||||
nodes/node_composite_scene_time.cc
|
||||
nodes/node_composite_separate_color.cc
|
||||
nodes/node_composite_set_alpha.cc
|
||||
nodes/node_composite_split.cc
|
||||
nodes/node_composite_stabilize_2d.cc
|
||||
nodes/node_composite_string_to_image.cc
|
||||
nodes/node_composite_strip_info.cc
|
||||
nodes/node_composite_switch.cc
|
||||
nodes/node_composite_switch_view.cc
|
||||
nodes/node_composite_time_curve.cc
|
||||
nodes/node_composite_tone_map.cc
|
||||
nodes/node_composite_track_position.cc
|
||||
nodes/node_composite_transform.cc
|
||||
nodes/node_composite_translate.cc
|
||||
nodes/node_composite_vector_blur.cc
|
||||
nodes/node_composite_viewer.cc
|
||||
|
||||
node_composite_tree.cc
|
||||
node_composite_util.cc
|
||||
|
||||
include/NOD_compositor_file_output.hh
|
||||
node_composite_util.hh
|
||||
)
|
||||
|
||||
set(LIB
|
||||
PRIVATE bf::blenkernel
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::blentranslation
|
||||
PRIVATE bf::blenloader
|
||||
PRIVATE bf::depsgraph
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::functions
|
||||
PRIVATE bf::gpu
|
||||
PRIVATE bf::imbuf
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
bf_compositor
|
||||
PRIVATE bf::render
|
||||
PRIVATE bf::windowmanager
|
||||
PRIVATE bf::sequencer
|
||||
PRIVATE bf::dependencies::opencolorio
|
||||
PRIVATE bf::dependencies::optional::tbb
|
||||
PRIVATE bf::dependencies::optional::openimagedenoise
|
||||
PRIVATE bf::dependencies::optional::fftw3
|
||||
)
|
||||
|
||||
add_node_discovery(
|
||||
bf_nodes_compositor_generated
|
||||
"${SRC}"
|
||||
${CMAKE_CURRENT_BINARY_DIR}/register_compositor_nodes.cc
|
||||
register_compositor_nodes
|
||||
)
|
||||
|
||||
list(APPEND LIB
|
||||
bf_nodes_compositor_generated
|
||||
)
|
||||
|
||||
blender_add_lib(bf_nodes_composite "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
target_link_libraries(bf_nodes_compositor_generated bf_nodes_composite)
|
||||
|
||||
blender_set_target_unity_build(bf_nodes_composite 10)
|
||||
|
||||
# RNA_prototypes.hh
|
||||
add_dependencies(bf_nodes_composite bf_rna)
|
||||
@@ -0,0 +1,124 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_image_format.hh"
|
||||
|
||||
#include "NOD_socket_items.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
struct FileOutputItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
|
||||
using ItemT = NodeCompositorFileOutputItem;
|
||||
static StructRNA **item_srna;
|
||||
static constexpr StringRefNull node_idname = "CompositorNodeOutputFile";
|
||||
static constexpr bool has_type = true;
|
||||
static constexpr bool has_name = true;
|
||||
static constexpr bool has_name_validation = true;
|
||||
static constexpr bool has_vector_dimensions = true;
|
||||
static constexpr bool can_have_empty_name = true;
|
||||
static constexpr char unique_name_separator = '_';
|
||||
struct operator_idnames {
|
||||
static constexpr StringRefNull add_item = "NODE_OT_file_output_item_add";
|
||||
static constexpr StringRefNull remove_item = "NODE_OT_file_output_item_remove";
|
||||
static constexpr StringRefNull move_item = "NODE_OT_file_output_item_move";
|
||||
};
|
||||
struct ui_idnames {
|
||||
static constexpr StringRefNull list = "DATA_UL_file_output_items";
|
||||
};
|
||||
struct rna_names {
|
||||
static constexpr StringRefNull items = "file_output_items";
|
||||
static constexpr StringRefNull active_index = "active_item_index";
|
||||
};
|
||||
|
||||
static socket_items::SocketItemsRef<NodeCompositorFileOutputItem> get_items_from_node(
|
||||
bNode &node)
|
||||
{
|
||||
auto *storage = static_cast<NodeCompositorFileOutput *>(node.storage);
|
||||
return {&storage->items, &storage->items_count, &storage->active_item_index};
|
||||
}
|
||||
|
||||
static void copy_item(const NodeCompositorFileOutputItem &source,
|
||||
NodeCompositorFileOutputItem &destination)
|
||||
{
|
||||
destination = source;
|
||||
destination.name = BLI_strdup_null(destination.name);
|
||||
BKE_image_format_copy(&destination.format, &source.format);
|
||||
}
|
||||
|
||||
static void destruct_item(NodeCompositorFileOutputItem *item)
|
||||
{
|
||||
MEM_SAFE_DELETE(item->name);
|
||||
BKE_image_format_free(&item->format);
|
||||
}
|
||||
|
||||
static void blend_write_item(BlendWriter *writer, const ItemT &item);
|
||||
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
|
||||
|
||||
static eNodeSocketDatatype get_socket_type(const NodeCompositorFileOutputItem &item)
|
||||
{
|
||||
return item.socket_type;
|
||||
}
|
||||
|
||||
static char **get_name(NodeCompositorFileOutputItem &item)
|
||||
{
|
||||
return &item.name;
|
||||
}
|
||||
|
||||
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/)
|
||||
{
|
||||
return ELEM(socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA);
|
||||
}
|
||||
|
||||
static int find_available_identifier(const NodeCompositorFileOutputItem *items,
|
||||
const int items_count)
|
||||
{
|
||||
if (items_count == 0) {
|
||||
return 0;
|
||||
}
|
||||
int max_identifier = items[0].identifier;
|
||||
for (int i = 0; i < items_count; i++) {
|
||||
max_identifier = math::max(items[i].identifier, max_identifier);
|
||||
}
|
||||
return max_identifier + 1;
|
||||
}
|
||||
|
||||
static void init_with_socket_type_and_name(bNode &node,
|
||||
NodeCompositorFileOutputItem &item,
|
||||
const eNodeSocketDatatype socket_type,
|
||||
const char *name,
|
||||
std::optional<int> dimensions = std::nullopt)
|
||||
{
|
||||
auto *storage = static_cast<NodeCompositorFileOutput *>(node.storage);
|
||||
item.identifier = FileOutputItemsAccessor::find_available_identifier(storage->items,
|
||||
storage->items_count);
|
||||
|
||||
item.socket_type = socket_type;
|
||||
item.vector_socket_dimensions = dimensions.value_or(3);
|
||||
socket_items::set_item_name_and_make_unique<FileOutputItemsAccessor>(node, item, name);
|
||||
|
||||
item.save_as_render = true;
|
||||
BKE_image_format_init(&item.format);
|
||||
BKE_image_format_update_color_space_for_type(&item.format);
|
||||
}
|
||||
|
||||
static std::string validate_name(const StringRef name);
|
||||
|
||||
static std::string socket_identifier_for_item(const NodeCompositorFileOutputItem &item)
|
||||
{
|
||||
return "Item_" + std::to_string(item.identifier);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,211 @@
|
||||
/* SPDX-FileCopyrightText: 2007 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup nodes
|
||||
*/
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_space_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_global.hh"
|
||||
#include "BKE_image.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_node_tree_update.hh"
|
||||
#include "BKE_tracking.hh"
|
||||
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_select.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
|
||||
#include "node_common.h"
|
||||
|
||||
#include "RNA_prototypes.hh"
|
||||
|
||||
#include "NOD_composite.hh"
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static void composite_get_from_context(const bContext *C,
|
||||
bke::bNodeTreeType * /*treetype*/,
|
||||
bNodeTree **r_ntree,
|
||||
ID **r_id,
|
||||
ID **r_from)
|
||||
{
|
||||
const SpaceNode *snode = CTX_wm_space_node(C);
|
||||
if (snode->node_tree_sub_type == SNODE_COMPOSITOR_SEQUENCER) {
|
||||
*r_ntree = nullptr;
|
||||
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
|
||||
if (!sequencer_scene) {
|
||||
return;
|
||||
}
|
||||
Strip *strip = seq::select_active_get(sequencer_scene);
|
||||
if (!strip) {
|
||||
return;
|
||||
}
|
||||
|
||||
bNodeTree *node_group = nullptr;
|
||||
if (strip->type == STRIP_TYPE_COMPOSITOR && strip->effectdata) {
|
||||
CompositorEffectVars *comp_data = static_cast<CompositorEffectVars *>(strip->effectdata);
|
||||
node_group = comp_data->node_group;
|
||||
}
|
||||
else {
|
||||
StripModifierData *smd = seq::modifier_get_active(strip);
|
||||
if (smd && smd->type == eSeqModifierType_Compositor) {
|
||||
SequencerCompositorModifierData *scmd =
|
||||
reinterpret_cast<SequencerCompositorModifierData *>(smd);
|
||||
node_group = scmd->node_group;
|
||||
}
|
||||
}
|
||||
|
||||
if (node_group) {
|
||||
*r_from = nullptr;
|
||||
*r_id = &sequencer_scene->id;
|
||||
*r_ntree = node_group;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
|
||||
*r_from = nullptr;
|
||||
*r_id = &scene->id;
|
||||
*r_ntree = scene->compositing_node_group;
|
||||
}
|
||||
|
||||
static void foreach_nodeclass(void *calldata, bke::bNodeClassCallback func)
|
||||
{
|
||||
func(calldata, NODE_CLASS_INPUT, N_("Input"));
|
||||
func(calldata, NODE_CLASS_OUTPUT, N_("Output"));
|
||||
func(calldata, NODE_CLASS_OP_COLOR, N_("Color"));
|
||||
func(calldata, NODE_CLASS_OP_VECTOR, N_("Vector"));
|
||||
func(calldata, NODE_CLASS_OP_FILTER, N_("Filter"));
|
||||
func(calldata, NODE_CLASS_CONVERTER, N_("Converter"));
|
||||
func(calldata, NODE_CLASS_MATTE, N_("Matte"));
|
||||
func(calldata, NODE_CLASS_DISTORT, N_("Distort"));
|
||||
func(calldata, NODE_CLASS_GROUP, N_("Group"));
|
||||
func(calldata, NODE_CLASS_INTERFACE, N_("Interface"));
|
||||
func(calldata, NODE_CLASS_LAYOUT, N_("Layout"));
|
||||
}
|
||||
|
||||
static void update(bNodeTree *ntree)
|
||||
{
|
||||
bke::node_tree_set_output(*ntree);
|
||||
|
||||
ntree_update_reroute_nodes(ntree);
|
||||
}
|
||||
|
||||
static bool composite_node_tree_socket_type_valid(bke::bNodeTreeType * /*ntreetype*/,
|
||||
bke::bNodeSocketType *socket_type)
|
||||
{
|
||||
return bke::node_is_static_socket_type(*socket_type) && ELEM(socket_type->type,
|
||||
SOCK_FLOAT,
|
||||
SOCK_INT,
|
||||
SOCK_BOOLEAN,
|
||||
SOCK_VECTOR,
|
||||
SOCK_INT_VECTOR,
|
||||
SOCK_RGBA,
|
||||
SOCK_MATRIX,
|
||||
SOCK_MENU,
|
||||
SOCK_ROTATION,
|
||||
SOCK_STRING,
|
||||
SOCK_OBJECT,
|
||||
SOCK_FONT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep consistent with the #is_conversion_supported function in #compositor::ConversionOperation
|
||||
* on the compositor side.
|
||||
*/
|
||||
static bool composite_validate_link(eNodeSocketDatatype from_type, eNodeSocketDatatype to_type)
|
||||
{
|
||||
/* Basic math types can be implicitly converted to each other. */
|
||||
if (ELEM(from_type,
|
||||
SOCK_FLOAT,
|
||||
SOCK_VECTOR,
|
||||
SOCK_INT_VECTOR,
|
||||
SOCK_RGBA,
|
||||
SOCK_BOOLEAN,
|
||||
SOCK_INT) &&
|
||||
ELEM(to_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_INT_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ELEM(from_type, SOCK_FLOAT, SOCK_VECTOR) && to_type == SOCK_ROTATION) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (from_type == SOCK_MATRIX && to_type == SOCK_ROTATION) {
|
||||
return true;
|
||||
}
|
||||
if (from_type == SOCK_ROTATION && to_type == SOCK_MATRIX) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (from_type == SOCK_ROTATION && to_type == SOCK_VECTOR) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return from_type == to_type;
|
||||
}
|
||||
|
||||
bke::bNodeTreeType *ntreeType_Composite;
|
||||
|
||||
void register_node_tree_type_cmp()
|
||||
{
|
||||
bke::bNodeTreeType *tt = ntreeType_Composite = MEM_new<bke::bNodeTreeType>(__func__);
|
||||
|
||||
tt->type = NTREE_COMPOSIT;
|
||||
tt->idname = "CompositorNodeTree"_ustr;
|
||||
tt->group_idname = "CompositorNodeGroup"_ustr;
|
||||
tt->ui_name = N_("Compositor");
|
||||
tt->ui_icon = ICON_NODE_COMPOSITING;
|
||||
tt->ui_description = N_("Create effects and post-process renders, images, and the 3D Viewport");
|
||||
tt->asset_catalog_path_prefix = "Compositing";
|
||||
|
||||
tt->foreach_nodeclass = foreach_nodeclass;
|
||||
tt->update = update;
|
||||
tt->get_from_context = composite_get_from_context;
|
||||
tt->validate_link = composite_validate_link;
|
||||
tt->valid_socket_type = composite_node_tree_socket_type_valid;
|
||||
|
||||
tt->rna_ext.srna = RNA_CompositorNodeTree;
|
||||
|
||||
bke::node_tree_type_add(*tt);
|
||||
}
|
||||
|
||||
/* *********************************************** */
|
||||
|
||||
void ntreeCompositTagRender(Scene *scene)
|
||||
{
|
||||
/* XXX Think using G_MAIN here is valid, since you want to update current file's scene nodes,
|
||||
* not the ones in temp main generated for rendering?
|
||||
* This is still rather weak though,
|
||||
* ideally render struct would store its own main AND original G_MAIN. */
|
||||
|
||||
for (Scene *sce_iter = static_cast<Scene *>(G_MAIN->scenes.first); sce_iter;
|
||||
sce_iter = static_cast<Scene *>(sce_iter->id.next))
|
||||
{
|
||||
if (sce_iter->compositing_node_group) {
|
||||
for (bNode *node : sce_iter->compositing_node_group->all_nodes()) {
|
||||
if (node->id == (ID *)scene) {
|
||||
BKE_ntree_update_tag_node_property(sce_iter->compositing_node_group, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BKE_ntree_update(*G_MAIN);
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,41 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup nodes
|
||||
*/
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
bool cmp_node_poll_default(const bke::bNodeType * /*ntype*/,
|
||||
const bNodeTree *ntree,
|
||||
const char **r_disabled_hint)
|
||||
{
|
||||
if (!STREQ(ntree->idname, "CompositorNodeTree")) {
|
||||
*r_disabled_hint = RPT_("Not a compositor node tree");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void cmp_node_type_base(bke::bNodeType *ntype,
|
||||
UString idname,
|
||||
const std::optional<int16_t> legacy_type)
|
||||
{
|
||||
bke::node_type_base(*ntype, idname, legacy_type);
|
||||
|
||||
ntype->poll = cmp_node_poll_default;
|
||||
ntype->insert_link = node_insert_link_default;
|
||||
ntype->gather_link_search_ops = nodes::search_link_ops_for_basic_node;
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,38 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup nodes
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "DNA_ID.h" // IWYU pragma: export
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node_legacy_types.hh" // IWYU pragma: export
|
||||
|
||||
#include "node_util.hh" // IWYU pragma: export
|
||||
|
||||
#include "NOD_composite.hh" // IWYU pragma: export
|
||||
#include "NOD_menu_value.hh" // IWYU pragma: export
|
||||
#include "NOD_register.hh" // IWYU pragma: export
|
||||
#include "NOD_socket.hh" // IWYU pragma: export
|
||||
#include "NOD_socket_declarations.hh" // IWYU pragma: export
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
#define CMP_SCALE_MAX 12000
|
||||
|
||||
bool cmp_node_poll_default(const bke::bNodeType *ntype,
|
||||
const bNodeTree *ntree,
|
||||
const char **r_disabled_hint);
|
||||
void cmp_node_type_base(bke::bNodeType *ntype,
|
||||
UString idname,
|
||||
std::optional<int16_t> legacy_type = std::nullopt);
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,99 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "COM_result.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_alpha_convert_cc {
|
||||
|
||||
static const EnumPropertyItem type_items[] = {
|
||||
{CMP_NODE_ALPHA_CONVERT_PREMULTIPLY,
|
||||
"STRAIGHT_TO_PREMULTIPLIED",
|
||||
0,
|
||||
N_("To Premultiplied"),
|
||||
N_("Convert straight to premultiplied")},
|
||||
{CMP_NODE_ALPHA_CONVERT_UNPREMULTIPLY,
|
||||
"PREMULTIPLIED_TO_STRAIGHT",
|
||||
0,
|
||||
N_("To Straight"),
|
||||
N_("Convert premultiplied to straight")},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Color>("Image"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f}).hide_value();
|
||||
b.add_output<decl::Color>("Image"_ustr).align_with_previous();
|
||||
|
||||
b.add_input<decl::Menu>("Type"_ustr)
|
||||
.default_value(CMP_NODE_ALPHA_CONVERT_PREMULTIPLY)
|
||||
.static_items(type_items)
|
||||
.optional_label();
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_composite_convert_alpha", inputs, outputs);
|
||||
}
|
||||
|
||||
static float4 convert_alpha(const float4 &color, const MenuValue &type)
|
||||
{
|
||||
switch (CMPNodeAlphaConvertMode(type.value)) {
|
||||
case CMP_NODE_ALPHA_CONVERT_PREMULTIPLY:
|
||||
return float4(color.xyz() * color.w, color.w);
|
||||
case CMP_NODE_ALPHA_CONVERT_UNPREMULTIPLY:
|
||||
return color.w == 0.0f ? color : float4(color.xyz() / color.w, color.w);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
using compositor::Color;
|
||||
|
||||
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto function = mf::build::SI2_SO<Color, MenuValue, Color>(
|
||||
"Alpha Convert",
|
||||
[](const Color &color, const MenuValue &type) -> Color {
|
||||
return Color(convert_alpha(float4(color), type));
|
||||
},
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
builder.set_matching_fn(function);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodePremulKey"_ustr, CMP_NODE_PREMULKEY);
|
||||
ntype.ui_name = "Alpha Convert";
|
||||
ntype.ui_description = "Convert to and from premultiplied (associated) alpha";
|
||||
ntype.enum_name_legacy = "PREMULKEY";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_alpha_convert_cc
|
||||
@@ -0,0 +1,195 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "COM_result.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_alpha_over_cc {
|
||||
|
||||
static const EnumPropertyItem type_items[] = {
|
||||
{CMP_NODE_ALPHA_OVER_OPERATION_TYPE_OVER,
|
||||
"OVER",
|
||||
ICON_NONE,
|
||||
N_("Over"),
|
||||
N_("The foreground goes over the background according to the alpha of the foreground")},
|
||||
{CMP_NODE_ALPHA_OVER_OPERATION_TYPE_DISJOINT_OVER,
|
||||
"DISJOINT_OVER",
|
||||
ICON_NONE,
|
||||
N_("Disjoint Over"),
|
||||
N_("The foreground goes over the background according to the alpha of the foreground while "
|
||||
"assuming the background is being held out by the foreground")},
|
||||
{CMP_NODE_ALPHA_OVER_OPERATION_TYPE_CONJOINT_OVER,
|
||||
"CONJOINT_OVER",
|
||||
ICON_NONE,
|
||||
N_("Conjoint Over"),
|
||||
N_("The foreground goes over the background according to the alpha of the foreground but the "
|
||||
"foreground completely covers the background if it is more opaque")},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Color>("Background"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
b.add_input<decl::Color>("Foreground"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
b.add_input<decl::Float>("Factor"_ustr, "Fac"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR);
|
||||
b.add_input<decl::Menu>("Type"_ustr)
|
||||
.default_value(CMP_NODE_ALPHA_OVER_OPERATION_TYPE_OVER)
|
||||
.static_items(type_items)
|
||||
.optional_label();
|
||||
b.add_input<decl::Bool>("Straight Alpha"_ustr)
|
||||
.default_value(false)
|
||||
.description(
|
||||
"Defines whether the foreground is in straight alpha form, which is necessary to know "
|
||||
"for proper alpha compositing. Images in the compositor are in premultiplied alpha form "
|
||||
"by default, so this should be false in most cases. But if, and only if, the foreground "
|
||||
"was converted to straight alpha form for some reason, this should be set to true");
|
||||
b.add_output<decl::Color>("Image"_ustr);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_composite_alpha_over", inputs, outputs);
|
||||
}
|
||||
|
||||
/* If straight_alpha is true, then the foreground is in straight alpha form and would need to be
|
||||
* premultiplied. */
|
||||
static float4 preprocess_foreground(const float4 &foreground, const bool straight_alpha)
|
||||
{
|
||||
const float alpha = math::clamp(foreground.w, 0.0f, 1.0f);
|
||||
const float4 premultiplied_foreground = float4(foreground.xyz() * alpha, alpha);
|
||||
return straight_alpha ? premultiplied_foreground : foreground;
|
||||
}
|
||||
|
||||
/* Computes the Porter and Duff Over compositing operation. */
|
||||
static float4 alpha_over(const float4 &background,
|
||||
const float4 &foreground,
|
||||
const float factor,
|
||||
const bool straight_alpha)
|
||||
{
|
||||
const float4 foreground_color = preprocess_foreground(foreground, straight_alpha);
|
||||
|
||||
const float foreground_alpha = math::clamp(foreground.w, 0.0f, 1.0f);
|
||||
const float4 mix_result = foreground_color + background * (1.0f - foreground_alpha);
|
||||
|
||||
return math::interpolate(background, mix_result, factor);
|
||||
}
|
||||
|
||||
/* Computes the Porter and Duff Over compositing operation while assuming the background is being
|
||||
* held out by the foreground. See for reference:
|
||||
*
|
||||
* https://benmcewan.com/blog/disjoint-over-and-conjoint-over-explained */
|
||||
static float4 alpha_over_disjoint(const float4 &background,
|
||||
const float4 &foreground,
|
||||
const float factor,
|
||||
const bool straight_alpha)
|
||||
{
|
||||
const float4 foreground_color = preprocess_foreground(foreground, straight_alpha);
|
||||
|
||||
const float foreground_alpha = math::clamp(foreground.w, 0.0f, 1.0f);
|
||||
const float background_alpha = math::clamp(background.w, 0.0f, 1.0f);
|
||||
|
||||
if (foreground_alpha + background_alpha < 1.0f) {
|
||||
const float4 mix_result = foreground_color + background;
|
||||
return math::interpolate(background, mix_result, factor);
|
||||
}
|
||||
|
||||
const float4 straight_background = math::safe_divide(background, background_alpha);
|
||||
const float4 mix_result = foreground_color + straight_background * (1.0f - foreground_alpha);
|
||||
|
||||
return math::interpolate(background, mix_result, factor);
|
||||
}
|
||||
|
||||
/* Computes the Porter and Duff Over compositing operation but the foreground completely covers the
|
||||
* background if it is more opaque but not necessary completely opaque. See for reference:
|
||||
*
|
||||
* https://benmcewan.com/blog/disjoint-over-and-conjoint-over-explained */
|
||||
static float4 alpha_over_conjoint(const float4 &background,
|
||||
const float4 &foreground,
|
||||
const float factor,
|
||||
const bool straight_alpha)
|
||||
{
|
||||
const float4 foreground_color = preprocess_foreground(foreground, straight_alpha);
|
||||
|
||||
const float foreground_alpha = math::clamp(foreground.w, 0.0f, 1.0f);
|
||||
const float background_alpha = math::clamp(background.w, 0.0f, 1.0f);
|
||||
|
||||
if (foreground_alpha > background_alpha) {
|
||||
const float4 mix_result = foreground_color;
|
||||
return math::interpolate(background, mix_result, factor);
|
||||
}
|
||||
|
||||
const float alpha_ratio = math::safe_divide(foreground_alpha, background_alpha);
|
||||
const float4 mix_result = foreground_color + background * (1.0f - alpha_ratio);
|
||||
|
||||
return math::interpolate(background, mix_result, factor);
|
||||
}
|
||||
|
||||
using compositor::Color;
|
||||
|
||||
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto function = mf::build::SI5_SO<Color, Color, float, MenuValue, bool, Color>(
|
||||
"Alpha Over",
|
||||
[=](const Color &background,
|
||||
const Color &foreground,
|
||||
const float factor,
|
||||
const MenuValue type,
|
||||
const bool straight_alpha) -> Color {
|
||||
switch (CMPNodeAlphaOverOperationType(type.value)) {
|
||||
case CMP_NODE_ALPHA_OVER_OPERATION_TYPE_OVER:
|
||||
return Color(
|
||||
alpha_over(float4(background), float4(foreground), factor, straight_alpha));
|
||||
case CMP_NODE_ALPHA_OVER_OPERATION_TYPE_DISJOINT_OVER:
|
||||
return Color(alpha_over_disjoint(
|
||||
float4(background), float4(foreground), factor, straight_alpha));
|
||||
case CMP_NODE_ALPHA_OVER_OPERATION_TYPE_CONJOINT_OVER:
|
||||
return Color(alpha_over_conjoint(
|
||||
float4(background), float4(foreground), factor, straight_alpha));
|
||||
}
|
||||
return background;
|
||||
},
|
||||
mf::build::exec_presets::SomeSpanOrSingle<0, 1>());
|
||||
|
||||
builder.set_matching_fn(function);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeAlphaOver"_ustr, CMP_NODE_ALPHAOVER);
|
||||
ntype.ui_name = "Alpha Over";
|
||||
ntype.ui_description = "Overlay a foreground image onto a background image";
|
||||
ntype.enum_name_legacy = "ALPHAOVER";
|
||||
ntype.nclass = NODE_CLASS_OP_COLOR;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_alpha_over_cc
|
||||
@@ -0,0 +1,111 @@
|
||||
/* SPDX-FileCopyrightText: 2017 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "COM_algorithm_smaa.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_anti_aliasing_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Color>("Image"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.hide_value()
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_output<decl::Color>("Image"_ustr)
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.align_with_previous();
|
||||
|
||||
b.add_input<decl::Float>("Threshold"_ustr)
|
||||
.default_value(0.2f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description(
|
||||
"Specifies the threshold or sensitivity to edges. Lowering this value you will be able "
|
||||
"to detect more edges at the expense of performance");
|
||||
b.add_input<decl::Float>("Contrast Limit"_ustr)
|
||||
.default_value(2.0f)
|
||||
.min(0.0f)
|
||||
.description(
|
||||
"If there is an neighbor edge that has a Contrast Limit times bigger contrast than "
|
||||
"current edge, current edge will be discarded. This allows to eliminate spurious "
|
||||
"crossing edges");
|
||||
b.add_input<decl::Float>("Corner Rounding"_ustr)
|
||||
.default_value(0.25f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description("Specifies how much sharp corners will be rounded");
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class AntiAliasingOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
smaa(this->context(),
|
||||
this->get_input("Image"),
|
||||
this->get_result("Image"),
|
||||
this->get_threshold(),
|
||||
this->get_local_contrast_adaptation_factor(),
|
||||
this->get_corner_rounding());
|
||||
}
|
||||
|
||||
/* We encode the threshold in the [0, 1] range, while the SMAA algorithm expects it in the
|
||||
* [0, 0.5] range. */
|
||||
float get_threshold()
|
||||
{
|
||||
return math::clamp(
|
||||
this->get_input("Threshold").get_single_value_default<float>(), 0.0f, 1.0f) /
|
||||
2.0f;
|
||||
}
|
||||
|
||||
float get_local_contrast_adaptation_factor()
|
||||
{
|
||||
return math::max(0.0f, this->get_input("Contrast Limit").get_single_value_default<float>());
|
||||
}
|
||||
|
||||
/* We encode the corner rounding factor in the float [0, 1] range, while the SMAA algorithm
|
||||
* expects it in the integer [0, 100] range. */
|
||||
int get_corner_rounding()
|
||||
{
|
||||
return int(math::clamp(this->get_input("Corner Rounding").get_single_value_default<float>(),
|
||||
0.0f,
|
||||
1.0f) *
|
||||
100.0f);
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new AntiAliasingOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeAntiAliasing"_ustr, CMP_NODE_ANTIALIASING);
|
||||
ntype.ui_name = "Anti-Aliasing";
|
||||
ntype.ui_description = "Smooth away jagged edges";
|
||||
ntype.enum_name_legacy = "ANTIALIASING";
|
||||
ntype.nclass = NODE_CLASS_OP_FILTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.flag |= NODE_PREVIEW;
|
||||
ntype.default_width = bke::NodeWidth::_180;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_anti_aliasing_cc
|
||||
@@ -0,0 +1,184 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "COM_algorithm_symmetric_separable_blur.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_bilateral_blur_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Color>("Image"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.hide_value()
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_output<decl::Color>("Image"_ustr)
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.align_with_previous();
|
||||
|
||||
b.add_input<decl::Color>("Determinator"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Int>("Size"_ustr)
|
||||
.default_value(0)
|
||||
.subtype(PROP_PIXEL)
|
||||
.min(0)
|
||||
.description("The size of the blur in pixels");
|
||||
b.add_input<decl::Float>("Threshold"_ustr)
|
||||
.default_value(0.1f)
|
||||
.min(0.0f)
|
||||
.description(
|
||||
"Pixels are considered in the blur area if the average difference between their "
|
||||
"determinator and the determinator of the center pixel is less than this threshold");
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class BilateralBlurOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const Result &input_image = this->get_input("Image");
|
||||
Result &output_image = this->get_result("Image");
|
||||
if (input_image.is_single_value() || this->get_blur_radius() == 0 ||
|
||||
this->get_threshold() == 0.0f)
|
||||
{
|
||||
output_image.share_data(input_image);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the determinator is a single value, then the node essentially becomes a box blur. */
|
||||
const Result &determinator_image = get_input("Determinator");
|
||||
if (determinator_image.is_single_value()) {
|
||||
symmetric_separable_blur(this->context(),
|
||||
input_image,
|
||||
output_image,
|
||||
float2(this->get_blur_radius()),
|
||||
math::FilterKernel::Box);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_gpu();
|
||||
}
|
||||
else {
|
||||
this->execute_cpu();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_gpu()
|
||||
{
|
||||
gpu::Shader *shader = context().get_shader("compositor_bilateral_blur");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1i(shader, "radius", get_blur_radius());
|
||||
GPU_shader_uniform_1f(shader, "threshold", get_threshold());
|
||||
|
||||
const Result &input_image = get_input("Image");
|
||||
input_image.bind_as_texture(shader, "input_tx");
|
||||
|
||||
const Result &determinator_image = get_input("Determinator");
|
||||
determinator_image.bind_as_texture(shader, "determinator_tx");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
output_image.unbind_as_image();
|
||||
input_image.unbind_as_texture();
|
||||
determinator_image.unbind_as_texture();
|
||||
}
|
||||
|
||||
void execute_cpu()
|
||||
{
|
||||
const int radius = this->get_blur_radius();
|
||||
const float threshold = this->get_threshold();
|
||||
|
||||
const Result &input = get_input("Image");
|
||||
const Result &determinator_image = get_input("Determinator");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output = get_result("Image");
|
||||
output.allocate_texture(domain);
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float4 center_determinator = float4(determinator_image.load_pixel<Color>(texel));
|
||||
|
||||
/* Go over the pixels in the blur window of the specified radius around the center pixel, and
|
||||
* for pixels whose determinator is close enough to the determinator of the center pixel,
|
||||
* accumulate their color as well as their weights. */
|
||||
float accumulated_weight = 0.0f;
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
for (int y = -radius; y <= radius; y++) {
|
||||
for (int x = -radius; x <= radius; x++) {
|
||||
float4 determinator = float4(
|
||||
determinator_image.load_pixel_extended<Color>(texel + int2(x, y)));
|
||||
float difference = math::dot(math::abs(center_determinator - determinator).xyz(),
|
||||
float3(1.0f)) /
|
||||
3.0f;
|
||||
|
||||
if (difference < threshold) {
|
||||
accumulated_weight += 1.0f;
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(x, y)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the accumulated color divided by the accumulated weight if any pixel in the window
|
||||
* was accumulated, otherwise, write a fallback black color. */
|
||||
float4 fallback = float4(float3(0.0f), 1.0f);
|
||||
float4 color = (accumulated_weight != 0.0f) ? (accumulated_color / accumulated_weight) :
|
||||
fallback;
|
||||
output.store_pixel(texel, Color(color));
|
||||
});
|
||||
}
|
||||
|
||||
int get_blur_radius()
|
||||
{
|
||||
return math::max(0, this->get_input("Size").get_single_value_default<int>());
|
||||
}
|
||||
|
||||
float get_threshold()
|
||||
{
|
||||
return math::max(0.0f, this->get_input("Threshold").get_single_value_default<float>());
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new BilateralBlurOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeBilateralblur"_ustr, CMP_NODE_BILATERALBLUR);
|
||||
ntype.ui_name = "Bilateral Blur";
|
||||
ntype.ui_description = "Adaptively blur image, while retaining sharp edges";
|
||||
ntype.enum_name_legacy = "BILATERALBLUR";
|
||||
ntype.nclass = NODE_CLASS_OP_FILTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_bilateral_blur_cc
|
||||
@@ -0,0 +1,73 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_blank_image_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Color>("Color"_ustr)
|
||||
.default_value({0.0f, 0.0f, 0.0f, 1.0f})
|
||||
.description("The color of all pixels in the image");
|
||||
b.add_input<decl::IntVector>("Size"_ustr)
|
||||
.dimensions(2)
|
||||
.default_value(int2(1920, 1080))
|
||||
.subtype(PROP_PIXEL)
|
||||
.min(1)
|
||||
.description("The size of the image");
|
||||
|
||||
b.add_output<decl::Color>("Image"_ustr)
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.description("An image of the given size and constant color");
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class BlankImageOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const int2 size = this->get_input("Size").get_single_value_default<int2>();
|
||||
const int2 image_size = math::max(int2(1), size);
|
||||
|
||||
Result &output = this->get_result("Image");
|
||||
output.allocate_texture(Domain(image_size));
|
||||
const Color color = this->get_input("Color").get_single_value_default<Color>();
|
||||
if (this->context().use_gpu()) {
|
||||
GPU_texture_clear(output, GPU_DATA_FLOAT, color);
|
||||
}
|
||||
else {
|
||||
parallel_for(output.domain().data_size,
|
||||
[&](const int2 texel) { output.store_pixel(texel, color); });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new BlankImageOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeBlankImage"_ustr);
|
||||
ntype.ui_name = "Blank Image";
|
||||
ntype.ui_description = "Returns an image with the given size and constant color";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_blank_image_cc
|
||||
@@ -0,0 +1,492 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "RNA_types.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "COM_algorithm_pad.hh"
|
||||
#include "COM_algorithm_parallel_reduction.hh"
|
||||
#include "COM_algorithm_recursive_gaussian_blur.hh"
|
||||
#include "COM_algorithm_symmetric_separable_blur.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_symmetric_blur_weights.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_blur_cc {
|
||||
|
||||
static const EnumPropertyItem type_items[] = {
|
||||
{CMP_NODE_BLUR_TYPE_BOX, "FLAT", 0, N_("Flat"), N_("Applies a box blur filter")},
|
||||
{CMP_NODE_BLUR_TYPE_TENT, "TENT", 0, N_("Tent"), N_("Applies a triangle blur filter")},
|
||||
{CMP_NODE_BLUR_TYPE_QUAD, "QUAD", 0, N_("Quadratic"), N_("Applies a quadratic blur filter")},
|
||||
{CMP_NODE_BLUR_TYPE_CUBIC, "CUBIC", 0, N_("Cubic"), N_("Applies a cubic blur filter")},
|
||||
{CMP_NODE_BLUR_TYPE_GAUSS, "GAUSS", 0, N_("Gaussian"), N_("Applies a Gaussian blur")},
|
||||
{CMP_NODE_BLUR_TYPE_FAST_GAUSS,
|
||||
"FAST_GAUSS",
|
||||
0,
|
||||
N_("Fast Gaussian"),
|
||||
N_("Applies a recursive Gaussian blur that can be faster and more accurate in some cases, "
|
||||
"but less accurate in other cases")},
|
||||
{CMP_NODE_BLUR_TYPE_CATROM,
|
||||
"CATROM",
|
||||
0,
|
||||
N_("Catrom"),
|
||||
N_("Applies a cubic Catmull-Rom filter")},
|
||||
{CMP_NODE_BLUR_TYPE_MITCH,
|
||||
"MITCH",
|
||||
0,
|
||||
N_("Mitch"),
|
||||
N_("Applies a cubic Mitchell-Netravali filter")},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Color>("Image"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.hide_value()
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_output<decl::Color>("Image"_ustr)
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.align_with_previous();
|
||||
|
||||
b.add_input<decl::Vector>("Size"_ustr)
|
||||
.dimensions(2)
|
||||
.default_value({0.0f, 0.0f})
|
||||
.subtype(PROP_PIXEL)
|
||||
.min(0.0f)
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Menu>("Type"_ustr)
|
||||
.default_value(CMP_NODE_BLUR_TYPE_GAUSS)
|
||||
.static_items(type_items)
|
||||
.optional_label();
|
||||
b.add_input<decl::Bool>("Extend Bounds"_ustr).default_value(false);
|
||||
b.add_input<decl::Bool>("Separable"_ustr)
|
||||
.default_value(true)
|
||||
.description(
|
||||
"Use faster approximation by blurring along the horizontal and vertical directions "
|
||||
"independently");
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*ntree*/, bNode *node)
|
||||
{
|
||||
/* Unused, but allocated for forward compatibility. */
|
||||
NodeBlurData *data = MEM_new<NodeBlurData>(__func__);
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static math::FilterKernel blur_type_to_kernel(CMPNodeBlurType type)
|
||||
{
|
||||
switch (type) {
|
||||
case CMP_NODE_BLUR_TYPE_BOX:
|
||||
return math::FilterKernel::Box;
|
||||
case CMP_NODE_BLUR_TYPE_TENT:
|
||||
return math::FilterKernel::Tent;
|
||||
case CMP_NODE_BLUR_TYPE_QUAD:
|
||||
return math::FilterKernel::Quad;
|
||||
case CMP_NODE_BLUR_TYPE_CUBIC:
|
||||
return math::FilterKernel::Cubic;
|
||||
case CMP_NODE_BLUR_TYPE_CATROM:
|
||||
return math::FilterKernel::Catrom;
|
||||
case CMP_NODE_BLUR_TYPE_GAUSS:
|
||||
return math::FilterKernel::Gauss;
|
||||
case CMP_NODE_BLUR_TYPE_MITCH:
|
||||
return math::FilterKernel::Mitch;
|
||||
case CMP_NODE_BLUR_TYPE_FAST_GAUSS:
|
||||
return math::FilterKernel::Gauss;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return math::FilterKernel::Box;
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class BlurOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const Result &input = this->get_input("Image");
|
||||
Result &output = this->get_result("Image");
|
||||
if (this->is_identity()) {
|
||||
output.share_data(input);
|
||||
return;
|
||||
}
|
||||
|
||||
const Result &size = this->get_input("Size");
|
||||
if (!size.is_single_value()) {
|
||||
this->execute_variable_size(input, size, output);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->get_type() == CMP_NODE_BLUR_TYPE_FAST_GAUSS) {
|
||||
recursive_gaussian_blur(
|
||||
this->context(), input, output, this->get_blur_size(), this->get_extend_bounds());
|
||||
}
|
||||
else if (use_separable_filter()) {
|
||||
symmetric_separable_blur(this->context(),
|
||||
input,
|
||||
output,
|
||||
this->get_blur_size(),
|
||||
blur_type_to_kernel(this->get_type()),
|
||||
this->get_extend_bounds());
|
||||
}
|
||||
else {
|
||||
this->execute_constant_size(input, output);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_constant_size(const Result &input, Result &output)
|
||||
{
|
||||
if (this->get_extend_bounds()) {
|
||||
Result padded_input = this->context().create_result(input.type());
|
||||
|
||||
const int2 padding_size = int2(math::ceil(this->get_blur_size()));
|
||||
|
||||
pad(this->context(), input, padded_input, padding_size, PaddingMethod::Zero);
|
||||
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_constant_size_gpu(padded_input, output);
|
||||
}
|
||||
else {
|
||||
this->execute_constant_size_cpu(padded_input, output);
|
||||
}
|
||||
|
||||
padded_input.release();
|
||||
}
|
||||
else {
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_constant_size_gpu(input, output);
|
||||
}
|
||||
else {
|
||||
this->execute_constant_size_cpu(input, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void execute_constant_size_gpu(const Result &input, Result &output)
|
||||
{
|
||||
gpu::Shader *shader = context().get_shader("compositor_symmetric_blur");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
input.bind_as_texture(shader, "input_tx");
|
||||
|
||||
const float2 blur_radius = this->get_blur_size();
|
||||
|
||||
const Result &weights = context().cache_manager().symmetric_blur_weights.get(
|
||||
context(), blur_type_to_kernel(this->get_type()), blur_radius);
|
||||
weights.bind_as_texture(shader, "weights_tx");
|
||||
|
||||
const Domain domain = input.domain();
|
||||
output.allocate_texture(domain);
|
||||
output.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
output.unbind_as_image();
|
||||
input.unbind_as_texture();
|
||||
weights.unbind_as_texture();
|
||||
}
|
||||
|
||||
void execute_constant_size_cpu(const Result &input, Result &output)
|
||||
{
|
||||
const float2 blur_radius = this->get_blur_size();
|
||||
const Result &weights = this->context().cache_manager().symmetric_blur_weights.get(
|
||||
this->context(), blur_type_to_kernel(this->get_type()), blur_radius);
|
||||
|
||||
const Domain domain = input.domain();
|
||||
output.allocate_texture(domain);
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
|
||||
/* First, compute the contribution of the center pixel. */
|
||||
float4 center_color = float4(input.load_pixel_extended<Color>(texel));
|
||||
accumulated_color += center_color * weights.load_pixel<float>(int2(0));
|
||||
|
||||
int2 weights_size = weights.domain().data_size;
|
||||
|
||||
/* Then, compute the contributions of the pixels along the x axis of the filter, noting that
|
||||
* the weights texture only stores the weights for the positive half, but since the filter is
|
||||
* symmetric, the same weight is used for the negative half and we add both of their
|
||||
* contributions. */
|
||||
for (int x = 1; x < weights_size.x; x++) {
|
||||
float weight = weights.load_pixel<float>(int2(x, 0));
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(x, 0))) * weight;
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(-x, 0))) *
|
||||
weight;
|
||||
}
|
||||
|
||||
/* Then, compute the contributions of the pixels along the y axis of the filter, noting that
|
||||
* the weights texture only stores the weights for the positive half, but since the filter is
|
||||
* symmetric, the same weight is used for the negative half and we add both of their
|
||||
* contributions. */
|
||||
for (int y = 1; y < weights_size.y; y++) {
|
||||
float weight = weights.load_pixel<float>(int2(0, y));
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(0, y))) * weight;
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(0, -y))) *
|
||||
weight;
|
||||
}
|
||||
|
||||
/* Finally, compute the contributions of the pixels in the four quadrants of the filter,
|
||||
* noting that the weights texture only stores the weights for the upper right quadrant, but
|
||||
* since the filter is symmetric, the same weight is used for the rest of the quadrants and
|
||||
* we add all four of their contributions. */
|
||||
for (int y = 1; y < weights_size.y; y++) {
|
||||
for (int x = 1; x < weights_size.x; x++) {
|
||||
float weight = weights.load_pixel<float>(int2(x, y));
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(x, y))) *
|
||||
weight;
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(-x, y))) *
|
||||
weight;
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(x, -y))) *
|
||||
weight;
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(-x, -y))) *
|
||||
weight;
|
||||
}
|
||||
}
|
||||
|
||||
output.store_pixel(texel, Color(accumulated_color));
|
||||
});
|
||||
}
|
||||
|
||||
void execute_variable_size(const Result &input, const Result &size, Result &output)
|
||||
{
|
||||
if (this->get_extend_bounds()) {
|
||||
Result padded_input = this->context().create_result(input.type());
|
||||
Result padded_size = this->context().create_result(ResultType::Float2);
|
||||
|
||||
const int2 padding_size = int2(math::ceil(this->compute_maximum_blur_size()));
|
||||
|
||||
pad(this->context(), input, padded_input, padding_size, PaddingMethod::Zero);
|
||||
pad(this->context(), size, padded_size, padding_size, PaddingMethod::Extend);
|
||||
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_variable_size_gpu(padded_input, padded_size, output);
|
||||
}
|
||||
else {
|
||||
this->execute_variable_size_cpu(padded_input, padded_size, output);
|
||||
}
|
||||
|
||||
padded_input.release();
|
||||
padded_size.release();
|
||||
}
|
||||
else {
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_variable_size_gpu(input, size, output);
|
||||
}
|
||||
else {
|
||||
this->execute_variable_size_cpu(input, size, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void execute_variable_size_gpu(const Result &input, const Result &size_input, Result &output)
|
||||
{
|
||||
const float2 blur_radius = this->compute_maximum_blur_size();
|
||||
const Result &weights = context().cache_manager().symmetric_blur_weights.get(
|
||||
context(), blur_type_to_kernel(this->get_type()), blur_radius);
|
||||
|
||||
gpu::Shader *shader = context().get_shader("compositor_symmetric_blur_variable_size");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
input.bind_as_texture(shader, "input_tx");
|
||||
weights.bind_as_texture(shader, "weights_tx");
|
||||
size_input.bind_as_texture(shader, "size_tx");
|
||||
|
||||
const Domain domain = input.domain();
|
||||
output.allocate_texture(domain);
|
||||
output.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
output.unbind_as_image();
|
||||
input.unbind_as_texture();
|
||||
weights.unbind_as_texture();
|
||||
size_input.unbind_as_texture();
|
||||
}
|
||||
|
||||
void execute_variable_size_cpu(const Result &input, const Result &size_input, Result &output)
|
||||
{
|
||||
const float2 blur_radius = this->compute_maximum_blur_size();
|
||||
const Result &weights = this->context().cache_manager().symmetric_blur_weights.get(
|
||||
this->context(), blur_type_to_kernel(this->get_type()), blur_radius);
|
||||
|
||||
const Domain domain = input.domain();
|
||||
output.allocate_texture(domain);
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
float4 accumulated_weight = float4(0.0f);
|
||||
|
||||
const float2 size = math::max(float2(0.0f), size_input.load_pixel_extended<float2>(texel));
|
||||
int2 radius = int2(math::ceil(size));
|
||||
float2 coordinates_scale = float2(1.0f) / (size + float2(1.0f));
|
||||
|
||||
/* First, compute the contribution of the center pixel. */
|
||||
float4 center_color = float4(input.load_pixel_extended<Color>(texel));
|
||||
float center_weight = weights.load_pixel<float>(int2(0));
|
||||
accumulated_color += center_color * center_weight;
|
||||
accumulated_weight += center_weight;
|
||||
|
||||
/* Then, compute the contributions of the pixels along the x axis of the filter, noting that
|
||||
* the weights texture only stores the weights for the positive half, but since the filter is
|
||||
* symmetric, the same weight is used for the negative half and we add both of their
|
||||
* contributions. */
|
||||
for (int x = 1; x <= radius.x; x++) {
|
||||
float weight_coordinates = (x + 0.5f) * coordinates_scale.x;
|
||||
float weight = weights.sample_bilinear_extended<float>(float2(weight_coordinates, 0.0f));
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(x, 0))) * weight;
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(-x, 0))) *
|
||||
weight;
|
||||
accumulated_weight += weight * 2.0f;
|
||||
}
|
||||
|
||||
/* Then, compute the contributions of the pixels along the y axis of the filter, noting that
|
||||
* the weights texture only stores the weights for the positive half, but since the filter is
|
||||
* symmetric, the same weight is used for the negative half and we add both of their
|
||||
* contributions. */
|
||||
for (int y = 1; y <= radius.y; y++) {
|
||||
float weight_coordinates = (y + 0.5f) * coordinates_scale.y;
|
||||
float weight = weights.sample_bilinear_extended<float>(float2(0.0f, weight_coordinates));
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(0, y))) * weight;
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(0, -y))) *
|
||||
weight;
|
||||
accumulated_weight += weight * 2.0f;
|
||||
}
|
||||
|
||||
/* Finally, compute the contributions of the pixels in the four quadrants of the filter,
|
||||
* noting that the weights texture only stores the weights for the upper right quadrant, but
|
||||
* since the filter is symmetric, the same weight is used for the rest of the quadrants and
|
||||
* we add all four of their contributions. */
|
||||
for (int y = 1; y <= radius.y; y++) {
|
||||
for (int x = 1; x <= radius.x; x++) {
|
||||
float2 weight_coordinates = (float2(x, y) + float2(0.5f)) * coordinates_scale;
|
||||
float weight = weights.sample_bilinear_extended<float>(weight_coordinates);
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(x, y))) *
|
||||
weight;
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(-x, y))) *
|
||||
weight;
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(x, -y))) *
|
||||
weight;
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(-x, -y))) *
|
||||
weight;
|
||||
accumulated_weight += weight * 4.0f;
|
||||
}
|
||||
}
|
||||
|
||||
accumulated_color = math::safe_divide(accumulated_color, accumulated_weight);
|
||||
|
||||
output.store_pixel(texel, Color(accumulated_color));
|
||||
});
|
||||
}
|
||||
|
||||
float2 compute_maximum_blur_size()
|
||||
{
|
||||
return math::max(float2(0.0f), maximum_float2(this->context(), this->get_input("Size")));
|
||||
}
|
||||
|
||||
bool is_identity()
|
||||
{
|
||||
const Result &input = this->get_input("Image");
|
||||
if (input.is_single_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const Result &size = this->get_input("Size");
|
||||
if (!size.is_single_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->get_blur_size() == float2(0.0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The blur node can operate with different filter types, evaluated on the normalized distance to
|
||||
* the center of the filter. Some of those filters are separable and can be computed as such. If
|
||||
* the Separable input is true, then the filter is always computed as separable even if it is not
|
||||
* in fact separable, in which case, the used filter is a cheaper approximation to the actual
|
||||
* filter. Otherwise, the filter is computed as separable if it is in fact separable and as a
|
||||
* normal 2D filter otherwise. */
|
||||
bool use_separable_filter()
|
||||
{
|
||||
if (this->get_separable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Only Gaussian filters are separable. The rest is not. */
|
||||
switch (this->get_type()) {
|
||||
case CMP_NODE_BLUR_TYPE_GAUSS:
|
||||
case CMP_NODE_BLUR_TYPE_FAST_GAUSS:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
float2 get_blur_size()
|
||||
{
|
||||
BLI_assert(this->get_input("Size").is_single_value());
|
||||
return math::max(float2(0.0f), this->get_input("Size").get_single_value<float2>());
|
||||
}
|
||||
|
||||
bool get_separable()
|
||||
{
|
||||
return this->get_input("Separable").get_single_value_default<bool>();
|
||||
}
|
||||
|
||||
bool get_extend_bounds()
|
||||
{
|
||||
return this->get_input("Extend Bounds").get_single_value_default<bool>();
|
||||
}
|
||||
|
||||
CMPNodeBlurType get_type()
|
||||
{
|
||||
return CMPNodeBlurType(this->get_input("Type").get_single_value_default<MenuValue>().value);
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new BlurOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeBlur"_ustr, CMP_NODE_BLUR);
|
||||
ntype.ui_name = "Blur";
|
||||
ntype.ui_description = "Blur an image, using several blur modes";
|
||||
ntype.enum_name_legacy = "BLUR";
|
||||
ntype.nclass = NODE_CLASS_OP_FILTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.flag |= NODE_PREVIEW;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeBlurData", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_blur_cc
|
||||
@@ -0,0 +1,399 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "COM_algorithm_pad.hh"
|
||||
#include "COM_algorithm_parallel_reduction.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_bokeh_blur_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Color>("Image"_ustr)
|
||||
.default_value({0.8f, 0.8f, 0.8f, 1.0f})
|
||||
.hide_value()
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_output<decl::Color>("Image"_ustr)
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.align_with_previous();
|
||||
|
||||
b.add_input<decl::Color>("Bokeh"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.compositor_realization_mode(CompositorInputRealizationMode::Transforms)
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Float>("Size"_ustr)
|
||||
.default_value(0.0f)
|
||||
.subtype(PROP_PIXEL)
|
||||
.min(0.0f)
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Float>("Mask"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Bool>("Extend Bounds"_ustr).default_value(false);
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class BokehBlurOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const Result &input = this->get_input("Image");
|
||||
Result &output = this->get_result("Image");
|
||||
if (this->is_identity()) {
|
||||
output.share_data(input);
|
||||
return;
|
||||
}
|
||||
|
||||
const Result &size = this->get_input("Size");
|
||||
if (this->get_extend_bounds()) {
|
||||
Result padded_input = this->context().create_result(ResultType::Color);
|
||||
Result padded_size = this->context().create_result(ResultType::Float);
|
||||
|
||||
const int2 padding_size = int2(this->compute_extended_boundary_size(size));
|
||||
|
||||
pad(this->context(), input, padded_input, padding_size, PaddingMethod::Zero);
|
||||
pad(this->context(), size, padded_size, padding_size, PaddingMethod::Extend);
|
||||
|
||||
this->execute_blur(padded_input, padded_size);
|
||||
padded_input.release();
|
||||
padded_size.release();
|
||||
}
|
||||
else {
|
||||
this->execute_blur(input, size);
|
||||
}
|
||||
}
|
||||
|
||||
/* Computes the number of pixels that the image should be extended by if Extend Bounds is
|
||||
* enabled. */
|
||||
int compute_extended_boundary_size(const Result &size)
|
||||
{
|
||||
BLI_assert(this->get_extend_bounds());
|
||||
|
||||
/* For constant sized blur, the extension should just be the blur radius. */
|
||||
if (size.is_single_value()) {
|
||||
return this->get_blur_radius();
|
||||
}
|
||||
|
||||
/* For variable sized blur, the extension should be the bokeh search radius. */
|
||||
return this->compute_variable_size_search_radius();
|
||||
}
|
||||
|
||||
void execute_blur(const Result &input, const Result &size)
|
||||
{
|
||||
if (size.is_single_value()) {
|
||||
this->execute_constant_size(input);
|
||||
}
|
||||
else {
|
||||
this->execute_variable_size(input, size);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_constant_size(const Result &input)
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_constant_size_gpu(input);
|
||||
}
|
||||
else {
|
||||
this->execute_constant_size_cpu(input);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_constant_size_gpu(const Result &input)
|
||||
{
|
||||
gpu::Shader *shader = context().get_shader("compositor_bokeh_blur");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1i(shader, "radius", this->get_blur_radius());
|
||||
|
||||
input.bind_as_texture(shader, "input_tx");
|
||||
|
||||
const Result &input_weights = this->get_input("Bokeh");
|
||||
input_weights.bind_as_texture(shader, "weights_tx");
|
||||
|
||||
const Result &input_mask = this->get_input("Mask");
|
||||
input_mask.bind_as_texture(shader, "mask_tx");
|
||||
|
||||
const Domain domain = input.domain();
|
||||
Result &output_image = this->get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
output_image.unbind_as_image();
|
||||
input.unbind_as_texture();
|
||||
input_weights.unbind_as_texture();
|
||||
input_mask.unbind_as_texture();
|
||||
}
|
||||
|
||||
void execute_constant_size_cpu(const Result &input)
|
||||
{
|
||||
const int radius = this->get_blur_radius();
|
||||
|
||||
const Result &mask_image = this->get_input("Mask");
|
||||
|
||||
const Domain domain = input.domain();
|
||||
Result &output = this->get_result("Image");
|
||||
output.allocate_texture(domain);
|
||||
|
||||
Result blur_kernel = this->compute_blur_kernel(radius);
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
/* The mask input is treated as a boolean. If it is zero, then no blurring happens for this
|
||||
* pixel. Otherwise, the pixel is blurred normally and the mask value is irrelevant. */
|
||||
float mask = mask_image.load_pixel<float, true>(texel);
|
||||
if (mask == 0.0f) {
|
||||
output.store_pixel(texel, input.load_pixel<Color>(texel));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Go over the window of the given radius and accumulate the colors multiplied by their
|
||||
* respective weights as well as the weights themselves. */
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
float4 accumulated_weight = float4(0.0f);
|
||||
for (int y = -radius; y <= radius; y++) {
|
||||
for (int x = -radius; x <= radius; x++) {
|
||||
float4 weight = float4(blur_kernel.load_pixel<Color>(int2(x, y) + radius));
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(x, y))) *
|
||||
weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
output.store_pixel(texel, Color(math::safe_divide(accumulated_color, accumulated_weight)));
|
||||
});
|
||||
|
||||
blur_kernel.release();
|
||||
}
|
||||
|
||||
void execute_variable_size(const Result &input, const Result &size)
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_variable_size_gpu(input, size);
|
||||
}
|
||||
else {
|
||||
this->execute_variable_size_cpu(input, size);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_variable_size_gpu(const Result &input, const Result &size)
|
||||
{
|
||||
const int search_radius = this->compute_variable_size_search_radius();
|
||||
|
||||
gpu::Shader *shader = this->context().get_shader("compositor_bokeh_blur_variable_size");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1i(shader, "search_radius", search_radius);
|
||||
|
||||
input.bind_as_texture(shader, "input_tx");
|
||||
|
||||
const Result &input_weights = this->get_input("Bokeh");
|
||||
input_weights.bind_as_texture(shader, "weights_tx");
|
||||
|
||||
size.bind_as_texture(shader, "size_tx");
|
||||
|
||||
const Result &input_mask = this->get_input("Mask");
|
||||
input_mask.bind_as_texture(shader, "mask_tx");
|
||||
|
||||
const Domain domain = input.domain();
|
||||
Result &output_image = this->get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
output_image.unbind_as_image();
|
||||
input.unbind_as_texture();
|
||||
input_weights.unbind_as_texture();
|
||||
size.unbind_as_texture();
|
||||
input_mask.unbind_as_texture();
|
||||
}
|
||||
|
||||
void execute_variable_size_cpu(const Result &input, const Result &size_input)
|
||||
{
|
||||
const int search_radius = this->compute_variable_size_search_radius();
|
||||
|
||||
const Result &weights = this->get_input("Bokeh");
|
||||
const Result &mask_image = this->get_input("Mask");
|
||||
|
||||
const Domain domain = input.domain();
|
||||
Result &output = this->get_result("Image");
|
||||
output.allocate_texture(domain);
|
||||
|
||||
/* Given the texel in the range [-radius, radius] in both axis, load the appropriate weight
|
||||
* from the weights image, where the given texel (0, 0) corresponds the center of weights
|
||||
* image. Note that we load the weights image inverted along both directions to maintain
|
||||
* the shape of the weights if it was not symmetrical. To understand why inversion makes sense,
|
||||
* consider a 1D weights image whose right half is all ones and whose left half is all zeros.
|
||||
* Further, consider that we are blurring a single white pixel on a black background. When
|
||||
* computing the value of a pixel that is to the right of the white pixel, the white pixel will
|
||||
* be in the left region of the search window, and consequently, without inversion, a zero will
|
||||
* be sampled from the left side of the weights image and result will be zero. However, what
|
||||
* we expect is that pixels to the right of the white pixel will be white, that is, they should
|
||||
* sample a weight of 1 from the right side of the weights image, hence the need for
|
||||
* inversion. */
|
||||
auto load_weight = [&](const int2 &texel, const float radius) {
|
||||
/* The center zero texel is always assigned a unit weight regardless of the corresponding
|
||||
* weight in the weights image. That's to guarantee that at last the center pixel will be
|
||||
* accumulated even if the weights image is zero at its center. */
|
||||
if (texel.x == 0 && texel.y == 0) {
|
||||
return float4(1.0f);
|
||||
}
|
||||
|
||||
/* Add the radius to transform the texel into the range [0, radius * 2], with an additional
|
||||
* 0.5 to sample at the center of the pixels, then divide by the upper bound plus one to
|
||||
* transform the texel into the normalized range [0, 1] needed to sample the weights sampler.
|
||||
* Finally, invert the textures coordinates by subtracting from 1 to maintain the shape of
|
||||
* the weights as mentioned in the function description. */
|
||||
return float4(weights.sample_bilinear_extended<Color>(
|
||||
1.0f - ((float2(texel) + float2(radius + 0.5f)) / (radius * 2.0f + 1.0f))));
|
||||
};
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
/* The mask input is treated as a boolean. If it is zero, then no blurring happens for this
|
||||
* pixel. Otherwise, the pixel is blurred normally and the mask value is irrelevant. */
|
||||
float mask = mask_image.load_pixel<float, true>(texel);
|
||||
if (mask == 0.0f) {
|
||||
output.store_pixel(texel, input.load_pixel<Color>(texel));
|
||||
return;
|
||||
}
|
||||
|
||||
float center_size = math::max(0.0f, size_input.load_pixel<float>(texel));
|
||||
|
||||
/* Go over the window of the given search radius and accumulate the colors multiplied by
|
||||
* their respective weights as well as the weights themselves, but only if both the size of
|
||||
* the center pixel and the size of the candidate pixel are less than both the x and y
|
||||
* distances of the candidate pixel. */
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
float4 accumulated_weight = float4(0.0f);
|
||||
for (int y = -search_radius; y <= search_radius; y++) {
|
||||
for (int x = -search_radius; x <= search_radius; x++) {
|
||||
float candidate_size = math::max(
|
||||
0.0f, size_input.load_pixel_extended<float>(texel + int2(x, y)));
|
||||
|
||||
/* Skip accumulation if either the x or y distances of the candidate pixel are larger
|
||||
* than either the center or candidate pixel size. Note that the max and min functions
|
||||
* here denote "either" in the aforementioned description. */
|
||||
float size = math::min(center_size, candidate_size);
|
||||
if (math::max(math::abs(x), math::abs(y)) > size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float4 weight = load_weight(int2(x, y), size);
|
||||
accumulated_color += float4(input.load_pixel_extended<Color>(texel + int2(x, y))) *
|
||||
weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
output.store_pixel(texel, Color(math::safe_divide(accumulated_color, accumulated_weight)));
|
||||
});
|
||||
}
|
||||
|
||||
/* Compute a blur kernel from the bokeh result by interpolating it to the size of the kernel.
|
||||
* Note that we load the bokeh result inverted along both directions to maintain the shape of the
|
||||
* weights if it was not symmetrical. To understand why inversion makes sense, consider a 1D
|
||||
* weights image whose right half is all ones and whose left half is all zeros. Further, consider
|
||||
* that we are blurring a single white pixel on a black background. When computing the value of a
|
||||
* pixel that is to the right of the white pixel, the white pixel will be in the left region of
|
||||
* the search window, and consequently, without inversion, a zero will be sampled from the left
|
||||
* side of the weights image and result will be zero. However, what we expect is that pixels to
|
||||
* the right of the white pixel will be white, that is, they should sample a weight of 1 from the
|
||||
* right side of the weights image, hence the need for inversion. */
|
||||
Result compute_blur_kernel(const int radius)
|
||||
{
|
||||
const Result &bokeh = this->get_input("Bokeh");
|
||||
|
||||
Result kernel = this->context().create_result(ResultType::Color);
|
||||
const int2 kernel_size = int2(radius * 2 + 1);
|
||||
kernel.allocate_texture(kernel_size);
|
||||
parallel_for(kernel_size, [&](const int2 texel) {
|
||||
/* Add 0.5 to sample at the center of the pixels, then divide by the kernel size to transform
|
||||
* the texel into the normalized range [0, 1] needed to sample the bokeh result. Finally,
|
||||
* invert the textures coordinates by subtracting from 1 to maintain the shape of the weights
|
||||
* as mentioned above. */
|
||||
const float2 weight_coordinates = 1.0f - ((float2(texel) + 0.5f) / float2(kernel_size));
|
||||
float4 weight = float4(bokeh.sample_bilinear_extended<Color>(weight_coordinates));
|
||||
kernel.store_pixel(texel, Color(weight));
|
||||
});
|
||||
|
||||
return kernel;
|
||||
}
|
||||
|
||||
int compute_variable_size_search_radius()
|
||||
{
|
||||
return math::max(0, int(maximum_float(context(), this->get_input("Size"))));
|
||||
}
|
||||
|
||||
int get_blur_radius()
|
||||
{
|
||||
return math::max(0, int(this->get_input("Size").get_single_value<float>()));
|
||||
}
|
||||
|
||||
bool is_identity()
|
||||
{
|
||||
const Result &input = this->get_input("Image");
|
||||
if (input.is_single_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const Result &size = this->get_input("Size");
|
||||
if (size.is_single_value() && this->get_blur_radius() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const Result &mask = this->get_input("Mask");
|
||||
if (mask.is_single_value() && mask.get_single_value<float>() == 0.0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool get_extend_bounds()
|
||||
{
|
||||
return this->get_input("Extend Bounds").get_single_value_default<bool>();
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new BokehBlurOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeBokehBlur"_ustr, CMP_NODE_BOKEHBLUR);
|
||||
ntype.ui_name = "Bokeh Blur";
|
||||
ntype.ui_description =
|
||||
"Generate a bokeh type blur similar to Defocus. Unlike defocus an in-focus region is "
|
||||
"defined in the compositor";
|
||||
ntype.enum_name_legacy = "BOKEHBLUR";
|
||||
ntype.nclass = NODE_CLASS_OP_FILTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
ntype.default_width = bke::NodeWidth::_160;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_bokeh_blur_cc
|
||||
@@ -0,0 +1,136 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "COM_bokeh_kernel.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_bokeh_image_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Int>("Flaps"_ustr)
|
||||
.default_value(5)
|
||||
.min(3)
|
||||
.max(24)
|
||||
.description("The number of flaps in the bokeh");
|
||||
b.add_input<decl::Float>("Angle"_ustr)
|
||||
.default_value(0.0f)
|
||||
.subtype(PROP_ANGLE)
|
||||
.description("The angle of the bokeh");
|
||||
b.add_input<decl::Float>("Roundness"_ustr)
|
||||
.default_value(0.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.description(
|
||||
"Specifies how round the bokeh is, maximum roundness produces a circular bokeh");
|
||||
b.add_input<decl::Float>("Catadioptric Size"_ustr)
|
||||
.default_value(0.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description("Specifies the size of the catadioptric iris, zero means no iris");
|
||||
b.add_input<decl::Float>("Color Shift"_ustr)
|
||||
.default_value(0.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(-1.0f)
|
||||
.max(1.0f)
|
||||
.description(
|
||||
"Specifies the amount of color shifting. 1 means maximum shifting towards blue while -1 "
|
||||
"means maximum shifting toward red");
|
||||
|
||||
b.add_output<decl::Color>("Image"_ustr).structure_type(StructureType::Dynamic);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*node_tree*/, bNode *node)
|
||||
{
|
||||
node->flag |= NODE_PREVIEW;
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class BokehImageOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const Domain domain = this->compute_domain();
|
||||
|
||||
const Result &bokeh_kernel = this->context().cache_manager().bokeh_kernels.get(
|
||||
this->context(),
|
||||
domain.data_size,
|
||||
this->get_flaps(),
|
||||
this->get_angle(),
|
||||
this->get_roundness(),
|
||||
this->get_catadioptric_size(),
|
||||
this->get_color_shift());
|
||||
|
||||
Result &output = this->get_result("Image");
|
||||
output.share_data(bokeh_kernel);
|
||||
}
|
||||
|
||||
Domain compute_domain() override
|
||||
{
|
||||
return Domain(int2(512));
|
||||
}
|
||||
|
||||
int get_flaps()
|
||||
{
|
||||
return math::clamp(this->get_input("Flaps").get_single_value_default<int>(), 3, 24);
|
||||
}
|
||||
|
||||
float get_angle()
|
||||
{
|
||||
return this->get_input("Angle").get_single_value_default<float>();
|
||||
}
|
||||
|
||||
float get_roundness()
|
||||
{
|
||||
return math::clamp(this->get_input("Roundness").get_single_value_default<float>(), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
float get_catadioptric_size()
|
||||
{
|
||||
return math::clamp(
|
||||
this->get_input("Catadioptric Size").get_single_value_default<float>(), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
float get_color_shift()
|
||||
{
|
||||
return math::clamp(
|
||||
this->get_input("Color Shift").get_single_value_default<float>(), -1.0f, 1.0f);
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new BokehImageOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeBokehImage"_ustr, CMP_NODE_BOKEHIMAGE);
|
||||
ntype.ui_name = "Bokeh Image";
|
||||
ntype.ui_description = "Generate image with bokeh shape for use with the Bokeh Blur filter node";
|
||||
ntype.enum_name_legacy = "BOKEHIMAGE";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.flag |= NODE_PREVIEW;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
ntype.default_width = bke::NodeWidth::_160;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_bokeh_image_cc
|
||||
@@ -0,0 +1,257 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_box_mask_cc {
|
||||
|
||||
static const EnumPropertyItem operation_items[] = {
|
||||
{CMP_NODE_MASKTYPE_ADD, "ADD", 0, N_("Add"), ""},
|
||||
{CMP_NODE_MASKTYPE_SUBTRACT, "SUBTRACT", 0, N_("Subtract"), ""},
|
||||
{CMP_NODE_MASKTYPE_MULTIPLY, "MULTIPLY", 0, N_("Multiply"), ""},
|
||||
{CMP_NODE_MASKTYPE_NOT, "NOT", 0, N_("Not"), ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Menu>("Operation"_ustr)
|
||||
.default_value(CMP_NODE_MASKTYPE_ADD)
|
||||
.static_items(operation_items)
|
||||
.optional_label();
|
||||
b.add_input<decl::Float>("Mask"_ustr)
|
||||
.subtype(PROP_FACTOR)
|
||||
.default_value(0.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Float>("Value"_ustr)
|
||||
.subtype(PROP_FACTOR)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Vector>("Position"_ustr)
|
||||
.subtype(PROP_FACTOR)
|
||||
.dimensions(2)
|
||||
.default_value({0.5f, 0.5f})
|
||||
.min(-0.5f)
|
||||
.max(1.5f);
|
||||
b.add_input<decl::Vector>("Size"_ustr)
|
||||
.subtype(PROP_FACTOR)
|
||||
.dimensions(2)
|
||||
.default_value({0.2f, 0.1f})
|
||||
.min(0.0f)
|
||||
.max(1.0f);
|
||||
b.add_input<decl::Float>("Rotation"_ustr).subtype(PROP_ANGLE);
|
||||
|
||||
b.add_output<decl::Float>("Mask"_ustr).structure_type(StructureType::Dynamic);
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
template<CMPNodeMaskType MaskType>
|
||||
static void box_mask(const Result &base_mask,
|
||||
const Result &value_mask,
|
||||
Result &output_mask,
|
||||
const Domain &domain,
|
||||
const float2 &location,
|
||||
const float2 &size,
|
||||
const float cos_angle,
|
||||
const float sin_angle)
|
||||
{
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float2 uv = float2(texel + domain.data_offset) / float2(domain.display_size - int2(1));
|
||||
uv -= location;
|
||||
uv.y *= float(domain.display_size.y) / float(domain.display_size.x);
|
||||
uv = float2x2(float2(cos_angle, -sin_angle), float2(sin_angle, cos_angle)) * uv;
|
||||
bool is_inside = math::abs(uv.x) < size.x && math::abs(uv.y) < size.y;
|
||||
|
||||
float base_mask_value = base_mask.load_pixel<float, true>(texel);
|
||||
float value = value_mask.load_pixel<float, true>(texel);
|
||||
|
||||
float output_mask_value = 0.0f;
|
||||
if constexpr (MaskType == CMP_NODE_MASKTYPE_ADD) {
|
||||
output_mask_value = is_inside ? math::max(base_mask_value, value) : base_mask_value;
|
||||
}
|
||||
else if constexpr (MaskType == CMP_NODE_MASKTYPE_SUBTRACT) {
|
||||
output_mask_value = is_inside ? math::clamp(base_mask_value - value, 0.0f, 1.0f) :
|
||||
base_mask_value;
|
||||
}
|
||||
else if constexpr (MaskType == CMP_NODE_MASKTYPE_MULTIPLY) {
|
||||
output_mask_value = is_inside ? base_mask_value * value : 0.0f;
|
||||
}
|
||||
else if constexpr (MaskType == CMP_NODE_MASKTYPE_NOT) {
|
||||
output_mask_value = is_inside ? (base_mask_value > 0.0f ? 0.0f : value) : base_mask_value;
|
||||
}
|
||||
|
||||
output_mask.store_pixel(texel, output_mask_value);
|
||||
});
|
||||
}
|
||||
|
||||
class BoxMaskOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_gpu();
|
||||
}
|
||||
else {
|
||||
this->execute_cpu();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_gpu()
|
||||
{
|
||||
gpu::Shader *shader = context().get_shader(get_shader_name());
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
|
||||
GPU_shader_uniform_2iv(shader, "display_size", domain.display_size);
|
||||
GPU_shader_uniform_2iv(shader, "data_offset", domain.data_offset);
|
||||
|
||||
GPU_shader_uniform_2fv(shader, "location", get_location());
|
||||
GPU_shader_uniform_2fv(shader, "size", get_size() / 2.0f);
|
||||
GPU_shader_uniform_1f(shader, "cos_angle", std::cos(get_angle()));
|
||||
GPU_shader_uniform_1f(shader, "sin_angle", std::sin(get_angle()));
|
||||
|
||||
const Result &input_mask = get_input("Mask");
|
||||
input_mask.bind_as_texture(shader, "base_mask_tx");
|
||||
|
||||
const Result &value = get_input("Value");
|
||||
value.bind_as_texture(shader, "mask_value_tx");
|
||||
|
||||
Result &output_mask = get_result("Mask");
|
||||
output_mask.allocate_texture(domain);
|
||||
output_mask.bind_as_image(shader, "output_mask_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
input_mask.unbind_as_texture();
|
||||
value.unbind_as_texture();
|
||||
output_mask.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
const char *get_shader_name()
|
||||
{
|
||||
switch (this->get_operation()) {
|
||||
case CMP_NODE_MASKTYPE_ADD:
|
||||
return "compositor_box_mask_add";
|
||||
case CMP_NODE_MASKTYPE_SUBTRACT:
|
||||
return "compositor_box_mask_subtract";
|
||||
case CMP_NODE_MASKTYPE_MULTIPLY:
|
||||
return "compositor_box_mask_multiply";
|
||||
case CMP_NODE_MASKTYPE_NOT:
|
||||
return "compositor_box_mask_not";
|
||||
}
|
||||
|
||||
return "compositor_box_mask_add";
|
||||
}
|
||||
|
||||
void execute_cpu()
|
||||
{
|
||||
const Result &base_mask = this->get_input("Mask");
|
||||
const Result &value_mask = this->get_input("Value");
|
||||
|
||||
Result &output_mask = this->get_result("Mask");
|
||||
const Domain domain = this->compute_domain();
|
||||
output_mask.allocate_texture(domain);
|
||||
|
||||
const float2 location = this->get_location();
|
||||
const float2 size = this->get_size() / 2.0f;
|
||||
const float cos_angle = math::cos(this->get_angle());
|
||||
const float sin_angle = math::sin(this->get_angle());
|
||||
|
||||
switch (this->get_operation()) {
|
||||
case CMP_NODE_MASKTYPE_ADD:
|
||||
box_mask<CMP_NODE_MASKTYPE_ADD>(
|
||||
base_mask, value_mask, output_mask, domain, location, size, cos_angle, sin_angle);
|
||||
return;
|
||||
case CMP_NODE_MASKTYPE_SUBTRACT:
|
||||
box_mask<CMP_NODE_MASKTYPE_SUBTRACT>(
|
||||
base_mask, value_mask, output_mask, domain, location, size, cos_angle, sin_angle);
|
||||
return;
|
||||
case CMP_NODE_MASKTYPE_MULTIPLY:
|
||||
box_mask<CMP_NODE_MASKTYPE_MULTIPLY>(
|
||||
base_mask, value_mask, output_mask, domain, location, size, cos_angle, sin_angle);
|
||||
return;
|
||||
case CMP_NODE_MASKTYPE_NOT:
|
||||
box_mask<CMP_NODE_MASKTYPE_NOT>(
|
||||
base_mask, value_mask, output_mask, domain, location, size, cos_angle, sin_angle);
|
||||
return;
|
||||
}
|
||||
|
||||
box_mask<CMP_NODE_MASKTYPE_ADD>(
|
||||
base_mask, value_mask, output_mask, domain, location, size, cos_angle, sin_angle);
|
||||
}
|
||||
|
||||
Domain compute_domain() override
|
||||
{
|
||||
if (get_input("Mask").is_single_value()) {
|
||||
return context().get_compositing_domain();
|
||||
}
|
||||
return get_input("Mask").domain();
|
||||
}
|
||||
|
||||
float2 get_location()
|
||||
{
|
||||
return this->get_input("Position").get_single_value_default<float2>();
|
||||
}
|
||||
|
||||
float2 get_size()
|
||||
{
|
||||
return math::max(float2(0.0f), this->get_input("Size").get_single_value_default<float2>());
|
||||
}
|
||||
|
||||
float get_angle()
|
||||
{
|
||||
return this->get_input("Rotation").get_single_value_default<float>();
|
||||
}
|
||||
|
||||
CMPNodeMaskType get_operation()
|
||||
{
|
||||
return CMPNodeMaskType(
|
||||
this->get_input("Operation").get_single_value_default<MenuValue>().value);
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new BoxMaskOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeBoxMask"_ustr, CMP_NODE_MASK_BOX);
|
||||
ntype.ui_name = "Box Mask";
|
||||
ntype.ui_description = "Create rectangular mask suitable for use as a simple matte";
|
||||
ntype.enum_name_legacy = "BOXMASK";
|
||||
ntype.nclass = NODE_CLASS_MATTE;
|
||||
ntype.declare = node_declare;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_box_mask_cc
|
||||
@@ -0,0 +1,98 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "COM_result.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_brightness_contrast_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Color>("Image"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f}).hide_value();
|
||||
b.add_output<decl::Color>("Image"_ustr).align_with_previous();
|
||||
|
||||
b.add_input<decl::Float>("Brightness"_ustr, "Bright"_ustr).min(-100.0f).max(100.0f);
|
||||
b.add_input<decl::Float>("Contrast"_ustr).min(-100.0f).max(100.0f);
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_composite_bright_contrast", inputs, outputs);
|
||||
}
|
||||
|
||||
/* The algorithm is by Werner D. Streidt, extracted of OpenCV `demhist.c`:
|
||||
* http://visca.com/ffactory/archives/5-99/msg00021.html */
|
||||
static float4 brightness_and_contrast(const float4 &color,
|
||||
const float brightness,
|
||||
const float contrast)
|
||||
{
|
||||
float scaled_brightness = brightness / 100.0f;
|
||||
float delta = contrast / 200.0f;
|
||||
|
||||
float multiplier, offset;
|
||||
if (contrast > 0.0f) {
|
||||
multiplier = 1.0f - delta * 2.0f;
|
||||
multiplier = 1.0f / math::max(multiplier, std::numeric_limits<float>::epsilon());
|
||||
offset = multiplier * (scaled_brightness - delta);
|
||||
}
|
||||
else {
|
||||
delta *= -1.0f;
|
||||
multiplier = math::max(1.0f - delta * 2.0f, 0.0f);
|
||||
offset = multiplier * scaled_brightness + delta;
|
||||
}
|
||||
|
||||
return float4(color.xyz() * multiplier + offset, color.w);
|
||||
}
|
||||
|
||||
using compositor::Color;
|
||||
|
||||
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto function = mf::build::SI3_SO<Color, float, float, Color>(
|
||||
"Brightness And Contrast",
|
||||
[](const Color &color, const float brightness, const float contrast) -> Color {
|
||||
return Color(brightness_and_contrast(float4(color), brightness, contrast));
|
||||
},
|
||||
mf::build::exec_presets::SomeSpanOrSingle<0>());
|
||||
builder.set_matching_fn(function);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeBrightContrast"_ustr, CMP_NODE_BRIGHTCONTRAST);
|
||||
ntype.ui_name = "Brightness/Contrast";
|
||||
ntype.ui_description = "Adjust brightness and contrast";
|
||||
ntype.enum_name_legacy = "BRIGHTCONTRAST";
|
||||
ntype.nclass = NODE_CLASS_OP_COLOR;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_brightness_contrast_cc
|
||||
@@ -0,0 +1,460 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
#include "NOD_socket_usage_inference.hh"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "COM_result.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_channel_key_cc {
|
||||
|
||||
static const EnumPropertyItem color_space_items[] = {
|
||||
{CMP_NODE_CHANNEL_MATTE_CS_RGB, "RGB", 0, N_("RGB"), N_("RGB (Red, Green, Blue) color space")},
|
||||
{CMP_NODE_CHANNEL_MATTE_CS_HSV,
|
||||
"HSV",
|
||||
0,
|
||||
N_("HSV"),
|
||||
N_("HSV (Hue, Saturation, Value) color space")},
|
||||
{CMP_NODE_CHANNEL_MATTE_CS_YUV,
|
||||
"YUV",
|
||||
0,
|
||||
N_("YUV"),
|
||||
N_("YUV (Y - luma, U V - chroma) color space")},
|
||||
{CMP_NODE_CHANNEL_MATTE_CS_YCC,
|
||||
"YCC",
|
||||
0,
|
||||
N_("YCbCr"),
|
||||
N_("YCbCr (Y - luma, Cb - blue-difference chroma, Cr - red-difference chroma) color space")},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
enum class RGBChannel : uint8_t {
|
||||
R = 0,
|
||||
G = 1,
|
||||
B = 2,
|
||||
};
|
||||
|
||||
static const EnumPropertyItem rgb_channel_items[] = {
|
||||
{int(RGBChannel::R), "R", 0, "R", ""},
|
||||
{int(RGBChannel::G), "G", 0, "G", ""},
|
||||
{int(RGBChannel::B), "B", 0, "B", ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
enum class HSVChannel : uint8_t {
|
||||
H = 0,
|
||||
S = 1,
|
||||
V = 2,
|
||||
};
|
||||
|
||||
static const EnumPropertyItem hsv_channel_items[] = {
|
||||
{int(HSVChannel::H), "H", 0, "H", ""},
|
||||
{int(HSVChannel::S), "S", 0, "S", ""},
|
||||
{int(HSVChannel::V), "V", 0, "V", ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
enum class YUVChannel : uint8_t {
|
||||
Y = 0,
|
||||
U = 1,
|
||||
V = 2,
|
||||
};
|
||||
|
||||
static const EnumPropertyItem yuv_channel_items[] = {
|
||||
{int(YUVChannel::Y), "Y", 0, "Y", ""},
|
||||
{int(YUVChannel::U), "U", 0, "U", ""},
|
||||
{int(YUVChannel::V), "V", 0, "V", ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
enum class YCbCrChannel : uint8_t {
|
||||
Y = 0,
|
||||
Cb = 1,
|
||||
Cr = 2,
|
||||
};
|
||||
|
||||
static const EnumPropertyItem ycbcr_channel_items[] = {
|
||||
{int(YCbCrChannel::Y), "Y", 0, "Y", ""},
|
||||
{int(YCbCrChannel::Cb), "CB", 0, "Cb", ""},
|
||||
{int(YCbCrChannel::Cr), "CR", 0, "Cr", ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static const EnumPropertyItem limit_method_items[] = {
|
||||
{CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE,
|
||||
"SINGLE",
|
||||
0,
|
||||
N_("Single"),
|
||||
N_("Limit by single channel")},
|
||||
{CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_MAX,
|
||||
"MAX",
|
||||
0,
|
||||
N_("Max"),
|
||||
N_("Limit by maximum of other channels")},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Color>("Image"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f}).hide_value();
|
||||
b.add_output<decl::Color>("Image"_ustr).align_with_previous();
|
||||
b.add_output<decl::Float>("Matte"_ustr);
|
||||
|
||||
b.add_input<decl::Float>("Minimum"_ustr)
|
||||
.default_value(0.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description("Channel values lower than this minimum are keyed");
|
||||
b.add_input<decl::Float>("Maximum"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description("Channel values higher than this maximum are not keyed");
|
||||
|
||||
b.add_input<decl::Menu>("Color Space"_ustr)
|
||||
.default_value(CMP_NODE_CHANNEL_MATTE_CS_RGB)
|
||||
.static_items(color_space_items)
|
||||
.expanded()
|
||||
.optional_label();
|
||||
b.add_input<decl::Menu>("RGB Key Channel"_ustr)
|
||||
.default_value(RGBChannel::G)
|
||||
.static_items(rgb_channel_items)
|
||||
.expanded()
|
||||
.translation_context(BLT_I18NCONTEXT_COLOR)
|
||||
.usage_by_menu("Color Space"_ustr, CMP_NODE_CHANNEL_MATTE_CS_RGB)
|
||||
.optional_label();
|
||||
b.add_input<decl::Menu>("HSV Key Channel"_ustr)
|
||||
.default_value(HSVChannel::H)
|
||||
.static_items(hsv_channel_items)
|
||||
.expanded()
|
||||
.translation_context(BLT_I18NCONTEXT_COLOR)
|
||||
.usage_by_menu("Color Space"_ustr, CMP_NODE_CHANNEL_MATTE_CS_HSV)
|
||||
.optional_label();
|
||||
b.add_input<decl::Menu>("YUV Key Channel"_ustr)
|
||||
.default_value(YUVChannel::V)
|
||||
.static_items(yuv_channel_items)
|
||||
.expanded()
|
||||
.usage_by_menu("Color Space"_ustr, CMP_NODE_CHANNEL_MATTE_CS_YUV)
|
||||
.optional_label();
|
||||
b.add_input<decl::Menu>("YCbCr Key Channel"_ustr)
|
||||
.default_value(YCbCrChannel::Cr)
|
||||
.static_items(ycbcr_channel_items)
|
||||
.expanded()
|
||||
.usage_by_menu("Color Space"_ustr, CMP_NODE_CHANNEL_MATTE_CS_YCC)
|
||||
.optional_label();
|
||||
|
||||
b.add_input<decl::Menu>("Limit Method"_ustr)
|
||||
.default_value(CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_MAX)
|
||||
.static_items(limit_method_items)
|
||||
.expanded()
|
||||
.optional_label();
|
||||
b.add_input<decl::Menu>("RGB Limit Channel"_ustr)
|
||||
.default_value(RGBChannel::R)
|
||||
.static_items(rgb_channel_items)
|
||||
.expanded()
|
||||
.optional_label()
|
||||
.make_available([](bNode &node) {
|
||||
bNodeSocket &limit_method_socket = *bke::node_find_socket(
|
||||
node, SOCK_IN, "Limit Method"_ustr);
|
||||
limit_method_socket.default_value_typed<bNodeSocketValueMenu>()->value =
|
||||
CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE;
|
||||
|
||||
bNodeSocket &color_space_socket = *bke::node_find_socket(
|
||||
node, SOCK_IN, "Color Space"_ustr);
|
||||
color_space_socket.default_value_typed<bNodeSocketValueMenu>()->value =
|
||||
CMP_NODE_CHANNEL_MATTE_CS_RGB;
|
||||
})
|
||||
.usage_inference(
|
||||
[](const socket_usage_inference::SocketUsageParams ¶ms) -> std::optional<bool> {
|
||||
return params.menu_input_may_be("Limit Method"_ustr,
|
||||
CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE) &&
|
||||
params.menu_input_may_be("Color Space"_ustr, CMP_NODE_CHANNEL_MATTE_CS_RGB);
|
||||
});
|
||||
b.add_input<decl::Menu>("HSV Limit Channel"_ustr)
|
||||
.default_value(HSVChannel::S)
|
||||
.static_items(hsv_channel_items)
|
||||
.expanded()
|
||||
.optional_label()
|
||||
.make_available([](bNode &node) {
|
||||
bNodeSocket &limit_method_socket = *bke::node_find_socket(
|
||||
node, SOCK_IN, "Limit Method"_ustr);
|
||||
limit_method_socket.default_value_typed<bNodeSocketValueMenu>()->value =
|
||||
CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE;
|
||||
|
||||
bNodeSocket &color_space_socket = *bke::node_find_socket(
|
||||
node, SOCK_IN, "Color Space"_ustr);
|
||||
color_space_socket.default_value_typed<bNodeSocketValueMenu>()->value =
|
||||
CMP_NODE_CHANNEL_MATTE_CS_HSV;
|
||||
})
|
||||
.usage_inference(
|
||||
[](const socket_usage_inference::SocketUsageParams ¶ms) -> std::optional<bool> {
|
||||
return params.menu_input_may_be("Limit Method"_ustr,
|
||||
CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE) &&
|
||||
params.menu_input_may_be("Color Space"_ustr, CMP_NODE_CHANNEL_MATTE_CS_HSV);
|
||||
});
|
||||
b.add_input<decl::Menu>("YUV Limit Channel"_ustr)
|
||||
.default_value(YUVChannel::U)
|
||||
.static_items(yuv_channel_items)
|
||||
.expanded()
|
||||
.optional_label()
|
||||
.make_available([](bNode &node) {
|
||||
bNodeSocket &limit_method_socket = *bke::node_find_socket(
|
||||
node, SOCK_IN, "Limit Method"_ustr);
|
||||
limit_method_socket.default_value_typed<bNodeSocketValueMenu>()->value =
|
||||
CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE;
|
||||
|
||||
bNodeSocket &color_space_socket = *bke::node_find_socket(
|
||||
node, SOCK_IN, "Color Space"_ustr);
|
||||
color_space_socket.default_value_typed<bNodeSocketValueMenu>()->value =
|
||||
CMP_NODE_CHANNEL_MATTE_CS_YUV;
|
||||
})
|
||||
.usage_inference(
|
||||
[](const socket_usage_inference::SocketUsageParams ¶ms) -> std::optional<bool> {
|
||||
return params.menu_input_may_be("Limit Method"_ustr,
|
||||
CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE) &&
|
||||
params.menu_input_may_be("Color Space"_ustr, CMP_NODE_CHANNEL_MATTE_CS_YUV);
|
||||
});
|
||||
b.add_input<decl::Menu>("YCbCr Limit Channel"_ustr)
|
||||
.default_value(YCbCrChannel::Cb)
|
||||
.static_items(ycbcr_channel_items)
|
||||
.expanded()
|
||||
.optional_label()
|
||||
.make_available([](bNode &node) {
|
||||
bNodeSocket &limit_method_socket = *bke::node_find_socket(
|
||||
node, SOCK_IN, "Limit Method"_ustr);
|
||||
limit_method_socket.default_value_typed<bNodeSocketValueMenu>()->value =
|
||||
CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE;
|
||||
|
||||
bNodeSocket &color_space_socket = *bke::node_find_socket(
|
||||
node, SOCK_IN, "Color Space"_ustr);
|
||||
color_space_socket.default_value_typed<bNodeSocketValueMenu>()->value =
|
||||
CMP_NODE_CHANNEL_MATTE_CS_YCC;
|
||||
})
|
||||
.usage_inference(
|
||||
[](const socket_usage_inference::SocketUsageParams ¶ms) -> std::optional<bool> {
|
||||
return params.menu_input_may_be("Limit Method"_ustr,
|
||||
CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_SINGLE) &&
|
||||
params.menu_input_may_be("Color Space"_ustr, CMP_NODE_CHANNEL_MATTE_CS_YCC);
|
||||
});
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*ntree*/, bNode *node)
|
||||
{
|
||||
/* Unused, but allocated for forward compatibility. */
|
||||
node->storage = MEM_new<NodeChroma>(__func__);
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_composite_channel_matte", inputs, outputs);
|
||||
}
|
||||
|
||||
static float3 compute_channels(const float4 color, const CMPNodeChannelMatteColorSpace color_space)
|
||||
{
|
||||
switch (color_space) {
|
||||
case CMP_NODE_CHANNEL_MATTE_CS_RGB: {
|
||||
return color.xyz();
|
||||
}
|
||||
case CMP_NODE_CHANNEL_MATTE_CS_HSV: {
|
||||
float4 hsv;
|
||||
rgb_to_hsv_v(color, hsv);
|
||||
return hsv.xyz();
|
||||
}
|
||||
case CMP_NODE_CHANNEL_MATTE_CS_YUV: {
|
||||
float4 yuv;
|
||||
rgb_to_yuv(color.x, color.y, color.z, &yuv.x, &yuv.y, &yuv.z, BLI_YUV_ITU_BT709);
|
||||
return yuv.xyz();
|
||||
}
|
||||
case CMP_NODE_CHANNEL_MATTE_CS_YCC: {
|
||||
float4 ycc;
|
||||
rgb_to_ycc(color.x, color.y, color.z, &ycc.x, &ycc.y, &ycc.z, BLI_YCC_ITU_BT709);
|
||||
ycc /= 255.0f;
|
||||
return ycc.xyz();
|
||||
}
|
||||
}
|
||||
|
||||
return color.xyz();
|
||||
}
|
||||
|
||||
static int get_channel_index(const CMPNodeChannelMatteColorSpace color_space,
|
||||
const int rgb_channel,
|
||||
const int hsv_channel,
|
||||
const int yuv_channel,
|
||||
const int ycc_channel)
|
||||
{
|
||||
switch (color_space) {
|
||||
case CMP_NODE_CHANNEL_MATTE_CS_RGB:
|
||||
return rgb_channel;
|
||||
case CMP_NODE_CHANNEL_MATTE_CS_HSV:
|
||||
return hsv_channel;
|
||||
case CMP_NODE_CHANNEL_MATTE_CS_YUV:
|
||||
return yuv_channel;
|
||||
case CMP_NODE_CHANNEL_MATTE_CS_YCC:
|
||||
return ycc_channel;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Compute the indices of the channels used to compute the limit value. We always assume the limit
|
||||
* algorithm is Max, if it is a single limit channel, store it in both limit channels, because
|
||||
* the maximum of two identical values is the same value. */
|
||||
static int2 compute_limit_channels(const CMPNodeChannelMatteLimitAlgorithm limit_method,
|
||||
const int matte_channel,
|
||||
const int limit_channel)
|
||||
{
|
||||
/* If the algorithm is Max, store the indices of the other two channels other than the matte
|
||||
* channel. */
|
||||
if (limit_method == CMP_NODE_CHANNEL_MATTE_LIMIT_ALGORITHM_MAX) {
|
||||
return int2((matte_channel + 1) % 3, (matte_channel + 2) % 3);
|
||||
}
|
||||
|
||||
/* If the algorithm is Single, store the index of the limit channel in both channels. */
|
||||
return int2(limit_channel);
|
||||
}
|
||||
|
||||
static void channel_key(const float4 color,
|
||||
const float minimum,
|
||||
const float maximum,
|
||||
const CMPNodeChannelMatteColorSpace color_space,
|
||||
const int rgb_key_channel,
|
||||
const int hsv_key_channel,
|
||||
const int yuv_key_channel,
|
||||
const int ycc_key_channel,
|
||||
const CMPNodeChannelMatteLimitAlgorithm limit_method,
|
||||
const int rgb_limit_channel,
|
||||
const int hsv_limit_channel,
|
||||
const int yuv_limit_channel,
|
||||
const int ycc_limit_channel,
|
||||
float4 &output_color,
|
||||
float &matte)
|
||||
{
|
||||
const float3 channels = compute_channels(color, color_space);
|
||||
const int matte_channel = get_channel_index(
|
||||
color_space, rgb_key_channel, hsv_key_channel, yuv_key_channel, ycc_key_channel);
|
||||
const int limit_channel = get_channel_index(
|
||||
color_space, rgb_limit_channel, hsv_limit_channel, yuv_limit_channel, ycc_limit_channel);
|
||||
const int2 limit_channels = compute_limit_channels(limit_method, matte_channel, limit_channel);
|
||||
|
||||
float matte_value = channels[matte_channel];
|
||||
float limit_value = math::max(channels[limit_channels.x], channels[limit_channels.y]);
|
||||
|
||||
float alpha = 1.0f - (matte_value - limit_value);
|
||||
if (alpha > maximum) {
|
||||
alpha = color.w;
|
||||
}
|
||||
else if (alpha < minimum) {
|
||||
alpha = 0.0f;
|
||||
}
|
||||
else {
|
||||
alpha = (alpha - minimum) / (maximum - minimum);
|
||||
}
|
||||
|
||||
matte = math::min(alpha, color.w);
|
||||
output_color = color * matte;
|
||||
}
|
||||
|
||||
using compositor::Color;
|
||||
|
||||
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto function =
|
||||
mf::build::detail::build_multi_function_with_n_inputs_two_outputs<Color, float>(
|
||||
"Channel Key",
|
||||
[=](const Color &color,
|
||||
const float &minimum,
|
||||
const float &maximum,
|
||||
const MenuValue &color_space,
|
||||
const MenuValue &rgb_key_channel,
|
||||
const MenuValue &hsv_key_channel,
|
||||
const MenuValue &yuv_key_channel,
|
||||
const MenuValue &ycc_key_channel,
|
||||
const MenuValue &limit_method,
|
||||
const MenuValue &rgb_limit_channel,
|
||||
const MenuValue &hsv_limit_channel,
|
||||
const MenuValue &yuv_limit_channel,
|
||||
const MenuValue &ycc_limit_channel,
|
||||
Color &output_color,
|
||||
float &matte) -> void {
|
||||
float4 out_color;
|
||||
channel_key(float4(color),
|
||||
minimum,
|
||||
maximum,
|
||||
CMPNodeChannelMatteColorSpace(color_space.value),
|
||||
rgb_key_channel.value,
|
||||
hsv_key_channel.value,
|
||||
yuv_key_channel.value,
|
||||
ycc_key_channel.value,
|
||||
CMPNodeChannelMatteLimitAlgorithm(limit_method.value),
|
||||
rgb_limit_channel.value,
|
||||
hsv_limit_channel.value,
|
||||
yuv_limit_channel.value,
|
||||
ycc_limit_channel.value,
|
||||
out_color,
|
||||
matte);
|
||||
output_color = Color(out_color);
|
||||
},
|
||||
mf::build::exec_presets::SomeSpanOrSingle<0>(),
|
||||
TypeSequence<Color,
|
||||
float,
|
||||
float,
|
||||
MenuValue,
|
||||
MenuValue,
|
||||
MenuValue,
|
||||
MenuValue,
|
||||
MenuValue,
|
||||
MenuValue,
|
||||
MenuValue,
|
||||
MenuValue,
|
||||
MenuValue,
|
||||
MenuValue>());
|
||||
|
||||
builder.set_matching_fn(function);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeChannelMatte"_ustr, CMP_NODE_CHANNEL_MATTE);
|
||||
ntype.ui_name = "Channel Key";
|
||||
ntype.ui_description = "Create matte based on differences in color channels";
|
||||
ntype.enum_name_legacy = "CHANNEL_MATTE";
|
||||
ntype.nclass = NODE_CLASS_MATTE;
|
||||
ntype.declare = node_declare;
|
||||
ntype.flag |= NODE_PREVIEW;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeChroma", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_channel_key_cc
|
||||
@@ -0,0 +1,151 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "COM_result.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_chroma_key_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Color>("Image"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f}).hide_value();
|
||||
b.add_output<decl::Color>("Image"_ustr).align_with_previous();
|
||||
b.add_output<decl::Float>("Matte"_ustr);
|
||||
|
||||
b.add_input<decl::Color>("Key Color"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
b.add_input<decl::Float>("Minimum"_ustr)
|
||||
.default_value(DEG2RADF(10.0f))
|
||||
.subtype(PROP_ANGLE)
|
||||
.description(
|
||||
"If the angle between the color and the key color in CrCb space is less than this "
|
||||
"minimum angle, it is keyed");
|
||||
b.add_input<decl::Float>("Maximum"_ustr)
|
||||
.default_value(DEG2RADF(30.0f))
|
||||
.subtype(PROP_ANGLE)
|
||||
.description(
|
||||
"If the angle between the color and the key color in CrCb space is larger than this "
|
||||
"maximum angle, it is not keyed");
|
||||
b.add_input<decl::Float>("Falloff"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.description(
|
||||
"Controls the falloff between keyed and non-keyed values. 0 means completely sharp and "
|
||||
"1 means completely smooth");
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_composite_chroma_matte", inputs, outputs);
|
||||
}
|
||||
|
||||
/* Algorithm from the book Video Demystified. Chapter 7. Chroma Keying. */
|
||||
static void chroma_matte(const float4 &color,
|
||||
const float4 &key,
|
||||
const float &minimum,
|
||||
const float &maximum,
|
||||
const float &falloff,
|
||||
float4 &result,
|
||||
float &matte)
|
||||
{
|
||||
float3 color_ycca;
|
||||
rgb_to_ycc(
|
||||
color.x, color.y, color.z, &color_ycca.x, &color_ycca.y, &color_ycca.z, BLI_YCC_ITU_BT709);
|
||||
color_ycca /= 255.0f;
|
||||
float3 key_ycca;
|
||||
rgb_to_ycc(key.x, key.y, key.z, &key_ycca.x, &key_ycca.y, &key_ycca.z, BLI_YCC_ITU_BT709);
|
||||
key_ycca /= 255.0f;
|
||||
|
||||
/* Normalize the CrCb components into the [-1, 1] range. */
|
||||
float2 color_cc = color_ycca.yz() * 2.0f - 1.0f;
|
||||
float2 key_cc = math::normalize(key_ycca.yz() * 2.0f - 1.0f);
|
||||
|
||||
/* Rotate the color onto the space of the key such that x axis of the color space passes
|
||||
* through the key color. */
|
||||
color_cc = math::from_direction(key_cc * float2(1.0f, -1.0f)) * color_cc;
|
||||
|
||||
/* Compute foreground key. If positive, the value is in the [0, 1] range. */
|
||||
float foreground_key = color_cc.x - (math::abs(color_cc.y) / math::tan(maximum / 2.0f));
|
||||
|
||||
/* Negative foreground key values retain the original alpha. Positive values are scaled by the
|
||||
* falloff, while colors that make an angle less than the minimum angle get a zero alpha. */
|
||||
float alpha = color.w;
|
||||
if (foreground_key > 0.0f) {
|
||||
alpha = 1.0f - (foreground_key / falloff);
|
||||
|
||||
if (math::abs(math::atan2(color_cc.y, color_cc.x)) < (minimum / 2.0f)) {
|
||||
alpha = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute output. */
|
||||
matte = math::min(alpha, color.w);
|
||||
result = color * matte;
|
||||
}
|
||||
|
||||
using compositor::Color;
|
||||
|
||||
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
builder.construct_and_set_matching_fn_cb([=]() {
|
||||
return mf::build::SI5_SO2<Color, Color, float, float, float, Color, float>(
|
||||
"Chroma Key",
|
||||
[=](const Color &color,
|
||||
const Color &key_color,
|
||||
const float &minimum,
|
||||
const float &maximum,
|
||||
const float &falloff,
|
||||
Color &output_color,
|
||||
float &matte) -> void {
|
||||
float4 out_color;
|
||||
chroma_matte(
|
||||
float4(color), float4(key_color), minimum, maximum, falloff, out_color, matte);
|
||||
output_color = Color(out_color);
|
||||
},
|
||||
mf::build::exec_presets::SomeSpanOrSingle<0, 1>());
|
||||
});
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeChromaMatte"_ustr, CMP_NODE_CHROMA_MATTE);
|
||||
ntype.ui_name = "Chroma Key";
|
||||
ntype.ui_description = "Create matte based on chroma values";
|
||||
ntype.enum_name_legacy = "CHROMA_MATTE";
|
||||
ntype.nclass = NODE_CLASS_MATTE;
|
||||
ntype.declare = node_declare;
|
||||
ntype.flag |= NODE_PREVIEW;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.default_width = bke::NodeWidth::_160;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_chroma_key_cc
|
||||
@@ -0,0 +1,73 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "COM_node_operation.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_color_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_output<decl::Color>("Color"_ustr)
|
||||
.default_value({0.5f, 0.5f, 0.5f, 1.0f})
|
||||
.custom_draw([](CustomSocketDrawParams ¶ms) {
|
||||
params.layout.alignment_set(ui::LayoutAlign::Expand);
|
||||
ui::Layout &col = params.layout.column(false);
|
||||
template_color_picker(
|
||||
&col, ¶ms.socket_ptr, "default_value", true, false, false, false);
|
||||
col.prop(¶ms.socket_ptr,
|
||||
"default_value",
|
||||
ui::ITEM_R_SLIDER | ui::ITEM_R_SPLIT_EMPTY_NAME,
|
||||
"",
|
||||
ICON_NONE);
|
||||
});
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class RGBOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
Result &result = get_result("Color");
|
||||
result.allocate_single_value();
|
||||
|
||||
const bNodeSocket *socket = static_cast<const bNodeSocket *>(node().outputs.first);
|
||||
Color color = Color(static_cast<const bNodeSocketValueRGBA *>(socket->default_value)->value);
|
||||
|
||||
result.set_single_value(color);
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new RGBOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeRGB"_ustr, CMP_NODE_RGB);
|
||||
ntype.ui_name = "Color";
|
||||
ntype.ui_description = "A color picker";
|
||||
ntype.enum_name_legacy = "RGB";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_color_cc
|
||||
@@ -0,0 +1,550 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_color.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "COM_result.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_color_balance_cc {
|
||||
|
||||
static const EnumPropertyItem type_items[] = {
|
||||
{CMP_NODE_COLOR_BALANCE_LGG, "LIFT_GAMMA_GAIN", 0, N_("Lift/Gamma/Gain"), ""},
|
||||
{CMP_NODE_COLOR_BALANCE_ASC_CDL,
|
||||
"OFFSET_POWER_SLOPE",
|
||||
0,
|
||||
N_("Offset/Power/Slope (ASC-CDL)"),
|
||||
N_("ASC-CDL standard color correction")},
|
||||
{CMP_NODE_COLOR_BALANCE_WHITEPOINT,
|
||||
"WHITEPOINT",
|
||||
0,
|
||||
N_("White Point"),
|
||||
N_("Chromatic adaption from a different white point")},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
|
||||
b.add_input<decl::Color>("Image"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f}).hide_value();
|
||||
b.add_output<decl::Color>("Image"_ustr).align_with_previous();
|
||||
|
||||
b.add_input<decl::Float>("Factor"_ustr, "Fac"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR);
|
||||
|
||||
b.add_input<decl::Menu>("Type"_ustr)
|
||||
.default_value(CMP_NODE_COLOR_BALANCE_LGG)
|
||||
.static_items(type_items)
|
||||
.optional_label();
|
||||
|
||||
b.add_input<decl::Float>("Lift"_ustr, "Base Lift"_ustr)
|
||||
.default_value(0.0f)
|
||||
.min(-1.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_LGG)
|
||||
.description("Correction for shadows");
|
||||
b.add_input<decl::Color>("Lift"_ustr, "Color Lift"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_LGG)
|
||||
.description("Correction for shadows");
|
||||
b.add_input<decl::Float>("Gamma"_ustr, "Base Gamma"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(2.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_LGG)
|
||||
.description("Correction for midtones");
|
||||
b.add_input<decl::Color>("Gamma"_ustr, "Color Gamma"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_LGG)
|
||||
.description("Correction for midtones");
|
||||
b.add_input<decl::Float>("Gain"_ustr, "Base Gain"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(2.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_LGG)
|
||||
.description("Correction for highlights");
|
||||
b.add_input<decl::Color>("Gain"_ustr, "Color Gain"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_LGG)
|
||||
.description("Correction for highlights");
|
||||
|
||||
b.add_input<decl::Float>("Offset"_ustr, "Base Offset"_ustr)
|
||||
.default_value(0.0f)
|
||||
.min(-1.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_ASC_CDL)
|
||||
.description("Correction for shadows");
|
||||
b.add_input<decl::Color>("Offset"_ustr, "Color Offset"_ustr)
|
||||
.default_value({0.0f, 0.0f, 0.0f, 1.0f})
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_ASC_CDL)
|
||||
.description("Correction for shadows");
|
||||
b.add_input<decl::Float>("Power"_ustr, "Base Power"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(2.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_ASC_CDL)
|
||||
.description("Correction for midtones");
|
||||
b.add_input<decl::Color>("Power"_ustr, "Color Power"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_ASC_CDL)
|
||||
.description("Correction for midtones");
|
||||
b.add_input<decl::Float>("Slope"_ustr, "Base Slope"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(2.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_ASC_CDL)
|
||||
.description("Correction for highlights");
|
||||
b.add_input<decl::Color>("Slope"_ustr, "Color Slope"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_ASC_CDL)
|
||||
.description("Correction for highlights");
|
||||
|
||||
PanelDeclarationBuilder &input_panel = b.add_panel("Input"_ustr);
|
||||
input_panel.add_input<decl::Float>("Temperature"_ustr, "Input Temperature"_ustr)
|
||||
.default_value(6500.0f)
|
||||
.subtype(PROP_COLOR_TEMPERATURE)
|
||||
.min(1800.0f)
|
||||
.max(100000.0f)
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_WHITEPOINT)
|
||||
.description("Color temperature of the input's white point");
|
||||
input_panel.add_input<decl::Float>("Tint"_ustr, "Input Tint"_ustr)
|
||||
.default_value(10.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(-150.0f)
|
||||
.max(150.0f)
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_WHITEPOINT)
|
||||
.description("Color tint of the input's white point (the default of 10 matches daylight)");
|
||||
input_panel.add_layout([](ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr) {
|
||||
ui::Layout &split = layout.split(0.2f, false);
|
||||
template_crypto_picker(&split, ptr, "input_whitepoint", ICON_EYEDROPPER);
|
||||
});
|
||||
|
||||
PanelDeclarationBuilder &output_panel = b.add_panel("Output"_ustr);
|
||||
output_panel.add_input<decl::Float>("Temperature"_ustr, "Output Temperature"_ustr)
|
||||
.default_value(6500.0f)
|
||||
.subtype(PROP_COLOR_TEMPERATURE)
|
||||
.min(1800.0f)
|
||||
.max(100000.0f)
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_WHITEPOINT)
|
||||
.description("Color temperature of the output's white point");
|
||||
output_panel.add_input<decl::Float>("Tint"_ustr, "Output Tint"_ustr)
|
||||
.default_value(10.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(-150.0f)
|
||||
.max(150.0f)
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_COLOR_BALANCE_WHITEPOINT)
|
||||
.description("Color tint of the output's white point (the default of 10 matches daylight)");
|
||||
output_panel.add_layout([](ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr) {
|
||||
ui::Layout &split = layout.split(0.2f, false);
|
||||
template_crypto_picker(&split, ptr, "output_whitepoint", ICON_EYEDROPPER);
|
||||
});
|
||||
}
|
||||
|
||||
static float3x3 get_white_point_matrix(const float input_temperature,
|
||||
const float input_tint,
|
||||
const float output_temperature,
|
||||
const float output_tint)
|
||||
{
|
||||
const float3x3 scene_to_xyz = IMB_colormanagement_get_scene_linear_to_xyz();
|
||||
const float3x3 xyz_to_scene = IMB_colormanagement_get_xyz_to_scene_linear();
|
||||
const float3 input = math::whitepoint_from_temp_tint(input_temperature, input_tint);
|
||||
const float3 output = math::whitepoint_from_temp_tint(output_temperature, output_tint);
|
||||
const float3x3 adaption = math::chromatic_adaption_matrix(input, output);
|
||||
return xyz_to_scene * adaption * scene_to_xyz;
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
const bNodeSocket &type = *node->input_by_identifier("Type"_ustr);
|
||||
const bool is_white_point = !type.is_directly_linked() &&
|
||||
type.default_value_typed<bNodeSocketValueMenu>()->value ==
|
||||
CMP_NODE_COLOR_BALANCE_WHITEPOINT;
|
||||
|
||||
const bNodeSocket &input_temperature = *node->input_by_identifier("Input Temperature"_ustr);
|
||||
const bNodeSocket &input_tint = *node->input_by_identifier("Input Tint"_ustr);
|
||||
const bNodeSocket &output_temperature = *node->input_by_identifier("Output Temperature"_ustr);
|
||||
const bNodeSocket &output_tint = *node->input_by_identifier("Output Tint"_ustr);
|
||||
|
||||
/* As an optimization for white point balancing, if all inputs are constant, compute the white
|
||||
* point matrix on the host and pass it to the shader. */
|
||||
if (is_white_point && !input_temperature.is_directly_linked() &&
|
||||
!input_tint.is_directly_linked() && !output_temperature.is_directly_linked() &&
|
||||
!output_tint.is_directly_linked())
|
||||
{
|
||||
const float4x4 white_point_matrix = float4x4(get_white_point_matrix(
|
||||
input_temperature.default_value_typed<bNodeSocketValueFloat>()->value,
|
||||
input_tint.default_value_typed<bNodeSocketValueFloat>()->value,
|
||||
output_temperature.default_value_typed<bNodeSocketValueFloat>()->value,
|
||||
output_tint.default_value_typed<bNodeSocketValueFloat>()->value));
|
||||
|
||||
return GPU_stack_link(material,
|
||||
node,
|
||||
"node_composite_color_balance_white_point_constant",
|
||||
inputs,
|
||||
outputs,
|
||||
GPU_uniform(white_point_matrix.base_ptr()));
|
||||
}
|
||||
|
||||
const float4x4 scene_to_xyz = float4x4(IMB_colormanagement_get_scene_linear_to_xyz());
|
||||
const float4x4 xyz_to_scene = float4x4(IMB_colormanagement_get_xyz_to_scene_linear());
|
||||
return GPU_stack_link(material,
|
||||
node,
|
||||
"node_composite_color_balance",
|
||||
inputs,
|
||||
outputs,
|
||||
GPU_uniform(scene_to_xyz.base_ptr()),
|
||||
GPU_uniform(xyz_to_scene.base_ptr()));
|
||||
}
|
||||
|
||||
static float4 lift_gamma_gain(const float4 color,
|
||||
const float base_lift,
|
||||
const float4 color_lift,
|
||||
const float base_gamma,
|
||||
const float4 color_gamma,
|
||||
const float base_gain,
|
||||
const float4 color_gain)
|
||||
{
|
||||
const float3 lift = base_lift + color_lift.xyz();
|
||||
const float3 lift_balanced = ((color.xyz() - 1.0f) * (2.0f - lift)) + 1.0f;
|
||||
|
||||
const float3 gain = base_gain * color_gain.xyz();
|
||||
const float3 gain_balanced = math::max(float3(0.0f), lift_balanced * gain);
|
||||
|
||||
const float3 gamma = base_gamma * color_gamma.xyz();
|
||||
const float3 gamma_balanced = math::pow(gain_balanced, 1.0f / math::max(gamma, float3(1e-6f)));
|
||||
|
||||
return float4(gamma_balanced, color.w);
|
||||
}
|
||||
|
||||
static float4 offset_power_slope(const float4 color,
|
||||
const float base_offset,
|
||||
const float4 color_offset,
|
||||
const float base_power,
|
||||
const float4 color_power,
|
||||
const float base_slope,
|
||||
const float4 color_slope)
|
||||
{
|
||||
const float3 slope = base_slope * color_slope.xyz();
|
||||
const float3 slope_balanced = color.xyz() * slope;
|
||||
|
||||
const float3 offset = base_offset + color_offset.xyz();
|
||||
const float3 offset_balanced = slope_balanced + offset;
|
||||
|
||||
const float3 power = base_power * color_power.xyz();
|
||||
const float3 power_balanced = math::pow(math::max(offset_balanced, float3(0.0f)), power);
|
||||
|
||||
return float4(power_balanced, color.w);
|
||||
}
|
||||
|
||||
static float4 white_point_constant(const float4 color,
|
||||
const float factor,
|
||||
const float3x3 white_point_matrix)
|
||||
{
|
||||
const float3 balanced = white_point_matrix * color.xyz();
|
||||
return float4(math::interpolate(color.xyz(), balanced, math::min(factor, 1.0f)), color.w);
|
||||
}
|
||||
|
||||
static float4 white_point_variable(const float4 color,
|
||||
const float input_temperature,
|
||||
const float input_tint,
|
||||
const float output_temperature,
|
||||
const float output_tint,
|
||||
const float3x3 &scene_to_xyz,
|
||||
const float3x3 &xyz_to_scene)
|
||||
{
|
||||
const float3 input = math::whitepoint_from_temp_tint(input_temperature, input_tint);
|
||||
const float3 output = math::whitepoint_from_temp_tint(output_temperature, output_tint);
|
||||
const float3x3 adaption = math::chromatic_adaption_matrix(input, output);
|
||||
const float3x3 white_point_matrix = xyz_to_scene * adaption * scene_to_xyz;
|
||||
|
||||
const float3 balanced = white_point_matrix * color.xyz();
|
||||
return float4(balanced, color.w);
|
||||
}
|
||||
|
||||
static float4 color_balance(const float4 color,
|
||||
const float factor,
|
||||
const CMPNodeColorBalanceMethod type,
|
||||
const float base_lift,
|
||||
const float4 color_lift,
|
||||
const float base_gamma,
|
||||
const float4 color_gamma,
|
||||
const float base_gain,
|
||||
const float4 color_gain,
|
||||
const float base_offset,
|
||||
const float4 color_offset,
|
||||
const float base_power,
|
||||
const float4 color_power,
|
||||
const float base_slope,
|
||||
const float4 color_slope,
|
||||
const float input_temperature,
|
||||
const float input_tint,
|
||||
const float output_temperature,
|
||||
const float output_tint,
|
||||
const float3x3 scene_to_xyz,
|
||||
const float3x3 xyz_to_scene)
|
||||
{
|
||||
float4 result = float4(0.0f);
|
||||
switch (type) {
|
||||
case CMP_NODE_COLOR_BALANCE_LGG:
|
||||
result = lift_gamma_gain(
|
||||
color, base_lift, color_lift, base_gamma, color_gamma, base_gain, color_gain);
|
||||
break;
|
||||
case CMP_NODE_COLOR_BALANCE_ASC_CDL:
|
||||
result = offset_power_slope(
|
||||
color, base_offset, color_offset, base_power, color_power, base_slope, color_slope);
|
||||
break;
|
||||
case CMP_NODE_COLOR_BALANCE_WHITEPOINT:
|
||||
result = white_point_variable(color,
|
||||
input_temperature,
|
||||
input_tint,
|
||||
output_temperature,
|
||||
output_tint,
|
||||
scene_to_xyz,
|
||||
xyz_to_scene);
|
||||
break;
|
||||
}
|
||||
|
||||
return float4(math::interpolate(color.xyz(), result.xyz(), math::min(factor, 1.0f)), color.w);
|
||||
}
|
||||
|
||||
using compositor::Color;
|
||||
|
||||
class ColorBalanceFunction : public mf::MultiFunction {
|
||||
public:
|
||||
ColorBalanceFunction()
|
||||
{
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Color Balance", signature};
|
||||
builder.single_input<Color>("Color");
|
||||
builder.single_input<float>("Factor");
|
||||
builder.single_input<MenuValue>("Type");
|
||||
|
||||
builder.single_input<float>("Base Lift");
|
||||
builder.single_input<Color>("Color Lift");
|
||||
builder.single_input<float>("Base Gamma");
|
||||
builder.single_input<Color>("Color Gamma");
|
||||
builder.single_input<float>("Base Gain");
|
||||
builder.single_input<Color>("Color Gain");
|
||||
|
||||
builder.single_input<float>("Base Offset");
|
||||
builder.single_input<Color>("Color Offset");
|
||||
builder.single_input<float>("Base Power");
|
||||
builder.single_input<Color>("Color Power");
|
||||
builder.single_input<float>("Base Slope");
|
||||
builder.single_input<Color>("Color Slope");
|
||||
|
||||
builder.single_input<float>("Input Temperature");
|
||||
builder.single_input<float>("Input Tint");
|
||||
builder.single_input<float>("Output Temperature");
|
||||
builder.single_input<float>("Output Tint");
|
||||
|
||||
builder.single_output<Color>("Result");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArray<Color> color_array = params.readonly_single_input<Color>(0, "Color");
|
||||
const VArray<float> factor_array = params.readonly_single_input<float>(1, "Factor");
|
||||
const VArray<MenuValue> type_array = params.readonly_single_input<MenuValue>(2, "Type");
|
||||
|
||||
const VArray<float> base_lift_array = params.readonly_single_input<float>(3, "Base Lift");
|
||||
const VArray<Color> color_lift_array = params.readonly_single_input<Color>(4, "Color Lift");
|
||||
const VArray<float> base_gamma_array = params.readonly_single_input<float>(5, "Base Gamma");
|
||||
const VArray<Color> color_gamma_array = params.readonly_single_input<Color>(6, "Color Gamma");
|
||||
const VArray<float> base_gain_array = params.readonly_single_input<float>(7, "Base Gain");
|
||||
const VArray<Color> color_gain_array = params.readonly_single_input<Color>(8, "Color Gain");
|
||||
|
||||
const VArray<float> base_offset_array = params.readonly_single_input<float>(9, "Base Offset");
|
||||
const VArray<Color> color_offset_array = params.readonly_single_input<Color>(10,
|
||||
"Color Offset");
|
||||
const VArray<float> base_power_array = params.readonly_single_input<float>(11, "Base Power");
|
||||
const VArray<Color> color_power_array = params.readonly_single_input<Color>(12, "Color Power");
|
||||
const VArray<float> base_slope_array = params.readonly_single_input<float>(13, "Base Slope");
|
||||
const VArray<Color> color_slope_array = params.readonly_single_input<Color>(14, "Color Slope");
|
||||
|
||||
const VArray<float> input_temperature_array = params.readonly_single_input<float>(
|
||||
15, "Input Temperature");
|
||||
const VArray<float> input_tint_array = params.readonly_single_input<float>(16, "Input Tint");
|
||||
const VArray<float> output_temperature_array = params.readonly_single_input<float>(
|
||||
17, "Output Temperature");
|
||||
const VArray<float> output_tint_array = params.readonly_single_input<float>(18, "Output Tint");
|
||||
|
||||
MutableSpan<Color> result = params.uninitialized_single_output<Color>(19, "Result");
|
||||
|
||||
const std::optional<MenuValue> type_single = type_array.get_if_single();
|
||||
const std::optional<float> input_temperature_single = input_temperature_array.get_if_single();
|
||||
const std::optional<float> input_tint_single = input_tint_array.get_if_single();
|
||||
const std::optional<float> output_temperature_single =
|
||||
output_temperature_array.get_if_single();
|
||||
const std::optional<float> output_tint_single = output_tint_array.get_if_single();
|
||||
|
||||
const bool is_white_point = type_single.has_value() &&
|
||||
type_single.value().value == CMP_NODE_COLOR_BALANCE_WHITEPOINT;
|
||||
|
||||
/* As an optimization for white point balancing, if all inputs are single, compute the white
|
||||
* point matrix outside of the loop. */
|
||||
if (is_white_point && input_temperature_single.has_value() && input_tint_single.has_value() &&
|
||||
output_temperature_single.has_value() && output_tint_single.has_value())
|
||||
{
|
||||
const float3x3 white_point_matrix = get_white_point_matrix(input_temperature_single.value(),
|
||||
input_tint_single.value(),
|
||||
output_temperature_single.value(),
|
||||
output_tint_single.value());
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
result[i] = Color(
|
||||
white_point_constant(float4(color_array[i]), factor_array[i], white_point_matrix));
|
||||
});
|
||||
}
|
||||
else {
|
||||
const float3x3 scene_to_xyz = IMB_colormanagement_get_scene_linear_to_xyz();
|
||||
const float3x3 xyz_to_scene = IMB_colormanagement_get_xyz_to_scene_linear();
|
||||
|
||||
bool all_but_color_single_value = true;
|
||||
for (int i = 0; i < 19; i++) {
|
||||
if (i == 1) {
|
||||
continue;
|
||||
}
|
||||
if (!params.readonly_single_input(i).is_single()) {
|
||||
all_but_color_single_value = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_but_color_single_value) {
|
||||
const float factor = factor_array.get_internal_single();
|
||||
const float type = CMPNodeColorBalanceMethod(type_array.get_internal_single().value);
|
||||
const float base_lift = base_lift_array.get_internal_single();
|
||||
const float4 color_lift = float4(color_lift_array.get_internal_single());
|
||||
const float base_gamma = base_gamma_array.get_internal_single();
|
||||
const float4 color_gamma = float4(color_gamma_array.get_internal_single());
|
||||
const float base_gain = base_gain_array.get_internal_single();
|
||||
const float4 color_gain = float4(color_gain_array.get_internal_single());
|
||||
const float base_offset = base_offset_array.get_internal_single();
|
||||
const float4 color_offset = float4(color_offset_array.get_internal_single());
|
||||
const float base_power = base_power_array.get_internal_single();
|
||||
const float4 color_power = float4(color_power_array.get_internal_single());
|
||||
const float base_slope = base_slope_array.get_internal_single();
|
||||
const float4 color_slope = float4(color_slope_array.get_internal_single());
|
||||
const float input_temperature = input_temperature_array.get_internal_single();
|
||||
const float input_tint = input_tint_array.get_internal_single();
|
||||
const float output_temperature = output_temperature_array.get_internal_single();
|
||||
const float output_tint = output_tint_array.get_internal_single();
|
||||
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
result[i] = Color(color_balance(float4(color_array[i]),
|
||||
factor,
|
||||
CMPNodeColorBalanceMethod(type),
|
||||
base_lift,
|
||||
color_lift,
|
||||
base_gamma,
|
||||
color_gamma,
|
||||
base_gain,
|
||||
color_gain,
|
||||
base_offset,
|
||||
color_offset,
|
||||
base_power,
|
||||
color_power,
|
||||
base_slope,
|
||||
color_slope,
|
||||
input_temperature,
|
||||
input_tint,
|
||||
output_temperature,
|
||||
output_tint,
|
||||
scene_to_xyz,
|
||||
xyz_to_scene));
|
||||
});
|
||||
}
|
||||
else {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
result[i] = Color(color_balance(float4(color_array[i]),
|
||||
factor_array[i],
|
||||
CMPNodeColorBalanceMethod(type_array[i].value),
|
||||
base_lift_array[i],
|
||||
float4(color_lift_array[i]),
|
||||
base_gamma_array[i],
|
||||
float4(color_gamma_array[i]),
|
||||
base_gain_array[i],
|
||||
float4(color_gain_array[i]),
|
||||
base_offset_array[i],
|
||||
float4(color_offset_array[i]),
|
||||
base_power_array[i],
|
||||
float4(color_power_array[i]),
|
||||
base_slope_array[i],
|
||||
float4(color_slope_array[i]),
|
||||
input_temperature_array[i],
|
||||
input_tint_array[i],
|
||||
output_temperature_array[i],
|
||||
output_tint_array[i],
|
||||
scene_to_xyz,
|
||||
xyz_to_scene));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const static ColorBalanceFunction function;
|
||||
builder.set_matching_fn(function);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeColorBalance"_ustr, CMP_NODE_COLORBALANCE);
|
||||
ntype.ui_name = "Color Balance";
|
||||
ntype.ui_description = "Adjust color and values";
|
||||
ntype.enum_name_legacy = "COLORBALANCE";
|
||||
ntype.nclass = NODE_CLASS_OP_COLOR;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_color_balance_cc
|
||||
@@ -0,0 +1,420 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "COM_result.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_color_correction_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
|
||||
b.add_input<decl::Color>("Image"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f}).hide_value();
|
||||
b.add_output<decl::Color>("Image"_ustr).align_with_previous();
|
||||
|
||||
b.add_input<decl::Float>("Mask"_ustr).default_value(1.0f).min(0.0f).max(1.0f);
|
||||
|
||||
PanelDeclarationBuilder &master_panel = b.add_panel("Master"_ustr).default_closed(true);
|
||||
master_panel.add_input<decl::Float>("Saturation"_ustr, "Master Saturation"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the saturation of the entire image");
|
||||
master_panel.add_input<decl::Float>("Contrast"_ustr, "Master Contrast"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the contrast of the entire image");
|
||||
master_panel.add_input<decl::Float>("Gamma"_ustr, "Master Gamma"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the gamma of the entire image");
|
||||
master_panel.add_input<decl::Float>("Gain"_ustr, "Master Gain"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the gain of the entire image");
|
||||
master_panel.add_input<decl::Float>("Offset"_ustr, "Master Offset"_ustr)
|
||||
.default_value(0.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(-1.0f)
|
||||
.max(1.0f)
|
||||
.description("Controls the offset of the entire image");
|
||||
|
||||
PanelDeclarationBuilder &highlights_panel = b.add_panel("Highlights"_ustr).default_closed(true);
|
||||
highlights_panel.add_input<decl::Float>("Saturation"_ustr, "Highlights Saturation"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the saturation of the highlights");
|
||||
highlights_panel.add_input<decl::Float>("Contrast"_ustr, "Highlights Contrast"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the contrast of the highlights");
|
||||
highlights_panel.add_input<decl::Float>("Gamma"_ustr, "Highlights Gamma"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the gamma of the highlights");
|
||||
highlights_panel.add_input<decl::Float>("Gain"_ustr, "Highlights Gain"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the gain of the highlights");
|
||||
highlights_panel.add_input<decl::Float>("Offset"_ustr, "Highlights Offset"_ustr)
|
||||
.default_value(0.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(-1.0f)
|
||||
.max(1.0f)
|
||||
.description("Controls the offset of the highlights");
|
||||
|
||||
PanelDeclarationBuilder &midtones_panel = b.add_panel("Midtones"_ustr).default_closed(true);
|
||||
midtones_panel.add_input<decl::Float>("Saturation"_ustr, "Midtones Saturation"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the saturation of the midtones");
|
||||
midtones_panel.add_input<decl::Float>("Contrast"_ustr, "Midtones Contrast"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the contrast of the midtones");
|
||||
midtones_panel.add_input<decl::Float>("Gamma"_ustr, "Midtones Gamma"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the gamma of the midtones");
|
||||
midtones_panel.add_input<decl::Float>("Gain"_ustr, "Midtones Gain"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the gain of the midtones");
|
||||
midtones_panel.add_input<decl::Float>("Offset"_ustr, "Midtones Offset"_ustr)
|
||||
.default_value(0.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(-1.0f)
|
||||
.max(1.0f)
|
||||
.description("Controls the offset of the midtones");
|
||||
|
||||
PanelDeclarationBuilder &shadows_panel = b.add_panel("Shadows"_ustr).default_closed(true);
|
||||
shadows_panel.add_input<decl::Float>("Saturation"_ustr, "Shadows Saturation"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the saturation of the shadows");
|
||||
shadows_panel.add_input<decl::Float>("Contrast"_ustr, "Shadows Contrast"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the contrast of the shadows");
|
||||
shadows_panel.add_input<decl::Float>("Gamma"_ustr, "Shadows Gamma"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the gamma of the shadows");
|
||||
shadows_panel.add_input<decl::Float>("Gain"_ustr, "Shadows Gain"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(4.0f)
|
||||
.description("Controls the gain of the shadows");
|
||||
shadows_panel.add_input<decl::Float>("Offset"_ustr, "Shadows Offset"_ustr)
|
||||
.default_value(0.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(-1.0f)
|
||||
.max(1.0f)
|
||||
.description("Controls the offset of the shadows");
|
||||
|
||||
PanelDeclarationBuilder &tonal_range_panel =
|
||||
b.add_panel("Tonal Range"_ustr).default_closed(true);
|
||||
tonal_range_panel.add_input<decl::Float>("Midtones Start"_ustr)
|
||||
.default_value(0.2f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description(
|
||||
"Specifies the luminance at which the midtones of the image start and the shadows end");
|
||||
tonal_range_panel.add_input<decl::Float>("Midtones End"_ustr)
|
||||
.default_value(0.7f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description(
|
||||
"Specifies the luminance at which the midtones of the image end and the highlights "
|
||||
"start");
|
||||
|
||||
PanelDeclarationBuilder &tone_range_panel = b.add_panel("Channels"_ustr).default_closed(true);
|
||||
tone_range_panel.add_input<decl::Bool>("Red"_ustr, "Apply On Red"_ustr)
|
||||
.default_value(true)
|
||||
.description("If true, the correction will be applied on the red channel");
|
||||
tone_range_panel.add_input<decl::Bool>("Green"_ustr, "Apply On Green"_ustr)
|
||||
.default_value(true)
|
||||
.description("If true, the correction will be applied on the green channel");
|
||||
tone_range_panel.add_input<decl::Bool>("Blue"_ustr, "Apply On Blue"_ustr)
|
||||
.default_value(true)
|
||||
.description("If true, the correction will be applied on the blue channel");
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
float luminance_coefficients[3];
|
||||
IMB_colormanagement_get_luminance_coefficients(luminance_coefficients);
|
||||
|
||||
return GPU_stack_link(material,
|
||||
node,
|
||||
"node_composite_color_correction",
|
||||
inputs,
|
||||
outputs,
|
||||
GPU_constant(luminance_coefficients));
|
||||
}
|
||||
|
||||
static float4 color_correction(const float4 &color,
|
||||
const float &mask,
|
||||
const float &master_saturation,
|
||||
const float &master_contrast,
|
||||
const float &master_gamma,
|
||||
const float &master_gain,
|
||||
const float &master_offset,
|
||||
const float &highlights_saturation,
|
||||
const float &highlights_contrast,
|
||||
const float &highlights_gamma,
|
||||
const float &highlights_gain,
|
||||
const float &highlights_offset,
|
||||
const float &midtones_saturation,
|
||||
const float &midtones_contrast,
|
||||
const float &midtones_gamma,
|
||||
const float &midtones_gain,
|
||||
const float &midtones_offset,
|
||||
const float &shadows_saturation,
|
||||
const float &shadows_contrast,
|
||||
const float &shadows_gamma,
|
||||
const float &shadows_gain,
|
||||
const float &shadows_offset,
|
||||
const float &start_midtones,
|
||||
const float &end_midtones,
|
||||
const bool &apply_on_red,
|
||||
const bool &apply_on_green,
|
||||
const bool &apply_on_blue,
|
||||
const float3 &luminance_coefficients)
|
||||
{
|
||||
const float margin = 0.10f;
|
||||
const float margin_divider = 0.5f / margin;
|
||||
float level = (color.x + color.y + color.z) / 3.0f;
|
||||
float level_shadows = 0.0f;
|
||||
float level_midtones = 0.0f;
|
||||
float level_highlights = 0.0f;
|
||||
if (level < (start_midtones - margin)) {
|
||||
level_shadows = 1.0f;
|
||||
}
|
||||
else if (level < (start_midtones + margin)) {
|
||||
level_midtones = ((level - start_midtones) * margin_divider) + 0.5f;
|
||||
level_shadows = 1.0f - level_midtones;
|
||||
}
|
||||
else if (level < (end_midtones - margin)) {
|
||||
level_midtones = 1.0f;
|
||||
}
|
||||
else if (level < (end_midtones + margin)) {
|
||||
level_highlights = ((level - end_midtones) * margin_divider) + 0.5f;
|
||||
level_midtones = 1.0f - level_highlights;
|
||||
}
|
||||
else {
|
||||
level_highlights = 1.0f;
|
||||
}
|
||||
|
||||
float contrast = level_shadows * shadows_contrast;
|
||||
contrast += level_midtones * midtones_contrast;
|
||||
contrast += level_highlights * highlights_contrast;
|
||||
contrast *= master_contrast;
|
||||
float saturation = level_shadows * shadows_saturation;
|
||||
saturation += level_midtones * midtones_saturation;
|
||||
saturation += level_highlights * highlights_saturation;
|
||||
saturation *= master_saturation;
|
||||
float gamma = level_shadows * shadows_gamma;
|
||||
gamma += level_midtones * midtones_gamma;
|
||||
gamma += level_highlights * highlights_gamma;
|
||||
gamma *= master_gamma;
|
||||
float gain = level_shadows * shadows_gain;
|
||||
gain += level_midtones * midtones_gain;
|
||||
gain += level_highlights * highlights_gain;
|
||||
gain *= master_gain;
|
||||
float offset = level_shadows * shadows_offset;
|
||||
offset += level_midtones * midtones_offset;
|
||||
offset += level_highlights * highlights_offset;
|
||||
offset += master_offset;
|
||||
|
||||
float inverse_gamma = 1.0f / gamma;
|
||||
float luma = math::dot(color.xyz(), luminance_coefficients);
|
||||
|
||||
float3 corrected = luma + saturation * (color.xyz() - luma);
|
||||
corrected = 0.5f + (corrected - 0.5f) * contrast;
|
||||
corrected = corrected * gain + offset;
|
||||
|
||||
/* Don't allow colors to go negative (or more negative than before) to keep them in gamut. */
|
||||
corrected = math::max(math::min(color.xyz(), float3(0.0f)), corrected);
|
||||
|
||||
corrected = math::fallback_pow(corrected, inverse_gamma, corrected);
|
||||
corrected = math::interpolate(color.xyz(), corrected, math::min(mask, 1.0f));
|
||||
|
||||
return float4(apply_on_red ? corrected.x : color.x,
|
||||
apply_on_green ? corrected.y : color.y,
|
||||
apply_on_blue ? corrected.z : color.z,
|
||||
color.w);
|
||||
}
|
||||
|
||||
using compositor::Color;
|
||||
|
||||
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
float3 luminance_coefficients;
|
||||
IMB_colormanagement_get_luminance_coefficients(luminance_coefficients);
|
||||
|
||||
builder.construct_and_set_matching_fn_cb([=]() {
|
||||
return mf::build::detail::build_multi_function_with_n_inputs_one_output<Color>(
|
||||
"Color Correction",
|
||||
[=](const Color &color,
|
||||
const float &mask,
|
||||
const float &master_saturation,
|
||||
const float &master_contrast,
|
||||
const float &master_gamma,
|
||||
const float &master_gain,
|
||||
const float &master_offset,
|
||||
const float &highlights_saturation,
|
||||
const float &highlights_contrast,
|
||||
const float &highlights_gamma,
|
||||
const float &highlights_gain,
|
||||
const float &highlights_offset,
|
||||
const float &midtones_saturation,
|
||||
const float &midtones_contrast,
|
||||
const float &midtones_gamma,
|
||||
const float &midtones_gain,
|
||||
const float &midtones_offset,
|
||||
const float &shadows_saturation,
|
||||
const float &shadows_contrast,
|
||||
const float &shadows_gamma,
|
||||
const float &shadows_gain,
|
||||
const float &shadows_offset,
|
||||
const float &start_midtones,
|
||||
const float &end_midtones,
|
||||
const bool &apply_on_red,
|
||||
const bool &apply_on_green,
|
||||
const bool &apply_on_blue) -> Color {
|
||||
return Color(color_correction(float4(color),
|
||||
mask,
|
||||
master_saturation,
|
||||
master_contrast,
|
||||
master_gamma,
|
||||
master_gain,
|
||||
master_offset,
|
||||
highlights_saturation,
|
||||
highlights_contrast,
|
||||
highlights_gamma,
|
||||
highlights_gain,
|
||||
highlights_offset,
|
||||
midtones_saturation,
|
||||
midtones_contrast,
|
||||
midtones_gamma,
|
||||
midtones_gain,
|
||||
midtones_offset,
|
||||
shadows_saturation,
|
||||
shadows_contrast,
|
||||
shadows_gamma,
|
||||
shadows_gain,
|
||||
shadows_offset,
|
||||
start_midtones,
|
||||
end_midtones,
|
||||
apply_on_red,
|
||||
apply_on_green,
|
||||
apply_on_blue,
|
||||
luminance_coefficients));
|
||||
},
|
||||
mf::build::exec_presets::SomeSpanOrSingle<0>(),
|
||||
TypeSequence<Color,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
float,
|
||||
bool,
|
||||
bool,
|
||||
bool>());
|
||||
});
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeColorCorrection"_ustr, CMP_NODE_COLORCORRECTION);
|
||||
ntype.ui_name = "Color Correction";
|
||||
ntype.ui_description =
|
||||
"Adjust the color of an image, separately in several tonal ranges (highlights, midtones and "
|
||||
"shadows)";
|
||||
ntype.enum_name_legacy = "COLORCORRECTION";
|
||||
ntype.nclass = NODE_CLASS_OP_COLOR;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_color_correction_cc
|
||||
@@ -0,0 +1,140 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "COM_result.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_color_key_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Color>("Image"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f}).hide_value();
|
||||
b.add_output<decl::Color>("Image"_ustr).align_with_previous();
|
||||
b.add_output<decl::Float>("Matte"_ustr);
|
||||
|
||||
b.add_input<decl::Color>("Key Color"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
b.add_input<decl::Float>("Hue"_ustr)
|
||||
.default_value(0.01f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description(
|
||||
"If the difference in hue between the color and key color is less than this threshold, "
|
||||
"it is keyed");
|
||||
b.add_input<decl::Float>("Saturation"_ustr)
|
||||
.default_value(0.1f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description(
|
||||
"If the difference in saturation between the color and key color is less than this "
|
||||
"threshold, it is keyed");
|
||||
b.add_input<decl::Float>("Value"_ustr)
|
||||
.default_value(0.1f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description(
|
||||
"If the difference in value between the color and key color is less than this "
|
||||
"threshold, it is keyed")
|
||||
.translation_context(BLT_I18NCONTEXT_COLOR);
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_composite_color_matte", inputs, outputs);
|
||||
}
|
||||
|
||||
static void color_matte(const float4 color,
|
||||
const float4 key,
|
||||
const float hue_threshold,
|
||||
const float saturation_epsilon,
|
||||
const float value_epsilon,
|
||||
float4 &result,
|
||||
float &matte)
|
||||
{
|
||||
float3 color_hsva;
|
||||
rgb_to_hsv_v(color, color_hsva);
|
||||
float3 key_hsva;
|
||||
rgb_to_hsv_v(key, key_hsva);
|
||||
|
||||
/* Divide by 2 because the hue wraps around. */
|
||||
float hue_epsilon = hue_threshold / 2.0f;
|
||||
|
||||
bool is_within_saturation = math::distance(color_hsva.y, key_hsva.y) < saturation_epsilon;
|
||||
bool is_within_value = math::distance(color_hsva.z, key_hsva.z) < value_epsilon;
|
||||
bool is_within_hue = math::distance(color_hsva.x, key_hsva.x) < hue_epsilon;
|
||||
/* Hue wraps around, so check the distance around the boundary. */
|
||||
float min_hue = math::min(color_hsva.x, key_hsva.x);
|
||||
float max_hue = math::max(color_hsva.x, key_hsva.x);
|
||||
is_within_hue = is_within_hue || ((min_hue + (1.0f - max_hue)) < hue_epsilon);
|
||||
|
||||
matte = (is_within_hue && is_within_saturation && is_within_value) ? 0.0f : color.w;
|
||||
result = color * matte;
|
||||
}
|
||||
|
||||
using compositor::Color;
|
||||
|
||||
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
builder.construct_and_set_matching_fn_cb([=]() {
|
||||
return mf::build::SI5_SO2<Color, Color, float, float, float, Color, float>(
|
||||
"Color Key",
|
||||
[=](const Color &color,
|
||||
const Color &key_color,
|
||||
const float &hue,
|
||||
const float &saturation,
|
||||
const float &value,
|
||||
Color &output_color,
|
||||
float &matte) -> void {
|
||||
float4 out_color;
|
||||
color_matte(float4(color), float4(key_color), hue, saturation, value, out_color, matte);
|
||||
output_color = Color(out_color);
|
||||
},
|
||||
mf::build::exec_presets::SomeSpanOrSingle<0, 1>());
|
||||
});
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeColorMatte"_ustr, CMP_NODE_COLOR_MATTE);
|
||||
ntype.ui_name = "Color Key";
|
||||
ntype.ui_description = "Create matte using a given color, for green or blue screen footage";
|
||||
ntype.enum_name_legacy = "COLOR_MATTE";
|
||||
ntype.nclass = NODE_CLASS_MATTE;
|
||||
ntype.declare = node_declare;
|
||||
ntype.flag |= NODE_PREVIEW;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.default_width = bke::NodeWidth::_160;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_color_key_cc
|
||||
@@ -0,0 +1,214 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "COM_result.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_color_spill_cc {
|
||||
|
||||
enum class RGBChannel : uint8_t {
|
||||
R = 0,
|
||||
G = 1,
|
||||
B = 2,
|
||||
};
|
||||
|
||||
static const EnumPropertyItem rgb_channel_items[] = {
|
||||
{int(RGBChannel::R), "R", 0, "R", ""},
|
||||
{int(RGBChannel::G), "G", 0, "G", ""},
|
||||
{int(RGBChannel::B), "B", 0, "B", ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static const EnumPropertyItem limit_method_items[] = {
|
||||
{CMP_NODE_COLOR_SPILL_LIMIT_ALGORITHM_SINGLE,
|
||||
"SINGLE",
|
||||
0,
|
||||
N_("Single"),
|
||||
N_("Limit by a single channel")},
|
||||
{CMP_NODE_COLOR_SPILL_LIMIT_ALGORITHM_AVERAGE,
|
||||
"AVERAGE",
|
||||
0,
|
||||
N_("Average"),
|
||||
N_("Limit by the average of the other two channels")},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.use_custom_socket_order();
|
||||
|
||||
b.add_output<decl::Color>("Image"_ustr);
|
||||
|
||||
b.add_input<decl::Color>("Image"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
b.add_input<decl::Float>("Factor"_ustr, "Fac"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR);
|
||||
b.add_input<decl::Menu>("Spill Channel"_ustr)
|
||||
.default_value(RGBChannel::G)
|
||||
.static_items(rgb_channel_items)
|
||||
.expanded()
|
||||
.translation_context(BLT_I18NCONTEXT_COLOR)
|
||||
.optional_label();
|
||||
b.add_input<decl::Menu>("Limit Method"_ustr)
|
||||
.default_value(CMP_NODE_COLOR_SPILL_LIMIT_ALGORITHM_SINGLE)
|
||||
.static_items(limit_method_items)
|
||||
.expanded()
|
||||
.optional_label();
|
||||
b.add_input<decl::Menu>("Limit Channel"_ustr)
|
||||
.default_value(RGBChannel::R)
|
||||
.static_items(rgb_channel_items)
|
||||
.expanded()
|
||||
.translation_context(BLT_I18NCONTEXT_COLOR)
|
||||
.optional_label()
|
||||
.usage_by_menu("Limit Method"_ustr, CMP_NODE_COLOR_SPILL_LIMIT_ALGORITHM_SINGLE);
|
||||
b.add_input<decl::Float>("Limit Strength"_ustr)
|
||||
.default_value(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(2.0f)
|
||||
.description("Specifies the limiting strength of the limit channel");
|
||||
|
||||
PanelDeclarationBuilder &use_spill_strength_panel =
|
||||
b.add_panel("Spill Strength"_ustr).default_closed(true);
|
||||
use_spill_strength_panel.add_input<decl::Bool>("Use Spill Strength"_ustr)
|
||||
.default_value(false)
|
||||
.panel_toggle()
|
||||
.description(
|
||||
"If enabled, the spill strength for each color channel can be specified. If disabled, "
|
||||
"the spill channel will have a unit scale, while other channels will be zero");
|
||||
use_spill_strength_panel.add_input<decl::Color>("Strength"_ustr, "Spill Strength"_ustr)
|
||||
.default_value({0.0f, 1.0f, 0.0f, 1.0f})
|
||||
.description("Specifies the spilling strength of each color channel");
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*ntree*/, bNode *node)
|
||||
{
|
||||
/* Unused, but allocated for forward compatibility. */
|
||||
node->storage = MEM_new<NodeColorspill>(__func__);
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_composite_color_spill", inputs, outputs);
|
||||
}
|
||||
|
||||
/* Compute the indices of the channels used to compute the limit value. We always assume the limit
|
||||
* algorithm is Average, if it is a single limit channel, store it in both limit channels, because
|
||||
* the average of two identical values is the same value. */
|
||||
static int2 compute_limit_channels(const CMPNodeColorSpillLimitAlgorithm limit_method,
|
||||
const int spill_channel,
|
||||
const int limit_channel)
|
||||
{
|
||||
/* If the algorithm is Average, store the indices of the other two channels other than the spill
|
||||
* channel. */
|
||||
if (limit_method == CMP_NODE_COLOR_SPILL_LIMIT_ALGORITHM_AVERAGE) {
|
||||
return int2((spill_channel + 1) % 3, (spill_channel + 2) % 3);
|
||||
}
|
||||
|
||||
/* If the algorithm is Single, store the index of the limit channel in both channels. */
|
||||
return int2(limit_channel);
|
||||
}
|
||||
|
||||
static float3 compute_spill_scale(const bool use_spill_strength,
|
||||
const float4 spill_strength,
|
||||
const int spill_channel)
|
||||
{
|
||||
if (use_spill_strength) {
|
||||
float3 scale = spill_strength.xyz();
|
||||
scale[spill_channel] *= -1.0f;
|
||||
return scale;
|
||||
}
|
||||
|
||||
float3 scale = float3(0.0f);
|
||||
scale[spill_channel] = -1.0f;
|
||||
return scale;
|
||||
}
|
||||
|
||||
static float4 color_spill(const float4 color,
|
||||
const float factor,
|
||||
const int spill_channel,
|
||||
const CMPNodeColorSpillLimitAlgorithm limit_method,
|
||||
const int limit_channel,
|
||||
const float limit_scale,
|
||||
const bool use_spill_strength,
|
||||
const float4 spill_strength)
|
||||
{
|
||||
const int2 limit_channels = compute_limit_channels(limit_method, spill_channel, limit_channel);
|
||||
const float average_limit = (color[limit_channels.x] + color[limit_channels.y]) / 2.0f;
|
||||
const float map = factor * color[spill_channel] - limit_scale * average_limit;
|
||||
const float3 spill_scale = compute_spill_scale(
|
||||
use_spill_strength, spill_strength, spill_channel);
|
||||
return float4(map > 0.0f ? color.xyz() + spill_scale * map : color.xyz(), color.w);
|
||||
}
|
||||
|
||||
using compositor::Color;
|
||||
|
||||
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto function =
|
||||
mf::build::SI8_SO<Color, float, MenuValue, MenuValue, MenuValue, float, bool, Color, Color>(
|
||||
"Color Spill",
|
||||
[=](const Color &color,
|
||||
const float &factor,
|
||||
const MenuValue spill_channel,
|
||||
const MenuValue limit_method,
|
||||
const MenuValue limit_channel,
|
||||
const float &limit_scale,
|
||||
const bool &use_spill_strength,
|
||||
const Color &spill_strength) -> Color {
|
||||
return Color(color_spill(float4(color),
|
||||
factor,
|
||||
spill_channel.value,
|
||||
CMPNodeColorSpillLimitAlgorithm(limit_method.value),
|
||||
limit_channel.value,
|
||||
limit_scale,
|
||||
use_spill_strength,
|
||||
float4(spill_strength)));
|
||||
},
|
||||
mf::build::exec_presets::SomeSpanOrSingle<0>());
|
||||
builder.set_matching_fn(function);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeColorSpill"_ustr, CMP_NODE_COLOR_SPILL);
|
||||
ntype.ui_name = "Color Spill";
|
||||
ntype.ui_description =
|
||||
"Remove colors from a blue or green screen, by reducing one RGB channel compared to the "
|
||||
"others";
|
||||
ntype.enum_name_legacy = "COLOR_SPILL";
|
||||
ntype.nclass = NODE_CLASS_MATTE;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeColorspill", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.default_width = bke::NodeWidth::_160;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_color_spill_cc
|
||||
@@ -0,0 +1,269 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_color.h"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "COM_result.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_combine_color_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeCMPCombSepColor)
|
||||
|
||||
static void node_init(bNodeTree * /*ntree*/, bNode *node)
|
||||
{
|
||||
NodeCMPCombSepColor *data = MEM_new<NodeCMPCombSepColor>(__func__);
|
||||
data->mode = CMP_NODE_COMBSEP_COLOR_RGB;
|
||||
data->ycc_mode = BLI_YCC_ITU_BT709;
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Float>("Red"_ustr)
|
||||
.default_value(0.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.label_fn([](const bNode &node) {
|
||||
switch (node_storage(node).mode) {
|
||||
case CMP_NODE_COMBSEP_COLOR_RGB:
|
||||
default:
|
||||
return IFACE_("Red");
|
||||
case CMP_NODE_COMBSEP_COLOR_HSV:
|
||||
case CMP_NODE_COMBSEP_COLOR_HSL:
|
||||
return IFACE_("Hue");
|
||||
case CMP_NODE_COMBSEP_COLOR_YCC:
|
||||
case CMP_NODE_COMBSEP_COLOR_YUV:
|
||||
return IFACE_("Y");
|
||||
}
|
||||
});
|
||||
b.add_input<decl::Float>("Green"_ustr)
|
||||
.default_value(0.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.label_fn([](const bNode &node) {
|
||||
switch (node_storage(node).mode) {
|
||||
case CMP_NODE_COMBSEP_COLOR_RGB:
|
||||
default:
|
||||
return IFACE_("Green");
|
||||
case CMP_NODE_COMBSEP_COLOR_HSV:
|
||||
case CMP_NODE_COMBSEP_COLOR_HSL:
|
||||
return IFACE_("Saturation");
|
||||
case CMP_NODE_COMBSEP_COLOR_YCC:
|
||||
return IFACE_("Cb");
|
||||
case CMP_NODE_COMBSEP_COLOR_YUV:
|
||||
return IFACE_("U");
|
||||
}
|
||||
});
|
||||
b.add_input<decl::Float>("Blue"_ustr)
|
||||
.default_value(0.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.label_fn([](const bNode &node) {
|
||||
switch (node_storage(node).mode) {
|
||||
case CMP_NODE_COMBSEP_COLOR_RGB:
|
||||
default:
|
||||
return IFACE_("Blue");
|
||||
case CMP_NODE_COMBSEP_COLOR_HSV:
|
||||
return CTX_IFACE_(BLT_I18NCONTEXT_COLOR, "Value");
|
||||
case CMP_NODE_COMBSEP_COLOR_HSL:
|
||||
return IFACE_("Lightness");
|
||||
case CMP_NODE_COMBSEP_COLOR_YCC:
|
||||
return IFACE_("Cr");
|
||||
case CMP_NODE_COMBSEP_COLOR_YUV:
|
||||
return IFACE_("V");
|
||||
}
|
||||
});
|
||||
b.add_input<decl::Float>("Alpha"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR);
|
||||
b.add_output<decl::Color>("Image"_ustr);
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
switch (node_storage(*node).mode) {
|
||||
case CMP_NODE_COMBSEP_COLOR_RGB:
|
||||
return GPU_stack_link(material, node, "node_composite_combine_rgba", inputs, outputs);
|
||||
case CMP_NODE_COMBSEP_COLOR_HSV:
|
||||
return GPU_stack_link(material, node, "node_composite_combine_hsva", inputs, outputs);
|
||||
case CMP_NODE_COMBSEP_COLOR_HSL:
|
||||
return GPU_stack_link(material, node, "node_composite_combine_hsla", inputs, outputs);
|
||||
case CMP_NODE_COMBSEP_COLOR_YUV:
|
||||
return GPU_stack_link(
|
||||
material, node, "node_composite_combine_yuva_itu_709", inputs, outputs);
|
||||
case CMP_NODE_COMBSEP_COLOR_YCC:
|
||||
switch (node_storage(*node).ycc_mode) {
|
||||
case BLI_YCC_ITU_BT601:
|
||||
return GPU_stack_link(
|
||||
material, node, "node_composite_combine_ycca_itu_601", inputs, outputs);
|
||||
case BLI_YCC_ITU_BT709:
|
||||
return GPU_stack_link(
|
||||
material, node, "node_composite_combine_ycca_itu_709", inputs, outputs);
|
||||
case BLI_YCC_JFIF_0_255:
|
||||
return GPU_stack_link(
|
||||
material, node, "node_composite_combine_ycca_jpeg", inputs, outputs);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
using compositor::Color;
|
||||
|
||||
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto rgba_function = mf::build::SI4_SO<float, float, float, float, Color>(
|
||||
"Combine Color RGBA",
|
||||
[](const float r, const float g, const float b, const float a) -> Color {
|
||||
return Color(r, g, b, a);
|
||||
},
|
||||
mf::build::exec_presets::Materialized());
|
||||
|
||||
static auto hsva_function = mf::build::SI4_SO<float, float, float, float, Color>(
|
||||
"Combine Color HSVA",
|
||||
[](const float h, const float s, const float v, const float a) -> Color {
|
||||
Color result;
|
||||
hsv_to_rgb(h, s, v, &result.r, &result.g, &result.b);
|
||||
result.a = a;
|
||||
return result;
|
||||
},
|
||||
mf::build::exec_presets::Materialized());
|
||||
|
||||
static auto hsla_function = mf::build::SI4_SO<float, float, float, float, Color>(
|
||||
"Combine Color HSLA",
|
||||
[](const float h, const float s, const float l, const float a) -> Color {
|
||||
Color result;
|
||||
hsl_to_rgb(h, s, l, &result.r, &result.g, &result.b);
|
||||
result.a = a;
|
||||
return result;
|
||||
},
|
||||
mf::build::exec_presets::Materialized());
|
||||
|
||||
static auto yuva_function = mf::build::SI4_SO<float, float, float, float, Color>(
|
||||
"Combine Color YUVA",
|
||||
[](const float y, const float u, const float v, const float a) -> Color {
|
||||
Color result;
|
||||
yuv_to_rgb(y, u, v, &result.r, &result.g, &result.b, BLI_YUV_ITU_BT709);
|
||||
result.a = a;
|
||||
return result;
|
||||
},
|
||||
mf::build::exec_presets::Materialized());
|
||||
|
||||
static auto ycca_itu_601_function = mf::build::SI4_SO<float, float, float, float, Color>(
|
||||
"Combine Color YCCA ITU 601",
|
||||
[](const float y, const float cb, const float cr, const float a) -> Color {
|
||||
Color result;
|
||||
ycc_to_rgb(y * 255.0f,
|
||||
cb * 255.0f,
|
||||
cr * 255.0f,
|
||||
&result.r,
|
||||
&result.g,
|
||||
&result.b,
|
||||
BLI_YCC_ITU_BT601);
|
||||
result.a = a;
|
||||
return result;
|
||||
},
|
||||
mf::build::exec_presets::Materialized());
|
||||
|
||||
static auto ycca_itu_709_function = mf::build::SI4_SO<float, float, float, float, Color>(
|
||||
"Combine Color YCCA ITU 709",
|
||||
[](const float y, const float cb, const float cr, const float a) -> Color {
|
||||
Color result;
|
||||
ycc_to_rgb(y * 255.0f,
|
||||
cb * 255.0f,
|
||||
cr * 255.0f,
|
||||
&result.r,
|
||||
&result.g,
|
||||
&result.b,
|
||||
BLI_YCC_ITU_BT709);
|
||||
result.a = a;
|
||||
return result;
|
||||
},
|
||||
mf::build::exec_presets::Materialized());
|
||||
|
||||
static auto ycca_jpeg_function = mf::build::SI4_SO<float, float, float, float, Color>(
|
||||
"Combine Color YCCA JPEG",
|
||||
[](const float y, const float cb, const float cr, const float a) -> Color {
|
||||
Color result;
|
||||
ycc_to_rgb(y * 255.0f,
|
||||
cb * 255.0f,
|
||||
cr * 255.0f,
|
||||
&result.r,
|
||||
&result.g,
|
||||
&result.b,
|
||||
BLI_YCC_JFIF_0_255);
|
||||
result.a = a;
|
||||
return result;
|
||||
},
|
||||
mf::build::exec_presets::Materialized());
|
||||
|
||||
switch (node_storage(builder.node()).mode) {
|
||||
case CMP_NODE_COMBSEP_COLOR_RGB:
|
||||
builder.set_matching_fn(rgba_function);
|
||||
break;
|
||||
case CMP_NODE_COMBSEP_COLOR_HSV:
|
||||
builder.set_matching_fn(hsva_function);
|
||||
break;
|
||||
case CMP_NODE_COMBSEP_COLOR_HSL:
|
||||
builder.set_matching_fn(hsla_function);
|
||||
break;
|
||||
case CMP_NODE_COMBSEP_COLOR_YUV:
|
||||
builder.set_matching_fn(yuva_function);
|
||||
break;
|
||||
case CMP_NODE_COMBSEP_COLOR_YCC:
|
||||
switch (node_storage(builder.node()).ycc_mode) {
|
||||
case BLI_YCC_ITU_BT601:
|
||||
builder.set_matching_fn(ycca_itu_601_function);
|
||||
break;
|
||||
case BLI_YCC_ITU_BT709:
|
||||
builder.set_matching_fn(ycca_itu_709_function);
|
||||
break;
|
||||
case BLI_YCC_JFIF_0_255:
|
||||
builder.set_matching_fn(ycca_jpeg_function);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeCombineColor"_ustr, CMP_NODE_COMBINE_COLOR);
|
||||
ntype.ui_name = "Combine Color";
|
||||
ntype.ui_description = "Combine an image from its composite color channels";
|
||||
ntype.enum_name_legacy = "COMBINE_COLOR";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeCMPCombSepColor", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_combine_color_cc
|
||||
@@ -0,0 +1,205 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_ocio_color_space_conversion_shader.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_convert_color_space_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeConvertColorSpace)
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Color>("Image"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.structure_type(StructureType::Dynamic);
|
||||
|
||||
b.add_output<decl::Color>("Image"_ustr).structure_type(StructureType::Dynamic);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*ntree*/, bNode *node)
|
||||
{
|
||||
NodeConvertColorSpace *ncs = MEM_new<NodeConvertColorSpace>("node colorspace");
|
||||
STRNCPY_UTF8(ncs->from_color_space, "scene_linear");
|
||||
STRNCPY_UTF8(ncs->to_color_space, "scene_linear");
|
||||
node->storage = ncs;
|
||||
}
|
||||
|
||||
static void node_draw_buttons(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop_with_menu(ptr,
|
||||
"from_color_space",
|
||||
ui::ITEM_R_SPLIT_EMPTY_NAME,
|
||||
std::nullopt,
|
||||
ICON_NONE,
|
||||
"UI_MT_color_space_select");
|
||||
layout.prop_with_menu(ptr,
|
||||
"to_color_space",
|
||||
ui::ITEM_R_SPLIT_EMPTY_NAME,
|
||||
std::nullopt,
|
||||
ICON_NONE,
|
||||
"UI_MT_color_space_select");
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class ConvertColorSpaceOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const Result &input_image = this->get_input("Image");
|
||||
if (this->is_identity()) {
|
||||
Result &output_image = this->get_result("Image");
|
||||
output_image.share_data(input_image);
|
||||
return;
|
||||
}
|
||||
|
||||
if (input_image.is_single_value()) {
|
||||
execute_single();
|
||||
return;
|
||||
}
|
||||
if (this->context().use_gpu()) {
|
||||
execute_gpu();
|
||||
}
|
||||
else {
|
||||
execute_cpu();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_gpu()
|
||||
{
|
||||
const char *source = node_storage(node()).from_color_space;
|
||||
const char *target = node_storage(node()).to_color_space;
|
||||
|
||||
OCIOColorSpaceConversionShader &ocio_shader =
|
||||
context().cache_manager().ocio_color_space_conversion_shaders.get(
|
||||
context(), source, target);
|
||||
|
||||
gpu::Shader *shader = ocio_shader.bind_shader_and_resources();
|
||||
|
||||
/* A null shader indicates that the conversion shader is just a stub implementation since OCIO
|
||||
* is disabled at compile time, so pass the input through in that case. */
|
||||
const Result &input_image = this->get_input("Image");
|
||||
Result &output_image = this->get_result("Image");
|
||||
if (!shader) {
|
||||
output_image.share_data(input_image);
|
||||
return;
|
||||
}
|
||||
|
||||
GPU_shader_uniform_1b(shader, "premultiply_output", false);
|
||||
|
||||
input_image.bind_as_texture(shader, ocio_shader.input_sampler_name());
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, ocio_shader.output_image_name());
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
input_image.unbind_as_texture();
|
||||
output_image.unbind_as_image();
|
||||
ocio_shader.unbind_shader_and_resources();
|
||||
}
|
||||
|
||||
void execute_cpu()
|
||||
{
|
||||
const char *source = node_storage(node()).from_color_space;
|
||||
const char *target = node_storage(node()).to_color_space;
|
||||
ColormanageProcessor color_processor = ColormanageProcessor::colorspace_processor_new(source,
|
||||
target);
|
||||
|
||||
Result &input_image = get_input("Image");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
output_image.store_pixel(texel, input_image.load_pixel<Color>(texel));
|
||||
});
|
||||
|
||||
color_processor.apply(static_cast<float *>(output_image.cpu_data_for_write().data()),
|
||||
domain.data_size.x,
|
||||
domain.data_size.y,
|
||||
input_image.channels_count(),
|
||||
false);
|
||||
}
|
||||
|
||||
void execute_single()
|
||||
{
|
||||
const char *source = node_storage(node()).from_color_space;
|
||||
const char *target = node_storage(node()).to_color_space;
|
||||
ColormanageProcessor color_processor = ColormanageProcessor::colorspace_processor_new(source,
|
||||
target);
|
||||
|
||||
Result &input_image = get_input("Image");
|
||||
Color color = input_image.get_single_value<Color>();
|
||||
|
||||
color_processor.apply_pixel(color, 3);
|
||||
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_single_value();
|
||||
output_image.set_single_value(color);
|
||||
}
|
||||
|
||||
bool is_identity()
|
||||
{
|
||||
const char *source = node_storage(node()).from_color_space;
|
||||
const char *target = node_storage(node()).to_color_space;
|
||||
|
||||
if (STREQ(source, target)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Data color spaces ignore any color transformation that gets applied to them. */
|
||||
if (IMB_colormanagement_space_name_is_data(source)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new ConvertColorSpaceOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeConvertColorSpace"_ustr, CMP_NODE_CONVERT_COLOR_SPACE);
|
||||
ntype.ui_name = "Convert Colorspace";
|
||||
ntype.ui_description = "Convert between color spaces";
|
||||
ntype.enum_name_legacy = "CONVERT_COLORSPACE";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.draw_buttons = node_draw_buttons;
|
||||
ntype.default_width = bke::NodeWidth::_160;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeConvertColorSpace", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
ntype.default_width = bke::NodeWidth::_160;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_convert_color_space_cc
|
||||
@@ -0,0 +1,219 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_color_types.h"
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
|
||||
#include "NOD_socket_declarations.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_ocio_color_space_conversion_shader.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_convert_to_display_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeConvertToDisplay)
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Color>("Image"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Bool>("Invert"_ustr)
|
||||
.default_value(false)
|
||||
.description(
|
||||
"Convert from display to scene linear instead. Not all view transforms can be inverted "
|
||||
"exactly, and the result may not match the original scene linear image");
|
||||
|
||||
b.add_output<decl::Color>("Image"_ustr).structure_type(StructureType::Dynamic);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*ntree*/, bNode *node)
|
||||
{
|
||||
NodeConvertToDisplay *nctd = MEM_new<NodeConvertToDisplay>(__func__);
|
||||
BKE_color_managed_display_settings_init(&nctd->display_settings);
|
||||
BKE_color_managed_view_settings_init(&nctd->view_settings, &nctd->display_settings, nullptr);
|
||||
nctd->view_settings.flag |= COLORMANAGE_VIEW_ONLY_VIEW_LOOK;
|
||||
node->storage = nctd;
|
||||
}
|
||||
|
||||
static void node_free(bNode *node)
|
||||
{
|
||||
NodeConvertToDisplay *nctd = static_cast<NodeConvertToDisplay *>(node->storage);
|
||||
BKE_color_managed_view_settings_free(&nctd->view_settings);
|
||||
MEM_delete(nctd);
|
||||
}
|
||||
|
||||
static void node_copy(bNodeTree * /*dest_ntree*/, bNode *dest_node, const bNode *src_node)
|
||||
{
|
||||
NodeConvertToDisplay *dest = MEM_new<NodeConvertToDisplay>(__func__);
|
||||
const NodeConvertToDisplay *src = static_cast<const NodeConvertToDisplay *>(src_node->storage);
|
||||
BKE_color_managed_view_settings_copy(&dest->view_settings, &src->view_settings);
|
||||
BKE_color_managed_display_settings_copy(&dest->display_settings, &src->display_settings);
|
||||
dest_node->storage = dest;
|
||||
}
|
||||
|
||||
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
|
||||
{
|
||||
const NodeConvertToDisplay *nctd = static_cast<const NodeConvertToDisplay *>(node.storage);
|
||||
BKE_color_managed_view_settings_blend_write(&writer, &nctd->view_settings);
|
||||
}
|
||||
|
||||
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
|
||||
{
|
||||
NodeConvertToDisplay *nctd = static_cast<NodeConvertToDisplay *>(node.storage);
|
||||
BKE_color_managed_view_settings_blend_read_data(&reader, &nctd->view_settings);
|
||||
}
|
||||
|
||||
static void node_draw_buttons(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
PointerRNA display_ptr = RNA_pointer_get(ptr, "display_settings");
|
||||
PointerRNA view_ptr = RNA_pointer_get(ptr, "view_settings");
|
||||
|
||||
layout.prop(&display_ptr, "display_device", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
layout.prop(&view_ptr, "view_transform", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
layout.prop(&view_ptr, "look", UI_ITEM_NONE, IFACE_("Look"), ICON_NONE);
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class ConvertToDisplayOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
bool do_inverse()
|
||||
{
|
||||
return this->get_input("Invert").get_single_value_default<bool>();
|
||||
}
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const Result &input_image = this->get_input("Image");
|
||||
|
||||
if (input_image.is_single_value()) {
|
||||
execute_single();
|
||||
return;
|
||||
}
|
||||
if (this->context().use_gpu()) {
|
||||
execute_gpu();
|
||||
}
|
||||
else {
|
||||
execute_cpu();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_gpu()
|
||||
{
|
||||
const NodeConvertToDisplay &nctd = node_storage(node());
|
||||
|
||||
OCIOToDisplayShader &ocio_shader = context().cache_manager().ocio_to_display_shaders.get(
|
||||
context(), nctd.display_settings, nctd.view_settings, do_inverse());
|
||||
|
||||
gpu::Shader *shader = ocio_shader.bind_shader_and_resources();
|
||||
|
||||
/* A null shader indicates that the conversion shader is just a stub implementation since OCIO
|
||||
* is disabled at compile time, so pass the input through in that case. */
|
||||
const Result &input_image = this->get_input("Image");
|
||||
Result &output_image = this->get_result("Image");
|
||||
if (!shader) {
|
||||
output_image.share_data(input_image);
|
||||
return;
|
||||
}
|
||||
|
||||
input_image.bind_as_texture(shader, ocio_shader.input_sampler_name());
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, ocio_shader.output_image_name());
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
input_image.unbind_as_texture();
|
||||
output_image.unbind_as_image();
|
||||
ocio_shader.unbind_shader_and_resources();
|
||||
}
|
||||
|
||||
void execute_cpu()
|
||||
{
|
||||
const NodeConvertToDisplay &nctd = node_storage(node());
|
||||
ColormanageProcessor color_processor = ColormanageProcessor::display_processor_new(
|
||||
&nctd.view_settings, &nctd.display_settings, DISPLAY_SPACE_VIDEO_OUTPUT, do_inverse());
|
||||
|
||||
Result &input_image = get_input("Image");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
output_image.store_pixel(texel, input_image.load_pixel<Color>(texel));
|
||||
});
|
||||
|
||||
color_processor.apply(static_cast<float *>(output_image.cpu_data_for_write().data()),
|
||||
domain.data_size.x,
|
||||
domain.data_size.y,
|
||||
input_image.channels_count(),
|
||||
false);
|
||||
}
|
||||
|
||||
void execute_single()
|
||||
{
|
||||
const NodeConvertToDisplay &nctd = node_storage(node());
|
||||
ColormanageProcessor color_processor = ColormanageProcessor::display_processor_new(
|
||||
&nctd.view_settings, &nctd.display_settings, DISPLAY_SPACE_VIDEO_OUTPUT, do_inverse());
|
||||
|
||||
Result &input_image = get_input("Image");
|
||||
Color color = input_image.get_single_value<Color>();
|
||||
|
||||
color_processor.apply_pixel(color, 3);
|
||||
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_single_value();
|
||||
output_image.set_single_value(color);
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new ConvertToDisplayOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeConvertToDisplay"_ustr, CMP_NODE_CONVERT_TO_DISPLAY);
|
||||
ntype.ui_name = "Convert to Display";
|
||||
ntype.ui_description =
|
||||
"Convert from scene linear to display color space, with a view transform and look for tone "
|
||||
"mapping";
|
||||
ntype.enum_name_legacy = "CONVERT_TO_DISPLAY";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.draw_buttons = node_draw_buttons;
|
||||
ntype.default_width = bke::NodeWidth::_160;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(ntype, "NodeConvertToDisplay", node_free, node_copy);
|
||||
ntype.blend_data_read_storage_content = node_blend_read;
|
||||
ntype.blend_write_storage_content = node_blend_write;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
ntype.default_width = bke::NodeWidth::_240;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_convert_to_display_cc
|
||||
@@ -0,0 +1,127 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "COM_algorithm_convolve.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_convolve_cc {
|
||||
|
||||
enum class KernelDataType : uint8_t {
|
||||
Float = 0,
|
||||
Color = 1,
|
||||
};
|
||||
|
||||
static const EnumPropertyItem kernel_data_type_items[] = {
|
||||
{int(KernelDataType::Float),
|
||||
"FLOAT",
|
||||
0,
|
||||
N_("Float"),
|
||||
N_("The kernel is a float and will be convolved with all input channels")},
|
||||
{int(KernelDataType::Color),
|
||||
"COLOR",
|
||||
0,
|
||||
N_("Color"),
|
||||
N_("The kernel is a color and each channel of the kernel will be convolved with each "
|
||||
"respective channel in the input")},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Color>("Image"_ustr).hide_value().structure_type(StructureType::Dynamic);
|
||||
b.add_output<decl::Color>("Image"_ustr)
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.align_with_previous();
|
||||
|
||||
b.add_input<decl::Menu>("Kernel Data Type"_ustr)
|
||||
.default_value(KernelDataType::Float)
|
||||
.static_items(kernel_data_type_items)
|
||||
.optional_label();
|
||||
b.add_input<decl::Float>("Kernel"_ustr, "Float Kernel"_ustr)
|
||||
.hide_value()
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.usage_by_single_menu(int(KernelDataType::Float))
|
||||
.compositor_realization_mode(CompositorInputRealizationMode::Transforms);
|
||||
b.add_input<decl::Color>("Kernel"_ustr, "Color Kernel"_ustr)
|
||||
.hide_value()
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.usage_by_single_menu(int(KernelDataType::Color))
|
||||
.compositor_realization_mode(CompositorInputRealizationMode::Transforms);
|
||||
b.add_input<decl::Bool>("Normalize Kernel"_ustr)
|
||||
.default_value(true)
|
||||
.description("Normalizes the kernel such that it integrates to one");
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class ConvolveOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const Result &input = this->get_input("Image");
|
||||
const Result &kernel = this->get_kernel_input();
|
||||
Result &output = this->get_result("Image");
|
||||
|
||||
if (input.is_single_value() || kernel.is_single_value()) {
|
||||
output.share_data(input);
|
||||
return;
|
||||
}
|
||||
|
||||
convolve(this->context(), input, kernel, output, this->get_normalize_kernel());
|
||||
}
|
||||
|
||||
const Result &get_kernel_input()
|
||||
{
|
||||
switch (this->get_kernel_data_type()) {
|
||||
case KernelDataType::Float:
|
||||
return this->get_input("Float Kernel");
|
||||
case KernelDataType::Color:
|
||||
return this->get_input("Color Kernel");
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return this->get_input("Float Kernel");
|
||||
}
|
||||
|
||||
KernelDataType get_kernel_data_type()
|
||||
{
|
||||
return KernelDataType(
|
||||
this->get_input("Kernel Data Type").get_single_value_default<MenuValue>().value);
|
||||
}
|
||||
|
||||
bool get_normalize_kernel()
|
||||
{
|
||||
return this->get_input("Normalize Kernel").get_single_value_default<bool>();
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new ConvolveOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeConvolve"_ustr);
|
||||
ntype.ui_name = "Convolve";
|
||||
ntype.ui_description = "Convolves an image with a kernel";
|
||||
ntype.nclass = NODE_CLASS_OP_FILTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_convolve_cc
|
||||
@@ -0,0 +1,471 @@
|
||||
/* SPDX-FileCopyrightText: 2013 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_geom.h"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
#include "GPU_texture.hh"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_tracking.hh"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "COM_algorithm_smaa.hh"
|
||||
#include "COM_domain.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_corner_pin_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
|
||||
b.add_input<decl::Color>("Image"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.hide_value()
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_output<decl::Color>("Image"_ustr)
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.align_with_previous();
|
||||
b.add_output<decl::Float>("Plane"_ustr).structure_type(StructureType::Dynamic);
|
||||
|
||||
b.add_input<decl::Vector>("Upper Left"_ustr)
|
||||
.subtype(PROP_FACTOR)
|
||||
.dimensions(2)
|
||||
.default_value({0.0f, 1.0f})
|
||||
.min(0.0f)
|
||||
.max(1.0f);
|
||||
b.add_input<decl::Vector>("Upper Right"_ustr)
|
||||
.subtype(PROP_FACTOR)
|
||||
.dimensions(2)
|
||||
.default_value({1.0f, 1.0f})
|
||||
.min(0.0f)
|
||||
.max(1.0f);
|
||||
b.add_input<decl::Vector>("Lower Left"_ustr)
|
||||
.subtype(PROP_FACTOR)
|
||||
.dimensions(2)
|
||||
.default_value({0.0f, 0.0f})
|
||||
.min(0.0f)
|
||||
.max(1.0f);
|
||||
b.add_input<decl::Vector>("Lower Right"_ustr)
|
||||
.subtype(PROP_FACTOR)
|
||||
.dimensions(2)
|
||||
.default_value({1.0f, 0.0f})
|
||||
.min(0.0f)
|
||||
.max(1.0f);
|
||||
|
||||
PanelDeclarationBuilder &sampling_panel = b.add_panel("Sampling"_ustr).default_closed(true);
|
||||
sampling_panel.add_input<decl::Menu>("Interpolation"_ustr)
|
||||
.default_value(CMP_NODE_INTERPOLATION_BILINEAR)
|
||||
.static_items(rna_enum_node_compositor_interpolation_items)
|
||||
.description("Interpolation method")
|
||||
.optional_label();
|
||||
sampling_panel.add_input<decl::Menu>("Extension X"_ustr)
|
||||
.default_value(CMP_NODE_EXTENSION_MODE_CLIP)
|
||||
.static_items(rna_enum_node_compositor_extension_items)
|
||||
.description("The extension mode applied to the X axis")
|
||||
.optional_label();
|
||||
sampling_panel.add_input<decl::Menu>("Extension Y"_ustr)
|
||||
.default_value(CMP_NODE_EXTENSION_MODE_CLIP)
|
||||
.static_items(rna_enum_node_compositor_extension_items)
|
||||
.description("The extension mode applied to the Y axis")
|
||||
.optional_label();
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*ntree*/, bNode *node)
|
||||
{
|
||||
/* Unused, kept for forward compatibility. */
|
||||
NodeCornerPinData *data = MEM_new<NodeCornerPinData>(__func__);
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class CornerPinOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const float3x3 homography_matrix = compute_homography_matrix();
|
||||
|
||||
const Result &input_image = this->get_input("Image");
|
||||
Result &output_image = this->get_result("Image");
|
||||
Result &output_mask = this->get_result("Plane");
|
||||
if (input_image.is_single_value() || homography_matrix == float3x3::identity()) {
|
||||
if (output_image.should_compute()) {
|
||||
output_image.share_data(input_image);
|
||||
}
|
||||
if (output_mask.should_compute()) {
|
||||
output_mask.allocate_single_value();
|
||||
output_mask.set_single_value(1.0f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Only compute the mask if extension modes are not set to clip and if it is not used as an
|
||||
* output. */
|
||||
if (this->should_compute_mask()) {
|
||||
Result plane_mask = compute_plane_mask(homography_matrix);
|
||||
Result anti_aliased_plane_mask = context().create_result(ResultType::Float);
|
||||
smaa(context(), plane_mask, anti_aliased_plane_mask);
|
||||
plane_mask.release();
|
||||
|
||||
if (output_image.should_compute()) {
|
||||
this->compute_plane(homography_matrix, &anti_aliased_plane_mask);
|
||||
}
|
||||
|
||||
if (output_mask.should_compute()) {
|
||||
output_mask.share_data(anti_aliased_plane_mask);
|
||||
}
|
||||
|
||||
anti_aliased_plane_mask.release();
|
||||
}
|
||||
else {
|
||||
if (output_image.should_compute()) {
|
||||
this->compute_plane(homography_matrix, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void compute_plane(const float3x3 &homography_matrix, Result *plane_mask)
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
this->compute_plane_gpu(homography_matrix, plane_mask);
|
||||
}
|
||||
else {
|
||||
this->compute_plane_cpu(homography_matrix, plane_mask);
|
||||
}
|
||||
}
|
||||
|
||||
void compute_plane_gpu(const float3x3 &homography_matrix, Result *plane_mask)
|
||||
{
|
||||
gpu::Shader *shader = this->context().get_shader(this->get_shader_name());
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_mat3_as_mat4(shader, "homography_matrix", homography_matrix.ptr());
|
||||
|
||||
Result &input_image = get_input("Image");
|
||||
GPU_texture_mipmap_mode(input_image, true, true);
|
||||
/* The texture sampler should use bilinear interpolation for both the bilinear and bicubic
|
||||
* cases, as the logic used by the bicubic realization shader expects textures to use
|
||||
* bilinear interpolation. */
|
||||
const Interpolation interpolation = this->get_interpolation();
|
||||
const Extension extension_mode_x = this->get_extension_mode_x();
|
||||
const Extension extension_mode_y = this->get_extension_mode_y();
|
||||
|
||||
const bool use_bilinear = ELEM(interpolation, Interpolation::Bicubic, Interpolation::Bilinear);
|
||||
const bool use_anisotropic = interpolation == Interpolation::Anisotropic;
|
||||
GPU_texture_filter_mode(input_image, use_bilinear);
|
||||
GPU_texture_anisotropic_filter(input_image, use_anisotropic);
|
||||
GPU_texture_extend_mode_x(input_image, map_extension_mode_to_extend_mode(extension_mode_x));
|
||||
GPU_texture_extend_mode_y(input_image, map_extension_mode_to_extend_mode(extension_mode_y));
|
||||
input_image.bind_as_texture(shader, "input_tx");
|
||||
if (plane_mask) {
|
||||
plane_mask->bind_as_texture(shader, "mask_tx");
|
||||
}
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
input_image.unbind_as_texture();
|
||||
if (plane_mask) {
|
||||
plane_mask->unbind_as_texture();
|
||||
}
|
||||
|
||||
output_image.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
void compute_plane_cpu(const float3x3 &homography_matrix, Result *plane_mask)
|
||||
{
|
||||
Result &input = get_input("Image");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output = get_result("Image");
|
||||
output.allocate_texture(domain);
|
||||
const Interpolation interpolation = this->get_interpolation();
|
||||
const Extension extension_mode_x = this->get_extension_mode_x();
|
||||
const Extension extension_mode_y = this->get_extension_mode_y();
|
||||
|
||||
const int2 size = domain.data_size;
|
||||
parallel_for(size, [&](const int2 texel) {
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
|
||||
|
||||
float3 transformed_coordinates = float3x3(homography_matrix) * float3(coordinates, 1.0f);
|
||||
/* Point is at infinity and will be zero when sampled, so early exit. */
|
||||
if (transformed_coordinates.z == 0.0f) {
|
||||
output.store_pixel(texel, Color(float4(0.0f)));
|
||||
return;
|
||||
}
|
||||
|
||||
float2 projected_coordinates = transformed_coordinates.xy() / transformed_coordinates.z;
|
||||
float4 sampled_color;
|
||||
|
||||
if (interpolation != Interpolation::Anisotropic) {
|
||||
sampled_color = float4(input.sample<Color>(
|
||||
projected_coordinates, interpolation, extension_mode_x, extension_mode_y));
|
||||
}
|
||||
else {
|
||||
/* Derivative of transformed_coordinates.xy / transformed_coordinates.z vs texels. */
|
||||
float2 x_gradient = (homography_matrix[0].xy() * transformed_coordinates.z -
|
||||
transformed_coordinates.xy() * homography_matrix[0].z) /
|
||||
(math::square(transformed_coordinates.z) * size.x);
|
||||
float2 y_gradient = (homography_matrix[1].xy() * transformed_coordinates.z -
|
||||
transformed_coordinates.xy() * homography_matrix[1].z) /
|
||||
(math::square(transformed_coordinates.z) * size.y);
|
||||
const float2x2 jacobian = float2x2(x_gradient, y_gradient);
|
||||
sampled_color = float4(input.sample<Color>(projected_coordinates,
|
||||
Interpolation::Anisotropic,
|
||||
Extension::Extend,
|
||||
Extension::Extend,
|
||||
jacobian));
|
||||
}
|
||||
|
||||
float4 plane_color = plane_mask ? sampled_color * plane_mask->load_pixel<float>(texel) :
|
||||
sampled_color;
|
||||
|
||||
output.store_pixel(texel, Color(plane_color));
|
||||
});
|
||||
}
|
||||
|
||||
Result compute_plane_mask(const float3x3 &homography_matrix)
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
return this->compute_plane_mask_gpu(homography_matrix);
|
||||
}
|
||||
|
||||
return this->compute_plane_mask_cpu(homography_matrix);
|
||||
}
|
||||
|
||||
Result compute_plane_mask_gpu(const float3x3 &homography_matrix)
|
||||
{
|
||||
const bool is_x_clipped = this->get_extension_mode_x() == Extension::Clip;
|
||||
const bool is_y_clipped = this->get_extension_mode_y() == Extension::Clip;
|
||||
|
||||
gpu::Shader *shader = context().get_shader("compositor_plane_deform_mask");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_mat3_as_mat4(shader, "homography_matrix", homography_matrix.ptr());
|
||||
GPU_shader_uniform_1b(shader, "is_x_clipped", is_x_clipped);
|
||||
GPU_shader_uniform_1b(shader, "is_y_clipped", is_y_clipped);
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result plane_mask = context().create_result(ResultType::Float);
|
||||
plane_mask.allocate_texture(domain);
|
||||
plane_mask.bind_as_image(shader, "mask_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
plane_mask.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
|
||||
return plane_mask;
|
||||
}
|
||||
|
||||
Result compute_plane_mask_cpu(const float3x3 &homography_matrix)
|
||||
{
|
||||
const bool is_x_clipped = this->get_extension_mode_x() == Extension::Clip;
|
||||
const bool is_y_clipped = this->get_extension_mode_y() == Extension::Clip;
|
||||
const Domain domain = compute_domain();
|
||||
Result plane_mask = context().create_result(ResultType::Float);
|
||||
plane_mask.allocate_texture(domain);
|
||||
|
||||
const int2 size = domain.data_size;
|
||||
parallel_for(size, [&](const int2 texel) {
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
|
||||
|
||||
float3 transformed_coordinates = float3x3(homography_matrix) * float3(coordinates, 1.0f);
|
||||
/* Point is at infinity and will be zero when sampled, so early exit. */
|
||||
if (transformed_coordinates.z == 0.0f) {
|
||||
plane_mask.store_pixel(texel, 0.0f);
|
||||
return;
|
||||
}
|
||||
float2 projected_coordinates = transformed_coordinates.xy() / transformed_coordinates.z;
|
||||
bool is_inside_plane_x = projected_coordinates.x >= 0.0f && projected_coordinates.x <= 1.0f;
|
||||
bool is_inside_plane_y = projected_coordinates.y >= 0.0f && projected_coordinates.y <= 1.0f;
|
||||
|
||||
/* If not inside the plane and not clipped, use extend or repeat extension mode for the
|
||||
* mask. */
|
||||
bool is_x_masked = is_inside_plane_x || !is_x_clipped;
|
||||
bool is_y_masked = is_inside_plane_y || !is_y_clipped;
|
||||
float mask_value = is_x_masked && is_y_masked ? 1.0f : 0.0f;
|
||||
|
||||
plane_mask.store_pixel(texel, mask_value);
|
||||
});
|
||||
|
||||
return plane_mask;
|
||||
}
|
||||
|
||||
float3x3 compute_homography_matrix()
|
||||
{
|
||||
float2 lower_left = get_input("Lower Left").get_single_value_default<float2>();
|
||||
float2 lower_right = get_input("Lower Right").get_single_value_default<float2>();
|
||||
float2 upper_right = get_input("Upper Right").get_single_value_default<float2>();
|
||||
float2 upper_left = get_input("Upper Left").get_single_value_default<float2>();
|
||||
|
||||
/* The inputs are invalid because the plane is not convex, fall back to an identity operation
|
||||
* in that case. */
|
||||
if (!is_quad_convex_v2(lower_left, lower_right, upper_right, upper_left)) {
|
||||
return float3x3::identity();
|
||||
}
|
||||
|
||||
/* Compute a 2D projection matrix that projects from the corners of the image in normalized
|
||||
* coordinates into the corners of the input plane. */
|
||||
float3x3 homography_matrix;
|
||||
float corners[4][2] = {{lower_left.x, lower_left.y},
|
||||
{lower_right.x, lower_right.y},
|
||||
{upper_right.x, upper_right.y},
|
||||
{upper_left.x, upper_left.y}};
|
||||
float identity_corners[4][2] = {{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}};
|
||||
BKE_tracking_homography_between_two_quads(corners, identity_corners, homography_matrix.ptr());
|
||||
return homography_matrix;
|
||||
}
|
||||
|
||||
Interpolation get_interpolation()
|
||||
{
|
||||
const CMPNodeInterpolation interpolation = static_cast<CMPNodeInterpolation>(
|
||||
this->get_input("Interpolation").get_single_value_default<MenuValue>().value);
|
||||
switch (interpolation) {
|
||||
case CMP_NODE_INTERPOLATION_NEAREST:
|
||||
return Interpolation::Nearest;
|
||||
case CMP_NODE_INTERPOLATION_BILINEAR:
|
||||
return Interpolation::Bilinear;
|
||||
case CMP_NODE_INTERPOLATION_BICUBIC:
|
||||
return Interpolation::Bicubic;
|
||||
case CMP_NODE_INTERPOLATION_ANISOTROPIC:
|
||||
return Interpolation::Anisotropic;
|
||||
}
|
||||
|
||||
return Interpolation::Nearest;
|
||||
}
|
||||
|
||||
Extension get_extension_mode_x()
|
||||
{
|
||||
if (this->get_interpolation() == Interpolation::Anisotropic) {
|
||||
return Extension::Clip;
|
||||
}
|
||||
|
||||
const CMPExtensionMode extension_x = static_cast<CMPExtensionMode>(
|
||||
this->get_input("Extension X").get_single_value_default<MenuValue>().value);
|
||||
switch (extension_x) {
|
||||
case CMP_NODE_EXTENSION_MODE_CLIP:
|
||||
return Extension::Clip;
|
||||
case CMP_NODE_EXTENSION_MODE_REPEAT:
|
||||
return Extension::Repeat;
|
||||
case CMP_NODE_EXTENSION_MODE_EXTEND:
|
||||
return Extension::Extend;
|
||||
}
|
||||
|
||||
return Extension::Clip;
|
||||
}
|
||||
|
||||
Extension get_extension_mode_y()
|
||||
{
|
||||
if (this->get_interpolation() == Interpolation::Anisotropic) {
|
||||
return Extension::Clip;
|
||||
}
|
||||
|
||||
const CMPExtensionMode extension_y = static_cast<CMPExtensionMode>(
|
||||
this->get_input("Extension Y").get_single_value_default<MenuValue>().value);
|
||||
switch (extension_y) {
|
||||
case CMP_NODE_EXTENSION_MODE_CLIP:
|
||||
return Extension::Clip;
|
||||
case CMP_NODE_EXTENSION_MODE_REPEAT:
|
||||
return Extension::Repeat;
|
||||
case CMP_NODE_EXTENSION_MODE_EXTEND:
|
||||
return Extension::Extend;
|
||||
}
|
||||
|
||||
return Extension::Clip;
|
||||
}
|
||||
|
||||
const char *get_shader_name()
|
||||
{
|
||||
if (this->should_compute_mask()) {
|
||||
switch (this->get_interpolation()) {
|
||||
case Interpolation::Nearest:
|
||||
case Interpolation::Bilinear:
|
||||
return "compositor_plane_deform_masked";
|
||||
case Interpolation::Bicubic:
|
||||
return "compositor_plane_deform_bicubic_masked";
|
||||
case Interpolation::Anisotropic:
|
||||
return "compositor_plane_deform_anisotropic_masked";
|
||||
}
|
||||
}
|
||||
|
||||
switch (this->get_interpolation()) {
|
||||
case Interpolation::Nearest:
|
||||
case Interpolation::Bilinear:
|
||||
return "compositor_plane_deform";
|
||||
case Interpolation::Bicubic:
|
||||
return "compositor_plane_deform_bicubic";
|
||||
/* Anisotropic does not implement extension modes. Return masked shader. */
|
||||
case Interpolation::Anisotropic:
|
||||
break;
|
||||
}
|
||||
|
||||
return "compositor_plane_deform_anisotropic_masked";
|
||||
}
|
||||
|
||||
bool should_compute_mask()
|
||||
{
|
||||
Result &output_mask = this->get_result("Plane");
|
||||
const bool is_clipped_x = this->get_extension_mode_x() == Extension::Clip;
|
||||
const bool is_clipped_y = this->get_extension_mode_y() == Extension::Clip;
|
||||
const bool output_needed = output_mask.should_compute();
|
||||
const bool use_anisotropic = this->get_interpolation() == Interpolation::Anisotropic;
|
||||
|
||||
return is_clipped_x || is_clipped_y || output_needed || use_anisotropic;
|
||||
}
|
||||
|
||||
Domain compute_domain() override
|
||||
{
|
||||
Domain domain = this->get_input("Image").domain();
|
||||
/* Reset the location of the domain such that translations take effect, this will result in
|
||||
* clipping but is more expected for the user. */
|
||||
domain.transformation.location() = float2(0.0f);
|
||||
return domain;
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new CornerPinOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeCornerPin"_ustr, CMP_NODE_CORNERPIN);
|
||||
ntype.ui_name = "Corner Pin";
|
||||
ntype.ui_description = "Plane warp transformation using explicit corner values";
|
||||
ntype.enum_name_legacy = "CORNERPIN";
|
||||
ntype.nclass = NODE_CLASS_DISTORT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeCornerPinData", node_free_standard_storage, node_copy_standard_storage);
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_corner_pin_cc
|
||||
@@ -0,0 +1,262 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_bounds.hh"
|
||||
#include "BLI_bounds_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_crop_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Color>("Image"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.hide_value()
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_output<decl::Color>("Image"_ustr)
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.align_with_previous();
|
||||
|
||||
b.add_input<decl::Int>("X"_ustr)
|
||||
.default_value(0)
|
||||
.min(0)
|
||||
|
||||
.description("The X position of the lower left corner of the crop region");
|
||||
b.add_input<decl::Int>("Y"_ustr)
|
||||
.default_value(0)
|
||||
.min(0)
|
||||
|
||||
.description("The Y position of the lower left corner of the crop region");
|
||||
b.add_input<decl::Int>("Width"_ustr)
|
||||
.default_value(1920)
|
||||
.subtype(PROP_PIXEL)
|
||||
.min(1)
|
||||
|
||||
.description("The width of the crop region");
|
||||
b.add_input<decl::Int>("Height"_ustr)
|
||||
.default_value(1080)
|
||||
.subtype(PROP_PIXEL)
|
||||
.min(1)
|
||||
|
||||
.description("The height of the crop region");
|
||||
b.add_input<decl::Bool>("Alpha Crop"_ustr)
|
||||
.default_value(false)
|
||||
|
||||
.description(
|
||||
"Sets the areas outside of the crop region to be transparent instead of actually "
|
||||
"cropping the size of the image");
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class CropOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
if (this->is_identity()) {
|
||||
const Result &input = this->get_input("Image");
|
||||
Result &output = this->get_result("Image");
|
||||
output.share_data(input);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->is_alpha_crop()) {
|
||||
this->execute_alpha_crop();
|
||||
}
|
||||
else {
|
||||
this->execute_image_crop();
|
||||
}
|
||||
}
|
||||
|
||||
/* Crop by replacing areas outside of the cropping bounds with zero alpha. The output have the
|
||||
* same domain as the input image. */
|
||||
void execute_alpha_crop()
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_alpha_crop_gpu();
|
||||
}
|
||||
else {
|
||||
this->execute_alpha_crop_cpu();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_alpha_crop_gpu()
|
||||
{
|
||||
gpu::Shader *shader = this->context().get_shader("compositor_alpha_crop");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
const Bounds<int2> bounds = this->compute_cropping_bounds();
|
||||
GPU_shader_uniform_2iv(shader, "lower_bound", bounds.min);
|
||||
GPU_shader_uniform_2iv(shader, "upper_bound", bounds.max);
|
||||
|
||||
const Result &input_image = this->get_input("Image");
|
||||
input_image.bind_as_texture(shader, "input_tx");
|
||||
|
||||
const Domain domain = this->compute_domain();
|
||||
|
||||
Result &output_image = this->get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
input_image.unbind_as_texture();
|
||||
output_image.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
void execute_alpha_crop_cpu()
|
||||
{
|
||||
const Bounds<int2> bounds = this->compute_cropping_bounds();
|
||||
|
||||
const Result &input = this->get_input("Image");
|
||||
|
||||
const Domain domain = this->compute_domain();
|
||||
Result &output = this->get_result("Image");
|
||||
output.allocate_texture(domain);
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
/* The lower bound is inclusive and upper bound is exclusive. */
|
||||
bool is_inside = texel.x >= bounds.min.x && texel.y >= bounds.min.y &&
|
||||
texel.x < bounds.max.x && texel.y < bounds.max.y;
|
||||
/* Write the pixel color if it is inside the cropping region, otherwise, write zero. */
|
||||
float4 color = is_inside ? float4(input.load_pixel<Color>(texel)) : float4(0.0f);
|
||||
output.store_pixel(texel, Color(color));
|
||||
});
|
||||
}
|
||||
|
||||
/* Crop the image into a new size that matches the cropping bounds. */
|
||||
void execute_image_crop()
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_image_crop_gpu();
|
||||
}
|
||||
else {
|
||||
this->execute_image_crop_cpu();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_image_crop_gpu()
|
||||
{
|
||||
const Bounds<int2> bounds = this->compute_cropping_bounds();
|
||||
|
||||
gpu::Shader *shader = this->context().get_shader("compositor_image_crop_float4");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_2iv(shader, "lower_bound", bounds.min);
|
||||
|
||||
const Result &input_image = this->get_input("Image");
|
||||
input_image.bind_as_texture(shader, "input_tx");
|
||||
|
||||
const int2 size = bounds.size();
|
||||
|
||||
Result &output_image = this->get_result("Image");
|
||||
output_image.allocate_texture(Domain(size, this->compute_domain().transformation));
|
||||
output_image.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, size);
|
||||
|
||||
input_image.unbind_as_texture();
|
||||
output_image.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
void execute_image_crop_cpu()
|
||||
{
|
||||
const Bounds<int2> bounds = this->compute_cropping_bounds();
|
||||
|
||||
const Result &input = this->get_input("Image");
|
||||
|
||||
const int2 size = bounds.size();
|
||||
Result &output = this->get_result("Image");
|
||||
output.allocate_texture(Domain(size, this->compute_domain().transformation));
|
||||
|
||||
parallel_for(size, [&](const int2 texel) {
|
||||
output.store_pixel(texel, input.load_pixel<Color>(texel + bounds.min));
|
||||
});
|
||||
}
|
||||
|
||||
/* Returns true if the operation does nothing and the input can be passed through. */
|
||||
bool is_identity()
|
||||
{
|
||||
const Result &input = this->get_input("Image");
|
||||
/* Single value inputs can't be cropped and are returned as is. */
|
||||
if (input.is_single_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const Bounds<int2> bounds = this->compute_cropping_bounds();
|
||||
const int2 input_size = input.domain().data_size;
|
||||
/* The cropping bounds cover the whole image, so no cropping happens. */
|
||||
if (bounds.min == int2(0) && bounds.max == input_size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Bounds<int2> compute_cropping_bounds()
|
||||
{
|
||||
const int2 input_size = this->get_input("Image").domain().data_size;
|
||||
|
||||
const int x = math::clamp(
|
||||
this->get_input("X").get_single_value_default<int>(), 0, input_size.x - 1);
|
||||
const int y = math::clamp(
|
||||
this->get_input("Y").get_single_value_default<int>(), 0, input_size.y - 1);
|
||||
const int width = math::max(1, this->get_input("Width").get_single_value_default<int>());
|
||||
const int height = math::max(1, this->get_input("Height").get_single_value_default<int>());
|
||||
|
||||
const Bounds<int2> input_bounds = Bounds<int2>(int2(0), input_size);
|
||||
|
||||
const Bounds<int2> crop_bounds = Bounds<int2>(int2(x, y), int2(x + width, y + height));
|
||||
return *bounds::intersect(crop_bounds, input_bounds);
|
||||
}
|
||||
|
||||
/* If true, the region outside of the cropping bounds will be set to a zero alpha value instead
|
||||
* of actually cropping the size of the image. */
|
||||
bool is_alpha_crop()
|
||||
{
|
||||
return this->get_input("Alpha Crop").get_single_value_default<bool>();
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new CropOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeCrop"_ustr, CMP_NODE_CROP);
|
||||
ntype.ui_name = "Crop";
|
||||
ntype.ui_description =
|
||||
"Crops image to a smaller region, either making the cropped area transparent or resizing "
|
||||
"the image";
|
||||
ntype.enum_name_legacy = "CROP";
|
||||
ntype.nclass = NODE_CLASS_DISTORT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_crop_cc
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,536 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "DNA_camera_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BKE_camera.h"
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "COM_algorithm_morphological_blur.hh"
|
||||
#include "COM_bokeh_kernel.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_defocus_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeDefocus)
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Color>("Image"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Float>("Z"_ustr).default_value(1.0f).min(0.0f).max(1.0f).structure_type(
|
||||
StructureType::Dynamic);
|
||||
b.add_output<decl::Color>("Image"_ustr).structure_type(StructureType::Dynamic);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*ntree*/, bNode *node)
|
||||
{
|
||||
/* defocus node */
|
||||
NodeDefocus *nbd = MEM_new<NodeDefocus>(__func__);
|
||||
nbd->bktype = 0;
|
||||
nbd->rotation = 0.0f;
|
||||
nbd->fstop = 128.0f;
|
||||
nbd->maxblur = 16;
|
||||
nbd->scale = 1.0f;
|
||||
nbd->no_zbuf = 1;
|
||||
node->storage = nbd;
|
||||
}
|
||||
|
||||
static void node_draw_buttons(ui::Layout &layout, bContext *C, PointerRNA *ptr)
|
||||
{
|
||||
|
||||
{
|
||||
ui::Layout &col = layout.column(false);
|
||||
col.label(IFACE_("Bokeh Type:"), ICON_NONE);
|
||||
col.prop(ptr, "bokeh", ui::ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
|
||||
col.prop(ptr, "angle", ui::ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
{
|
||||
ui::Layout &col = layout.column(false);
|
||||
col.active_set(RNA_boolean_get(ptr, "use_zbuffer") == true);
|
||||
col.prop(ptr, "f_stop", ui::ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
layout.prop(ptr, "blur_max", ui::ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
|
||||
|
||||
template_id(&layout, C, ptr, "scene", nullptr, nullptr, nullptr);
|
||||
|
||||
{
|
||||
ui::Layout &col = layout.column(false);
|
||||
col.prop(ptr, "use_zbuffer", ui::ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
|
||||
ui::Layout &sub = col.column(false);
|
||||
sub.active_set(RNA_boolean_get(ptr, "use_zbuffer") == false);
|
||||
sub.prop(ptr, "z_scale", ui::ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class DefocusOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const Result &input = this->get_input("Image");
|
||||
Result &output = this->get_result("Image");
|
||||
if (input.is_single_value() || node_storage(node()).maxblur < 1.0f) {
|
||||
output.share_data(input);
|
||||
return;
|
||||
}
|
||||
|
||||
Result radius = compute_defocus_radius();
|
||||
|
||||
const int maximum_defocus_radius = math::ceil(compute_maximum_defocus_radius());
|
||||
|
||||
/* The special zero value indicate a circle, in which case, the roundness should be set to
|
||||
* 1, and the number of sides can be anything and is arbitrarily set to 3. */
|
||||
const bool is_circle = node_storage(node()).bktype == 0;
|
||||
const int2 kernel_size = int2(maximum_defocus_radius * 2 + 1);
|
||||
const int sides = is_circle ? 3 : node_storage(node()).bktype;
|
||||
const float rotation = node_storage(node()).rotation;
|
||||
const float roundness = is_circle ? 1.0f : 0.0f;
|
||||
const Result &bokeh_kernel = context().cache_manager().bokeh_kernels.get(
|
||||
context(), kernel_size, sides, rotation, roundness, 0.0f, 0.0f);
|
||||
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_gpu(input, radius, bokeh_kernel, output, maximum_defocus_radius);
|
||||
}
|
||||
else {
|
||||
this->execute_cpu(input, radius, bokeh_kernel, output, maximum_defocus_radius);
|
||||
}
|
||||
|
||||
radius.release();
|
||||
}
|
||||
|
||||
void execute_gpu(const Result &input,
|
||||
const Result &radius,
|
||||
const Result &bokeh_kernel,
|
||||
Result &output,
|
||||
const int search_radius)
|
||||
{
|
||||
gpu::Shader *shader = context().get_shader("compositor_defocus_blur");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1i(shader, "search_radius", search_radius);
|
||||
|
||||
input.bind_as_texture(shader, "input_tx");
|
||||
|
||||
radius.bind_as_texture(shader, "radius_tx");
|
||||
|
||||
GPU_texture_filter_mode(bokeh_kernel, true);
|
||||
bokeh_kernel.bind_as_texture(shader, "weights_tx");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
output.allocate_texture(domain);
|
||||
output.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
input.unbind_as_texture();
|
||||
radius.unbind_as_texture();
|
||||
bokeh_kernel.unbind_as_texture();
|
||||
output.unbind_as_image();
|
||||
}
|
||||
|
||||
void execute_cpu(const Result &input,
|
||||
const Result &radius,
|
||||
const Result &bokeh_kernel,
|
||||
Result &output,
|
||||
const int search_radius)
|
||||
{
|
||||
const Domain domain = compute_domain();
|
||||
output.allocate_texture(domain);
|
||||
|
||||
/* Given the texel in the range [-radius, radius] in both axis, load the appropriate weight
|
||||
* from the weights image, where the given texel (0, 0) corresponds the center of weights
|
||||
* image. Note that we load the weights image inverted along both directions to maintain
|
||||
* the shape of the weights if it was not symmetrical. To understand why inversion makes sense,
|
||||
* consider a 1D weights image whose right half is all ones and whose left half is all zeros.
|
||||
* Further, consider that we are blurring a single white pixel on a black background. When
|
||||
* computing the value of a pixel that is to the right of the white pixel, the white pixel will
|
||||
* be in the left region of the search window, and consequently, without inversion, a zero will
|
||||
* be sampled from the left side of the weights image and result will be zero. However, what
|
||||
* we expect is that pixels to the right of the white pixel will be white, that is, they should
|
||||
* sample a weight of 1 from the right side of the weights image, hence the need for
|
||||
* inversion. */
|
||||
auto load_weight = [&](const int2 texel, const float radius) {
|
||||
/* Add the radius to transform the texel into the range [0, radius * 2], with an additional
|
||||
* 0.5 to sample at the center of the pixels, then divide by the upper bound plus one to
|
||||
* transform the texel into the normalized range [0, 1] needed to sample the weights sampler.
|
||||
* Finally, invert the textures coordinates by subtracting from 1 to maintain the shape of
|
||||
* the weights as mentioned in the function description. */
|
||||
return float4(bokeh_kernel.sample_bilinear_extended<Color>(
|
||||
1.0f - ((float2(texel) + float2(radius + 0.5f)) / (radius * 2.0f + 1.0f))));
|
||||
};
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float center_radius = math::max(0.0f, radius.load_pixel<float, true>(texel));
|
||||
|
||||
/* Go over the window of the given search radius and accumulate the colors multiplied by
|
||||
* their respective weights as well as the weights themselves, but only if both the radius of
|
||||
* the center pixel and the radius of the candidate pixel are less than both the x and y
|
||||
* distances of the candidate pixel. */
|
||||
float4 accumulated_color = float4(0.0);
|
||||
float4 accumulated_weight = float4(0.0);
|
||||
for (int y = -search_radius; y <= search_radius; y++) {
|
||||
for (int x = -search_radius; x <= search_radius; x++) {
|
||||
float candidate_radius = math::max(
|
||||
0.0f, radius.load_pixel_extended<float, true>(texel + int2(x, y)));
|
||||
|
||||
/* Skip accumulation if either the x or y distances of the candidate pixel are larger
|
||||
* than either the center or candidate pixel radius. Note that the max and min functions
|
||||
* here denote "either" in the aforementioned description. */
|
||||
float radius = math::min(center_radius, candidate_radius);
|
||||
if (math::max(math::abs(x), math::abs(y)) > radius) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float4 weight = load_weight(int2(x, y), radius);
|
||||
float4 input_color = float4(input.load_pixel_extended<Color>(texel + int2(x, y)));
|
||||
|
||||
accumulated_color += input_color * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
accumulated_color = math::safe_divide(accumulated_color, accumulated_weight);
|
||||
|
||||
output.store_pixel(texel, Color(accumulated_color));
|
||||
});
|
||||
}
|
||||
|
||||
Result compute_defocus_radius()
|
||||
{
|
||||
if (node_storage(node()).no_zbuf) {
|
||||
return compute_defocus_radius_from_scale();
|
||||
}
|
||||
return compute_defocus_radius_from_depth();
|
||||
}
|
||||
|
||||
Result compute_defocus_radius_from_scale()
|
||||
{
|
||||
Result &input_depth = get_input("Z");
|
||||
if (this->context().use_gpu() && !input_depth.is_single_value()) {
|
||||
return compute_defocus_radius_from_scale_gpu();
|
||||
}
|
||||
return compute_defocus_radius_from_scale_cpu();
|
||||
}
|
||||
|
||||
Result compute_defocus_radius_from_scale_gpu()
|
||||
{
|
||||
gpu::Shader *shader = context().get_shader("compositor_defocus_radius_from_scale");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1f(shader, "scale", node_storage(node()).scale);
|
||||
GPU_shader_uniform_1f(shader, "max_radius", node_storage(node()).maxblur);
|
||||
|
||||
Result &input_depth = get_input("Z");
|
||||
input_depth.bind_as_texture(shader, "radius_tx");
|
||||
|
||||
Result output_radius = context().create_result(ResultType::Float);
|
||||
const Domain domain = input_depth.domain();
|
||||
output_radius.allocate_texture(domain);
|
||||
output_radius.bind_as_image(shader, "radius_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
input_depth.unbind_as_texture();
|
||||
output_radius.unbind_as_image();
|
||||
|
||||
return output_radius;
|
||||
}
|
||||
|
||||
Result compute_defocus_radius_from_scale_cpu()
|
||||
{
|
||||
const float scale = node_storage(node()).scale;
|
||||
const float max_radius = node_storage(node()).maxblur;
|
||||
|
||||
Result &input_depth = get_input("Z");
|
||||
|
||||
Result output_radius = context().create_result(ResultType::Float);
|
||||
|
||||
auto compute_radius = [&](const float depth) {
|
||||
return math::clamp(depth * scale, 0.0f, max_radius);
|
||||
};
|
||||
|
||||
if (input_depth.is_single_value()) {
|
||||
output_radius.allocate_single_value();
|
||||
output_radius.set_single_value(compute_radius(input_depth.get_single_value<float>()));
|
||||
return output_radius;
|
||||
}
|
||||
|
||||
const Domain domain = input_depth.domain();
|
||||
output_radius.allocate_texture(domain);
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float depth = input_depth.load_pixel<float>(texel);
|
||||
output_radius.store_pixel(texel, compute_radius(depth));
|
||||
});
|
||||
|
||||
return output_radius;
|
||||
}
|
||||
|
||||
Result compute_defocus_radius_from_depth()
|
||||
{
|
||||
Result &input_depth = get_input("Z");
|
||||
Result output_radius = context().create_result(ResultType::Float);
|
||||
if (this->context().use_gpu() && !input_depth.is_single_value()) {
|
||||
compute_defocus_radius_from_depth_gpu(output_radius);
|
||||
}
|
||||
else {
|
||||
compute_defocus_radius_from_depth_cpu(output_radius);
|
||||
}
|
||||
|
||||
if (output_radius.is_single_value()) {
|
||||
return output_radius;
|
||||
}
|
||||
|
||||
/* We apply a dilate morphological operator on the radius computed from depth, the operator
|
||||
* radius is the maximum possible defocus radius. This is done such that objects in
|
||||
* focus---that is, objects whose defocus radius is small---are not affected by nearby out of
|
||||
* focus objects, hence the use of erosion. */
|
||||
const float morphological_radius = compute_maximum_defocus_radius();
|
||||
Result eroded_radius = context().create_result(ResultType::Float);
|
||||
morphological_blur(context(), output_radius, eroded_radius, float2(morphological_radius));
|
||||
output_radius.release();
|
||||
|
||||
return eroded_radius;
|
||||
}
|
||||
|
||||
void compute_defocus_radius_from_depth_gpu(Result &output_radius)
|
||||
{
|
||||
gpu::Shader *shader = context().get_shader("compositor_defocus_radius_from_depth");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
const float distance_to_image_of_focus = compute_distance_to_image_of_focus();
|
||||
GPU_shader_uniform_1f(shader, "f_stop", get_f_stop());
|
||||
GPU_shader_uniform_1f(shader, "focal_length", get_focal_length());
|
||||
GPU_shader_uniform_1f(shader, "max_radius", node_storage(node()).maxblur);
|
||||
GPU_shader_uniform_1f(shader, "pixels_per_meter", compute_pixels_per_meter());
|
||||
GPU_shader_uniform_1f(shader, "distance_to_image_of_focus", distance_to_image_of_focus);
|
||||
|
||||
Result &input_depth = get_input("Z");
|
||||
input_depth.bind_as_texture(shader, "depth_tx");
|
||||
|
||||
const Domain domain = input_depth.domain();
|
||||
output_radius.allocate_texture(domain);
|
||||
output_radius.bind_as_image(shader, "radius_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
input_depth.unbind_as_texture();
|
||||
output_radius.unbind_as_image();
|
||||
}
|
||||
|
||||
void compute_defocus_radius_from_depth_cpu(Result &output_radius)
|
||||
{
|
||||
const float f_stop = this->get_f_stop();
|
||||
const float focal_length = this->get_focal_length();
|
||||
const float max_radius = node_storage(this->node()).maxblur;
|
||||
const float pixels_per_meter = this->compute_pixels_per_meter();
|
||||
const float distance_to_image_of_focus = this->compute_distance_to_image_of_focus();
|
||||
|
||||
Result &input_depth = get_input("Z");
|
||||
|
||||
/* Given a depth value, compute the radius of the circle of confusion in pixels based on
|
||||
* equation (8) of the paper:
|
||||
*
|
||||
* Potmesil, Michael, and Indranil Chakravarty. "A lens and aperture camera model for
|
||||
* synthetic image generation." ACM SIGGRAPH Computer Graphics 15.3 (1981): 297-305. */
|
||||
auto compute_radius = [&](const float depth) {
|
||||
/* Compute `Vu` in equation (7). */
|
||||
const float distance_to_image_of_object = (focal_length * depth) / (depth - focal_length);
|
||||
|
||||
/* Compute C in equation (8). Notice that the last multiplier was included in the absolute
|
||||
* since it is negative when the object distance is less than the focal length, as noted in
|
||||
* equation (7). */
|
||||
float diameter = math::abs((distance_to_image_of_object - distance_to_image_of_focus) *
|
||||
(focal_length / (f_stop * distance_to_image_of_object)));
|
||||
|
||||
/* The diameter is in meters, so multiply by the pixels per meter. */
|
||||
float radius = (diameter / 2.0f) * pixels_per_meter;
|
||||
|
||||
return math::min(max_radius, radius);
|
||||
};
|
||||
|
||||
if (input_depth.is_single_value()) {
|
||||
output_radius.allocate_single_value();
|
||||
output_radius.set_single_value(compute_radius(input_depth.get_single_value<float>()));
|
||||
return;
|
||||
}
|
||||
|
||||
const Domain domain = input_depth.domain();
|
||||
output_radius.allocate_texture(domain);
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float depth = input_depth.load_pixel<float>(texel);
|
||||
output_radius.store_pixel(texel, compute_radius(depth));
|
||||
});
|
||||
}
|
||||
|
||||
/* Computes the maximum possible defocus radius in pixels. */
|
||||
float compute_maximum_defocus_radius()
|
||||
{
|
||||
if (node_storage(node()).no_zbuf) {
|
||||
return node_storage(node()).maxblur;
|
||||
}
|
||||
|
||||
const float maximum_diameter = compute_maximum_diameter_of_circle_of_confusion();
|
||||
const float pixels_per_meter = compute_pixels_per_meter();
|
||||
const float radius = (maximum_diameter / 2.0f) * pixels_per_meter;
|
||||
return math::min(radius, node_storage(node()).maxblur);
|
||||
}
|
||||
|
||||
/* Computes the diameter of the circle of confusion at infinity. This computes the limit in
|
||||
* figure (5) of the paper:
|
||||
*
|
||||
* Potmesil, Michael, and Indranil Chakravarty. "A lens and aperture camera model for synthetic
|
||||
* image generation." ACM SIGGRAPH Computer Graphics 15.3 (1981): 297-305.
|
||||
*
|
||||
* Notice that the diameter is asymmetric around the focus point, and we are computing the
|
||||
* limiting diameter at infinity, while another limiting diameter exist at zero distance from the
|
||||
* lens. This is a limitation of the implementation, as it assumes far defocusing only. */
|
||||
float compute_maximum_diameter_of_circle_of_confusion()
|
||||
{
|
||||
const float f_stop = get_f_stop();
|
||||
const float focal_length = get_focal_length();
|
||||
const float distance_to_image_of_focus = compute_distance_to_image_of_focus();
|
||||
return math::abs((distance_to_image_of_focus / (f_stop * focal_length)) -
|
||||
(focal_length / f_stop));
|
||||
}
|
||||
|
||||
/* Computes the distance in meters to the image of the focus point across a lens of the specified
|
||||
* focal length. This computes `Vp` in equation (7) of the paper:
|
||||
*
|
||||
* Potmesil, Michael, and Indranil Chakravarty. "A lens and aperture camera model for synthetic
|
||||
* image generation." ACM SIGGRAPH Computer Graphics 15.3 (1981): 297-305. */
|
||||
float compute_distance_to_image_of_focus()
|
||||
{
|
||||
const float focal_length = get_focal_length();
|
||||
const float focus_distance = compute_focus_distance();
|
||||
return (focal_length * focus_distance) / (focus_distance - focal_length);
|
||||
}
|
||||
|
||||
/* Returns the focal length in meters. Fall back to 50 mm in case of an invalid camera. Ensure a
|
||||
* minimum of 1e-6. */
|
||||
float get_focal_length()
|
||||
{
|
||||
const Camera *camera = get_camera();
|
||||
return camera ? math::max(1e-6f, camera->lens / 1000.0f) : 50.0f / 1000.0f;
|
||||
}
|
||||
|
||||
/* Computes the distance to the point that is completely in focus. Default to 10 meters for null
|
||||
* camera. */
|
||||
float compute_focus_distance()
|
||||
{
|
||||
const Object *camera_object = get_camera_object();
|
||||
if (!camera_object) {
|
||||
return 10.0f;
|
||||
}
|
||||
return BKE_camera_object_dof_distance(camera_object);
|
||||
}
|
||||
|
||||
/* Computes the number of pixels per meter of the sensor size. This is essentially the resolution
|
||||
* over the sensor size, using the sensor fit axis. Fall back to DEFAULT_SENSOR_WIDTH in case of
|
||||
* an invalid camera. Note that the stored sensor size is in millimeter, so convert to meters. */
|
||||
float compute_pixels_per_meter()
|
||||
{
|
||||
const int2 size = compute_domain().data_size;
|
||||
const Camera *camera = get_camera();
|
||||
const float default_value = size.x / (DEFAULT_SENSOR_WIDTH / 1000.0f);
|
||||
if (!camera) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
switch (camera->sensor_fit) {
|
||||
case CAMERA_SENSOR_FIT_HOR:
|
||||
return size.x / (camera->sensor_x / 1000.0f);
|
||||
case CAMERA_SENSOR_FIT_VERT:
|
||||
return size.y / (camera->sensor_y / 1000.0f);
|
||||
case CAMERA_SENSOR_FIT_AUTO: {
|
||||
return size.x > size.y ? size.x / (camera->sensor_x / 1000.0f) :
|
||||
size.y / (camera->sensor_y / 1000.0f);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return default_value;
|
||||
}
|
||||
|
||||
/* Returns the f-stop number. Fall back to 1e-3 for zero f-stop. */
|
||||
float get_f_stop()
|
||||
{
|
||||
return math::max(1e-3f, node_storage(node()).fstop);
|
||||
}
|
||||
|
||||
const Camera *get_camera()
|
||||
{
|
||||
const Object *camera_object = get_camera_object();
|
||||
if (!camera_object || camera_object->type != OB_CAMERA) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return reinterpret_cast<Camera *>(camera_object->data);
|
||||
}
|
||||
|
||||
const Object *get_camera_object()
|
||||
{
|
||||
Object *marker_camera = BKE_scene_camera_switch_find(this->get_scene(),
|
||||
this->context().get_frame_number());
|
||||
return marker_camera ? marker_camera : this->get_scene()->camera;
|
||||
}
|
||||
|
||||
const Scene *get_scene()
|
||||
{
|
||||
return node().id ? reinterpret_cast<Scene *>(node().id) : &context().get_scene();
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new DefocusOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeDefocus"_ustr, CMP_NODE_DEFOCUS);
|
||||
ntype.ui_name = "Defocus";
|
||||
ntype.ui_description = "Apply depth of field in 2D, using a Z depth map or mask";
|
||||
ntype.enum_name_legacy = "DEFOCUS";
|
||||
ntype.nclass = NODE_CLASS_OP_FILTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.draw_buttons = node_draw_buttons;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeDefocus", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_defocus_cc
|
||||
@@ -0,0 +1,397 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#ifndef __APPLE__
|
||||
# include "BLI_system.h"
|
||||
#endif
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "COM_denoised_auxiliary_pass.hh"
|
||||
#include "COM_derived_resources.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
#include "COM_utilities_oidn.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
# include <OpenImageDenoise/oidn.hpp>
|
||||
#endif
|
||||
|
||||
namespace blender::nodes::node_composite_denoise_cc {
|
||||
|
||||
static const EnumPropertyItem prefilter_items[] = {
|
||||
{CMP_NODE_DENOISE_PREFILTER_NONE,
|
||||
"NONE",
|
||||
0,
|
||||
N_("None"),
|
||||
N_("No prefiltering, use when guiding passes are noise-free")},
|
||||
{CMP_NODE_DENOISE_PREFILTER_FAST,
|
||||
"FAST",
|
||||
0,
|
||||
N_("Fast"),
|
||||
N_("Denoise image and guiding passes together. Improves quality when guiding passes are "
|
||||
"noisy using least amount of extra processing time.")},
|
||||
{CMP_NODE_DENOISE_PREFILTER_ACCURATE,
|
||||
"ACCURATE",
|
||||
0,
|
||||
N_("Accurate"),
|
||||
N_("Prefilter noisy guiding passes before denoising image. Improves quality when guiding "
|
||||
"passes are noisy using extra processing time.")},
|
||||
{0, nullptr, 0, nullptr, nullptr}};
|
||||
|
||||
static const EnumPropertyItem quality_items[] = {
|
||||
{CMP_NODE_DENOISE_QUALITY_SCENE,
|
||||
"FOLLOW_SCENE",
|
||||
0,
|
||||
N_("Follow Scene"),
|
||||
N_("Use the scene's denoising quality setting")},
|
||||
{CMP_NODE_DENOISE_QUALITY_HIGH, "HIGH", 0, "High", "High quality"},
|
||||
{CMP_NODE_DENOISE_QUALITY_BALANCED,
|
||||
"BALANCED",
|
||||
0,
|
||||
N_("Balanced"),
|
||||
N_("Balanced between performance and quality")},
|
||||
{CMP_NODE_DENOISE_QUALITY_FAST, "FAST", 0, "Fast", "High performance"},
|
||||
{0, nullptr, 0, nullptr, nullptr}};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Color>("Image"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.hide_value()
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_output<decl::Color>("Image"_ustr)
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.align_with_previous();
|
||||
|
||||
b.add_input<decl::Color>("Albedo"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.hide_value()
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Vector>("Normal"_ustr)
|
||||
.default_value({0.0f, 0.0f, 0.0f})
|
||||
.min(-1.0f)
|
||||
.max(1.0f)
|
||||
.hide_value()
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Bool>("HDR"_ustr).default_value(true);
|
||||
b.add_input<decl::Menu>("Prefilter"_ustr)
|
||||
.default_value(CMP_NODE_DENOISE_PREFILTER_ACCURATE)
|
||||
.static_items(prefilter_items)
|
||||
.optional_label();
|
||||
b.add_input<decl::Menu>("Quality"_ustr)
|
||||
.default_value(CMP_NODE_DENOISE_QUALITY_SCENE)
|
||||
.static_items(quality_items)
|
||||
.optional_label();
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*ntree*/, bNode *node)
|
||||
{
|
||||
/* Unused, kept for forward compatibility. */
|
||||
NodeDenoise *ndg = MEM_new<NodeDenoise>(__func__);
|
||||
node->storage = ndg;
|
||||
}
|
||||
|
||||
static bool is_oidn_supported()
|
||||
{
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
# if defined(__APPLE__)
|
||||
/* Always supported through Accelerate framework BNNS. */
|
||||
return true;
|
||||
# elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
/* OIDN 2.2 and up supports ARM64 on Windows and Linux. */
|
||||
return true;
|
||||
# else
|
||||
return BLI_cpu_support_sse42();
|
||||
# endif
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void node_draw_buttons(ui::Layout &layout, bContext * /*C*/, PointerRNA * /*ptr*/)
|
||||
{
|
||||
#ifndef WITH_OPENIMAGEDENOISE
|
||||
layout.label(RPT_("Disabled. Built without OpenImageDenoise"), ICON_ERROR);
|
||||
#else
|
||||
if (!is_oidn_supported()) {
|
||||
layout.label(RPT_("Disabled. Platform not supported"), ICON_ERROR);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
/* A callback to cancel the filter operations by evaluating the context's is_canceled method. The
|
||||
* API specifies that true indicates the filter should continue, while false indicates it should
|
||||
* stop, so invert the condition. This callback can also be used to track progress using the given
|
||||
* n argument, but we currently don't make use of it. See OIDNProgressMonitorFunction in the API
|
||||
* for more information. */
|
||||
[[maybe_unused]] static bool oidn_progress_monitor_function(void *user_ptr, double /*n*/)
|
||||
{
|
||||
const Context *context = static_cast<const Context *>(user_ptr);
|
||||
return !context->is_canceled();
|
||||
}
|
||||
|
||||
class DenoiseOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const Result &input_image = this->get_input("Image");
|
||||
Result &output_image = this->get_result("Image");
|
||||
|
||||
if (!is_oidn_supported() || input_image.is_single_value()) {
|
||||
output_image.share_data(input_image);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
oidn::DeviceRef device = create_oidn_device(this->context());
|
||||
device.set("setAffinity", false);
|
||||
device.commit();
|
||||
|
||||
/* For GPU, we download the input into a temporary result and mark it to be freed later, and
|
||||
* since it is temporary, we also perform denoising in-place. */
|
||||
Result denoise_input = this->context().create_result(input_image.type());
|
||||
Result *denoise_output = nullptr;
|
||||
if (this->context().use_gpu()) {
|
||||
Result input_image_cpu = input_image.download_to_cpu();
|
||||
denoise_input.share_data(input_image_cpu);
|
||||
input_image_cpu.release();
|
||||
|
||||
denoise_output = &denoise_input;
|
||||
}
|
||||
else {
|
||||
denoise_input.share_data(input_image);
|
||||
|
||||
output_image.allocate_texture(input_image.domain());
|
||||
denoise_output = &output_image;
|
||||
}
|
||||
|
||||
oidn::BufferRef input_buffer = create_oidn_buffer(device, denoise_input);
|
||||
oidn::BufferRef output_buffer = create_oidn_buffer(device, *denoise_output);
|
||||
|
||||
oidn::FilterRef filter = device.newFilter("RT");
|
||||
const int2 size = input_image.domain().data_size;
|
||||
const int pixel_stride = sizeof(float) * denoise_input.channels_count();
|
||||
filter.setImage("color", input_buffer, oidn::Format::Float3, size.x, size.y, 0, pixel_stride);
|
||||
filter.setImage(
|
||||
"output", output_buffer, oidn::Format::Float3, size.x, size.y, 0, pixel_stride);
|
||||
filter.set("hdr", use_hdr());
|
||||
filter.set("cleanAux", auxiliary_passes_are_clean());
|
||||
this->set_filter_quality(filter);
|
||||
filter.setProgressMonitorFunction(oidn_progress_monitor_function, &context());
|
||||
|
||||
/* If the albedo input is not a single value input, set it to the albedo input of the filter,
|
||||
* denoising it if needed. */
|
||||
Result &input_albedo = this->get_input("Albedo");
|
||||
Result denoise_albedo = this->context().create_result(input_albedo.type());
|
||||
if (!input_albedo.is_single_value()) {
|
||||
if (this->should_denoise_auxiliary_passes()) {
|
||||
denoise_albedo.share_data(input_albedo.derived_resources()
|
||||
.denoised_auxiliary_passes
|
||||
.get(this->context(),
|
||||
input_albedo,
|
||||
DenoisedAuxiliaryPassType::Albedo,
|
||||
this->get_quality())
|
||||
.result);
|
||||
}
|
||||
else {
|
||||
if (this->context().use_gpu()) {
|
||||
Result input_albedo_cpu = input_albedo.download_to_cpu();
|
||||
denoise_albedo.share_data(input_albedo_cpu);
|
||||
input_albedo_cpu.release();
|
||||
}
|
||||
else {
|
||||
denoise_albedo.share_data(input_albedo);
|
||||
}
|
||||
}
|
||||
|
||||
oidn::BufferRef albedo_buffer = create_oidn_buffer(device, denoise_albedo);
|
||||
const int pixel_stride = sizeof(float) * denoise_albedo.channels_count();
|
||||
filter.setImage(
|
||||
"albedo", albedo_buffer, oidn::Format::Float3, size.x, size.y, 0, pixel_stride);
|
||||
}
|
||||
|
||||
/* If the albedo and normal inputs are not single value inputs, set the normal input to the
|
||||
* albedo input of the filter, denoising it if needed. Notice that we also consider the albedo
|
||||
* input because OIDN doesn't support denoising with only the normal auxiliary pass. */
|
||||
Result &input_normal = this->get_input("Normal");
|
||||
Result denoise_normal = this->context().create_result(input_normal.type());
|
||||
if (!input_albedo.is_single_value() && !input_normal.is_single_value()) {
|
||||
if (should_denoise_auxiliary_passes()) {
|
||||
denoise_normal.share_data(input_normal.derived_resources()
|
||||
.denoised_auxiliary_passes
|
||||
.get(this->context(),
|
||||
input_normal,
|
||||
DenoisedAuxiliaryPassType::Normal,
|
||||
this->get_quality())
|
||||
.result);
|
||||
}
|
||||
else {
|
||||
if (this->context().use_gpu()) {
|
||||
Result input_normal_cpu = input_normal.download_to_cpu();
|
||||
denoise_normal.share_data(input_normal_cpu);
|
||||
input_normal_cpu.release();
|
||||
}
|
||||
else {
|
||||
denoise_normal.share_data(input_normal);
|
||||
}
|
||||
}
|
||||
|
||||
oidn::BufferRef normal_buffer = create_oidn_buffer(device, denoise_normal);
|
||||
const int pixel_stride = sizeof(float) * denoise_normal.channels_count();
|
||||
filter.setImage(
|
||||
"normal", normal_buffer, oidn::Format::Float3, size.x, size.y, 0, pixel_stride);
|
||||
}
|
||||
|
||||
filter.commit();
|
||||
filter.execute();
|
||||
|
||||
if (output_buffer.getStorage() != oidn::Storage::Host) {
|
||||
output_buffer.read(
|
||||
0, denoise_output->size_in_bytes(), denoise_output->cpu_data_for_write().data());
|
||||
}
|
||||
|
||||
if (this->context().use_gpu()) {
|
||||
Result output_gpu = denoise_output->upload_to_gpu(true);
|
||||
output_image.share_data(output_gpu);
|
||||
output_gpu.release();
|
||||
}
|
||||
else {
|
||||
/* OIDN already wrote to the output directly, however, OIDN skips the alpha channel, so we
|
||||
* need to restore it. */
|
||||
parallel_for(size, [&](const int2 texel) {
|
||||
const float alpha = input_image.load_pixel<Color>(texel).a;
|
||||
output_image.store_pixel(
|
||||
texel, Color(float4(float4(output_image.load_pixel<Color>(texel)).xyz(), alpha)));
|
||||
});
|
||||
}
|
||||
|
||||
denoise_input.release();
|
||||
denoise_albedo.release();
|
||||
denoise_normal.release();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* If the pre-filter mode is set to CMP_NODE_DENOISE_PREFILTER_NONE, that it means the supplied
|
||||
* auxiliary passes are already noise-free, if it is set to CMP_NODE_DENOISE_PREFILTER_ACCURATE,
|
||||
* the auxiliary passes will be denoised before denoising the main image, so in both cases, the
|
||||
* auxiliary passes are considered clean. If it is set to CMP_NODE_DENOISE_PREFILTER_FAST on the
|
||||
* other hand, the auxiliary passes are assumed to be noisy and are thus not clean, and will be
|
||||
* denoised while denoising the main image. */
|
||||
bool auxiliary_passes_are_clean()
|
||||
{
|
||||
return get_prefilter_mode() != CMP_NODE_DENOISE_PREFILTER_FAST;
|
||||
}
|
||||
|
||||
/* Returns whether the auxiliary passes should be denoised, see the auxiliary_passes_are_clean
|
||||
* method for more information. */
|
||||
bool should_denoise_auxiliary_passes()
|
||||
{
|
||||
return get_prefilter_mode() == CMP_NODE_DENOISE_PREFILTER_ACCURATE;
|
||||
}
|
||||
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
# if OIDN_VERSION_MAJOR >= 2
|
||||
oidn::Quality get_quality()
|
||||
{
|
||||
const CMPNodeDenoiseQuality node_quality = this->get_quality_mode();
|
||||
|
||||
if (node_quality == CMP_NODE_DENOISE_QUALITY_SCENE) {
|
||||
const eCompositorDenoiseQaulity scene_quality = context().get_denoise_quality();
|
||||
switch (scene_quality) {
|
||||
# if OIDN_VERSION >= 20300
|
||||
case SCE_COMPOSITOR_DENOISE_FAST:
|
||||
return oidn::Quality::Fast;
|
||||
# endif
|
||||
case SCE_COMPOSITOR_DENOISE_BALANCED:
|
||||
return oidn::Quality::Balanced;
|
||||
case SCE_COMPOSITOR_DENOISE_HIGH:
|
||||
return oidn::Quality::High;
|
||||
}
|
||||
|
||||
return oidn::Quality::High;
|
||||
}
|
||||
|
||||
switch (node_quality) {
|
||||
# if OIDN_VERSION >= 20300
|
||||
case CMP_NODE_DENOISE_QUALITY_FAST:
|
||||
return oidn::Quality::Fast;
|
||||
# endif
|
||||
case CMP_NODE_DENOISE_QUALITY_BALANCED:
|
||||
return oidn::Quality::Balanced;
|
||||
case CMP_NODE_DENOISE_QUALITY_HIGH:
|
||||
case CMP_NODE_DENOISE_QUALITY_SCENE:
|
||||
return oidn::Quality::High;
|
||||
}
|
||||
|
||||
return oidn::Quality::High;
|
||||
}
|
||||
# endif /* OIDN_VERSION_MAJOR >= 2 */
|
||||
|
||||
void set_filter_quality([[maybe_unused]] oidn::FilterRef &filter)
|
||||
{
|
||||
# if OIDN_VERSION_MAJOR >= 2
|
||||
oidn::Quality quality = this->get_quality();
|
||||
filter.set("quality", quality);
|
||||
# endif
|
||||
}
|
||||
#endif /* WITH_OPENIMAGEDENOISE */
|
||||
|
||||
bool use_hdr()
|
||||
{
|
||||
return this->get_input("HDR").get_single_value_default<bool>();
|
||||
}
|
||||
|
||||
CMPNodeDenoisePrefilter get_prefilter_mode()
|
||||
{
|
||||
return CMPNodeDenoisePrefilter(
|
||||
this->get_input("Prefilter").get_single_value_default<MenuValue>().value);
|
||||
}
|
||||
|
||||
CMPNodeDenoiseQuality get_quality_mode()
|
||||
{
|
||||
return CMPNodeDenoiseQuality(
|
||||
this->get_input("Quality").get_single_value_default<MenuValue>().value);
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new DenoiseOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeDenoise"_ustr, CMP_NODE_DENOISE);
|
||||
ntype.ui_name = "Denoise";
|
||||
ntype.ui_description = "Denoise renders from Cycles and other ray tracing renderers";
|
||||
ntype.enum_name_legacy = "DENOISE";
|
||||
ntype.nclass = NODE_CLASS_OP_FILTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.draw_buttons = node_draw_buttons;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeDenoise", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_denoise_cc
|
||||
@@ -0,0 +1,437 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "COM_algorithm_smaa.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_depth_combin_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Color>("A"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Float>("Depth A"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(10000.0f)
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Color>("B"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Float>("Depth B"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(10000.0f)
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Bool>("Use Alpha"_ustr)
|
||||
.default_value(false)
|
||||
.description(
|
||||
"Use the alpha of the first input as mixing factor and return the more opaque alpha of "
|
||||
"the two inputs");
|
||||
b.add_input<decl::Bool>("Anti-Alias"_ustr)
|
||||
.default_value(true)
|
||||
.description(
|
||||
"Anti-alias the generated mask before combining for smoother boundaries at the cost of "
|
||||
"more expensive processing");
|
||||
|
||||
b.add_output<decl::Color>("Result"_ustr).structure_type(StructureType::Dynamic);
|
||||
b.add_output<decl::Float>("Depth"_ustr).structure_type(StructureType::Dynamic);
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class ZCombineOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
if (this->get_input("A").is_single_value() && this->get_input("B").is_single_value() &&
|
||||
this->get_input("Depth A").is_single_value() &&
|
||||
this->get_input("Depth B").is_single_value())
|
||||
{
|
||||
execute_single_value();
|
||||
}
|
||||
else if (use_anti_aliasing()) {
|
||||
execute_anti_aliased();
|
||||
}
|
||||
else {
|
||||
execute_simple();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_single_value()
|
||||
{
|
||||
const float4 first_color = float4(get_input("A").get_single_value<Color>());
|
||||
const float4 second_color = float4(get_input("B").get_single_value<Color>());
|
||||
const float first_z_value = get_input("Depth A").get_single_value<float>();
|
||||
const float second_z_value = get_input("Depth B").get_single_value<float>();
|
||||
|
||||
/* Mix between the first and second images using a mask such that the image with the object
|
||||
* closer to the camera is returned. The mask value is then 1, and thus returns the first image
|
||||
* if its Depth value is less than that of the second image. Otherwise, its value is 0, and
|
||||
* thus returns the second image. Furthermore, if the object in the first image is closer but
|
||||
* has a non-opaque alpha, then the alpha is used as a mask, but only if Use Alpha is enabled.
|
||||
*/
|
||||
const float z_combine_factor = float(first_z_value < second_z_value);
|
||||
const float alpha_factor = use_alpha() ? first_color.w : 1.0f;
|
||||
const float mix_factor = z_combine_factor * alpha_factor;
|
||||
|
||||
Result &combined = get_result("Result");
|
||||
if (combined.should_compute()) {
|
||||
float4 combined_color = math::interpolate(second_color, first_color, mix_factor);
|
||||
/* Use the more opaque alpha from the two images. */
|
||||
combined_color.w = use_alpha() ? math::max(second_color.w, first_color.w) : combined_color.w;
|
||||
|
||||
combined.allocate_single_value();
|
||||
combined.set_single_value(Color(combined_color));
|
||||
}
|
||||
|
||||
Result &combined_z = get_result("Depth");
|
||||
if (combined_z.should_compute()) {
|
||||
const float combined_z_value = math::interpolate(second_z_value, first_z_value, mix_factor);
|
||||
combined_z.allocate_single_value();
|
||||
combined_z.set_single_value(combined_z_value);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_simple()
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_simple_gpu();
|
||||
}
|
||||
else {
|
||||
this->execute_simple_cpu();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_simple_gpu()
|
||||
{
|
||||
if (this->get_result("Result").should_compute()) {
|
||||
this->execute_simple_image_gpu();
|
||||
}
|
||||
|
||||
if (this->get_result("Depth").should_compute()) {
|
||||
this->execute_simple_depth_gpu();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_simple_image_gpu()
|
||||
{
|
||||
gpu::Shader *shader = this->context().get_shader("compositor_z_combine_simple_image");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1b(shader, "use_alpha", this->use_alpha());
|
||||
|
||||
const Result &first = this->get_input("A");
|
||||
first.bind_as_texture(shader, "first_tx");
|
||||
const Result &first_z = this->get_input("Depth A");
|
||||
first_z.bind_as_texture(shader, "first_z_tx");
|
||||
const Result &second = this->get_input("B");
|
||||
second.bind_as_texture(shader, "second_tx");
|
||||
const Result &second_z = this->get_input("Depth B");
|
||||
second_z.bind_as_texture(shader, "second_z_tx");
|
||||
|
||||
Result &combined = this->get_result("Result");
|
||||
const Domain domain = this->compute_domain();
|
||||
combined.allocate_texture(domain);
|
||||
combined.bind_as_image(shader, "combined_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
first.unbind_as_texture();
|
||||
first_z.unbind_as_texture();
|
||||
second.unbind_as_texture();
|
||||
second_z.unbind_as_texture();
|
||||
combined.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
void execute_simple_depth_gpu()
|
||||
{
|
||||
gpu::Shader *shader = this->context().get_shader("compositor_z_combine_simple_depth");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
const Result &first_z = this->get_input("Depth A");
|
||||
first_z.bind_as_texture(shader, "first_z_tx");
|
||||
const Result &second_z = this->get_input("Depth B");
|
||||
second_z.bind_as_texture(shader, "second_z_tx");
|
||||
|
||||
Result &combined_z = this->get_result("Depth");
|
||||
const Domain domain = this->compute_domain();
|
||||
combined_z.allocate_texture(domain);
|
||||
combined_z.bind_as_image(shader, "combined_z_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
first_z.unbind_as_texture();
|
||||
second_z.unbind_as_texture();
|
||||
combined_z.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
void execute_simple_cpu()
|
||||
{
|
||||
const bool use_alpha = this->use_alpha();
|
||||
|
||||
const Result &first = this->get_input("A");
|
||||
const Result &first_z = this->get_input("Depth A");
|
||||
const Result &second = this->get_input("B");
|
||||
const Result &second_z = this->get_input("Depth B");
|
||||
|
||||
const Domain domain = this->compute_domain();
|
||||
Result &combined = this->get_result("Result");
|
||||
if (combined.should_compute()) {
|
||||
combined.allocate_texture(domain);
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float4 first_color = float4(first.load_pixel<Color, true>(texel));
|
||||
float4 second_color = float4(second.load_pixel<Color, true>(texel));
|
||||
float first_z_value = first_z.load_pixel<float, true>(texel);
|
||||
float second_z_value = second_z.load_pixel<float, true>(texel);
|
||||
|
||||
/* Choose the closer pixel as the foreground, that is, the pixel with the lower z value. If
|
||||
* Use Alpha is disabled, return the foreground, otherwise, mix between the foreground and
|
||||
* background using the alpha of the foreground. */
|
||||
float4 foreground_color = first_z_value < second_z_value ? first_color : second_color;
|
||||
float4 background_color = first_z_value < second_z_value ? second_color : first_color;
|
||||
float mix_factor = use_alpha ? foreground_color.w : 1.0f;
|
||||
float4 combined_color = math::interpolate(background_color, foreground_color, mix_factor);
|
||||
|
||||
/* Use the more opaque alpha from the two images. */
|
||||
combined_color.w = use_alpha ? math::max(second_color.w, first_color.w) : combined_color.w;
|
||||
combined.store_pixel(texel, Color(combined_color));
|
||||
});
|
||||
}
|
||||
|
||||
Result &combined_z_output = this->get_result("Depth");
|
||||
if (combined_z_output.should_compute()) {
|
||||
combined_z_output.allocate_texture(domain);
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float first_z_value = first_z.load_pixel<float, true>(texel);
|
||||
float second_z_value = second_z.load_pixel<float, true>(texel);
|
||||
float combined_z = math::min(first_z_value, second_z_value);
|
||||
combined_z_output.store_pixel(texel, combined_z);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void execute_anti_aliased()
|
||||
{
|
||||
Result mask = compute_mask();
|
||||
|
||||
Result anti_aliased_mask = this->context().create_result(ResultType::Float);
|
||||
smaa(this->context(), mask, anti_aliased_mask);
|
||||
mask.release();
|
||||
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_anti_aliased_gpu(anti_aliased_mask);
|
||||
}
|
||||
else {
|
||||
this->execute_anti_aliased_cpu(anti_aliased_mask);
|
||||
}
|
||||
|
||||
anti_aliased_mask.release();
|
||||
}
|
||||
|
||||
void execute_anti_aliased_gpu(const Result &mask)
|
||||
{
|
||||
if (this->get_result("Result").should_compute()) {
|
||||
this->execute_anti_aliased_image_gpu(mask);
|
||||
}
|
||||
|
||||
if (this->get_result("Depth").should_compute()) {
|
||||
this->execute_anti_aliased_depth_gpu();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_anti_aliased_image_gpu(const Result &mask)
|
||||
{
|
||||
gpu::Shader *shader = this->context().get_shader("compositor_z_combine_from_mask_image");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1b(shader, "use_alpha", this->use_alpha());
|
||||
|
||||
const Result &first = this->get_input("A");
|
||||
first.bind_as_texture(shader, "first_tx");
|
||||
const Result &second = this->get_input("B");
|
||||
second.bind_as_texture(shader, "second_tx");
|
||||
mask.bind_as_texture(shader, "mask_tx");
|
||||
|
||||
Result &combined = this->get_result("Result");
|
||||
const Domain domain = this->compute_domain();
|
||||
combined.allocate_texture(domain);
|
||||
combined.bind_as_image(shader, "combined_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
first.unbind_as_texture();
|
||||
second.unbind_as_texture();
|
||||
mask.unbind_as_texture();
|
||||
combined.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
void execute_anti_aliased_depth_gpu()
|
||||
{
|
||||
gpu::Shader *shader = this->context().get_shader("compositor_z_combine_from_mask_depth");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
const Result &first_z = this->get_input("Depth A");
|
||||
first_z.bind_as_texture(shader, "first_z_tx");
|
||||
const Result &second_z = this->get_input("Depth B");
|
||||
second_z.bind_as_texture(shader, "second_z_tx");
|
||||
|
||||
Result &combined_z = this->get_result("Depth");
|
||||
const Domain domain = this->compute_domain();
|
||||
combined_z.allocate_texture(domain);
|
||||
combined_z.bind_as_image(shader, "combined_z_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
first_z.unbind_as_texture();
|
||||
second_z.unbind_as_texture();
|
||||
combined_z.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
void execute_anti_aliased_cpu(const Result &mask)
|
||||
{
|
||||
const bool use_alpha = this->use_alpha();
|
||||
|
||||
const Result &first = this->get_input("A");
|
||||
const Result &first_z = this->get_input("Depth A");
|
||||
const Result &second = this->get_input("B");
|
||||
const Result &second_z = this->get_input("Depth B");
|
||||
|
||||
const Domain domain = this->compute_domain();
|
||||
Result &combined = this->get_result("Result");
|
||||
if (combined.should_compute()) {
|
||||
combined.allocate_texture(domain);
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float4 first_color = float4(first.load_pixel<Color, true>(texel));
|
||||
float4 second_color = float4(second.load_pixel<Color, true>(texel));
|
||||
float mask_value = mask.load_pixel<float>(texel);
|
||||
|
||||
/* Choose the closer pixel as the foreground, that is, the masked pixel with the lower z
|
||||
* value. If Use Alpha is disabled, return the foreground, otherwise, mix between the
|
||||
* foreground and background using the alpha of the foreground. */
|
||||
float4 foreground_color = math::interpolate(second_color, first_color, mask_value);
|
||||
float4 background_color = math::interpolate(first_color, second_color, mask_value);
|
||||
float mix_factor = use_alpha ? foreground_color.w : 1.0f;
|
||||
float4 combined_color = math::interpolate(background_color, foreground_color, mix_factor);
|
||||
|
||||
/* Use the more opaque alpha from the two images. */
|
||||
combined_color.w = use_alpha ? math::max(second_color.w, first_color.w) : combined_color.w;
|
||||
combined.store_pixel(texel, Color(combined_color));
|
||||
});
|
||||
}
|
||||
|
||||
Result &combined_z_output = this->get_result("Depth");
|
||||
if (combined_z_output.should_compute()) {
|
||||
combined_z_output.allocate_texture(domain);
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float first_z_value = first_z.load_pixel<float, true>(texel);
|
||||
float second_z_value = second_z.load_pixel<float, true>(texel);
|
||||
float combined_z = math::min(first_z_value, second_z_value);
|
||||
combined_z_output.store_pixel(texel, combined_z);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Result compute_mask()
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
return this->compute_mask_gpu();
|
||||
}
|
||||
|
||||
return this->compute_mask_cpu();
|
||||
}
|
||||
|
||||
Result compute_mask_gpu()
|
||||
{
|
||||
gpu::Shader *shader = context().get_shader("compositor_z_combine_compute_mask");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
const Result &first_z = get_input("Depth A");
|
||||
first_z.bind_as_texture(shader, "first_z_tx");
|
||||
const Result &second_z = get_input("Depth B");
|
||||
second_z.bind_as_texture(shader, "second_z_tx");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result mask = context().create_result(ResultType::Float);
|
||||
mask.allocate_texture(domain);
|
||||
mask.bind_as_image(shader, "mask_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
first_z.unbind_as_texture();
|
||||
second_z.unbind_as_texture();
|
||||
mask.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
Result compute_mask_cpu()
|
||||
{
|
||||
const Result &first_z = this->get_input("Depth A");
|
||||
const Result &second_z = this->get_input("Depth B");
|
||||
|
||||
const Domain domain = this->compute_domain();
|
||||
Result mask = this->context().create_result(ResultType::Float);
|
||||
mask.allocate_texture(domain);
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float first_z_value = first_z.load_pixel<float, true>(texel);
|
||||
float second_z_value = second_z.load_pixel<float, true>(texel);
|
||||
float z_combine_factor = float(first_z_value < second_z_value);
|
||||
mask.store_pixel(texel, z_combine_factor);
|
||||
});
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
bool use_alpha()
|
||||
{
|
||||
return this->get_input("Use Alpha").get_single_value_default<bool>();
|
||||
}
|
||||
|
||||
bool use_anti_aliasing()
|
||||
{
|
||||
return this->get_input("Anti-Alias").get_single_value_default<bool>();
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new ZCombineOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeZcombine"_ustr, CMP_NODE_ZCOMBINE);
|
||||
ntype.ui_name = "Depth Combine";
|
||||
ntype.ui_description = "Combine two images using depth maps";
|
||||
ntype.enum_name_legacy = "ZCOMBINE";
|
||||
ntype.nclass = NODE_CLASS_OP_COLOR;
|
||||
ntype.declare = node_declare;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_depth_combin_cc
|
||||
@@ -0,0 +1,216 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_despeckle_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::Color>("Image"_ustr)
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.hide_value()
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_output<decl::Color>("Image"_ustr)
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.align_with_previous();
|
||||
|
||||
b.add_input<decl::Float>("Factor"_ustr, "Fac"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Float>("Color Threshold"_ustr)
|
||||
.default_value(0.5f)
|
||||
.min(0.0f)
|
||||
.description(
|
||||
"Pixels are despeckled only if their color difference from the average color of their "
|
||||
"neighbors exceeds this threshold");
|
||||
b.add_input<decl::Float>("Neighbor Threshold"_ustr)
|
||||
.default_value(0.5f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description(
|
||||
"Pixels are despeckled only if the number of pixels in their neighborhood that are "
|
||||
"different exceed this ratio threshold relative to the total number of neighbors. "
|
||||
"Neighbors are considered different if they exceed the color threshold input");
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class DespeckleOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const Result &input = this->get_input("Image");
|
||||
if (input.is_single_value()) {
|
||||
Result &output = this->get_result("Image");
|
||||
output.share_data(input);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_gpu();
|
||||
}
|
||||
else {
|
||||
this->execute_cpu();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_gpu()
|
||||
{
|
||||
gpu::Shader *shader = context().get_shader("compositor_despeckle");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1f(shader, "color_threshold", get_color_threshold());
|
||||
GPU_shader_uniform_1f(shader, "neighbor_threshold", get_neighbor_threshold());
|
||||
|
||||
const Result &input_image = get_input("Image");
|
||||
input_image.bind_as_texture(shader, "input_tx");
|
||||
|
||||
const Result &factor_image = get_input("Fac");
|
||||
factor_image.bind_as_texture(shader, "factor_tx");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
output_image.unbind_as_image();
|
||||
input_image.unbind_as_texture();
|
||||
factor_image.unbind_as_texture();
|
||||
}
|
||||
|
||||
void execute_cpu()
|
||||
{
|
||||
const float color_threshold = this->get_color_threshold();
|
||||
const float neighbor_threshold = this->get_neighbor_threshold();
|
||||
|
||||
const Result &input = get_input("Image");
|
||||
const Result &factor_image = get_input("Fac");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output = get_result("Image");
|
||||
output.allocate_texture(domain);
|
||||
|
||||
/* A 3x3 weights kernel whose weights are the inverse of the distance to the center of the
|
||||
* kernel. So the center weight is zero, the corners weights are (1 / sqrt(2)), and the rest
|
||||
* of the weights are 1. The total sum of weights is 4 plus quadruple the corner weight. */
|
||||
float corner_weight = 1.0f / math::sqrt(2.0f);
|
||||
float sum_of_weights = 4.0f + corner_weight * 4.0f;
|
||||
float3x3 weights = float3x3(float3(corner_weight, 1.0f, corner_weight),
|
||||
float3(1.0f, 0.0f, 1.0f),
|
||||
float3(corner_weight, 1.0f, corner_weight));
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
float4 center_color = float4(input.load_pixel<Color>(texel));
|
||||
|
||||
/* Go over the pixels in the 3x3 window around the center pixel and compute the total sum of
|
||||
* their colors multiplied by their weights. Additionally, for pixels whose colors are not
|
||||
* close enough to the color of the center pixel, accumulate their color as well as their
|
||||
* weights. */
|
||||
float4 sum_of_colors = float4(0.0f);
|
||||
float accumulated_weight = 0.0f;
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float weight = weights[j][i];
|
||||
float4 color = float4(input.load_pixel_extended<Color>(texel + int2(i - 1, j - 1))) *
|
||||
weight;
|
||||
sum_of_colors += color;
|
||||
if (!math::is_equal(center_color.xyz(), color.xyz(), color_threshold)) {
|
||||
accumulated_color += color;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If the accumulated weight is zero, that means all pixels in the 3x3 window are similar and
|
||||
* no need to despeckle anything, so write the original center color and return. */
|
||||
if (accumulated_weight == 0.0f) {
|
||||
output.store_pixel(texel, Color(center_color));
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the ratio between the accumulated weights and the total sum of weights is not larger
|
||||
* than the user specified neighbor threshold, then the number of pixels in the neighborhood
|
||||
* that are not close enough to the center pixel is low, and no need to despeckle anything,
|
||||
* so write the original center color and return. */
|
||||
if (accumulated_weight / sum_of_weights < neighbor_threshold) {
|
||||
output.store_pixel(texel, Color(center_color));
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the weighted average color of the neighborhood is close enough to the center pixel,
|
||||
* then no need to despeckle anything, so write the original center color and return. */
|
||||
if (math::is_equal(
|
||||
center_color.xyz(), (sum_of_colors / sum_of_weights).xyz(), color_threshold))
|
||||
{
|
||||
output.store_pixel(texel, Color(center_color));
|
||||
return;
|
||||
}
|
||||
|
||||
/* We need to despeckle, so write the mean accumulated color. */
|
||||
float factor = factor_image.load_pixel<float, true>(texel);
|
||||
float4 mean_color = accumulated_color / accumulated_weight;
|
||||
output.store_pixel(texel, Color(math::interpolate(center_color, mean_color, factor)));
|
||||
});
|
||||
}
|
||||
|
||||
float get_color_threshold()
|
||||
{
|
||||
return math::max(0.0f, this->get_input("Color Threshold").get_single_value_default<float>());
|
||||
}
|
||||
|
||||
float get_neighbor_threshold()
|
||||
{
|
||||
return math::clamp(
|
||||
this->get_input("Neighbor Threshold").get_single_value_default<float>(), 0.0f, 1.0f);
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new DespeckleOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeDespeckle"_ustr, CMP_NODE_DESPECKLE);
|
||||
ntype.ui_name = "Despeckle";
|
||||
ntype.ui_description =
|
||||
"Smooth areas of an image in which noise is noticeable, while leaving complex areas "
|
||||
"untouched";
|
||||
ntype.enum_name_legacy = "DESPECKLE";
|
||||
ntype.nclass = NODE_CLASS_OP_FILTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.flag |= NODE_PREVIEW;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_despeckle_cc
|
||||
@@ -0,0 +1,116 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "COM_result.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_difference_key_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Color>("Image 1"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
b.add_input<decl::Color>("Image 2"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
b.add_input<decl::Float>("Tolerance"_ustr)
|
||||
.default_value(0.1f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description(
|
||||
"If the average color difference between the two images is less than this threshold, "
|
||||
"it is keyed");
|
||||
b.add_input<decl::Float>("Falloff"_ustr)
|
||||
.default_value(0.1f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.description(
|
||||
"If the average color difference between the two images is less than this threshold, "
|
||||
"it is partially keyed, otherwise, it is not keyed");
|
||||
|
||||
b.add_output<decl::Color>("Image"_ustr);
|
||||
b.add_output<decl::Float>("Matte"_ustr);
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_composite_difference_matte", inputs, outputs);
|
||||
}
|
||||
|
||||
static void difference_matte(const float4 &color,
|
||||
const float4 &key,
|
||||
const float &tolerance,
|
||||
const float &falloff,
|
||||
float4 &result,
|
||||
float &matte)
|
||||
{
|
||||
float difference = math::dot(math::abs(color - key).xyz(), float3(1.0f)) / 3.0f;
|
||||
|
||||
bool is_opaque = difference > tolerance + falloff;
|
||||
float alpha = is_opaque ? color.w :
|
||||
math::safe_divide(math::max(0.0f, difference - tolerance), falloff);
|
||||
|
||||
matte = math::min(alpha, color.w);
|
||||
result = color * matte;
|
||||
}
|
||||
|
||||
using compositor::Color;
|
||||
|
||||
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
builder.construct_and_set_matching_fn_cb([=]() {
|
||||
return mf::build::SI4_SO2<Color, Color, float, float, Color, float>(
|
||||
"Difference Key",
|
||||
[=](const Color &color,
|
||||
const Color &key,
|
||||
const float &tolerance,
|
||||
const float &falloff,
|
||||
Color &output_color,
|
||||
float &matte) -> void {
|
||||
float4 out_color;
|
||||
difference_matte(float4(color), float4(key), tolerance, falloff, out_color, matte);
|
||||
output_color = Color(out_color);
|
||||
},
|
||||
mf::build::exec_presets::SomeSpanOrSingle<0, 1>());
|
||||
});
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeDiffMatte"_ustr, CMP_NODE_DIFF_MATTE);
|
||||
ntype.ui_name = "Difference Key";
|
||||
ntype.ui_description =
|
||||
"Produce a matte that isolates foreground content by comparing it with a reference "
|
||||
"background image";
|
||||
ntype.enum_name_legacy = "DIFF_MATTE";
|
||||
ntype.nclass = NODE_CLASS_MATTE;
|
||||
ntype.declare = node_declare;
|
||||
ntype.flag |= NODE_PREVIEW;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_difference_key_cc
|
||||
@@ -0,0 +1,578 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
#include "RNA_types.hh"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "COM_algorithm_jump_flooding.hh"
|
||||
#include "COM_algorithm_morphological_distance.hh"
|
||||
#include "COM_algorithm_morphological_distance_feather.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_dilate_erode_cc {
|
||||
|
||||
static const EnumPropertyItem type_items[] = {
|
||||
{CMP_NODE_DILATE_ERODE_STEP, "STEP", 0, N_("Steps"), ""},
|
||||
{CMP_NODE_DILATE_ERODE_DISTANCE_THRESHOLD, "THRESHOLD", 0, N_("Threshold"), ""},
|
||||
{CMP_NODE_DILATE_ERODE_DISTANCE, "DISTANCE", 0, N_("Distance"), ""},
|
||||
{CMP_NODE_DILATE_ERODE_DISTANCE_FEATHER, "FEATHER", 0, N_("Feather"), ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Float>("Mask"_ustr)
|
||||
.default_value(0.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.structure_type(StructureType::Dynamic);
|
||||
b.add_input<decl::Int>("Size"_ustr)
|
||||
.default_value(0)
|
||||
.subtype(PROP_PIXEL)
|
||||
.description(
|
||||
"The size of dilation/erosion in pixels. Positive values dilates and negative values "
|
||||
"erodes");
|
||||
b.add_input<decl::Menu>("Type"_ustr)
|
||||
.default_value(CMP_NODE_DILATE_ERODE_STEP)
|
||||
.static_items(type_items)
|
||||
.optional_label();
|
||||
b.add_input<decl::Float>("Falloff Size"_ustr)
|
||||
.default_value(0.0f)
|
||||
.subtype(PROP_PIXEL)
|
||||
.min(0.0f)
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_DILATE_ERODE_DISTANCE_THRESHOLD)
|
||||
.description(
|
||||
"The size of the falloff from the edges in pixels. If less than two pixels, the edges "
|
||||
"will be anti-aliased");
|
||||
b.add_input<decl::Menu>("Falloff"_ustr)
|
||||
.default_value(PROP_SMOOTH)
|
||||
.static_items(rna_enum_proportional_falloff_curve_only_items)
|
||||
.optional_label()
|
||||
.usage_by_menu("Type"_ustr, CMP_NODE_DILATE_ERODE_DISTANCE_FEATHER)
|
||||
.translation_context(BLT_I18NCONTEXT_ID_CURVE_LEGACY);
|
||||
|
||||
b.add_output<decl::Float>("Mask"_ustr).structure_type(StructureType::Dynamic);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*ntree*/, bNode *node)
|
||||
{
|
||||
/* Unused but kept for forward compatibility. */
|
||||
NodeDilateErode *data = MEM_new<NodeDilateErode>(__func__);
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class DilateErodeOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const Result &input = this->get_input("Mask");
|
||||
Result &output = this->get_result("Mask");
|
||||
|
||||
if (this->is_identity()) {
|
||||
output.share_data(input);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this->get_type()) {
|
||||
case CMP_NODE_DILATE_ERODE_STEP:
|
||||
execute_step();
|
||||
return;
|
||||
case CMP_NODE_DILATE_ERODE_DISTANCE:
|
||||
execute_distance();
|
||||
return;
|
||||
case CMP_NODE_DILATE_ERODE_DISTANCE_THRESHOLD:
|
||||
execute_distance_threshold();
|
||||
return;
|
||||
case CMP_NODE_DILATE_ERODE_DISTANCE_FEATHER:
|
||||
execute_distance_feather();
|
||||
return;
|
||||
}
|
||||
|
||||
output.share_data(input);
|
||||
}
|
||||
|
||||
/* ----------------------------
|
||||
* Step Morphological Operator.
|
||||
* ---------------------------- */
|
||||
|
||||
void execute_step()
|
||||
{
|
||||
Result horizontal_pass_result = execute_step_horizontal_pass();
|
||||
execute_step_vertical_pass(horizontal_pass_result);
|
||||
horizontal_pass_result.release();
|
||||
}
|
||||
|
||||
Result execute_step_horizontal_pass()
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
return this->execute_step_horizontal_pass_gpu();
|
||||
}
|
||||
return this->execute_step_horizontal_pass_cpu();
|
||||
}
|
||||
|
||||
Result execute_step_horizontal_pass_gpu()
|
||||
{
|
||||
gpu::Shader *shader = context().get_shader(get_morphological_step_shader_name());
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1i(shader, "radius", this->get_structuring_element_size() / 2);
|
||||
|
||||
const Result &input_mask = get_input("Mask");
|
||||
input_mask.bind_as_texture(shader, "input_tx");
|
||||
|
||||
/* We allocate an output image of a transposed size, that is, with a height equivalent to the
|
||||
* width of the input and vice versa. This is done as a performance optimization. The shader
|
||||
* will process the image horizontally and write it to the intermediate output transposed. Then
|
||||
* the vertical pass will execute the same horizontal pass shader, but since its input is
|
||||
* transposed, it will effectively do a vertical pass and write to the output transposed,
|
||||
* effectively undoing the transposition in the horizontal pass. This is done to improve
|
||||
* spatial cache locality in the shader and to avoid having two separate shaders for each of
|
||||
* the passes. */
|
||||
const Domain domain = compute_domain();
|
||||
const int2 transposed_domain = int2(domain.data_size.y, domain.data_size.x);
|
||||
|
||||
Result horizontal_pass_result = context().create_result(ResultType::Float);
|
||||
horizontal_pass_result.allocate_texture(transposed_domain);
|
||||
horizontal_pass_result.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
input_mask.unbind_as_texture();
|
||||
horizontal_pass_result.unbind_as_image();
|
||||
|
||||
return horizontal_pass_result;
|
||||
}
|
||||
|
||||
Result execute_step_horizontal_pass_cpu()
|
||||
{
|
||||
const Result &input = get_input("Mask");
|
||||
|
||||
/* We allocate an output image of a transposed size, that is, with a height equivalent to the
|
||||
* width of the input and vice versa. This is done as a performance optimization. The shader
|
||||
* will process the image horizontally and write it to the intermediate output transposed. Then
|
||||
* the vertical pass will execute the same horizontal pass shader, but since its input is
|
||||
* transposed, it will effectively do a vertical pass and write to the output transposed,
|
||||
* effectively undoing the transposition in the horizontal pass. This is done to improve
|
||||
* spatial cache locality in the shader and to avoid having two separate shaders for each of
|
||||
* the passes. */
|
||||
const Domain domain = compute_domain();
|
||||
const int2 transposed_domain = int2(domain.data_size.y, domain.data_size.x);
|
||||
|
||||
Result horizontal_pass_result = context().create_result(ResultType::Float);
|
||||
horizontal_pass_result.allocate_texture(transposed_domain);
|
||||
|
||||
if (this->is_dilation()) {
|
||||
this->execute_step_pass_cpu<true>(input, horizontal_pass_result);
|
||||
}
|
||||
else {
|
||||
this->execute_step_pass_cpu<false>(input, horizontal_pass_result);
|
||||
}
|
||||
|
||||
return horizontal_pass_result;
|
||||
}
|
||||
|
||||
void execute_step_vertical_pass(Result &horizontal_pass_result)
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_step_vertical_pass_gpu(horizontal_pass_result);
|
||||
}
|
||||
else {
|
||||
this->execute_step_vertical_pass_cpu(horizontal_pass_result);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_step_vertical_pass_gpu(Result &horizontal_pass_result)
|
||||
{
|
||||
gpu::Shader *shader = context().get_shader(get_morphological_step_shader_name());
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1i(shader, "radius", this->get_structuring_element_size() / 2);
|
||||
|
||||
horizontal_pass_result.bind_as_texture(shader, "input_tx");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_mask = get_result("Mask");
|
||||
output_mask.allocate_texture(domain);
|
||||
output_mask.bind_as_image(shader, "output_img");
|
||||
|
||||
/* Notice that the domain is transposed, see the note on the horizontal pass method for more
|
||||
* information on the reasoning behind this. */
|
||||
compute_dispatch_threads_at_least(shader, int2(domain.data_size.y, domain.data_size.x));
|
||||
|
||||
GPU_shader_unbind();
|
||||
horizontal_pass_result.unbind_as_texture();
|
||||
output_mask.unbind_as_image();
|
||||
}
|
||||
|
||||
const char *get_morphological_step_shader_name()
|
||||
{
|
||||
if (this->is_dilation()) {
|
||||
return "compositor_morphological_step_dilate";
|
||||
}
|
||||
return "compositor_morphological_step_erode";
|
||||
}
|
||||
|
||||
void execute_step_vertical_pass_cpu(Result &horizontal_pass_result)
|
||||
{
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_mask = get_result("Mask");
|
||||
output_mask.allocate_texture(domain);
|
||||
|
||||
if (this->is_dilation()) {
|
||||
this->execute_step_pass_cpu<true>(horizontal_pass_result, output_mask);
|
||||
}
|
||||
else {
|
||||
this->execute_step_pass_cpu<false>(horizontal_pass_result, output_mask);
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply a van Herk/Gil-Werman algorithm on the input based on:
|
||||
*
|
||||
* Domanski, Luke, Pascal Vallotton, and Dadong Wang. "Parallel van Herk/Gil-Werman image
|
||||
* morphology on GPUs using CUDA." GTC 2009 Conference posters. 2009.
|
||||
*
|
||||
* The output is written transposed for more efficient execution, see the horizontal pass method
|
||||
* for more information. The template argument IsDilate decides if dilation or erosion will be
|
||||
* performed. */
|
||||
template<bool IsDilate> void execute_step_pass_cpu(const Result &input, Result &output)
|
||||
{
|
||||
const float limit = IsDilate ? std::numeric_limits<float>::lowest() :
|
||||
std::numeric_limits<float>::max();
|
||||
const auto morphology_operator = [](const float a, const float b) {
|
||||
if constexpr (IsDilate) {
|
||||
return math::max(a, b);
|
||||
}
|
||||
else {
|
||||
return math::min(a, b);
|
||||
}
|
||||
};
|
||||
|
||||
/* Notice that the domain is transposed, see the note on the horizontal pass method for more
|
||||
* information on the reasoning behind this. */
|
||||
const int2 image_size = int2(output.domain().data_size.y, output.domain().data_size.x);
|
||||
|
||||
/* We process rows in tiles whose size is the same as the structuring element size. So we
|
||||
* compute the number of tiles using ceiling division, noting that the last tile might not be
|
||||
* complete. */
|
||||
const int size = this->get_structuring_element_size();
|
||||
const int tiles_count = int(math::ceil(float(image_size.x) / size));
|
||||
|
||||
/* Process along rows in parallel. */
|
||||
threading::parallel_for(IndexRange(image_size.y), 1, [&](const IndexRange sub_y_range) {
|
||||
Array<float> prefix_table(size);
|
||||
Array<float> suffix_table(size);
|
||||
for (const int64_t y : sub_y_range) {
|
||||
for (const int64_t tile_index : IndexRange(tiles_count)) {
|
||||
const int64_t tile_start = tile_index * size;
|
||||
/* Compute the x texel location of the pixel at the center of the tile. Noting that the
|
||||
* size of the structuring element is guaranteed to be odd. */
|
||||
const int64_t tile_center = tile_start + size / 2;
|
||||
|
||||
float prefix_value = limit;
|
||||
float suffix_value = limit;
|
||||
/* Starting from the pixel at the center of the tile, recursively compute the prefix
|
||||
* table to the right and the suffix table to the left by applying the morphology
|
||||
* operator. */
|
||||
for (const int64_t i : IndexRange(size)) {
|
||||
const float right_value = input.load_pixel_fallback(int2(tile_center + i, y), limit);
|
||||
prefix_value = morphology_operator(prefix_value, right_value);
|
||||
prefix_table[i] = prefix_value;
|
||||
|
||||
/* Note that we access pixels increasingly to the left, so invert the suffix table when
|
||||
* writing to it. */
|
||||
const float left_value = input.load_pixel_fallback(int2(tile_center - i, y), limit);
|
||||
suffix_value = morphology_operator(suffix_value, left_value);
|
||||
suffix_table[size - 1 - i] = suffix_value;
|
||||
}
|
||||
|
||||
const IndexRange tile_range = IndexRange(tile_start, size);
|
||||
const IndexRange safe_tile_range = tile_range.intersect(IndexRange(image_size.x));
|
||||
/* For each pixel in the tile, write the result of applying the morphology operator on
|
||||
* the prefix and suffix values. */
|
||||
for (const int64_t x : safe_tile_range) {
|
||||
/* Compute the local table index, since the prefix and suffix tables are local to each
|
||||
* tile. */
|
||||
const int64_t table_index = x - tile_start;
|
||||
const float prefix_value = prefix_table[table_index];
|
||||
const float suffix_value = suffix_table[table_index];
|
||||
|
||||
const float value = morphology_operator(prefix_value, suffix_value);
|
||||
|
||||
/* Write the value using the transposed texel. See the horizontal pass method for more
|
||||
* information on the rational behind this. */
|
||||
output.store_pixel(int2(y, x), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* --------------------------------
|
||||
* Distance Morphological Operator.
|
||||
* -------------------------------- */
|
||||
|
||||
void execute_distance()
|
||||
{
|
||||
morphological_distance(context(), get_input("Mask"), get_result("Mask"), this->get_size());
|
||||
}
|
||||
|
||||
/* ------------------------------------------
|
||||
* Distance Threshold Morphological Operator.
|
||||
* ------------------------------------------ */
|
||||
|
||||
void execute_distance_threshold()
|
||||
{
|
||||
Result masked_pixels = this->context().create_result(ResultType::Int2, ResultPrecision::Half);
|
||||
Result unmasked_pixels = this->context().create_result(ResultType::Int2,
|
||||
ResultPrecision::Half);
|
||||
this->compute_distance_threshold_seeds(masked_pixels, unmasked_pixels);
|
||||
|
||||
Result flooded_masked_pixels = this->context().create_result(ResultType::Int2,
|
||||
ResultPrecision::Half);
|
||||
Result flooded_unmasked_pixels = this->context().create_result(ResultType::Int2,
|
||||
ResultPrecision::Half);
|
||||
jump_flooding(this->context(), masked_pixels, flooded_masked_pixels);
|
||||
masked_pixels.release();
|
||||
jump_flooding(this->context(), unmasked_pixels, flooded_unmasked_pixels);
|
||||
unmasked_pixels.release();
|
||||
|
||||
this->compute_distance_threshold(flooded_masked_pixels, flooded_unmasked_pixels);
|
||||
flooded_masked_pixels.release();
|
||||
flooded_unmasked_pixels.release();
|
||||
}
|
||||
|
||||
/* Compute an image that marks both masked and unmasked pixels as seed pixels for the jump
|
||||
* flooding algorithm. */
|
||||
void compute_distance_threshold_seeds(Result &masked_pixels, Result &unmasked_pixels)
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
this->compute_distance_threshold_seeds_gpu(masked_pixels, unmasked_pixels);
|
||||
return;
|
||||
}
|
||||
|
||||
this->compute_distance_threshold_seeds_cpu(masked_pixels, unmasked_pixels);
|
||||
}
|
||||
|
||||
void compute_distance_threshold_seeds_gpu(Result &masked_pixels, Result &unmasked_pixels)
|
||||
{
|
||||
gpu::Shader *shader = this->context().get_shader(
|
||||
"compositor_morphological_distance_threshold_seeds", ResultPrecision::Half);
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
const Result &mask = this->get_input("Mask");
|
||||
mask.bind_as_texture(shader, "mask_tx");
|
||||
|
||||
const Domain domain = mask.domain();
|
||||
masked_pixels.allocate_texture(domain);
|
||||
masked_pixels.bind_as_image(shader, "masked_pixels_img");
|
||||
unmasked_pixels.allocate_texture(domain);
|
||||
unmasked_pixels.bind_as_image(shader, "unmasked_pixels_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
mask.unbind_as_texture();
|
||||
masked_pixels.unbind_as_image();
|
||||
unmasked_pixels.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
void compute_distance_threshold_seeds_cpu(Result &masked_pixels, Result &unmasked_pixels)
|
||||
{
|
||||
const Result &mask = this->get_input("Mask");
|
||||
|
||||
const Domain domain = mask.domain();
|
||||
masked_pixels.allocate_texture(domain);
|
||||
unmasked_pixels.allocate_texture(domain);
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
const bool is_masked = mask.load_pixel<float>(texel) > 0.5f;
|
||||
|
||||
const int2 masked_jump_flooding_value = initialize_jump_flooding_value(texel, is_masked);
|
||||
masked_pixels.store_pixel(texel, masked_jump_flooding_value);
|
||||
|
||||
const int2 unmasked_jump_flooding_value = initialize_jump_flooding_value(texel, !is_masked);
|
||||
unmasked_pixels.store_pixel(texel, unmasked_jump_flooding_value);
|
||||
});
|
||||
}
|
||||
|
||||
void compute_distance_threshold(const Result &flooded_masked_pixels,
|
||||
const Result &flooded_unmasked_pixels)
|
||||
{
|
||||
if (this->context().use_gpu()) {
|
||||
this->compute_distance_threshold_gpu(flooded_masked_pixels, flooded_unmasked_pixels);
|
||||
return;
|
||||
}
|
||||
|
||||
this->compute_distance_threshold_cpu(flooded_masked_pixels, flooded_unmasked_pixels);
|
||||
}
|
||||
|
||||
void compute_distance_threshold_gpu(const Result &flooded_masked_pixels,
|
||||
const Result &flooded_unmasked_pixels)
|
||||
{
|
||||
gpu::Shader *shader = this->context().get_shader(
|
||||
"compositor_morphological_distance_threshold");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1i(shader, "distance_offset", this->get_size());
|
||||
GPU_shader_uniform_1f(shader, "falloff_size", this->get_falloff_size());
|
||||
|
||||
const Result &input_mask = this->get_input("Mask");
|
||||
input_mask.bind_as_texture(shader, "mask_tx");
|
||||
|
||||
flooded_masked_pixels.bind_as_texture(shader, "flooded_masked_pixels_tx");
|
||||
flooded_unmasked_pixels.bind_as_texture(shader, "flooded_unmasked_pixels_tx");
|
||||
|
||||
const Domain domain = this->compute_domain();
|
||||
Result &output = this->get_result("Mask");
|
||||
output.allocate_texture(domain);
|
||||
output.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.data_size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
output.unbind_as_image();
|
||||
input_mask.unbind_as_texture();
|
||||
}
|
||||
|
||||
void compute_distance_threshold_cpu(const Result &flooded_masked_pixels,
|
||||
const Result &flooded_unmasked_pixels)
|
||||
{
|
||||
const Result &mask = this->get_input("Mask");
|
||||
|
||||
const Domain domain = this->compute_domain();
|
||||
Result &output = this->get_result("Mask");
|
||||
output.allocate_texture(domain);
|
||||
|
||||
const float falloff_size = this->get_falloff_size();
|
||||
const int distance_offset = this->get_size();
|
||||
|
||||
parallel_for(domain.data_size, [&](const int2 texel) {
|
||||
const bool is_masked = mask.load_pixel<float>(texel) > 0.5f;
|
||||
const int2 closest_masked_texel = flooded_masked_pixels.load_pixel<int2>(texel);
|
||||
const int2 closest_unmasked_texel = flooded_unmasked_pixels.load_pixel<int2>(texel);
|
||||
const int2 closest_different_texel = is_masked ? closest_unmasked_texel :
|
||||
closest_masked_texel;
|
||||
const float distance_to_different = math::distance(float2(texel),
|
||||
float2(closest_different_texel));
|
||||
const float signed_distance = is_masked ? distance_to_different : -distance_to_different;
|
||||
const float value = math::clamp(
|
||||
(signed_distance + distance_offset) / falloff_size, 0.0f, 1.0f);
|
||||
|
||||
output.store_pixel(texel, value);
|
||||
});
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
* Distance Feather Morphological Operator.
|
||||
* ---------------------------------------- */
|
||||
|
||||
void execute_distance_feather()
|
||||
{
|
||||
morphological_distance_feather(
|
||||
context(), get_input("Mask"), get_result("Mask"), this->get_size(), this->get_falloff());
|
||||
}
|
||||
|
||||
/* ---------------
|
||||
* Common Methods.
|
||||
* --------------- */
|
||||
|
||||
bool is_identity()
|
||||
{
|
||||
const Result &input = get_input("Mask");
|
||||
if (input.is_single_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this->get_type() == CMP_NODE_DILATE_ERODE_DISTANCE_THRESHOLD &&
|
||||
this->get_falloff_size() != 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->get_size() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Gets the size of the structuring element. See the get_size method for more information. */
|
||||
int get_structuring_element_size()
|
||||
{
|
||||
return math::abs(this->get_size()) * 2 + 1;
|
||||
}
|
||||
|
||||
/* Returns true if dilation should be performed, as opposed to erosion. See the get_size()
|
||||
* method for more information. */
|
||||
bool is_dilation()
|
||||
{
|
||||
return this->get_size() > 0;
|
||||
}
|
||||
|
||||
/* The signed radius of the structuring element, that is, half the structuring element size. The
|
||||
* sign indicates either dilation or erosion, where negative values means erosion. */
|
||||
int get_size()
|
||||
{
|
||||
return this->get_input("Size").get_single_value_default<int>();
|
||||
}
|
||||
|
||||
float get_falloff_size()
|
||||
{
|
||||
return math::max(0.0f, this->get_input("Falloff Size").get_single_value_default<float>());
|
||||
}
|
||||
|
||||
CMPNodeDilateErodeMethod get_type()
|
||||
{
|
||||
return CMPNodeDilateErodeMethod(
|
||||
this->get_input("Type").get_single_value_default<MenuValue>().value);
|
||||
}
|
||||
|
||||
int get_falloff()
|
||||
{
|
||||
return this->get_input("Falloff").get_single_value_default<MenuValue>().value;
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new DilateErodeOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeDilateErode"_ustr, CMP_NODE_DILATEERODE);
|
||||
ntype.ui_name = "Dilate/Erode";
|
||||
ntype.ui_description = "Expand and shrink masks";
|
||||
ntype.enum_name_legacy = "DILATEERODE";
|
||||
ntype.nclass = NODE_CLASS_OP_FILTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeDilateErode", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_dilate_erode_cc
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user