diff --git a/scripts/addons_core/node_wrangler/operators.py b/scripts/addons_core/node_wrangler/operators.py index e88690e052d..69333e90b8d 100644 --- a/scripts/addons_core/node_wrangler/operators.py +++ b/scripts/addons_core/node_wrangler/operators.py @@ -1964,7 +1964,7 @@ class NWLinkToOutputNode(Operator): 'LINESTYLE': 'ShaderNodeOutputLineStyle'} output_type = { 'ShaderNodeTree': shader_outputs[context.space_data.shader_type], - 'CompositorNodeTree': 'CompositorNodeComposite', + 'CompositorNodeTree': 'NodeGroupOutput', 'TextureNodeTree': 'TextureNodeOutput', 'GeometryNodeTree': 'NodeGroupOutput', }[tree_type] diff --git a/scripts/startup/bl_operators/clip.py b/scripts/startup/bl_operators/clip.py index 182950e30ab..d3c88ea1655 100644 --- a/scripts/startup/bl_operators/clip.py +++ b/scripts/startup/bl_operators/clip.py @@ -784,7 +784,7 @@ class CLIP_OT_setup_tracking_scene(Operator): # Create nodes. rlayer_fg = self._findOrCreateNode(tree, 'CompositorNodeRLayers') rlayer_bg = tree.nodes.new(type='CompositorNodeRLayers') - composite = self._findOrCreateNode(tree, 'CompositorNodeComposite') + output = self._findOrCreateNode(tree, 'NodeGroupOutput') movieclip = tree.nodes.new(type='CompositorNodeMovieClip') distortion = tree.nodes.new(type='CompositorNodeMovieDistortion') @@ -831,7 +831,7 @@ class CLIP_OT_setup_tracking_scene(Operator): tree.links.new(shadowcatcher.outputs["Image"], alphaover.inputs[1]) - tree.links.new(alphaover.outputs["Image"], composite.inputs["Image"]) + tree.links.new(alphaover.outputs["Image"], output.inputs[0]) tree.links.new(alphaover.outputs["Image"], viewer.inputs["Image"]) # Place nodes. @@ -862,11 +862,11 @@ class CLIP_OT_setup_tracking_scene(Operator): alphaover.location = shadowcatcher.location alphaover.location += Vector((250.0, -250.0)) - composite.location = alphaover.location - composite.location += Vector((300.0, -100.0)) + output.location = alphaover.location + output.location += Vector((300.0, -100.0)) - viewer.location = composite.location - composite.location += Vector((0.0, 200.0)) + viewer.location = output.location + output.location += Vector((0.0, 200.0)) # Ensure no nodes were created on the position of existing node. self._offsetNodes(tree) diff --git a/scripts/startup/bl_ui/node_add_menu_compositor.py b/scripts/startup/bl_ui/node_add_menu_compositor.py index 9bb52e1a7b4..ad9c497cf2b 100644 --- a/scripts/startup/bl_ui/node_add_menu_compositor.py +++ b/scripts/startup/bl_ui/node_add_menu_compositor.py @@ -20,6 +20,7 @@ class NODE_MT_category_compositor_input(Menu): layout = self.layout layout.menu("NODE_MT_category_compositor_input_constant") layout.separator() + node_add_menu.add_node_type(layout, "NodeGroupInput") node_add_menu.add_node_type(layout, "CompositorNodeBokehImage") node_add_menu.add_node_type(layout, "CompositorNodeImage") node_add_menu.add_node_type(layout, "CompositorNodeImageInfo") @@ -27,9 +28,6 @@ class NODE_MT_category_compositor_input(Menu): node_add_menu.add_node_type(layout, "CompositorNodeMask") node_add_menu.add_node_type(layout, "CompositorNodeMovieClip") - if is_group: - layout.separator() - node_add_menu.add_node_type(layout, "NodeGroupInput") layout.separator() layout.menu("NODE_MT_category_compositor_input_scene") @@ -71,15 +69,11 @@ class NODE_MT_category_compositor_output(Menu): is_group = (len(snode.path) > 1) layout = self.layout - node_add_menu.add_node_type(layout, "CompositorNodeComposite") + node_add_menu.add_node_type(layout, "NodeGroupOutput") node_add_menu.add_node_type(layout, "CompositorNodeViewer") layout.separator() node_add_menu.add_node_type(layout, "CompositorNodeOutputFile") - if is_group: - layout.separator() - node_add_menu.add_node_type(layout, "NodeGroupOutput") - node_add_menu.draw_assets_for_catalog(layout, self.bl_label) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index b93fc826d76..ee836a032e8 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 40 +#define BLENDER_FILE_SUBVERSION 41 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenkernel/BKE_node_legacy_types.hh b/source/blender/blenkernel/BKE_node_legacy_types.hh index bab32c1f180..524a8d2f195 100644 --- a/source/blender/blenkernel/BKE_node_legacy_types.hh +++ b/source/blender/blenkernel/BKE_node_legacy_types.hh @@ -167,7 +167,7 @@ #define CMP_NODE_HUE_SAT 219 #define CMP_NODE_IMAGE 220 #define CMP_NODE_R_LAYERS 221 -#define CMP_NODE_COMPOSITE 222 +#define CMP_NODE_COMPOSITE_DEPRECATED 222 #define CMP_NODE_OUTPUT_FILE 223 #define CMP_NODE_TEXTURE_DEPRECATED 224 #define CMP_NODE_TRANSLATE 225 diff --git a/source/blender/blenloader/intern/versioning_260.cc b/source/blender/blenloader/intern/versioning_260.cc index d871eb188a9..5b9fe56c05d 100644 --- a/source/blender/blenloader/intern/versioning_260.cc +++ b/source/blender/blenloader/intern/versioning_260.cc @@ -669,7 +669,7 @@ static const char *node_get_static_idname(int type, int treetype) return "CompositorNodeImage"; case CMP_NODE_R_LAYERS: return "CompositorNodeRLayers"; - case CMP_NODE_COMPOSITE: + case CMP_NODE_COMPOSITE_DEPRECATED: return "CompositorNodeComposite"; case CMP_NODE_OUTPUT_FILE: return "CompositorNodeOutputFile"; diff --git a/source/blender/blenloader/intern/versioning_270.cc b/source/blender/blenloader/intern/versioning_270.cc index 2bc611444d9..bd52a8c332c 100644 --- a/source/blender/blenloader/intern/versioning_270.cc +++ b/source/blender/blenloader/intern/versioning_270.cc @@ -478,7 +478,7 @@ void blo_do_versions_270(FileData *fd, Library * /*lib*/, Main *bmain) FOREACH_NODETREE_BEGIN (bmain, ntree, id) { if (ntree->type == NTREE_COMPOSIT) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (ELEM(node->type_legacy, CMP_NODE_COMPOSITE, CMP_NODE_OUTPUT_FILE)) { + if (ELEM(node->type_legacy, CMP_NODE_COMPOSITE_DEPRECATED, CMP_NODE_OUTPUT_FILE)) { node->id = nullptr; } } diff --git a/source/blender/blenloader/intern/versioning_430.cc b/source/blender/blenloader/intern/versioning_430.cc index 49d45f0f8a2..2f0f5bb52c6 100644 --- a/source/blender/blenloader/intern/versioning_430.cc +++ b/source/blender/blenloader/intern/versioning_430.cc @@ -483,7 +483,7 @@ void blo_do_versions_430(FileData * /*fd*/, Library * /*lib*/, Main *bmain) continue; } LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) { - if (ELEM(node->type_legacy, CMP_NODE_VIEWER, CMP_NODE_COMPOSITE)) { + if (ELEM(node->type_legacy, CMP_NODE_VIEWER, CMP_NODE_COMPOSITE_DEPRECATED)) { node->flag &= ~NODE_PREVIEW; } } diff --git a/source/blender/blenloader/intern/versioning_450.cc b/source/blender/blenloader/intern/versioning_450.cc index 0bc56cc3649..b6fee2be3f0 100644 --- a/source/blender/blenloader/intern/versioning_450.cc +++ b/source/blender/blenloader/intern/versioning_450.cc @@ -2695,7 +2695,7 @@ static void do_version_composite_viewer_remove_alpha(bNodeTree *node_tree) /* Find links going into the composite and viewer nodes. */ LISTBASE_FOREACH (bNodeLink *, link, &node_tree->links) { - if (!ELEM(link->tonode->type_legacy, CMP_NODE_COMPOSITE, CMP_NODE_VIEWER)) { + if (!ELEM(link->tonode->type_legacy, CMP_NODE_COMPOSITE_DEPRECATED, CMP_NODE_VIEWER)) { continue; } @@ -2708,7 +2708,7 @@ static void do_version_composite_viewer_remove_alpha(bNodeTree *node_tree) } LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) { - if (!ELEM(node->type_legacy, CMP_NODE_COMPOSITE, CMP_NODE_VIEWER)) { + if (!ELEM(node->type_legacy, CMP_NODE_COMPOSITE_DEPRECATED, CMP_NODE_VIEWER)) { continue; } diff --git a/source/blender/blenloader/intern/versioning_500.cc b/source/blender/blenloader/intern/versioning_500.cc index 2ef5e0563b5..340789553ea 100644 --- a/source/blender/blenloader/intern/versioning_500.cc +++ b/source/blender/blenloader/intern/versioning_500.cc @@ -1172,6 +1172,50 @@ static void do_version_convert_gp_jitter_values(Brush *brush) } } +/* The Composite node was removed and a Group Output node should be used instead, so we need to + * make the replacement. But first note that the Group Output node relies on the node tree + * interface, so we ensure a default interface with a single input and output. This is only for + * root trees used as scene compositing node groups, for other node trees, we remove all composite + * nodes since they are no longer supported inside groups. */ +static void do_version_composite_node_in_scene_tree(bNodeTree &node_tree, bNode &node) +{ + blender::bke::node_tree_set_type(node_tree); + + /* Remove inactive nodes. */ + if (!(node.flag & NODE_DO_OUTPUT)) { + version_node_remove(node_tree, node); + return; + } + + bNodeSocket *old_image_input = blender::bke::node_find_socket(node, SOCK_IN, "Image"); + + /* Find the link going into the Image input of the Composite node. */ + bNodeLink *image_link = nullptr; + LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) { + if (link->tosock == old_image_input) { + image_link = link; + } + } + + bNode *group_output_node = blender::bke::node_add_node(nullptr, node_tree, "NodeGroupOutput"); + group_output_node->parent = node.parent; + group_output_node->location[0] = node.location[0]; + group_output_node->location[1] = node.location[1]; + + bNodeSocket *image_input = static_cast(group_output_node->inputs.first); + BLI_assert(blender::StringRef(image_input->name) == "Image"); + copy_v4_v4(image_input->default_value_typed()->value, + old_image_input->default_value_typed()->value); + + if (image_link) { + version_node_add_link( + node_tree, *image_link->fromnode, *image_link->fromsock, *group_output_node, *image_input); + blender::bke::node_remove_link(&node_tree, *image_link); + } + + version_node_remove(node_tree, node); +} + void do_versions_after_linking_500(FileData *fd, Main *bmain) { if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 9)) { @@ -1197,6 +1241,39 @@ void do_versions_after_linking_500(FileData *fd, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 41)) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + bNodeTree *node_tree = version_get_scene_compositor_node_tree(bmain, scene); + if (node_tree) { + /* Add a default interface for the node tree. See the versioning function below for more + * details. */ + node_tree->tree_interface.clear_items(); + node_tree->tree_interface.add_socket( + DATA_("Image"), "", "NodeSocketColor", NODE_INTERFACE_SOCKET_INPUT, nullptr); + node_tree->tree_interface.add_socket( + DATA_("Image"), "", "NodeSocketColor", NODE_INTERFACE_SOCKET_OUTPUT, nullptr); + + LISTBASE_FOREACH_BACKWARD_MUTABLE (bNode *, node, &node_tree->nodes) { + if (node->type_legacy == CMP_NODE_COMPOSITE_DEPRECATED) { + do_version_composite_node_in_scene_tree(*node_tree, *node); + } + } + } + } + FOREACH_NODETREE_BEGIN (bmain, node_tree, id) { + blender::bke::node_tree_set_type(*node_tree); + if (node_tree->type == NTREE_COMPOSIT) { + LISTBASE_FOREACH_BACKWARD_MUTABLE (bNode *, node, &node_tree->nodes) { + if (node->type_legacy == CMP_NODE_COMPOSITE_DEPRECATED) { + /* See do_version_composite_node_in_scene_tree. */ + version_node_remove(*node_tree, *node); + } + } + } + } + FOREACH_NODETREE_END; + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/blenloader/intern/versioning_common.cc b/source/blender/blenloader/intern/versioning_common.cc index 463f537ceb5..766d3a3f8c2 100644 --- a/source/blender/blenloader/intern/versioning_common.cc +++ b/source/blender/blenloader/intern/versioning_common.cc @@ -658,6 +658,15 @@ bool all_scenes_use(Main *bmain, const blender::Span engines) return true; } +bNodeTree *version_get_scene_compositor_node_tree(Main *bmain, Scene *scene) +{ + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 4)) { + return scene->nodetree; + } + + return scene->compositing_node_group; +} + static bool blendfile_or_libraries_versions_atleast(Main *bmain, const short versionfile, const short subversionfile) diff --git a/source/blender/blenloader/intern/versioning_common.hh b/source/blender/blenloader/intern/versioning_common.hh index bbc7c9386e3..04356d75430 100644 --- a/source/blender/blenloader/intern/versioning_common.hh +++ b/source/blender/blenloader/intern/versioning_common.hh @@ -255,3 +255,8 @@ static void adjust_fcurve_key_frame_values(FCurve *fcurve, /* Recalculate the automatic handles of the FCurve after adjustments. */ BKE_fcurve_handles_recalc(fcurve); } + +/* Gets the compositing node tree of the given scene. The deprecated nodetree member is returned + * for older versions before reusable node trees were introduced in bd61e69be5, while the new + * compositing_node_group is returned otherwise. */ +bNodeTree *version_get_scene_compositor_node_tree(Main *bmain, Scene *scene); diff --git a/source/blender/compositor/COM_context.hh b/source/blender/compositor/COM_context.hh index 8d1ad6e53df..51e604343b8 100644 --- a/source/blender/compositor/COM_context.hh +++ b/source/blender/compositor/COM_context.hh @@ -106,7 +106,7 @@ class Context { /* True if the compositor should treat viewers as composite outputs because it has no concept of * or support for viewers. */ - virtual bool treat_viewer_as_composite_output() const; + virtual bool treat_viewer_as_compositor_output() const; /* Populates the given meta data from the render stamp information of the given render pass. */ virtual void populate_meta_data_for_pass(const Scene *scene, diff --git a/source/blender/compositor/intern/context.cc b/source/blender/compositor/intern/context.cc index 3b182d01348..1fea3e5e4df 100644 --- a/source/blender/compositor/intern/context.cc +++ b/source/blender/compositor/intern/context.cc @@ -20,7 +20,7 @@ namespace blender::compositor { -bool Context::treat_viewer_as_composite_output() const +bool Context::treat_viewer_as_compositor_output() const { return false; } diff --git a/source/blender/compositor/intern/scheduler.cc b/source/blender/compositor/intern/scheduler.cc index 13ea938644b..94f9a5384d3 100644 --- a/source/blender/compositor/intern/scheduler.cc +++ b/source/blender/compositor/intern/scheduler.cc @@ -11,8 +11,6 @@ #include "NOD_derived_node_tree.hh" -#include "BKE_node_legacy_types.hh" - #include "COM_context.hh" #include "COM_scheduler.hh" #include "COM_utilities.hh" @@ -39,8 +37,8 @@ static bool is_tree_context_muted(const DTreeContext &tree_context) } /* Add the active viewer node in the given tree context to the given stack. If viewer nodes are - * treated as composite outputs, this function will also add either the viewer or the composite - * node since composite nodes were skipped in add_output_nodes such that viewer nodes take + * treated as compositor outputs, this function will also add either the viewer or the group output + * node since group output nodes were skipped in add_output_nodes such that viewer nodes take * precedence. */ static bool add_viewer_nodes_in_context(const Context &context, const DTreeContext *tree_context, @@ -58,14 +56,14 @@ static bool add_viewer_nodes_in_context(const Context &context, } } - /* If we are not treating viewers as composite outputs, there is nothing else to do. Otherwise, - * the active composite node might be added. */ - if (!context.treat_viewer_as_composite_output()) { + /* If we are not treating viewers as compositor outputs, there is nothing else to do. Otherwise, + * the active group output node might be added. */ + if (!context.treat_viewer_as_compositor_output()) { return false; } /* No active viewers exist in this tree context, add the active Composite node if one exist. */ - for (const bNode *node : tree_context->btree().nodes_by_type("CompositorNodeComposite")) { + for (const bNode *node : tree_context->btree().nodes_by_type("NodeGroupOutput")) { if (node->flag & NODE_DO_OUTPUT && !node->is_muted()) { node_stack.push(DNode(tree_context, node)); return true; @@ -101,7 +99,7 @@ static void add_file_output_nodes(const DTreeContext &tree_context, Stack } /* Add the output nodes whose result should be computed to the given stack. This includes File - * Output, Composite, and Viewer nodes. Viewer nodes are a special case, as only the nodes that + * Output, Group Output, and Viewer nodes. Viewer nodes are a special case, as only the nodes that * satisfies the requirements in the add_viewer_nodes_in_context function are added. First, the * active context is searched for viewer nodes, if non were found, the root context is searched. * For more information on what contexts mean here, see the DerivedNodeTree::active_context() @@ -117,13 +115,13 @@ static void add_output_nodes(const Context &context, add_file_output_nodes(root_context, node_stack); } - /* Add the active composite node in the root tree if needed, but only if we are not treating - * viewer outputs as composite ones. That's because in cases where viewer nodes will be treated - * as composite outputs, viewer nodes will take precedence, so this is handled as a special case + /* Add the active group output node in the root tree if needed, but only if we are not treating + * viewer outputs as compositor ones. That's because in cases where viewer nodes will be treated + * as compositor outputs, viewer nodes will take precedence, so this is handled as a special case * in the add_viewer_nodes_in_context function instead and no need to add it here. */ if (bool(context.needed_outputs() & OutputTypes::Composite)) { - if (!context.treat_viewer_as_composite_output()) { - for (const bNode *node : root_context.btree().nodes_by_type("CompositorNodeComposite")) { + if (!context.treat_viewer_as_compositor_output()) { + for (const bNode *node : root_context.btree().nodes_by_type("NodeGroupOutput")) { if (node->flag & NODE_DO_OUTPUT && !node->is_muted()) { node_stack.push(DNode(&root_context, node)); break; diff --git a/source/blender/draw/engines/compositor/compositor_engine.cc b/source/blender/draw/engines/compositor/compositor_engine.cc index 86c798f21a2..9886b059cd2 100644 --- a/source/blender/draw/engines/compositor/compositor_engine.cc +++ b/source/blender/draw/engines/compositor/compositor_engine.cc @@ -81,7 +81,7 @@ class Context : public compositor::Context { /* The viewport compositor does not support viewer outputs, so treat viewers as composite * outputs. */ - bool treat_viewer_as_composite_output() const override + bool treat_viewer_as_compositor_output() const override { return true; } diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index a3e606d8cec..969a529e52b 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -604,7 +604,12 @@ void ED_node_composit_default_init(const bContext *C, bNodeTree *ntree) BLI_assert(ntree != nullptr && ntree->type == NTREE_COMPOSIT); BLI_assert(BLI_listbase_count(&ntree->nodes) == 0); - bNode *composite = blender::bke::node_add_static_node(C, *ntree, CMP_NODE_COMPOSITE); + ntree->tree_interface.add_socket( + DATA_("Image"), "", "NodeSocketColor", NODE_INTERFACE_SOCKET_INPUT, nullptr); + ntree->tree_interface.add_socket( + DATA_("Image"), "", "NodeSocketColor", NODE_INTERFACE_SOCKET_OUTPUT, nullptr); + + bNode *composite = blender::bke::node_add_node(C, *ntree, "NodeGroupOutput"); composite->location[0] = 200.0f; composite->location[1] = 0.0f; @@ -619,7 +624,7 @@ void ED_node_composit_default_init(const bContext *C, bNodeTree *ntree) bNode *viewer = blender::bke::node_add_static_node(C, *ntree, CMP_NODE_VIEWER); viewer->location[0] = 200.0f; - viewer->location[1] = -60.0f; + viewer->location[1] = -80.0f; /* Viewer and Composite nodes are linked to Render Layer's output image socket through a reroute * node. */ @@ -833,19 +838,6 @@ void ED_node_set_active( BKE_main_ensure_invariants(*bmain, ntree->id); } } - else if (node->type_legacy == CMP_NODE_COMPOSITE) { - if (was_output == 0) { - for (bNode *node_iter : ntree->all_nodes()) { - if (node_iter->type_legacy == CMP_NODE_COMPOSITE) { - node_iter->flag &= ~NODE_DO_OUTPUT; - } - } - - node->flag |= NODE_DO_OUTPUT; - BKE_ntree_update_tag_active_output_changed(ntree); - BKE_main_ensure_invariants(*bmain, ntree->id); - } - } else if (do_update) { BKE_main_ensure_invariants(*bmain, ntree->id); } diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 8edaea77fce..cd2a32d4551 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -10618,7 +10618,6 @@ static void rna_def_nodes(BlenderRNA *brna) define("CompositorNode", "CompositorNodeCombRGBA"); define("CompositorNode", "CompositorNodeCombYCCA", def_cmp_ycc); define("CompositorNode", "CompositorNodeCombYUVA"); - define("CompositorNode", "CompositorNodeComposite"); define("CompositorNode", "CompositorNodeConvertColorSpace", def_cmp_convert_color_space); define("CompositorNode", "CompositorNodeCornerPin", def_cmp_cornerpin); define("CompositorNode", "CompositorNodeCrop"); diff --git a/source/blender/nodes/NOD_composite.hh b/source/blender/nodes/NOD_composite.hh index 6bb64bed651..4fb4ea2e3fb 100644 --- a/source/blender/nodes/NOD_composite.hh +++ b/source/blender/nodes/NOD_composite.hh @@ -98,5 +98,8 @@ namespace blender::nodes { compositor::NodeOperation *get_group_input_compositor_operation(compositor::Context &context, DNode node); +compositor::NodeOperation *get_group_output_compositor_operation(compositor::Context &context, + DNode node); +void get_compositor_group_output_extra_info(blender::nodes::NodeExtraInfoParams ¶meters); -} +} // namespace blender::nodes diff --git a/source/blender/nodes/composite/CMakeLists.txt b/source/blender/nodes/composite/CMakeLists.txt index ff6110b54f0..276742e390c 100644 --- a/source/blender/nodes/composite/CMakeLists.txt +++ b/source/blender/nodes/composite/CMakeLists.txt @@ -37,7 +37,6 @@ set(SRC nodes/node_composite_colorbalance.cc nodes/node_composite_colorcorrection.cc nodes/node_composite_common.cc - nodes/node_composite_composite.cc nodes/node_composite_convert_color_space.cc nodes/node_composite_cornerpin.cc nodes/node_composite_crop.cc @@ -61,6 +60,7 @@ set(SRC nodes/node_composite_gamma.cc nodes/node_composite_glare.cc nodes/node_composite_group_input.cc + nodes/node_composite_group_output.cc nodes/node_composite_hue_sat_val.cc nodes/node_composite_huecorrect.cc nodes/node_composite_id_mask.cc diff --git a/source/blender/nodes/composite/node_composite_tree.cc b/source/blender/nodes/composite/node_composite_tree.cc index 6e4008d8b68..a89240c1171 100644 --- a/source/blender/nodes/composite/node_composite_tree.cc +++ b/source/blender/nodes/composite/node_composite_tree.cc @@ -203,7 +203,7 @@ void ntreeCompositTagRender(Scene *scene) { if (sce_iter->compositing_node_group) { for (bNode *node : sce_iter->compositing_node_group->all_nodes()) { - if (node->id == (ID *)scene || node->type_legacy == CMP_NODE_COMPOSITE) { + if (node->id == (ID *)scene) { BKE_ntree_update_tag_node_property(sce_iter->compositing_node_group, node); } } diff --git a/source/blender/nodes/composite/nodes/node_composite_composite.cc b/source/blender/nodes/composite/nodes/node_composite_composite.cc deleted file mode 100644 index dd60ea7fa84..00000000000 --- a/source/blender/nodes/composite/nodes/node_composite_composite.cc +++ /dev/null @@ -1,161 +0,0 @@ -/* SPDX-FileCopyrightText: 2006 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup cmpnodes - */ - -#include "BLI_bounds_types.hh" -#include "BLI_math_vector_types.hh" - -#include "UI_resources.hh" - -#include "GPU_shader.hh" -#include "GPU_texture.hh" - -#include "COM_node_operation.hh" -#include "COM_utilities.hh" - -#include "node_composite_util.hh" - -/* **************** COMPOSITE ******************** */ - -namespace blender::nodes::node_composite_composite_cc { - -static void cmp_node_composite_declare(NodeDeclarationBuilder &b) -{ - b.add_input("Image") - .default_value({0.0f, 0.0f, 0.0f, 1.0f}) - .structure_type(StructureType::Dynamic); -} - -using namespace blender::compositor; - -class CompositeOperation : public NodeOperation { - public: - using NodeOperation::NodeOperation; - - void execute() override - { - if (!this->context().is_valid_compositing_region()) { - return; - } - - const Result &image = this->get_input("Image"); - if (image.is_single_value()) { - this->execute_clear(); - } - else { - this->execute_copy(); - } - } - - void execute_clear() - { - const Result &image = this->get_input("Image"); - - float4 color = image.get_single_value(); - - const Domain domain = this->compute_domain(); - Result output = this->context().get_output_result(); - if (this->context().use_gpu()) { - GPU_texture_clear(output, GPU_DATA_FLOAT, color); - } - else { - parallel_for(domain.size, [&](const int2 texel) { output.store_pixel(texel, color); }); - } - } - - void execute_copy() - { - if (this->context().use_gpu()) { - this->execute_copy_gpu(); - } - else { - this->execute_copy_cpu(); - } - } - - void execute_copy_gpu() - { - const Result &image = this->get_input("Image"); - const Domain domain = this->compute_domain(); - Result output = this->context().get_output_result(); - - GPUShader *shader = this->context().get_shader("compositor_write_output", output.precision()); - GPU_shader_bind(shader); - - const Bounds bounds = this->get_output_bounds(); - GPU_shader_uniform_2iv(shader, "lower_bound", bounds.min); - GPU_shader_uniform_2iv(shader, "upper_bound", bounds.max); - - image.bind_as_texture(shader, "input_tx"); - - output.bind_as_image(shader, "output_img"); - - compute_dispatch_threads_at_least(shader, domain.size); - - image.unbind_as_texture(); - output.unbind_as_image(); - GPU_shader_unbind(); - } - - void execute_copy_cpu() - { - const Domain domain = this->compute_domain(); - const Result &image = this->get_input("Image"); - Result output = this->context().get_output_result(); - - const Bounds bounds = this->get_output_bounds(); - parallel_for(domain.size, [&](const int2 texel) { - const int2 output_texel = texel + bounds.min; - if (output_texel.x > bounds.max.x || output_texel.y > bounds.max.y) { - return; - } - output.store_pixel(texel + bounds.min, image.load_pixel(texel)); - }); - } - - /* Returns the bounds of the area of the compositing region. Only write into the compositing - * region, which might be limited to a smaller region of the output result. */ - Bounds get_output_bounds() - { - const rcti compositing_region = this->context().get_compositing_region(); - return Bounds(int2(compositing_region.xmin, compositing_region.ymin), - int2(compositing_region.xmax, compositing_region.ymax)); - } - - /* The operation domain has the same size as the compositing region without any transformations - * applied. */ - Domain compute_domain() override - { - return Domain(this->context().get_compositing_region_size()); - } -}; - -static NodeOperation *get_compositor_operation(Context &context, DNode node) -{ - return new CompositeOperation(context, node); -} - -} // namespace blender::nodes::node_composite_composite_cc - -static void register_node_type_cmp_composite() -{ - namespace file_ns = blender::nodes::node_composite_composite_cc; - - static blender::bke::bNodeType ntype; - - cmp_node_type_base(&ntype, "CompositorNodeComposite", CMP_NODE_COMPOSITE); - ntype.ui_name = "Composite"; - ntype.ui_description = "Final render output"; - ntype.enum_name_legacy = "COMPOSITE"; - ntype.nclass = NODE_CLASS_OUTPUT; - ntype.declare = file_ns::cmp_node_composite_declare; - ntype.get_compositor_operation = file_ns::get_compositor_operation; - ntype.no_muting = true; - - blender::bke::node_register_type(ntype); -} -NOD_REGISTER_NODE(register_node_type_cmp_composite) diff --git a/source/blender/nodes/composite/nodes/node_composite_group_output.cc b/source/blender/nodes/composite/nodes/node_composite_group_output.cc new file mode 100644 index 00000000000..c1df1ddf750 --- /dev/null +++ b/source/blender/nodes/composite/nodes/node_composite_group_output.cc @@ -0,0 +1,195 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_bounds_types.hh" +#include "BLI_math_vector_types.hh" + +#include "BLT_translation.hh" + +#include "UI_resources.hh" + +#include "DNA_space_types.h" + +#include "GPU_shader.hh" + +#include "BKE_context.hh" + +#include "NOD_composite.hh" +#include "NOD_node_extra_info.hh" + +#include "COM_node_operation.hh" +#include "COM_utilities.hh" + +namespace blender::nodes::node_composite_group_output_cc { + +using namespace blender::compositor; + +class GroupOutputOperation : public NodeOperation { + public: + GroupOutputOperation(Context &context, DNode node) : NodeOperation(context, node) + { + for (const bNodeSocket *input : node->input_sockets()) { + if (!is_socket_available(input)) { + continue; + } + + /* The structure type of the inputs of Group Output nodes are inferred, so we need to + * manually specify this here. */ + InputDescriptor &descriptor = this->get_input_descriptor(input->identifier); + descriptor.expects_single_value = false; + } + } + + void execute() override + { + if (!this->context().is_valid_compositing_region()) { + return; + } + + /* Get the first input to be written to the output. The rest of the inputs are ignored. Only + * color sockets are supported. */ + const bNodeSocket *input_socket = this->node()->input_sockets()[0]; + if (input_socket->type != SOCK_RGBA) { + return; + } + + const Result &image = this->get_input(input_socket->identifier); + if (image.is_single_value()) { + this->execute_clear(image); + } + else { + this->execute_copy(image); + } + } + + void execute_clear(const Result &image) + { + float4 color = image.get_single_value(); + + const Domain domain = this->compute_domain(); + Result output = this->context().get_output_result(); + if (this->context().use_gpu()) { + GPU_texture_clear(output, GPU_DATA_FLOAT, color); + } + else { + parallel_for(domain.size, [&](const int2 texel) { output.store_pixel(texel, color); }); + } + } + + void execute_copy(const Result &image) + { + if (this->context().use_gpu()) { + this->execute_copy_gpu(image); + } + else { + this->execute_copy_cpu(image); + } + } + + void execute_copy_gpu(const Result &image) + { + const Domain domain = this->compute_domain(); + Result output = this->context().get_output_result(); + + GPUShader *shader = this->context().get_shader("compositor_write_output", output.precision()); + GPU_shader_bind(shader); + + const Bounds bounds = this->get_output_bounds(); + GPU_shader_uniform_2iv(shader, "lower_bound", bounds.min); + GPU_shader_uniform_2iv(shader, "upper_bound", bounds.max); + + image.bind_as_texture(shader, "input_tx"); + + output.bind_as_image(shader, "output_img"); + + compute_dispatch_threads_at_least(shader, domain.size); + + image.unbind_as_texture(); + output.unbind_as_image(); + GPU_shader_unbind(); + } + + void execute_copy_cpu(const Result &image) + { + const Domain domain = this->compute_domain(); + Result output = this->context().get_output_result(); + + const Bounds bounds = this->get_output_bounds(); + parallel_for(domain.size, [&](const int2 texel) { + const int2 output_texel = texel + bounds.min; + if (output_texel.x > bounds.max.x || output_texel.y > bounds.max.y) { + return; + } + output.store_pixel(texel + bounds.min, image.load_pixel(texel)); + }); + } + + /* Returns the bounds of the area of the compositing region. Only write into the compositing + * region, which might be limited to a smaller region of the output result. */ + Bounds get_output_bounds() + { + const rcti compositing_region = this->context().get_compositing_region(); + return Bounds(int2(compositing_region.xmin, compositing_region.ymin), + int2(compositing_region.xmax, compositing_region.ymax)); + } + + /* The operation domain has the same size as the compositing region without any transformations + * applied. */ + Domain compute_domain() override + { + return Domain(this->context().get_compositing_region_size()); + } +}; + +} // namespace blender::nodes::node_composite_group_output_cc + +namespace blender::nodes { + +compositor::NodeOperation *get_group_output_compositor_operation(compositor::Context &context, + DNode node) +{ + return new node_composite_group_output_cc::GroupOutputOperation(context, node); +} + +void get_compositor_group_output_extra_info(blender::nodes::NodeExtraInfoParams ¶meters) +{ + if (parameters.tree.type != NTREE_COMPOSIT) { + return; + } + + SpaceNode *space_node = CTX_wm_space_node(¶meters.C); + if (space_node->edittree != space_node->nodetree) { + return; + } + + Span group_outputs = parameters.node.input_sockets().drop_back(1); + if (group_outputs.is_empty()) { + blender::nodes::NodeExtraInfoRow row; + row.text = IFACE_("No Output"); + row.icon = ICON_ERROR; + row.tooltip = TIP_("Node group must have a Color output socket"); + parameters.rows.append(std::move(row)); + return; + } + + if (group_outputs[0]->type != SOCK_RGBA) { + blender::nodes::NodeExtraInfoRow row; + row.text = IFACE_("Wrong Output Type"); + row.icon = ICON_ERROR; + row.tooltip = TIP_("Node group's first output must be a color output"); + parameters.rows.append(std::move(row)); + return; + } + + if (group_outputs.size() > 1) { + blender::nodes::NodeExtraInfoRow row; + row.text = IFACE_("Ignored Outputs"); + row.icon = ICON_WARNING_LARGE; + row.tooltip = TIP_("Only the first output is considered while the rest are ignored"); + parameters.rows.append(std::move(row)); + return; + } +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/composite/nodes/node_composite_viewer.cc b/source/blender/nodes/composite/nodes/node_composite_viewer.cc index b1c21f982ed..71d6e4ff874 100644 --- a/source/blender/nodes/composite/nodes/node_composite_viewer.cc +++ b/source/blender/nodes/composite/nodes/node_composite_viewer.cc @@ -53,7 +53,7 @@ class ViewerOperation : public NodeOperation { { /* Viewers are treated as composite outputs that should be in the bounds of the compositing * region, so do nothing if the compositing region is invalid. */ - if (this->context().treat_viewer_as_composite_output() && + if (this->context().treat_viewer_as_compositor_output() && !this->context().is_valid_compositing_region()) { return; @@ -143,7 +143,7 @@ class ViewerOperation : public NodeOperation { { /* Viewers are treated as composite outputs that should be in the bounds of the compositing * region. */ - if (context().treat_viewer_as_composite_output()) { + if (context().treat_viewer_as_compositor_output()) { const rcti compositing_region = context().get_compositing_region(); return Bounds(int2(compositing_region.xmin, compositing_region.ymin), int2(compositing_region.xmax, compositing_region.ymax)); @@ -157,7 +157,7 @@ class ViewerOperation : public NodeOperation { { /* Viewers are treated as composite outputs that should be in the domain of the compositing * region. */ - if (context().treat_viewer_as_composite_output()) { + if (context().treat_viewer_as_compositor_output()) { return Domain(context().get_compositing_region_size()); } diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc index 312ae97398f..61d8e0f4f41 100644 --- a/source/blender/nodes/intern/node_common.cc +++ b/source/blender/nodes/intern/node_common.cc @@ -469,7 +469,7 @@ void node_group_declare(NodeDeclarationBuilder &b) group->ensure_interface_cache(); Map structure_type_by_socket; - if (group->type == NTREE_GEOMETRY) { + if (ELEM(group->type, NTREE_GEOMETRY, NTREE_COMPOSIT)) { structure_type_by_socket.reserve(group->interface_items().size()); const Span inputs = group->interface_inputs(); @@ -901,6 +901,7 @@ bNodeSocket *node_group_output_find_socket(bNode *node, const StringRef identifi static void node_group_output_extra_info(blender::nodes::NodeExtraInfoParams ¶ms) { + get_compositor_group_output_extra_info(params); const blender::Span group_output_nodes = params.tree.nodes_by_type( "NodeGroupOutput"); if (group_output_nodes.size() <= 1) { @@ -931,6 +932,7 @@ void register_node_type_group_output() ntype->declare = blender::nodes::group_output_declare; ntype->insert_link = blender::nodes::group_output_insert_link; ntype->get_extra_info = node_group_output_extra_info; + ntype->get_compositor_operation = blender::nodes::get_group_output_compositor_operation; ntype->no_muting = true; diff --git a/source/blender/nodes/intern/socket_usage_inference.cc b/source/blender/nodes/intern/socket_usage_inference.cc index 3f69feb8013..377181b1697 100644 --- a/source/blender/nodes/intern/socket_usage_inference.cc +++ b/source/blender/nodes/intern/socket_usage_inference.cc @@ -252,7 +252,6 @@ struct SocketUsageInferencer { case SH_NODE_OUTPUT_LINESTYLE: case SH_NODE_OUTPUT_MATERIAL: case CMP_NODE_OUTPUT_FILE: - case CMP_NODE_COMPOSITE: case TEX_NODE_OUTPUT: { this->usage_task__input__output_node(socket); break; diff --git a/source/blender/render/intern/pipeline.cc b/source/blender/render/intern/pipeline.cc index 89c0b3ec719..43b51e3860c 100644 --- a/source/blender/render/intern/pipeline.cc +++ b/source/blender/render/intern/pipeline.cc @@ -1222,26 +1222,20 @@ static bool compositor_needs_render(Scene *scene) return false; } -/** Returns true if the node tree has a composite output node. */ -static bool node_tree_has_composite_output(const bNodeTree *node_tree) +/** Returns true if the node tree has a group output node. */ +static bool node_tree_has_group_output(const bNodeTree *node_tree) { if (node_tree == nullptr) { return false; } - for (const bNode *node : node_tree->all_nodes()) { - if (node->is_muted()) { - continue; - } - if (node->type_legacy == CMP_NODE_COMPOSITE && node->flag & NODE_DO_OUTPUT) { + node_tree->ensure_topology_cache(); + for (const bNode *node : node_tree->nodes_by_type("NodeGroupOutput")) { + if (node->flag & NODE_DO_OUTPUT && !node->is_muted()) { return true; } - if (node->is_group() && node->id) { - if (node_tree_has_composite_output(reinterpret_cast(node->id))) { - return true; - } - } } + return false; } @@ -1328,9 +1322,9 @@ static void do_render_compositor(Render *re) /* Scene render process already updates animsys. */ update_newframe = true; - /* The compositor does not have an output, skip writing the render result. See R_SKIP_WRITE for - * more information. */ - if (!node_tree_has_composite_output(re->pipeline_scene_eval->compositing_node_group)) { + /* The compositor does not have a group output, skip writing the render result. See + * R_SKIP_WRITE for more information. */ + if (!node_tree_has_group_output(re->pipeline_scene_eval->compositing_node_group)) { re->flag |= R_SKIP_WRITE; } } @@ -1775,27 +1769,34 @@ static int check_valid_camera(Scene *scene, Object *camera_override, ReportList return true; } -static bool node_tree_has_any_compositor_output(const bNodeTree *ntree) +static bool node_tree_has_file_output(const bNodeTree *node_tree) { - for (const bNode *node : ntree->all_nodes()) { - if (ELEM(node->type_legacy, CMP_NODE_COMPOSITE, CMP_NODE_OUTPUT_FILE)) { + node_tree->ensure_topology_cache(); + for (const bNode *node : node_tree->nodes_by_type("CompositorNodeOutputFile")) { + if (!node->is_muted()) { return true; } - if (node->is_group()) { - if (node->id) { - if (node_tree_has_any_compositor_output((const bNodeTree *)node->id)) { - return true; - } - } + } + + for (const bNode *node : node_tree->group_nodes()) { + if (node->is_muted() || !node->id) { + continue; + } + + if (node_tree_has_file_output(reinterpret_cast(node->id))) { + return true; } } return false; } -static int check_compositor_output(Scene *scene) +static bool scene_has_compositor_output(Scene *scene) { - return node_tree_has_any_compositor_output(scene->compositing_node_group); + if (node_tree_has_group_output(scene->compositing_node_group)) { + return true; + } + return node_tree_has_file_output(scene->compositing_node_group); } /* Identify if the compositor can run on the GPU. Currently, this only checks if the compositor is @@ -1847,8 +1848,8 @@ bool RE_is_rendering_allowed(Scene *scene, } else if (scemode & R_DOCOMP && scene->compositing_node_group) { /* Compositor */ - if (!check_compositor_output(scene)) { - BKE_report(reports, RPT_ERROR, "No render output node in scene"); + if (!scene_has_compositor_output(scene)) { + BKE_report(reports, RPT_ERROR, "No Group Output or File Output nodes in scene"); return false; } diff --git a/source/blender/render/intern/render_types.h b/source/blender/render/intern/render_types.h index 44e8038e1bb..ef11e936a7a 100644 --- a/source/blender/render/intern/render_types.h +++ b/source/blender/render/intern/render_types.h @@ -261,8 +261,8 @@ struct Render : public BaseRender { /** #R.flag */ #define R_ANIMATION 1 << 0 /* Indicates that the render pipeline should not write its render result. This happens for instance - * when the render pipeline uses the compositor, but the compositor node tree does not have an - * output composite node or a render layer input, and consequently no render result. In that case, - * the output will be written from the File Output nodes, since the render pipeline will early fail - * if neither a File Output nor a Composite node exist in the scene. */ + * when the render pipeline uses the compositor, but the compositor node tree does not have a group + * output node or a render layer input, and consequently no render result. In that case, the output + * will be written from the File Output nodes, since the render pipeline will early fail if neither + * a File Output nor a Group Output node exist in the scene. */ #define R_SKIP_WRITE 1 << 1