diff --git a/intern/cycles/blender/CMakeLists.txt b/intern/cycles/blender/CMakeLists.txt index ea428210975..a1aa107ae31 100644 --- a/intern/cycles/blender/CMakeLists.txt +++ b/intern/cycles/blender/CMakeLists.txt @@ -59,6 +59,7 @@ set(LIB PRIVATE bf::gpu PRIVATE bf::intern::guardedalloc PRIVATE bf::intern::clog + PRIVATE bf::nodes PRIVATE bf::render cycles_bvh cycles_device diff --git a/intern/cycles/blender/shader.cpp b/intern/cycles/blender/shader.cpp index 1aefbb107a9..7ad750ba644 100644 --- a/intern/cycles/blender/shader.cpp +++ b/intern/cycles/blender/shader.cpp @@ -20,6 +20,9 @@ #include "util/task.h" #include "BKE_duplilist.hh" +#include "BKE_node.hh" + +#include "NOD_shader_nodes_inline.hh" CCL_NAMESPACE_BEGIN @@ -1241,7 +1244,16 @@ static void add_nodes(Scene *scene, ShaderGraph *graph, BL::ShaderNodeTree &b_ntree, const ProxyMap &proxy_input_map, - const ProxyMap &proxy_output_map) + const ProxyMap &proxy_output_map); + +static void add_nodes_inlined(Scene *scene, + BL::RenderEngine &b_engine, + BL::BlendData &b_data, + BL::Scene &b_scene, + ShaderGraph *graph, + BL::ShaderNodeTree &b_ntree, + const ProxyMap &proxy_input_map, + const ProxyMap &proxy_output_map) { /* add nodes */ PtrInputMap input_map; @@ -1360,6 +1372,7 @@ static void add_nodes(Scene *scene, } } } + /* TODO: All the previous cases can be removed? */ else { ShaderNode *node = nullptr; @@ -1437,6 +1450,29 @@ static void add_nodes(Scene *scene, } } +static void add_nodes(Scene *scene, + BL::RenderEngine &b_engine, + BL::BlendData &b_data, + BL::Scene &b_scene, + ShaderGraph *graph, + BL::ShaderNodeTree &b_ntree, + const ProxyMap &proxy_input_map, + const ProxyMap &proxy_output_map) +{ + bNodeTree *ntree = b_ntree.ptr.data_as(); + bNodeTree *localtree = blender::bke::node_tree_add_tree( + nullptr, (blender::StringRef(ntree->id.name) + " Inlined").c_str(), ntree->idname); + blender::nodes::InlineShaderNodeTreeParams inline_params; + inline_params.allow_preserving_repeat_zones = false; + blender::nodes::inline_shader_node_tree(*ntree, *localtree, inline_params); + + BL::ShaderNodeTree b_localtree(RNA_id_pointer_create(&localtree->id)); + add_nodes_inlined( + scene, b_engine, b_data, b_scene, graph, b_localtree, proxy_input_map, proxy_output_map); + + BKE_id_free(nullptr, &localtree->id); +} + static void add_nodes(Scene *scene, BL::RenderEngine &b_engine, BL::BlendData &b_data, diff --git a/scripts/startup/bl_operators/node.py b/scripts/startup/bl_operators/node.py index fa1fa7ab479..fdf3c7812d6 100644 --- a/scripts/startup/bl_operators/node.py +++ b/scripts/startup/bl_operators/node.py @@ -239,7 +239,7 @@ class NodeAddZoneOperator(NodeAddOperator): input_node.location -= Vector(self.offset) output_node.location += Vector(self.offset) - if self.add_default_geometry_link: + if tree.type == "GEOMETRY" and self.add_default_geometry_link: # Connect geometry sockets by default if available. # Get the sockets by their types, because the name is not guaranteed due to i18n. from_socket = next(s for s in input_node.outputs if s.type == 'GEOMETRY') diff --git a/scripts/startup/bl_ui/node_add_menu_shader.py b/scripts/startup/bl_ui/node_add_menu_shader.py index 8dc3d63e708..d792a589fbd 100644 --- a/scripts/startup/bl_ui/node_add_menu_shader.py +++ b/scripts/startup/bl_ui/node_add_menu_shader.py @@ -426,6 +426,11 @@ class NODE_MT_shader_node_add_all(Menu): layout.separator() layout.menu("NODE_MT_category_shader_group") layout.menu("NODE_MT_category_layout") + node_add_menu.add_repeat_zone(layout, label="Repeat") + node_add_menu.add_closure_zone(layout, label="Closure") + node_add_menu.add_node_type(layout, "NodeEvaluateClosure") + node_add_menu.add_node_type(layout, "NodeCombineBundle") + node_add_menu.add_node_type(layout, "NodeSeparateBundle") node_add_menu.draw_root_assets(layout) diff --git a/source/blender/blenkernel/BKE_compute_context_cache.hh b/source/blender/blenkernel/BKE_compute_context_cache.hh index a3740a17fc2..04944b3c6dc 100644 --- a/source/blender/blenkernel/BKE_compute_context_cache.hh +++ b/source/blender/blenkernel/BKE_compute_context_cache.hh @@ -31,6 +31,7 @@ class ComputeContextCache { Map, const ModifierComputeContext *> modifier_contexts_cache_; Map operator_contexts_cache_; + Map shader_contexts_cache_; Map, const GroupNodeComputeContext *> group_node_contexts_cache_; Map, const SimulationZoneComputeContext *> @@ -51,6 +52,8 @@ class ComputeContextCache { const OperatorComputeContext &for_operator(const ComputeContext *parent); const OperatorComputeContext &for_operator(const ComputeContext *parent, const bNodeTree &tree); + const ShaderComputeContext &for_shader(const ComputeContext *parent, const bNodeTree *tree); + const GroupNodeComputeContext &for_group_node(const ComputeContext *parent, int32_t node_id, const bNodeTree *tree = nullptr); diff --git a/source/blender/blenkernel/BKE_compute_contexts.hh b/source/blender/blenkernel/BKE_compute_contexts.hh index e7dc412999a..6d8e311e9e9 100644 --- a/source/blender/blenkernel/BKE_compute_contexts.hh +++ b/source/blender/blenkernel/BKE_compute_contexts.hh @@ -205,4 +205,16 @@ class OperatorComputeContext : public ComputeContext { void print_current_in_line(std::ostream &stream) const override; }; +class ShaderComputeContext : public ComputeContext { + private: + const bNodeTree *tree_ = nullptr; + + public: + ShaderComputeContext(const ComputeContext *parent = nullptr, const bNodeTree *tree = nullptr); + + private: + ComputeContextHash compute_hash() const override; + void print_current_in_line(std::ostream &stream) const override; +}; + } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_node.hh b/source/blender/blenkernel/BKE_node.hh index d8f2621b7e2..d1e26165098 100644 --- a/source/blender/blenkernel/BKE_node.hh +++ b/source/blender/blenkernel/BKE_node.hh @@ -592,8 +592,6 @@ bNodeTree **node_tree_ptr_from_id(ID *id); */ bNodeTree *node_tree_from_id(ID *id); -void node_tree_free_local_tree(bNodeTree *ntree); - /** * Check recursively if a node tree contains another. */ @@ -678,7 +676,10 @@ void node_remove_socket(bNodeTree &ntree, bNode &node, bNodeSocket &sock); void node_modify_socket_type_static( bNodeTree *ntree, bNode *node, bNodeSocket *sock, int type, int subtype); -bNode *node_add_node(const bContext *C, bNodeTree &ntree, StringRef idname); +bNode *node_add_node(const bContext *C, + bNodeTree &ntree, + StringRef idname, + std::optional unique_identifier = std::nullopt); bNode *node_add_static_node(const bContext *C, bNodeTree &ntree, int type); /** diff --git a/source/blender/blenkernel/BKE_node_runtime.hh b/source/blender/blenkernel/BKE_node_runtime.hh index 8d9a753bc0a..15a091a9eb4 100644 --- a/source/blender/blenkernel/BKE_node_runtime.hh +++ b/source/blender/blenkernel/BKE_node_runtime.hh @@ -207,6 +207,13 @@ class bNodeTreeRuntime : NonCopyable, NonMovable { */ MultiValueMap link_errors; + /** + * Error messages for shading nodes. Those don't have more contextual information yet. Maps + * #bNode::identifier to error messages. + */ + Map> shader_node_errors; + Mutex shader_node_errors_mutex; + /** * Protects access to all topology cache variables below. This is necessary so that the cache can * be updated on a const #bNodeTree. diff --git a/source/blender/blenkernel/intern/compute_contexts.cc b/source/blender/blenkernel/intern/compute_contexts.cc index e8c00ea3a26..e5eadd4e482 100644 --- a/source/blender/blenkernel/intern/compute_contexts.cc +++ b/source/blender/blenkernel/intern/compute_contexts.cc @@ -9,6 +9,7 @@ #include "BKE_compute_context_cache.hh" #include "BKE_compute_contexts.hh" +#include "BKE_lib_id.hh" #include "BKE_node.hh" #include "BKE_node_runtime.hh" @@ -195,6 +196,24 @@ void OperatorComputeContext::print_current_in_line(std::ostream &stream) const stream << "Operator"; } +ShaderComputeContext::ShaderComputeContext(const ComputeContext *parent, const bNodeTree *tree) + : ComputeContext(parent), tree_(tree) +{ +} + +ComputeContextHash ShaderComputeContext::compute_hash() const +{ + return ComputeContextHash::from(parent_, "SHADER"); +} + +void ShaderComputeContext::print_current_in_line(std::ostream &stream) const +{ + stream << "Shader "; + if (tree_) { + stream << BKE_id_name(tree_->id); + } +} + const ModifierComputeContext &ComputeContextCache::for_modifier(const ComputeContext *parent, const NodesModifierData &nmd) { @@ -224,6 +243,13 @@ const OperatorComputeContext &ComputeContextCache::for_operator(const ComputeCon parent, [&]() { return &this->for_any_uncached(parent, tree); }); } +const ShaderComputeContext &ComputeContextCache::for_shader(const ComputeContext *parent, + const bNodeTree *tree) +{ + return *shader_contexts_cache_.lookup_or_add_cb( + parent, [&]() { return &this->for_any_uncached(parent, tree); }); +} + const GroupNodeComputeContext &ComputeContextCache::for_group_node(const ComputeContext *parent, const int32_t node_id, const bNodeTree *tree) diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 7538a22666b..5808d72f25b 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -3326,12 +3326,21 @@ void node_unique_id(bNodeTree &ntree, bNode &node) BLI_assert(node.runtime->index_in_tree == ntree.runtime->nodes_by_id.index_of(&node)); } -bNode *node_add_node(const bContext *C, bNodeTree &ntree, const StringRef idname) +bNode *node_add_node(const bContext *C, + bNodeTree &ntree, + const StringRef idname, + std::optional unique_identifier) { bNode *node = MEM_callocN(__func__); node->runtime = MEM_new(__func__); BLI_addtail(&ntree.nodes, node); - node_unique_id(ntree, *node); + if (unique_identifier) { + node->identifier = *unique_identifier; + ntree.runtime->nodes_by_id.add_new(node); + } + else { + node_unique_id(ntree, *node); + } node->ui_order = ntree.all_nodes().size(); idname.copy_utf8_truncated(node->idname); @@ -3887,7 +3896,7 @@ static bNodeTree *node_tree_add_tree_do(Main *bmain, */ int flag = 0; if (is_embedded || bmain == nullptr) { - flag |= LIB_ID_CREATE_NO_MAIN; + flag |= LIB_ID_CREATE_NO_MAIN | LIB_ID_CREATE_NO_USER_REFCOUNT; } BLI_assert_msg(!owner_library || !owner_id, "Embedded NTrees should never have a defined owner library here"); @@ -4293,17 +4302,6 @@ void node_tree_free_embedded_tree(bNodeTree *ntree) BKE_libblock_free_data_py(&ntree->id); } -void node_tree_free_local_tree(bNodeTree *ntree) -{ - if (ntree->id.tag & ID_TAG_LOCALIZED) { - node_tree_free_tree(*ntree); - } - else { - node_tree_free_tree(*ntree); - BKE_libblock_free_data(&ntree->id, true); - } -} - void node_tree_set_output(bNodeTree &ntree) { const bool is_compositor = ntree.type == NTREE_COMPOSIT; diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index d0daf57c49f..c9ce46aa136 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -510,6 +510,7 @@ class NodeTreeMainUpdater { ntree.runtime->link_errors.clear(); ntree.runtime->invalid_zone_output_node_ids.clear(); + ntree.runtime->shader_node_errors.clear(); if (this->update_panel_toggle_names(ntree)) { result.interface_changed = true; diff --git a/source/blender/draw/engines/eevee/eevee_shader.cc b/source/blender/draw/engines/eevee/eevee_shader.cc index cba645aaffb..4a0ec4c22d3 100644 --- a/source/blender/draw/engines/eevee/eevee_shader.cc +++ b/source/blender/draw/engines/eevee/eevee_shader.cc @@ -12,6 +12,8 @@ #include "GPU_capabilities.hh" #include "BKE_material.hh" +#include "BKE_node_runtime.hh" + #include "DNA_world_types.h" #include "gpu_shader_create_info.hh" @@ -1161,6 +1163,25 @@ static GPUPass *pass_replacement_cb(void *void_thunk, GPUMaterial *mat) return nullptr; } +static void store_node_tree_errors(GPUMaterialFromNodeTreeResult &material_from_tree) +{ + Depsgraph *depsgraph = DRW_context_get()->depsgraph; + if (!depsgraph) { + return; + } + if (!DEG_is_active(depsgraph)) { + return; + } + for (const GPUMaterialFromNodeTreeResult::Error &error : material_from_tree.errors) { + const bNodeTree &tree = error.node->owner_tree(); + if (const bNodeTree *tree_orig = DEG_get_original(&tree)) { + std::lock_guard lock(tree_orig->runtime->shader_node_errors_mutex); + tree_orig->runtime->shader_node_errors.lookup_or_add_default(error.node->identifier) + .add(error.message); + } + } +} + GPUMaterial *ShaderModule::material_shader_get(::Material *blender_mat, bNodeTree *nodetree, eMaterialPipeline pipeline_type, @@ -1179,16 +1200,19 @@ GPUMaterial *ShaderModule::material_shader_get(::Material *blender_mat, CallbackThunk thunk = {this, default_mat}; - return GPU_material_from_nodetree(blender_mat, - nodetree, - &blender_mat->gpumaterial, - blender_mat->id.name, - GPU_MAT_EEVEE, - shader_uuid, - deferred_compilation, - codegen_callback, - &thunk, - is_default_material ? nullptr : pass_replacement_cb); + GPUMaterialFromNodeTreeResult material_from_tree = GPU_material_from_nodetree( + blender_mat, + nodetree, + &blender_mat->gpumaterial, + blender_mat->id.name, + GPU_MAT_EEVEE, + shader_uuid, + deferred_compilation, + codegen_callback, + &thunk, + is_default_material ? nullptr : pass_replacement_cb); + store_node_tree_errors(material_from_tree); + return material_from_tree.material; } GPUMaterial *ShaderModule::world_shader_get(::World *blender_world, @@ -1200,15 +1224,18 @@ GPUMaterial *ShaderModule::world_shader_get(::World *blender_world, CallbackThunk thunk = {this, nullptr}; - return GPU_material_from_nodetree(nullptr, - nodetree, - &blender_world->gpumaterial, - blender_world->id.name, - GPU_MAT_EEVEE, - shader_uuid, - deferred_compilation, - codegen_callback, - &thunk); + GPUMaterialFromNodeTreeResult material_from_tree = GPU_material_from_nodetree( + nullptr, + nodetree, + &blender_world->gpumaterial, + blender_world->id.name, + GPU_MAT_EEVEE, + shader_uuid, + deferred_compilation, + codegen_callback, + &thunk); + store_node_tree_errors(material_from_tree); + return material_from_tree.material; } /** \} */ diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index c01291ad5b0..57fdd979c51 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -155,6 +155,8 @@ struct TreeDrawContext { */ Array> extra_info_rows_per_node; + Map> shader_node_errors; + ~TreeDrawContext() { for (MutableSpan rows : this->extra_info_rows_per_node) { @@ -2083,6 +2085,28 @@ static void node_add_error_message_button(const TreeDrawContext &tree_draw_ctx, }); return; } + if (ntree.type == NTREE_SHADER) { + const VectorSet *errors = tree_draw_ctx.shader_node_errors.lookup_ptr( + node.identifier); + if (!errors) { + return; + } + if (errors->is_empty()) { + return; + } + uiBut *but = add_error_message_button(block, rect, ICON_ERROR, icon_offset); + UI_but_func_quick_tooltip_set(but, [errors = *errors](const uiBut * /*but*/) { + std::string tooltip; + for (const int i : errors.index_range()) { + const StringRefNull error = errors[i]; + tooltip += error.c_str(); + if (i + 1 < errors.size()) { + tooltip += ".\n"; + } + } + return tooltip; + }); + } } static std::optional geo_node_get_execution_time( @@ -4615,12 +4639,20 @@ static void draw_nodetree(const bContext &C, tree_draw_ctx.compositor_per_node_execution_time = &scene->runtime->compositor.per_node_execution_time; } - else if (ntree.type == NTREE_SHADER && USER_EXPERIMENTAL_TEST(&U, use_shader_node_previews) && - BKE_scene_uses_shader_previews(CTX_data_scene(&C)) && - snode->overlay.flag & SN_OVERLAY_SHOW_OVERLAYS && - snode->overlay.flag & SN_OVERLAY_SHOW_PREVIEWS) - { - tree_draw_ctx.nested_group_infos = get_nested_previews(C, *snode); + else if (ntree.type == NTREE_SHADER) { + if (USER_EXPERIMENTAL_TEST(&U, use_shader_node_previews) && + BKE_scene_uses_shader_previews(CTX_data_scene(&C)) && + snode->overlay.flag & SN_OVERLAY_SHOW_OVERLAYS && + snode->overlay.flag & SN_OVERLAY_SHOW_PREVIEWS) + { + tree_draw_ctx.nested_group_infos = get_nested_previews(C, *snode); + } + { + std::lock_guard lock(ntree.runtime->shader_node_errors_mutex); + /* Make a local copy to avoid mutex access for each node. Typically, there are only very few + * error message. */ + tree_draw_ctx.shader_node_errors = ntree.runtime->shader_node_errors; + } } for (const int i : nodes.index_range()) { diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index ccace7686aa..9bda58507c8 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -47,6 +47,7 @@ #include "DEG_depsgraph_debug.hh" #include "DEG_depsgraph_query.hh" +#include "NOD_shader_nodes_inline.hh" #include "RE_engine.h" #include "RE_pipeline.h" @@ -1852,6 +1853,41 @@ void NODE_OT_activate_viewer(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } +static wmOperatorStatus test_inline_shader_nodes_exec(bContext *C, wmOperator * /*op*/) +{ + SpaceNode &snode = *CTX_wm_space_node(C); + bNodeTree &ntree = *snode.edittree; + Main &bmain = *CTX_data_main(C); + + bNodeTree *new_tree = bke::node_tree_add_tree( + &bmain, (StringRef(ntree.id.name) + " Inlined").c_str(), ntree.idname); + + nodes::InlineShaderNodeTreeParams params; + params.allow_preserving_repeat_zones = false; + nodes::inline_shader_node_tree(ntree, *new_tree, params); + bNode *group_node = bke::node_add_node(C, ntree, ntree.typeinfo->group_idname); + group_node->id = &new_tree->id; + node_deselect_all(ntree); + bke::node_set_selected(*group_node, true); + bke::node_set_active(ntree, *group_node); + + BKE_main_ensure_invariants(bmain); + + return OPERATOR_FINISHED; +} + +void NODE_OT_test_inlining_shader_nodes(wmOperatorType *ot) +{ + ot->name = "Test Inlining Shader Nodes"; + ot->description = "Create a new inlined shader node tree as is consumed by renderers"; + ot->idname = "NODE_OT_test_inlining_shader_nodes"; + + ot->exec = test_inline_shader_nodes_exec; + ot->poll = ED_operator_node_active; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + static wmOperatorStatus node_deactivate_viewer_exec(bContext *C, wmOperator * /*op*/) { SpaceNode &snode = *CTX_wm_space_node(C); diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index 33f76457f6f..36247cea4a6 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -385,6 +385,7 @@ void NODE_OT_options_toggle(wmOperatorType *ot); void NODE_OT_node_copy_color(wmOperatorType *ot); void NODE_OT_deactivate_viewer(wmOperatorType *ot); void NODE_OT_activate_viewer(wmOperatorType *ot); +void NODE_OT_test_inlining_shader_nodes(wmOperatorType *ot); void NODE_OT_read_viewlayers(wmOperatorType *ot); void NODE_OT_render_changed(wmOperatorType *ot); diff --git a/source/blender/editors/space_node/node_ops.cc b/source/blender/editors/space_node/node_ops.cc index 35c51a774b0..3f124825ce9 100644 --- a/source/blender/editors/space_node/node_ops.cc +++ b/source/blender/editors/space_node/node_ops.cc @@ -45,6 +45,7 @@ void node_operatortypes() WM_operatortype_append(NODE_OT_node_copy_color); WM_operatortype_append(NODE_OT_deactivate_viewer); WM_operatortype_append(NODE_OT_activate_viewer); + WM_operatortype_append(NODE_OT_test_inlining_shader_nodes); WM_operatortype_append(NODE_OT_duplicate); WM_operatortype_append(NODE_OT_delete); diff --git a/source/blender/editors/space_node/space_node.cc b/source/blender/editors/space_node/space_node.cc index 257695f2d28..1b85a6a43af 100644 --- a/source/blender/editors/space_node/space_node.cc +++ b/source/blender/editors/space_node/space_node.cc @@ -8,6 +8,7 @@ #include "AS_asset_representation.hh" +#include "BKE_node_socket_value.hh" #include "BLI_listbase.h" #include "BLI_math_vector.h" #include "BLI_stack.hh" @@ -475,18 +476,24 @@ static std::optional compute_context_for_tree_path( static const ComputeContext *get_node_editor_root_compute_context( const SpaceNode &snode, bke::ComputeContextCache &compute_context_cache) { - switch (SpaceNodeGeometryNodesType(snode.node_tree_sub_type)) { - case SNODE_GEOMETRY_MODIFIER: { - std::optional object_and_modifier = - ed::space_node::get_modifier_for_node_editor(snode); - if (!object_and_modifier) { - return nullptr; + if (snode.nodetree->type == NTREE_GEOMETRY) { + switch (SpaceNodeGeometryNodesType(snode.node_tree_sub_type)) { + case SNODE_GEOMETRY_MODIFIER: { + std::optional object_and_modifier = + ed::space_node::get_modifier_for_node_editor(snode); + if (!object_and_modifier) { + return nullptr; + } + return &compute_context_cache.for_modifier(nullptr, *object_and_modifier->nmd); + } + case SNODE_GEOMETRY_TOOL: { + return &compute_context_cache.for_operator(nullptr); } - return &compute_context_cache.for_modifier(nullptr, *object_and_modifier->nmd); - } - case SNODE_GEOMETRY_TOOL: { - return &compute_context_cache.for_operator(nullptr); } + return nullptr; + } + if (snode.nodetree->type == NTREE_SHADER) { + return &compute_context_cache.for_shader(nullptr, snode.nodetree); } return nullptr; } @@ -497,7 +504,7 @@ static const ComputeContext *get_node_editor_root_compute_context( if (!snode.edittree) { return nullptr; } - if (snode.edittree->type != NTREE_GEOMETRY) { + if (!ELEM(snode.edittree->type, NTREE_GEOMETRY, NTREE_SHADER)) { return nullptr; } const ComputeContext *root_context = get_node_editor_root_compute_context(snode, diff --git a/source/blender/gpu/GPU_material.hh b/source/blender/gpu/GPU_material.hh index 8cb19d50e70..33bb5a21dc6 100644 --- a/source/blender/gpu/GPU_material.hh +++ b/source/blender/gpu/GPU_material.hh @@ -95,8 +95,18 @@ using GPUCodegenCallbackFn = void (*)(void *thunk, */ using GPUMaterialPassReplacementCallbackFn = GPUPass *(*)(void *thunk, GPUMaterial *mat); +struct GPUMaterialFromNodeTreeResult { + GPUMaterial *material = nullptr; + + struct Error { + const bNode *node; + std::string message; + }; + blender::Vector errors; +}; + /** WARNING: gpumaterials thread safety must be ensured by the caller. */ -GPUMaterial *GPU_material_from_nodetree( +GPUMaterialFromNodeTreeResult GPU_material_from_nodetree( Material *ma, bNodeTree *ntree, ListBase *gpumaterials, diff --git a/source/blender/gpu/intern/gpu_material.cc b/source/blender/gpu/intern/gpu_material.cc index 390c68c907d..20a33176e28 100644 --- a/source/blender/gpu/intern/gpu_material.cc +++ b/source/blender/gpu/intern/gpu_material.cc @@ -10,6 +10,7 @@ #include +#include "BKE_lib_id.hh" #include "MEM_guardedalloc.h" #include "DNA_material_types.h" @@ -25,8 +26,10 @@ #include "BKE_main.hh" #include "BKE_material.hh" #include "BKE_node.hh" +#include "BKE_node_runtime.hh" #include "NOD_shader.h" +#include "NOD_shader_nodes_inline.hh" #include "GPU_material.hh" #include "GPU_pass.hh" @@ -129,16 +132,17 @@ struct GPUMaterial { /* Public API */ -GPUMaterial *GPU_material_from_nodetree(Material *ma, - bNodeTree *ntree, - ListBase *gpumaterials, - const char *name, - eGPUMaterialEngine engine, - uint64_t shader_uuid, - bool deferred_compilation, - GPUCodegenCallbackFn callback, - void *thunk, - GPUMaterialPassReplacementCallbackFn pass_replacement_cb) +GPUMaterialFromNodeTreeResult GPU_material_from_nodetree( + Material *ma, + bNodeTree *ntree, + ListBase *gpumaterials, + const char *name, + eGPUMaterialEngine engine, + uint64_t shader_uuid, + bool deferred_compilation, + GPUCodegenCallbackFn callback, + void *thunk, + GPUMaterialPassReplacementCallbackFn pass_replacement_cb) { /* Search if this material is not already compiled. */ LISTBASE_FOREACH (LinkData *, link, gpumaterials) { @@ -147,17 +151,31 @@ GPUMaterial *GPU_material_from_nodetree(Material *ma, if (!deferred_compilation) { GPU_pass_ensure_its_ready(mat->pass); } - return mat; + return {mat}; } } + GPUMaterialFromNodeTreeResult result; + GPUMaterial *mat = MEM_new(__func__, engine); mat->source_material = ma; mat->uuid = shader_uuid; mat->name = name; + result.material = mat; /* Localize tree to create links for reroute and mute. */ - bNodeTree *localtree = blender::bke::node_tree_localize(ntree, nullptr); + bNodeTree *localtree = blender::bke::node_tree_add_tree( + nullptr, (blender::StringRef(ntree->id.name) + " Inlined").c_str(), ntree->idname); + blender::nodes::InlineShaderNodeTreeParams inline_params; + inline_params.allow_preserving_repeat_zones = false; + blender::nodes::inline_shader_node_tree(*ntree, *localtree, inline_params); + + for (blender::nodes::InlineShaderNodeTreeParams::ErrorMessage &error : + inline_params.r_error_messages) + { + result.errors.append({error.node, std::move(error.message)}); + } + ntreeGPUMaterialNodes(localtree, mat); gpu_material_ramp_texture_build(mat); @@ -190,9 +208,7 @@ GPUMaterial *GPU_material_from_nodetree(Material *ma, gpu_node_graph_free_nodes(&mat->graph); /* Only free after GPU_pass_shader_get where blender::gpu::UniformBuf read data from the local * tree. */ - blender::bke::node_tree_free_local_tree(localtree); - BLI_assert(!localtree->id.py_instance); /* Or call #BKE_libblock_free_data_py. */ - MEM_freeN(localtree); + BKE_id_free(nullptr, &localtree->id); /* Note that even if building the shader fails in some way, we want to keep * it to avoid trying to compile again and again, and simply do not use @@ -201,7 +217,7 @@ GPUMaterial *GPU_material_from_nodetree(Material *ma, link->data = mat; BLI_addtail(gpumaterials, link); - return mat; + return result; } GPUMaterial *GPU_material_from_callbacks(eGPUMaterialEngine engine, diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 4af19158798..7115cca859c 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -96,6 +96,7 @@ set(SRC intern/node_socket_declarations.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 @@ -141,6 +142,7 @@ set(SRC 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 diff --git a/source/blender/nodes/NOD_shader_nodes_inline.hh b/source/blender/nodes/NOD_shader_nodes_inline.hh new file mode 100644 index 00000000000..ff6a5319d22 --- /dev/null +++ b/source/blender/nodes/NOD_shader_nodes_inline.hh @@ -0,0 +1,31 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_vector.hh" + +struct bNodeTree; +struct bNode; + +namespace blender::nodes { + +struct InlineShaderNodeTreeParams { + bool allow_preserving_repeat_zones = false; + + 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 r_error_messages; +}; + +bool inline_shader_node_tree(const bNodeTree &src_tree, + bNodeTree &dst_tree, + InlineShaderNodeTreeParams ¶ms); + +} // namespace blender::nodes diff --git a/source/blender/nodes/geometry/include/NOD_geo_bundle.hh b/source/blender/nodes/geometry/include/NOD_geo_bundle.hh index 2d56288ff50..93545108d53 100644 --- a/source/blender/nodes/geometry/include/NOD_geo_bundle.hh +++ b/source/blender/nodes/geometry/include/NOD_geo_bundle.hh @@ -10,24 +10,40 @@ namespace blender::nodes { -inline bool socket_type_supported_in_bundle(const eNodeSocketDatatype socket_type) +inline bool socket_type_supported_in_bundle(const eNodeSocketDatatype socket_type, + const int ntree_type) { - return ELEM(socket_type, - SOCK_FLOAT, - SOCK_VECTOR, - SOCK_RGBA, - SOCK_BOOLEAN, - SOCK_ROTATION, - SOCK_MATRIX, - SOCK_INT, - SOCK_STRING, - SOCK_GEOMETRY, - SOCK_OBJECT, - SOCK_MATERIAL, - SOCK_IMAGE, - SOCK_COLLECTION, - SOCK_BUNDLE, - SOCK_CLOSURE); + switch (ntree_type) { + case NTREE_GEOMETRY: + return ELEM(socket_type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_BOOLEAN, + SOCK_ROTATION, + SOCK_MATRIX, + SOCK_INT, + SOCK_STRING, + SOCK_GEOMETRY, + SOCK_OBJECT, + SOCK_MATERIAL, + SOCK_IMAGE, + SOCK_COLLECTION, + SOCK_BUNDLE, + SOCK_CLOSURE); + case NTREE_SHADER: + return ELEM(socket_type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_SHADER, + SOCK_BUNDLE, + SOCK_CLOSURE, + SOCK_INT); + default: + BLI_assert_unreachable(); + return false; + } } struct CombineBundleItemsAccessor : public socket_items::SocketItemsAccessorDefaults { @@ -82,9 +98,9 @@ struct CombineBundleItemsAccessor : public socket_items::SocketItemsAccessorDefa return &item.name; } - static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/) + static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type) { - return socket_type_supported_in_bundle(socket_type); + return socket_type_supported_in_bundle(socket_type, ntree_type); } static void init_with_socket_type_and_name(bNode &node, @@ -158,9 +174,9 @@ struct SeparateBundleItemsAccessor : public socket_items::SocketItemsAccessorDef return &item.name; } - static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/) + static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type) { - return socket_type_supported_in_bundle(socket_type); + return socket_type_supported_in_bundle(socket_type, ntree_type); } static void init_with_socket_type_and_name(bNode &node, diff --git a/source/blender/nodes/geometry/include/NOD_geo_closure.hh b/source/blender/nodes/geometry/include/NOD_geo_closure.hh index aebf3a5f2ba..816befc0e42 100644 --- a/source/blender/nodes/geometry/include/NOD_geo_closure.hh +++ b/source/blender/nodes/geometry/include/NOD_geo_closure.hh @@ -10,24 +10,40 @@ namespace blender::nodes { -inline bool socket_type_supported_in_closure(const eNodeSocketDatatype socket_type) +inline bool socket_type_supported_in_closure(const eNodeSocketDatatype socket_type, + const int ntree_type) { - return ELEM(socket_type, - SOCK_FLOAT, - SOCK_VECTOR, - SOCK_RGBA, - SOCK_BOOLEAN, - SOCK_ROTATION, - SOCK_MATRIX, - SOCK_INT, - SOCK_STRING, - SOCK_GEOMETRY, - SOCK_OBJECT, - SOCK_MATERIAL, - SOCK_IMAGE, - SOCK_COLLECTION, - SOCK_BUNDLE, - SOCK_CLOSURE); + switch (ntree_type) { + case NTREE_GEOMETRY: + return ELEM(socket_type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_BOOLEAN, + SOCK_ROTATION, + SOCK_MATRIX, + SOCK_INT, + SOCK_STRING, + SOCK_GEOMETRY, + SOCK_OBJECT, + SOCK_MATERIAL, + SOCK_IMAGE, + SOCK_COLLECTION, + SOCK_BUNDLE, + SOCK_CLOSURE); + case NTREE_SHADER: + return ELEM(socket_type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_SHADER, + SOCK_BUNDLE, + SOCK_CLOSURE, + SOCK_INT); + default: + BLI_assert_unreachable(); + return false; + } } struct ClosureInputItemsAccessor : public socket_items::SocketItemsAccessorDefaults { @@ -82,9 +98,9 @@ struct ClosureInputItemsAccessor : public socket_items::SocketItemsAccessorDefau return &item.name; } - static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/) + static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type) { - return socket_type_supported_in_closure(socket_type); + return socket_type_supported_in_closure(socket_type, ntree_type); } static void init_with_socket_type_and_name(bNode &node, @@ -156,9 +172,9 @@ struct ClosureOutputItemsAccessor : public socket_items::SocketItemsAccessorDefa return &item.name; } - static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/) + static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type) { - return socket_type_supported_in_closure(socket_type); + return socket_type_supported_in_closure(socket_type, ntree_type); } static void init_with_socket_type_and_name(bNode &node, @@ -230,9 +246,9 @@ struct EvaluateClosureInputItemsAccessor : public socket_items::SocketItemsAcces return &item.name; } - static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/) + static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type) { - return socket_type_supported_in_closure(socket_type); + return socket_type_supported_in_closure(socket_type, ntree_type); } static void init_with_socket_type_and_name(bNode &node, @@ -306,9 +322,9 @@ struct EvaluateClosureOutputItemsAccessor : public socket_items::SocketItemsAcce return &item.name; } - static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/) + static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type) { - return socket_type_supported_in_closure(socket_type); + return socket_type_supported_in_closure(socket_type, ntree_type); } static void init_with_socket_type_and_name(bNode &node, diff --git a/source/blender/nodes/geometry/include/NOD_geo_repeat.hh b/source/blender/nodes/geometry/include/NOD_geo_repeat.hh index 89502de1920..9121821fbb5 100644 --- a/source/blender/nodes/geometry/include/NOD_geo_repeat.hh +++ b/source/blender/nodes/geometry/include/NOD_geo_repeat.hh @@ -64,24 +64,39 @@ struct RepeatItemsAccessor : public socket_items::SocketItemsAccessorDefaults { return &item.name; } - static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/) + static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int ntree_type) { - return ELEM(socket_type, - SOCK_FLOAT, - SOCK_VECTOR, - SOCK_RGBA, - SOCK_BOOLEAN, - SOCK_ROTATION, - SOCK_MATRIX, - SOCK_INT, - SOCK_STRING, - SOCK_GEOMETRY, - SOCK_OBJECT, - SOCK_MATERIAL, - SOCK_IMAGE, - SOCK_COLLECTION, - SOCK_BUNDLE, - SOCK_CLOSURE); + switch (ntree_type) { + case NTREE_GEOMETRY: + return ELEM(socket_type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_BOOLEAN, + SOCK_ROTATION, + SOCK_MATRIX, + SOCK_INT, + SOCK_STRING, + SOCK_GEOMETRY, + SOCK_OBJECT, + SOCK_MATERIAL, + SOCK_IMAGE, + SOCK_COLLECTION, + SOCK_BUNDLE, + SOCK_CLOSURE); + case NTREE_SHADER: + return ELEM(socket_type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_SHADER, + SOCK_BUNDLE, + SOCK_CLOSURE, + SOCK_INT); + default: + BLI_assert_unreachable(); + return false; + } } static void init_with_socket_type_and_name(bNode &node, diff --git a/source/blender/nodes/geometry/nodes/node_geo_closure.cc b/source/blender/nodes/geometry/nodes/node_geo_closure.cc index f162f76d8a7..047ec81bd29 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_closure.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_closure.cc @@ -16,6 +16,7 @@ #include "NOD_sync_sockets.hh" #include "BLO_read_write.hh" +#include "shader/node_shader_util.hh" namespace blender::nodes::node_geo_closure_cc { @@ -116,7 +117,7 @@ static bool node_insert_link(bke::NodeInsertLinkParams ¶ms) static void node_register() { static blender::bke::bNodeType ntype; - geo_node_type_base(&ntype, "NodeClosureInput", NODE_CLOSURE_INPUT); + common_node_type_base(&ntype, "NodeClosureInput", NODE_CLOSURE_INPUT); ntype.ui_name = "Closure Input"; ntype.nclass = NODE_CLASS_INTERFACE; ntype.declare = node_declare; @@ -244,7 +245,7 @@ static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader & static void node_register() { static blender::bke::bNodeType ntype; - geo_node_type_base(&ntype, "NodeClosureOutput", NODE_CLOSURE_OUTPUT); + common_node_type_base(&ntype, "NodeClosureOutput", NODE_CLOSURE_OUTPUT); ntype.ui_name = "Closure Output"; ntype.nclass = NODE_CLASS_INTERFACE; ntype.declare = node_declare; diff --git a/source/blender/nodes/geometry/nodes/node_geo_combine_bundle.cc b/source/blender/nodes/geometry/nodes/node_geo_combine_bundle.cc index 944567c84cb..f753399f7ce 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_combine_bundle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_combine_bundle.cc @@ -18,6 +18,7 @@ #include "NOD_geometry_nodes_bundle.hh" #include "UI_interface_layout.hh" +#include "shader/node_shader_util.hh" namespace blender::nodes::node_geo_combine_bundle_cc { @@ -175,7 +176,7 @@ static void node_register() { static blender::bke::bNodeType ntype; - geo_node_type_base(&ntype, "NodeCombineBundle", NODE_COMBINE_BUNDLE); + common_node_type_base(&ntype, "NodeCombineBundle", NODE_COMBINE_BUNDLE); ntype.ui_name = "Combine Bundle"; ntype.ui_description = "Combine multiple socket values into one."; ntype.nclass = NODE_CLASS_CONVERTER; diff --git a/source/blender/nodes/geometry/nodes/node_geo_evaluate_closure.cc b/source/blender/nodes/geometry/nodes/node_geo_evaluate_closure.cc index 8f42e361b86..e6f553a512c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_evaluate_closure.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_evaluate_closure.cc @@ -17,6 +17,7 @@ #include "BLO_read_write.hh" #include "node_geometry_util.hh" +#include "shader/node_shader_util.hh" namespace blender::nodes::node_geo_evaluate_closure_cc { @@ -198,7 +199,7 @@ static void node_register() { static blender::bke::bNodeType ntype; - geo_node_type_base(&ntype, "NodeEvaluateClosure", NODE_EVALUATE_CLOSURE); + common_node_type_base(&ntype, "NodeEvaluateClosure", NODE_EVALUATE_CLOSURE); ntype.ui_name = "Evaluate Closure"; ntype.nclass = NODE_CLASS_CONVERTER; ntype.declare = node_declare; diff --git a/source/blender/nodes/geometry/nodes/node_geo_repeat.cc b/source/blender/nodes/geometry/nodes/node_geo_repeat.cc index 369fd9bacb8..0e931d1ef5b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_repeat.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_repeat.cc @@ -24,6 +24,7 @@ #include "UI_interface_layout.hh" #include "node_geometry_util.hh" +#include "shader/node_shader_util.hh" namespace blender::nodes::node_geo_repeat_cc { @@ -134,7 +135,7 @@ static bool node_insert_link(bke::NodeInsertLinkParams ¶ms) static void node_register() { static blender::bke::bNodeType ntype; - geo_node_type_base(&ntype, "GeometryNodeRepeatInput", GEO_NODE_REPEAT_INPUT); + common_node_type_base(&ntype, "GeometryNodeRepeatInput", GEO_NODE_REPEAT_INPUT); ntype.ui_name = "Repeat Input"; ntype.enum_name_legacy = "REPEAT_INPUT"; ntype.nclass = NODE_CLASS_INTERFACE; @@ -188,17 +189,19 @@ static void node_declare(NodeDeclarationBuilder &b) .align_with_previous(); } -static void node_init(bNodeTree * /*tree*/, bNode *node) +static void node_init(bNodeTree *tree, bNode *node) { NodeGeometryRepeatOutput *data = MEM_callocN(__func__); data->next_identifier = 0; - data->items = MEM_calloc_arrayN(1, __func__); - data->items[0].name = BLI_strdup(DATA_("Geometry")); - data->items[0].socket_type = SOCK_GEOMETRY; - data->items[0].identifier = data->next_identifier++; - data->items_num = 1; + if (tree->type == NTREE_GEOMETRY) { + data->items = MEM_calloc_arrayN(1, __func__); + data->items[0].name = BLI_strdup(DATA_("Geometry")); + data->items[0].socket_type = SOCK_GEOMETRY; + data->items[0].identifier = data->next_identifier++; + data->items_num = 1; + } node->storage = data; } @@ -281,7 +284,7 @@ static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader & static void node_register() { static blender::bke::bNodeType ntype; - geo_node_type_base(&ntype, "GeometryNodeRepeatOutput", GEO_NODE_REPEAT_OUTPUT); + common_node_type_base(&ntype, "GeometryNodeRepeatOutput", GEO_NODE_REPEAT_OUTPUT); ntype.ui_name = "Repeat Output"; ntype.enum_name_legacy = "REPEAT_OUTPUT"; ntype.nclass = NODE_CLASS_INTERFACE; diff --git a/source/blender/nodes/geometry/nodes/node_geo_separate_bundle.cc b/source/blender/nodes/geometry/nodes/node_geo_separate_bundle.cc index 4229cd4b4e6..9f396008b14 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_separate_bundle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_separate_bundle.cc @@ -19,6 +19,7 @@ #include "BLO_read_write.hh" #include "UI_interface_layout.hh" +#include "shader/node_shader_util.hh" #include @@ -221,7 +222,7 @@ static void node_register() { static blender::bke::bNodeType ntype; - geo_node_type_base(&ntype, "NodeSeparateBundle", NODE_SEPARATE_BUNDLE); + common_node_type_base(&ntype, "NodeSeparateBundle", NODE_SEPARATE_BUNDLE); ntype.ui_name = "Separate Bundle"; ntype.ui_description = "Split a bundle into multiple sockets."; ntype.nclass = NODE_CLASS_CONVERTER; diff --git a/source/blender/nodes/intern/shader_nodes_inline.cc b/source/blender/nodes/intern/shader_nodes_inline.cc new file mode 100644 index 00000000000..3ce9f5f3b43 --- /dev/null +++ b/source/blender/nodes/intern/shader_nodes_inline.cc @@ -0,0 +1,1217 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include +#include + +#include "BKE_compute_context_cache.hh" +#include "BKE_lib_id.hh" +#include "BKE_node_tree_zones.hh" +#include "BKE_type_conversions.hh" + +#include "BLI_listbase.h" +#include "BLI_math_vector.h" +#include "BLI_stack.hh" + +#include "NOD_multi_function.hh" +#include "NOD_node_declaration.hh" +#include "NOD_node_in_compute_context.hh" +#include "NOD_shader_nodes_inline.hh" + +namespace blender::nodes { +namespace { + +struct BundleSocketValue; +using BundleSocketValuePtr = std::shared_ptr; + +struct FallbackValue {}; + +struct NodeAndSocket { + bNode *node = nullptr; + bNodeSocket *socket = nullptr; +}; + +struct PrimitiveSocketValue { + std::variant value; + + const void *buffer() const + { + return std::visit([](auto &&value) -> const void * { return &value; }, value); + } + + void *buffer() + { + return const_cast(const_cast(this)->buffer()); + } + + static PrimitiveSocketValue from_value(const GPointer value) + { + const CPPType &type = *value.type(); + if (type.is()) { + return {*static_cast(value.get())}; + } + if (type.is()) { + return {*static_cast(value.get())}; + } + if (type.is()) { + return {*static_cast(value.get())}; + } + if (type.is()) { + return {*static_cast(value.get())}; + } + if (type.is()) { + return {*static_cast(value.get())}; + } + BLI_assert_unreachable(); + return {}; + } +}; + +/** References an output socket in the generated node tree. */ +struct LinkedSocketValue { + bNode *node = nullptr; + bNodeSocket *socket = nullptr; +}; + +/** References an input socket in the source node tree. */ +struct InputSocketValue { + const bNodeSocket *socket = nullptr; +}; + +struct ClosureZoneValue { + const bke::bNodeTreeZone *zone = nullptr; + const ComputeContext *closure_creation_context = nullptr; +}; + +struct SocketValue { + /** + * The value of an arbitrary socket value can have one of many different types. At a high level + * it can either have a specific constant-folded value, or it references a socket that can't be + * constant-folded. + */ + std::variant + value; + + /** Try to get the value as a primitive value. */ + std::optional to_primitive(const bke::bNodeSocketType &type) const + { + if (const auto *primitive_value = std::get_if(&this->value)) { + return *primitive_value; + } + if (const auto *input_socket_value = std::get_if(&this->value)) { + const bNodeSocket &socket = *input_socket_value->socket; + BLI_assert(socket.type == type.type); + if (!socket.runtime->declaration) { + return std::nullopt; + } + if (socket.runtime->declaration->default_input_type != NODE_DEFAULT_INPUT_VALUE) { + return std::nullopt; + } + switch (socket.typeinfo->type) { + case SOCK_FLOAT: + return PrimitiveSocketValue{socket.default_value_typed()->value}; + case SOCK_INT: + return PrimitiveSocketValue{socket.default_value_typed()->value}; + case SOCK_BOOLEAN: + return PrimitiveSocketValue{ + socket.default_value_typed()->value}; + case SOCK_VECTOR: + return PrimitiveSocketValue{ + float3(socket.default_value_typed()->value)}; + case SOCK_RGBA: + return PrimitiveSocketValue{ + ColorGeometry4f(socket.default_value_typed()->value)}; + default: + return std::nullopt; + } + } + if (std::get_if(&this->value)) { + switch (type.type) { + case SOCK_INT: + case SOCK_BOOLEAN: + case SOCK_VECTOR: + case SOCK_RGBA: + case SOCK_FLOAT: + return PrimitiveSocketValue::from_value( + {type.base_cpp_type, type.base_cpp_type->default_value()}); + default: + return std::nullopt; + } + } + return std::nullopt; + } +}; + +struct BundleSocketValue { + struct Item { + std::string key; + SocketValue value; + const bke::bNodeSocketType *socket_type = nullptr; + }; + + Vector items; +}; + +struct PreservedZone { + bNode *input_node = nullptr; + bNode *output_node = nullptr; +}; + +class ShaderNodesInliner { + private: + /** Cache for intermediate values used during the inline process. */ + ResourceScope scope_; + /** The original tree the has to be inlined. */ + const bNodeTree &src_tree_; + /** The tree where the inlined nodes will be added. */ + bNodeTree &dst_tree_; + /** Parameters passed in by the caller. */ + InlineShaderNodeTreeParams ¶ms_; + /** Simplifies building the all the compute contexts for nodes in zones and groups. */ + bke::ComputeContextCache compute_context_cache_; + /** Stores the computed value for each socket. The final value for each socket may be constant */ + Map value_by_socket_; + /** + * Remember zone nodes that have been copied to the destination so that they can be connected + * again in the end. + */ + Map copied_zone_by_zone_output_node_; + /** Sockets that still have to be evaluated. */ + Stack scheduled_sockets_stack_; + /** Knows how to compute between different data types. */ + const bke::DataTypeConversions &data_type_conversions_; + /** This is used to generate unique names and ids. */ + int dst_node_counter_ = 0; + + public: + ShaderNodesInliner(const bNodeTree &src_tree, + bNodeTree &dst_tree, + InlineShaderNodeTreeParams ¶ms) + : src_tree_(src_tree), + dst_tree_(dst_tree), + params_(params), + data_type_conversions_(bke::get_implicit_type_conversions()) + { + if (dst_tree.id.tag & ID_TAG_NO_MAIN) { + BLI_assert(src_tree.id.tag & ID_TAG_NO_MAIN); + } + } + + bool do_inline() + { + src_tree_.ensure_topology_cache(); + if (src_tree_.has_available_link_cycle()) { + return false; + } + + const Vector final_output_sockets = this->find_final_output_sockets(); + + /* Evaluation starts at the final output sockets which will request the evaluation of whether + * sockets are linked to them. */ + for (const SocketInContext &socket : final_output_sockets) { + this->schedule_socket(socket); + } + + /* Evaluate until all scheduled sockets have a value. While evaluating a single socket, it may + * either end up having a value, or request more other sockets that need to be evaluated first. + * + * This uses an explicit stack instead of recursion to avoid stack overflows which can easily + * happen when there are long chains of nodes (or e.g. repeat zones with many iterations). */ + while (!scheduled_sockets_stack_.is_empty()) { + const SocketInContext socket = scheduled_sockets_stack_.peek(); + const int old_stack_size = scheduled_sockets_stack_.size(); + + this->handle_socket(socket); + + if (scheduled_sockets_stack_.size() == old_stack_size) { + /* No additional dependencies were pushed, so this socket is fully handled and can be + * popped from the stack. */ + BLI_assert(socket == scheduled_sockets_stack_.peek()); + scheduled_sockets_stack_.pop(); + } + } + + /* Create actual output nodes. */ + Map final_output_nodes; + for (const SocketInContext &socket : final_output_sockets) { + const NodeInContext src_node = socket.owner_node(); + bNode *copied_node = final_output_nodes.lookup_or_add_cb(src_node, [&]() { + Map socket_map; + bNode *copied_node = bke::node_copy_with_mapping(&dst_tree_, + *src_node.node, + this->node_copy_flag(), + std::nullopt, + this->get_next_node_identifier(), + socket_map); + copied_node->parent = nullptr; + return copied_node; + }); + bNodeSocket *copied_socket = static_cast( + BLI_findlink(&copied_node->inputs, socket.socket->index())); + this->set_socket_value(*copied_node, *copied_socket, value_by_socket_.lookup(socket)); + } + + this->restore_zones_in_output_tree(); + this->position_nodes_in_output_tree(); + return true; + } + + Vector find_final_output_sockets() const + { + Vector output_sockets; + + auto add_output_type = [&](const char *output_type) { + for (const bNode *node : src_tree_.nodes_by_type(output_type)) { + for (const bNodeSocket *socket : node->input_sockets()) { + output_sockets.append({nullptr, socket}); + } + } + }; + + /* owner_id can be null for DefaultSurfaceNodeTree. */ + ID_Type tree_type = src_tree_.owner_id ? GS(src_tree_.owner_id->name) : ID_MA; + + switch (tree_type) { + case ID_MA: + add_output_type("ShaderNodeOutputMaterial"); + add_output_type("ShaderNodeOutputAOV"); + add_output_type("ShaderNodeOutputLight"); + break; + case ID_WO: + add_output_type("ShaderNodeOutputWorld"); + add_output_type("ShaderNodeOutputAOV"); + break; + case ID_LA: + add_output_type("ShaderNodeOutputLight"); + break; + default: + BLI_assert_unreachable(); + } + + return output_sockets; + } + + void handle_socket(const SocketInContext &socket) + { + if (!socket->is_available()) { + return; + } + if (value_by_socket_.contains(socket)) { + /* The socket already has a value, so there is nothing to do. */ + return; + } + if (socket->is_input()) { + this->handle_input_socket(socket); + } + else { + this->handle_output_socket(socket); + } + } + + void handle_input_socket(const SocketInContext &socket) + { + /* Multi-inputs are not supported in shader nodes currently. */ + BLI_assert(!socket->is_multi_input()); + + const bNodeLink *used_link = nullptr; + for (const bNodeLink *link : socket->directly_linked_links()) { + if (!link->is_used()) { + continue; + } + used_link = link; + } + if (!used_link) { + /* If there is no link on the input, use the value of the socket directly. */ + this->store_socket_value(socket, {InputSocketValue{socket.socket}}); + return; + } + + /* TODO: Find the correct context for the origin socket. It should work fine without but + * results in a larger generated node tree. */ + const SocketInContext origin_socket = {socket.context, used_link->fromsock}; + if (const auto *value = value_by_socket_.lookup_ptr(origin_socket)) { + /* If the socket linked to the input has a value already, copy that value to the current + * socket, potentially with an implicit conversion. */ + this->store_socket_value(socket, + this->handle_implicit_conversion(*value, + *used_link->fromsock->typeinfo, + *used_link->tosock->typeinfo)); + return; + } + /* If the origin socket does not have a value yet, only schedule it for evaluation for now.*/ + this->schedule_socket(origin_socket); + } + + void handle_output_socket(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + if (node->is_reroute()) { + this->handle_output_socket__reroute(socket); + return; + } + if (node->is_muted()) { + this->handle_output_socket__muted(socket); + return; + } + if (node->is_group()) { + this->handle_output_socket__group(socket); + return; + } + if (node->is_group_input()) { + this->handle_output_socket__group_input(socket); + return; + } + if (node->is_type("GeometryNodeRepeatOutput")) { + if (!this->should_preserve_repeat_zone_node(*node)) { + this->handle_output_socket__repeat_output(socket); + return; + } + } + if (node->is_type("GeometryNodeRepeatInput")) { + if (!this->should_preserve_repeat_zone_node(*node)) { + this->handle_output_socket__repeat_input(socket); + return; + } + } + if (node->is_type("NodeClosureOutput")) { + this->handle_output_socket__closure_output(socket); + return; + } + if (node->is_type("NodeClosureInput")) { + this->handle_output_socket__closure_input(socket); + return; + } + if (node->is_type("NodeEvaluateClosure")) { + this->handle_output_socket__evaluate_closure(socket); + return; + } + if (node->is_type("NodeCombineBundle")) { + this->handle_output_socket__combine_bundle(socket); + return; + } + if (node->is_type("NodeSeparateBundle")) { + this->handle_output_socket__separate_bundle(socket); + return; + } + this->handle_output_socket__eval(socket); + } + + void handle_output_socket__reroute(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + const SocketInContext input_socket = {socket.context, &node->input_socket(0)}; + this->forward_value_or_schedule(socket, input_socket); + } + + void handle_output_socket__muted(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + for (const bNodeLink &internal_link : node->internal_links()) { + if (internal_link.tosock == socket.socket) { + const SocketInContext src_socket = {socket.context, internal_link.fromsock}; + if (const SocketValue *value = value_by_socket_.lookup_ptr(src_socket)) { + /* Pass the value of the internally linked input socket, with an implicit conversion if + * necessary. */ + this->store_socket_value( + socket, + this->handle_implicit_conversion( + *value, *internal_link.fromsock->typeinfo, *internal_link.tosock->typeinfo)); + return; + } + this->schedule_socket(src_socket); + return; + } + } + /* The output socket does not have a corresponding input, so use its fallback value. */ + this->store_socket_value_fallback(socket); + } + + void handle_output_socket__group(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + const bNodeTree *group = reinterpret_cast(node->id); + if (!group || ID_MISSING(&group->id)) { + this->store_socket_value_fallback(socket); + return; + } + group->ensure_interface_cache(); + group->ensure_topology_cache(); + const bNode *group_output_node = group->group_output_node(); + if (!group_output_node) { + this->store_socket_value_fallback(socket); + return; + } + /* Get the value of an output of a group node by evaluating the corresponding output of the + * node group. Since this socket is in a different tree, the compute context is different. */ + const ComputeContext &group_compute_context = compute_context_cache_.for_group_node( + socket.context, node->identifier, &node->owner_tree()); + const SocketInContext group_output_socket_ctx = { + &group_compute_context, &group_output_node->input_socket(socket->index())}; + this->forward_value_or_schedule(socket, group_output_socket_ctx); + } + + void handle_output_socket__group_input(const SocketInContext &socket) + { + if (const auto *group_node_compute_context = + dynamic_cast(socket.context)) + { + /* Get the value of a group input from the corresponding input socket of the parent group + * node. */ + const ComputeContext *parent_compute_context = group_node_compute_context->parent(); + const bNode *group_node = group_node_compute_context->node(); + BLI_assert(group_node); + const bNodeSocket &group_node_input = group_node->input_socket(socket->index()); + const SocketInContext group_input_socket_ctx = {parent_compute_context, &group_node_input}; + this->forward_value_or_schedule(socket, group_input_socket_ctx); + return; + } + this->store_socket_value_fallback(socket); + } + + bool should_preserve_repeat_zone_node(const bNode &repeat_zone_node) const + { + BLI_assert(repeat_zone_node.is_type("GeometryNodeRepeatOutput") || + repeat_zone_node.is_type("GeometryNodeRepeatInput")); + if (!params_.allow_preserving_repeat_zones) { + return false; + } + const bNodeTree &tree = repeat_zone_node.owner_tree(); + const bke::bNodeTreeZones *zones = tree.zones(); + if (!zones) { + return false; + } + const bke::bNodeTreeZone *zone = zones->get_zone_by_node(repeat_zone_node.identifier); + if (!zone) { + return false; + } + const bNode *repeat_zone_output_node = zone->output_node(); + if (!repeat_zone_output_node) { + return false; + } + const auto &storage = *static_cast( + repeat_zone_output_node->storage); + for (const int i : IndexRange(storage.items_num)) { + const NodeRepeatItem &item = storage.items[i]; + if (!ELEM(item.socket_type, SOCK_INT, SOCK_FLOAT, SOCK_BOOLEAN, SOCK_RGBA, SOCK_VECTOR)) { + /* Repeat zones with more special types have to be inlined. */ + return false; + } + } + return true; + } + + void handle_output_socket__repeat_output(const SocketInContext &socket) + { + const bNode &repeat_output_node = socket->owner_node(); + const bNodeTree &tree = socket->owner_tree(); + + const bke::bNodeTreeZones *zones = tree.zones(); + if (!zones) { + this->store_socket_value_fallback(socket); + return; + } + const bke::bNodeTreeZone *zone = zones->get_zone_by_node(repeat_output_node.identifier); + if (!zone) { + this->store_socket_value_fallback(socket); + return; + } + const NodeInContext repeat_input_node = {socket.context, zone->input_node()}; + const SocketInContext iterations_input = repeat_input_node.input_socket(0); + const SocketValue *iterations_socket_value = value_by_socket_.lookup_ptr(iterations_input); + if (!iterations_socket_value) { + /* The number of iterations is not known yet, so only schedule that socket for now. */ + this->schedule_socket(iterations_input); + return; + } + const std::optional iterations_value_opt = + iterations_socket_value->to_primitive(*iterations_input->typeinfo); + if (!iterations_value_opt) { + /* Number of iterations is not a primitive value. */ + this->store_socket_value_fallback(socket); + params_.r_error_messages.append( + {repeat_input_node.node, TIP_("Iterations input has to be a constant value")}); + return; + } + const int iterations = std::get(iterations_value_opt->value); + if (iterations <= 0) { + /* If the number of iterations is zero, the values are copied directly from the repeat input + * node. */ + const SocketInContext origin_socket = repeat_input_node.input_socket(1 + socket->index()); + this->forward_value_or_schedule(socket, origin_socket); + return; + } + /* Otherwise, the value is copied from the output of the last iteration. */ + const ComputeContext &last_iteration_context = compute_context_cache_.for_repeat_zone( + socket.context, repeat_output_node, iterations - 1); + const SocketInContext origin_socket = {&last_iteration_context, + &repeat_output_node.input_socket(socket->index())}; + this->forward_value_or_schedule(socket, origin_socket); + } + + void handle_output_socket__repeat_input(const SocketInContext &socket) + { + const bNode &repeat_input_node = socket->owner_node(); + const auto *repeat_zone_context = dynamic_cast( + socket.context); + if (!repeat_zone_context) { + this->store_socket_value_fallback(socket); + return; + } + /* The index of the current iteration comes from the context. */ + const int iteration = repeat_zone_context->iteration(); + + if (socket->index() == 0) { + /* The first output is the current iteration index. */ + this->store_socket_value(socket, {PrimitiveSocketValue{iteration}}); + return; + } + + if (iteration == 0) { + /* In the first iteration, the values are copied from the corresponding input socket. */ + const SocketInContext origin_socket = {repeat_zone_context->parent(), + &repeat_input_node.input_socket(socket->index())}; + this->forward_value_or_schedule(socket, origin_socket); + return; + } + /* For later iterations, the values are copied from the corresponding output of the previous + * iteration. */ + const bNode &repeat_output_node = *repeat_input_node.owner_tree().node_by_id( + repeat_zone_context->output_node_id()); + const int previous_iteration = iteration - 1; + const ComputeContext &previous_iteration_context = compute_context_cache_.for_repeat_zone( + repeat_zone_context->parent(), repeat_output_node, previous_iteration); + const SocketInContext origin_socket = {&previous_iteration_context, + &repeat_output_node.input_socket(socket->index() - 1)}; + this->forward_value_or_schedule(socket, origin_socket); + } + + void handle_output_socket__closure_output(const SocketInContext &socket) + { + const bNode &node = socket->owner_node(); + const bke::bNodeTreeZones *zones = node.owner_tree().zones(); + if (!zones) { + this->store_socket_value_fallback(socket); + return; + } + const bke::bNodeTreeZone *zone = zones->get_zone_by_node(node.identifier); + if (!zone) { + this->store_socket_value_fallback(socket); + return; + } + /* Just store a reference to the closure. */ + this->store_socket_value(socket, {ClosureZoneValue{zone, socket.context}}); + } + + void handle_output_socket__evaluate_closure(const SocketInContext &socket) + { + const NodeInContext evaluate_closure_node = socket.owner_node(); + const SocketInContext closure_input_socket = evaluate_closure_node.input_socket(0); + const SocketValue *closure_input_value = value_by_socket_.lookup_ptr(closure_input_socket); + if (!closure_input_value) { + /* The closure to evaluate is not known yet, so schedule the closure input before it can be + * evaluated. */ + this->schedule_socket(closure_input_socket); + return; + } + const ClosureZoneValue *closure_zone_value = std::get_if( + &closure_input_value->value); + if (!closure_zone_value) { + this->store_socket_value_fallback(socket); + return; + } + const auto *evaluate_closure_storage = static_cast( + evaluate_closure_node->storage); + const bNode &closure_output_node = *closure_zone_value->zone->output_node(); + const auto &closure_storage = *static_cast( + closure_output_node.storage); + const StringRef key = evaluate_closure_storage->output_items.items[socket->index()].name; + + const ClosureSourceLocation closure_source_location{ + &closure_output_node.owner_tree(), + closure_output_node.identifier, + closure_zone_value->closure_creation_context ? + closure_zone_value->closure_creation_context->hash() : + ComputeContextHash{}}; + const bke::EvaluateClosureComputeContext &closure_eval_context = + compute_context_cache_.for_evaluate_closure(socket.context, + evaluate_closure_node->identifier, + &socket->owner_tree(), + closure_source_location); + + if (closure_eval_context.is_recursive()) { + this->store_socket_value_fallback(socket); + params_.r_error_messages.append( + {&*evaluate_closure_node, TIP_("Recursive closures are not supported")}); + return; + } + + for (const int i : IndexRange(closure_storage.output_items.items_num)) { + const NodeClosureOutputItem &item = closure_storage.output_items.items[i]; + if (key != item.name) { + continue; + } + /* Get the value of the output by evaluating the corresponding output in the closure zone. */ + const SocketInContext origin_socket = {&closure_eval_context, + &closure_output_node.input_socket(i)}; + this->forward_value_or_schedule(socket, origin_socket); + return; + } + this->store_socket_value_fallback(socket); + } + + void handle_output_socket__closure_input(const SocketInContext &socket) + { + const bNode &closure_input_node = socket->owner_node(); + const auto *closure_eval_context = dynamic_cast( + socket.context); + if (!closure_eval_context) { + this->store_socket_value_fallback(socket); + return; + } + const bNode &closure_output_node = *closure_input_node.owner_tree().node_by_id( + closure_eval_context->closure_source_location()->closure_output_node_id); + const NodeInContext closure_eval_node = {closure_eval_context->parent(), + closure_eval_context->node()}; + + const auto &closure_storage = *static_cast( + closure_output_node.storage); + const auto &eval_closure_storage = *static_cast( + closure_eval_node->storage); + + const StringRef key = closure_storage.input_items.items[socket->index()].name; + for (const int i : IndexRange(eval_closure_storage.input_items.items_num)) { + const NodeEvaluateClosureInputItem &item = eval_closure_storage.input_items.items[i]; + if (key != item.name) { + continue; + } + /* The input of a closure zone gets its value from the corresponding input of the Evaluate + * Closure node that evaluates it. */ + const SocketInContext origin_socket = closure_eval_node.input_socket(i + 1); + this->forward_value_or_schedule(socket, origin_socket); + return; + } + this->store_socket_value_fallback(socket); + } + + void handle_output_socket__combine_bundle(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + const auto &storage = *static_cast(node->storage); + + bool all_inputs_available = true; + for (const bNodeSocket *input_socket : node->input_sockets()) { + const SocketInContext input_socket_ctx = {socket.context, input_socket}; + if (!value_by_socket_.lookup_ptr(input_socket_ctx)) { + this->schedule_socket(input_socket_ctx); + all_inputs_available = false; + } + } + if (!all_inputs_available) { + /* Can't create the bundle yet. Wait until all inputs are available. */ + return; + } + /* Build the actual bundle socket value from the input values. */ + auto bundle_value = std::make_shared(); + for (const int i : IndexRange(storage.items_num)) { + const SocketInContext input_socket = node.input_socket(i); + const NodeCombineBundleItem &item = storage.items[i]; + const StringRef key = item.name; + const auto &socket_value = value_by_socket_.lookup(input_socket); + bundle_value->items.append({key, socket_value, input_socket->typeinfo}); + } + this->store_socket_value(socket, {bundle_value}); + } + + void handle_output_socket__separate_bundle(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + const auto &storage = *static_cast(node->storage); + + const SocketInContext input_socket = node.input_socket(0); + const SocketValue *socket_value = value_by_socket_.lookup_ptr(input_socket); + if (!socket_value) { + /* The input bundle is not known yet, so schedule it for now. */ + this->schedule_socket(input_socket); + return; + } + const auto *bundle_value_ptr = std::get_if(&socket_value->value); + if (!bundle_value_ptr) { + /* The bundle is empty. Use the fallback value. */ + this->store_socket_value_fallback(socket); + return; + } + const BundleSocketValue &bundle_value = **bundle_value_ptr; + + const StringRef key = storage.items[socket->index()].name; + for (const BundleSocketValue::Item &item : bundle_value.items) { + if (key != item.key) { + continue; + } + /* Extract the value from the bundle.*/ + const SocketValue converted_value = this->handle_implicit_conversion( + item.value, *item.socket_type, *socket->typeinfo); + this->store_socket_value(socket, converted_value); + return; + } + /* The bundle does not contain the requested key, so use the fallback value. */ + this->store_socket_value_fallback(socket); + } + + /** + * Evaluate a node to compute the value of the given output socket. This may also compute all the + * other outputs of the node. + */ + void handle_output_socket__eval(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + bool has_missing_inputs = false; + bool all_inputs_primitive = true; + for (const bNodeSocket *input_socket : node->input_sockets()) { + if (!input_socket->is_available()) { + continue; + } + const SocketInContext input_socket_ctx = {socket.context, input_socket}; + const SocketValue *value = value_by_socket_.lookup_ptr(input_socket_ctx); + if (!value) { + this->schedule_socket(input_socket_ctx); + has_missing_inputs = true; + continue; + } + if (!value->to_primitive(*input_socket->typeinfo)) { + all_inputs_primitive = false; + } + } + if (has_missing_inputs) { + /* The node can only be evaluated if all inputs values are known. */ + return; + } + const bke::bNodeType &node_type = *node->typeinfo; + if (node_type.build_multi_function && all_inputs_primitive) { + /* Do constant folding. */ + this->handle_output_socket__eval_multi_function(node); + return; + } + /* The node can't be constant-folded. So copy it to the destination tree instead. */ + this->handle_output_socket__eval_copy_node(node); + } + + void handle_output_socket__eval_multi_function(const NodeInContext &node) + { + NodeMultiFunctionBuilder builder{*node.node, node->owner_tree()}; + node->typeinfo->build_multi_function(builder); + const mf::MultiFunction &fn = builder.function(); + mf::ContextBuilder context; + IndexMask mask(1); + mf::ParamsBuilder params{fn, &mask}; + + /* Prepare inputs to the multi-function evaluation. */ + for (const bNodeSocket *input_socket : node->input_sockets()) { + if (!input_socket->is_available()) { + continue; + } + const SocketInContext input_socket_ctx = {node.context, input_socket}; + const PrimitiveSocketValue value = + *value_by_socket_.lookup(input_socket_ctx).to_primitive(*input_socket->typeinfo); + params.add_readonly_single_input( + GVArray::from_single(*input_socket->typeinfo->base_cpp_type, 1, value.buffer())); + } + + /* Prepare output buffers. */ + Vector output_values; + for (const bNodeSocket *output_socket : node->output_sockets()) { + if (!output_socket->is_available()) { + continue; + } + void *value = scope_.allocate_owned(*output_socket->typeinfo->base_cpp_type); + output_values.append(value); + params.add_uninitialized_single_output( + GMutableSpan(output_socket->typeinfo->base_cpp_type, value, 1)); + } + + fn.call(mask, params, context); + + /* Store constant-folded values for the output sockets. */ + int current_output_i = 0; + for (const bNodeSocket *output_socket : node->output_sockets()) { + if (!output_socket->is_available()) { + continue; + } + const void *value = output_values[current_output_i++]; + this->store_socket_value( + {node.context, output_socket}, + {PrimitiveSocketValue::from_value({output_socket->typeinfo->base_cpp_type, value})}); + } + } + + void handle_output_socket__eval_copy_node(const NodeInContext &node) + { + Map socket_map; + /* We generate our own identifier and name here to get unique values without having to scan all + * already existing nodes. */ + const int identifier = this->get_next_node_identifier(); + const std::string unique_name = fmt::format("{}_{}", identifier, node.node->name); + bNode &copied_node = *bke::node_copy_with_mapping( + &dst_tree_, + *node.node, + this->node_copy_flag(), + unique_name.size() < sizeof(bNode::name) ? std::make_optional(unique_name) : + std::nullopt, + identifier, + socket_map); + + /* Clear the parent frame pointer, because it does not exist in the destination tree. */ + copied_node.parent = nullptr; + + /* Setup input sockets for the copied node. */ + for (const bNodeSocket *src_input_socket : node->input_sockets()) { + if (!src_input_socket->is_available()) { + continue; + } + bNodeSocket &dst_input_socket = *socket_map.lookup(src_input_socket); + const SocketInContext input_socket_ctx = {node.context, src_input_socket}; + const SocketValue &value = value_by_socket_.lookup(input_socket_ctx); + this->set_socket_value(copied_node, dst_input_socket, value); + } + for (const bNodeSocket *src_output_socket : node->output_sockets()) { + if (!src_output_socket->is_available()) { + continue; + } + bNodeSocket &dst_output_socket = *socket_map.lookup(src_output_socket); + const SocketInContext output_socket_ctx = {node.context, src_output_socket}; + this->store_socket_value(output_socket_ctx, + {LinkedSocketValue{&copied_node, &dst_output_socket}}); + } + this->remember_copied_zone_node_if_necessary(node, copied_node); + } + + void remember_copied_zone_node_if_necessary(const NodeInContext &node, bNode &copied_node) + { + const bNodeTree &tree = node->owner_tree(); + const bke::bNodeTreeZones *zones = tree.zones(); + if (!zones) { + return; + } + const bke::bNodeTreeZone *zone = zones->get_zone_by_node(node->identifier); + if (!zone) { + return; + } + if (!ELEM(node->identifier, zone->input_node_id, zone->output_node_id)) { + return; + } + const NodeInContext zone_output_node = {node.context, zone->output_node()}; + PreservedZone &copied_zone = copied_zone_by_zone_output_node_.lookup_or_add_default( + zone_output_node); + if (node == zone_output_node) { + copied_zone.output_node = &copied_node; + } + else { + copied_zone.input_node = &copied_node; + } + } + + /** Converts the given socket value if necessary. */ + SocketValue handle_implicit_conversion(const SocketValue &src_value, + const bke::bNodeSocketType &from_socket_type, + const bke::bNodeSocketType &to_socket_type) + { + if (from_socket_type.type == to_socket_type.type) { + return src_value; + } + if (std::get_if(&src_value.value)) { + return src_value; + } + const std::optional src_primitive_value = src_value.to_primitive( + from_socket_type); + if (src_primitive_value && to_socket_type.base_cpp_type) { + if (data_type_conversions_.is_convertible(*from_socket_type.base_cpp_type, + *to_socket_type.base_cpp_type)) + { + const void *src_buffer = src_primitive_value->buffer(); + BUFFER_FOR_CPP_TYPE_VALUE(*to_socket_type.base_cpp_type, dst_buffer); + data_type_conversions_.convert_to_uninitialized(*from_socket_type.base_cpp_type, + *to_socket_type.base_cpp_type, + src_buffer, + dst_buffer); + return { + PrimitiveSocketValue::from_value(GPointer{to_socket_type.base_cpp_type, dst_buffer})}; + } + } + if (src_primitive_value && to_socket_type.type == SOCK_SHADER) { + /* Insert a Color node when converting a primitive value to a shader. */ + bNode *color_node = this->add_node("ShaderNodeRGB"); + const void *src_buffer = src_primitive_value->buffer(); + ColorGeometry4f color; + data_type_conversions_.convert_to_uninitialized( + *from_socket_type.base_cpp_type, CPPType::get(), src_buffer, &color); + bNodeSocket *output_socket = static_cast(color_node->outputs.first); + auto *socket_storage = static_cast(output_socket->default_value); + copy_v3_v3(socket_storage->value, color); + socket_storage->value[3] = 1.0f; + return {LinkedSocketValue{color_node, output_socket}}; + } + + return SocketValue{FallbackValue{}}; + } + + void set_socket_value(bNode &dst_node, bNodeSocket &dst_socket, const SocketValue &value) + { + if (dst_socket.flag & SOCK_HIDE_VALUE) { + if (const auto *input_socket_value = std::get_if(&value.value)) { + if (input_socket_value->socket->flag & SOCK_HIDE_VALUE) { + /* Don't add a value or link of the source and destination sockets don't have a value. */ + return; + } + } + } + if (const std::optional primitive_value = value.to_primitive( + *dst_socket.typeinfo)) + { + if (dst_socket.flag & SOCK_HIDE_VALUE) { + /* Can't store the primitive value directly on the socket. So create a new input node and + * link it instead. */ + const NodeAndSocket node_and_socket = this->primitive_value_to_output_socket( + *primitive_value); + if (dst_tree_.typeinfo->validate_link(node_and_socket.socket->typeinfo->type, + dst_socket.typeinfo->type)) + { + bke::node_add_link( + dst_tree_, *node_and_socket.node, *node_and_socket.socket, dst_node, dst_socket); + } + } + else { + this->set_primitive_value_on_socket(dst_socket, *primitive_value); + } + return; + } + if (std::get_if(&value.value)) { + /* Cases were the input has a primitive value are handled above. */ + return; + } + if (std::get_if(&value.value)) { + /* Cases were the input has a primitive fallback value are handled above. */ + return; + } + if (std::get_if(&value.value)) { + /* This type can't be assigned to a socket. The bundle has to be separated first. */ + BLI_assert_unreachable(); + return; + } + if (std::get_if(&value.value)) { + /* This type can't be assigned to a socket. One has to evaluate a closure. */ + BLI_assert_unreachable(); + return; + } + if (const auto *src_socket_value = std::get_if(&value.value)) { + if (dst_tree_.typeinfo->validate_link(src_socket_value->socket->typeinfo->type, + dst_socket.typeinfo->type)) + { + bke::node_add_link( + dst_tree_, *src_socket_value->node, *src_socket_value->socket, dst_node, dst_socket); + } + return; + } + BLI_assert_unreachable(); + } + + NodeAndSocket primitive_value_to_output_socket(const PrimitiveSocketValue &value) + { + if (const float *value_float = std::get_if(&value.value)) { + bNode *node = this->add_node("ShaderNodeValue"); + bNodeSocket *socket = static_cast(node->outputs.first); + socket->default_value_typed()->value = *value_float; + return {node, socket}; + } + if (const int *value_int = std::get_if(&value.value)) { + bNode *node = this->add_node("ShaderNodeValue"); + bNodeSocket *socket = static_cast(node->outputs.first); + socket->default_value_typed()->value = *value_int; + return {node, socket}; + } + if (const bool *value_bool = std::get_if(&value.value)) { + bNode *node = this->add_node("ShaderNodeValue"); + bNodeSocket *socket = static_cast(node->outputs.first); + socket->default_value_typed()->value = *value_bool; + return {node, socket}; + } + if (const float3 *value_float3 = std::get_if(&value.value)) { + bNode *node = this->add_node("ShaderNodeCombineXYZ"); + bNodeSocket *output_socket = static_cast(node->outputs.first); + bNodeSocket *input_x = static_cast(node->inputs.first); + bNodeSocket *input_y = input_x->next; + bNodeSocket *input_z = input_y->next; + input_x->default_value_typed()->value = value_float3->x; + input_y->default_value_typed()->value = value_float3->y; + input_z->default_value_typed()->value = value_float3->z; + return {node, output_socket}; + } + if (const ColorGeometry4f *value_color = std::get_if(&value.value)) { + bNode *node = this->add_node("ShaderNodeRGB"); + bNodeSocket *output_socket = static_cast(node->outputs.first); + auto *socket_storage = static_cast(output_socket->default_value); + copy_v3_v3(socket_storage->value, *value_color); + socket_storage->value[3] = 1.0f; + return {node, output_socket}; + } + BLI_assert_unreachable(); + return {}; + } + + bNode *add_node(const StringRefNull idname) + { + return bke::node_add_node(nullptr, dst_tree_, idname, this->get_next_node_identifier()); + } + + int get_next_node_identifier() + { + return ++dst_node_counter_; + } + + void set_primitive_value_on_socket(bNodeSocket &socket, const PrimitiveSocketValue &value) + { + switch (socket.type) { + case SOCK_FLOAT: { + socket.default_value_typed()->value = std::get(value.value); + break; + } + case SOCK_INT: { + socket.default_value_typed()->value = std::get(value.value); + break; + } + case SOCK_BOOLEAN: { + socket.default_value_typed()->value = std::get(value.value); + break; + } + case SOCK_VECTOR: { + copy_v3_v3(socket.default_value_typed()->value, + std::get(value.value)); + break; + } + case SOCK_RGBA: { + copy_v4_v4(socket.default_value_typed()->value, + std::get(value.value)); + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } + } + + void restore_zones_in_output_tree() + { + for (const PreservedZone &copied_zone : copied_zone_by_zone_output_node_.values()) { + if (!copied_zone.input_node || !copied_zone.output_node) { + continue; + } + const bke::bNodeZoneType *zone_type = bke::zone_type_by_node_type( + copied_zone.input_node->type_legacy); + if (!zone_type) { + continue; + } + int &output_id = zone_type->get_corresponding_output_id(*copied_zone.input_node); + output_id = copied_zone.output_node->identifier; + } + } + + void position_nodes_in_output_tree() + { + bNodeTree &tree = dst_tree_; + tree.ensure_topology_cache(); + + Map num_by_depth; + Map depth_by_node; + + /* Simple algorithm that does a very rough layout of the generated tree. This does not produce + * great results generally, but is usually good enough when debugging smaller node trees. */ + for (bNode *node : tree.toposort_right_to_left()) { + int depth = 0; + for (bNodeSocket *socket : node->output_sockets()) { + for (bNodeSocket *target : socket->directly_linked_sockets()) { + depth = std::max(depth, depth_by_node.lookup(&target->owner_node()) + 1); + } + } + depth_by_node.add_new(node, depth); + const int index_at_depth = num_by_depth.lookup_or_add(depth, 0)++; + node->location[0] = 200 - depth * 200; + node->location[1] = -index_at_depth * 300; + } + } + + /** + * Utility to that copies the value of the origin socket to the current socket. If the origin + * value does not exist yet, the origin socket is only scheduled. + */ + void forward_value_or_schedule(const SocketInContext &socket, const SocketInContext &origin) + { + if (const SocketValue *value = value_by_socket_.lookup_ptr(origin)) { + if (socket->type == origin->type) { + this->store_socket_value(socket, *value); + return; + } + this->store_socket_value( + socket, this->handle_implicit_conversion(*value, *origin->typeinfo, *socket->typeinfo)); + return; + } + this->schedule_socket(origin); + } + + void store_socket_value(const SocketInContext &socket, SocketValue value) + { + value_by_socket_.add_new(socket, std::move(value)); + } + + void store_socket_value_fallback(const SocketInContext &socket) + { + value_by_socket_.add_new(socket, {FallbackValue{}}); + } + + void schedule_socket(const SocketInContext &socket) + { + scheduled_sockets_stack_.push(socket); + } + + int node_copy_flag() const + { + const bool use_refcounting = !(dst_tree_.id.tag & ID_TAG_NO_MAIN); + return use_refcounting ? 0 : LIB_ID_CREATE_NO_USER_REFCOUNT; + } +}; + +} // namespace + +bool inline_shader_node_tree(const bNodeTree &src_tree, + bNodeTree &dst_tree, + InlineShaderNodeTreeParams ¶ms) +{ + ShaderNodesInliner inliner(src_tree, dst_tree, params); + + if (inliner.do_inline()) { + /* Update deprecated bNodeSocket.link pointers because some code still depends on it. */ + LISTBASE_FOREACH (bNode *, node, &dst_tree.nodes) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { + sock->link = nullptr; + } + LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { + sock->link = nullptr; + } + } + LISTBASE_FOREACH (bNodeLink *, link, &dst_tree.links) { + link->tosock->link = link; + BLI_assert(dst_tree.typeinfo->validate_link(link->fromsock->typeinfo->type, + link->tosock->typeinfo->type)); + link->flag |= NODE_LINK_VALID; + } + return true; + } + + return false; +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/shader/materialx/material.cc b/source/blender/nodes/shader/materialx/material.cc index 08b7226c751..0f3abc30f93 100644 --- a/source/blender/nodes/shader/materialx/material.cc +++ b/source/blender/nodes/shader/materialx/material.cc @@ -6,11 +6,14 @@ #include "node_parser.h" +#include "BKE_lib_id.hh" + #include "DEG_depsgraph.hh" #include "DNA_material_types.h" #include "NOD_shader.h" +#include "NOD_shader_nodes_inline.hh" #include "material.h" @@ -60,8 +63,15 @@ MaterialX::DocumentPtr export_to_materialx(Depsgraph *depsgraph, NodeGraph graph(depsgraph, material, export_params, doc); if (material->use_nodes) { - material->nodetree->ensure_topology_cache(); - bNode *output_node = ntreeShaderOutputNode(material->nodetree, SHD_OUTPUT_ALL); + bNodeTree *local_tree = bke::node_tree_add_tree( + nullptr, "Inlined Tree", material->nodetree->idname); + BLI_SCOPED_DEFER([&]() { BKE_id_free(nullptr, &local_tree->id); }); + + InlineShaderNodeTreeParams params; + inline_shader_node_tree(*material->nodetree, *local_tree, params); + + local_tree->ensure_topology_cache(); + bNode *output_node = ntreeShaderOutputNode(local_tree, SHD_OUTPUT_ALL); if (output_node && output_node->typeinfo->materialx_fn) { NodeParserData data = {graph, NodeItem::Type::Material, nullptr, graph.empty_node()}; output_node->typeinfo->materialx_fn(&data, output_node, nullptr); diff --git a/source/blender/nodes/shader/node_shader_tree.cc b/source/blender/nodes/shader/node_shader_tree.cc index 00b68a94b48..3607c4c7bc6 100644 --- a/source/blender/nodes/shader/node_shader_tree.cc +++ b/source/blender/nodes/shader/node_shader_tree.cc @@ -157,6 +157,9 @@ static bool shader_validate_link(eNodeSocketDatatype from, eNodeSocketDatatype t if (from == SOCK_SHADER) { return to == SOCK_SHADER; } + if (ELEM(to, SOCK_BUNDLE, SOCK_CLOSURE) || ELEM(from, SOCK_BUNDLE, SOCK_CLOSURE)) { + return from == to; + } return true; } @@ -169,7 +172,9 @@ static bool shader_node_tree_socket_type_valid(blender::bke::bNodeTreeType * /*n SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA, - SOCK_SHADER); + SOCK_SHADER, + SOCK_BUNDLE, + SOCK_CLOSURE); } blender::bke::bNodeTreeType *ntreeType_Shader; @@ -280,178 +285,6 @@ static bNodeSocket *ntree_shader_node_output_get(bNode *node, int n) return reinterpret_cast(BLI_findlink(&node->outputs, n)); } -/* Return true on success. */ -static bool ntree_shader_expand_socket_default(bNodeTree *localtree, - bNode *node, - bNodeSocket *socket) -{ - bNode *value_node; - bNodeSocket *value_socket; - bNodeSocketValueVector *src_vector; - bNodeSocketValueRGBA *src_rgba, *dst_rgba; - bNodeSocketValueFloat *src_float, *dst_float; - bNodeSocketValueInt *src_int; - bNodeSocketValueBoolean *src_bool; - - switch (socket->type) { - case SOCK_VECTOR: - value_node = blender::bke::node_add_static_node(nullptr, *localtree, SH_NODE_RGB); - value_socket = ntree_shader_node_find_output(value_node, "Color"); - BLI_assert(value_socket != nullptr); - src_vector = static_cast(socket->default_value); - dst_rgba = static_cast(value_socket->default_value); - copy_v3_v3(dst_rgba->value, src_vector->value); - dst_rgba->value[3] = 1.0f; /* should never be read */ - break; - case SOCK_RGBA: - value_node = blender::bke::node_add_static_node(nullptr, *localtree, SH_NODE_RGB); - value_socket = ntree_shader_node_find_output(value_node, "Color"); - BLI_assert(value_socket != nullptr); - src_rgba = static_cast(socket->default_value); - dst_rgba = static_cast(value_socket->default_value); - copy_v4_v4(dst_rgba->value, src_rgba->value); - break; - case SOCK_BOOLEAN: - /* HACK: Support as float. */ - value_node = blender::bke::node_add_static_node(nullptr, *localtree, SH_NODE_VALUE); - value_socket = ntree_shader_node_find_output(value_node, "Value"); - BLI_assert(value_socket != nullptr); - src_bool = static_cast(socket->default_value); - dst_float = static_cast(value_socket->default_value); - dst_float->value = float(src_bool->value); - break; - case SOCK_INT: - /* HACK: Support as float. */ - value_node = blender::bke::node_add_static_node(nullptr, *localtree, SH_NODE_VALUE); - value_socket = ntree_shader_node_find_output(value_node, "Value"); - BLI_assert(value_socket != nullptr); - src_int = static_cast(socket->default_value); - dst_float = static_cast(value_socket->default_value); - dst_float->value = float(src_int->value); - break; - case SOCK_FLOAT: - value_node = blender::bke::node_add_static_node(nullptr, *localtree, SH_NODE_VALUE); - value_socket = ntree_shader_node_find_output(value_node, "Value"); - BLI_assert(value_socket != nullptr); - src_float = static_cast(socket->default_value); - dst_float = static_cast(value_socket->default_value); - dst_float->value = src_float->value; - break; - default: - return false; - } - blender::bke::node_add_link(*localtree, *value_node, *value_socket, *node, *socket); - return true; -} - -static void ntree_shader_unlink_hidden_value_sockets(bNode *group_node, bNodeSocket *isock) -{ - bNodeTree *group_ntree = (bNodeTree *)group_node->id; - bool removed_link = false; - - LISTBASE_FOREACH (bNode *, node, &group_ntree->nodes) { - const bool is_group = node->is_group() && (node->id != nullptr); - - LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { - if (!is_group && (sock->flag & SOCK_HIDE_VALUE) == 0) { - continue; - } - /* If socket is linked to a group input node and sockets id match. */ - if (sock && sock->link && sock->link->fromnode->is_group_input()) { - if (STREQ(isock->identifier, sock->link->fromsock->identifier)) { - if (is_group) { - /* Recursively unlink sockets within the nested group. */ - ntree_shader_unlink_hidden_value_sockets(node, sock); - } - else { - blender::bke::node_remove_link(group_ntree, *sock->link); - removed_link = true; - } - } - } - } - } - - if (removed_link) { - BKE_ntree_update_after_single_tree_change(*G.main, *group_ntree); - } -} - -/* Node groups once expanded looses their input sockets values. - * To fix this, link value/rgba nodes into the sockets and copy the group sockets values. */ -static void ntree_shader_groups_expand_inputs(bNodeTree *localtree) -{ - bool link_added = false; - - LISTBASE_FOREACH (bNode *, node, &localtree->nodes) { - const bool is_group = node->is_group() && (node->id != nullptr); - const bool is_group_output = node->is_group_output() && (node->flag & NODE_DO_OUTPUT); - - if (is_group) { - /* Do it recursively. */ - ntree_shader_groups_expand_inputs((bNodeTree *)node->id); - } - - if (is_group || is_group_output) { - LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { - if (socket->link != nullptr && !(socket->link->flag & NODE_LINK_MUTED)) { - bNodeLink *link = socket->link; - /* Fix the case where the socket is actually converting the data. (see #71374) - * We only do the case of lossy conversion to float. */ - if ((socket->type == SOCK_FLOAT) && (link->fromsock->type != link->tosock->type)) { - if (link->fromsock->type == SOCK_RGBA) { - bNode *tmp = blender::bke::node_add_static_node( - nullptr, *localtree, SH_NODE_RGBTOBW); - blender::bke::node_add_link(*localtree, - *link->fromnode, - *link->fromsock, - *tmp, - *static_cast(tmp->inputs.first)); - blender::bke::node_add_link(*localtree, - *tmp, - *static_cast(tmp->outputs.first), - *node, - *socket); - } - else if (link->fromsock->type == SOCK_VECTOR) { - bNode *tmp = blender::bke::node_add_static_node( - nullptr, *localtree, SH_NODE_VECTOR_MATH); - tmp->custom1 = NODE_VECTOR_MATH_DOT_PRODUCT; - bNodeSocket *dot_input1 = static_cast(tmp->inputs.first); - bNodeSocket *dot_input2 = static_cast(dot_input1->next); - bNodeSocketValueVector *input2_socket_value = static_cast( - dot_input2->default_value); - copy_v3_fl(input2_socket_value->value, 1.0f / 3.0f); - blender::bke::node_add_link( - *localtree, *link->fromnode, *link->fromsock, *tmp, *dot_input1); - blender::bke::node_add_link(*localtree, - *tmp, - *static_cast(tmp->outputs.last), - *node, - *socket); - } - } - continue; - } - - if (is_group) { - /* Detect the case where an input is plugged into a hidden value socket. - * In this case we should just remove the link to trigger the socket default override. */ - ntree_shader_unlink_hidden_value_sockets(node, socket); - } - - if (ntree_shader_expand_socket_default(localtree, node, socket)) { - link_added = true; - } - } - } - } - - if (link_added) { - BKE_ntree_update_after_single_tree_change(*G.main, *localtree); - } -} - static void ntree_shader_unlink_script_nodes(bNodeTree *ntree) { /* To avoid more trouble in the node tree processing (especially inside @@ -465,133 +298,6 @@ static void ntree_shader_unlink_script_nodes(bNodeTree *ntree) } } } - -static void ntree_shader_groups_remove_muted_links(bNodeTree *ntree) -{ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->is_group()) { - if (node->id != nullptr) { - ntree_shader_groups_remove_muted_links(reinterpret_cast(node->id)); - } - } - } - LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) { - if (link->flag & NODE_LINK_MUTED) { - blender::bke::node_remove_link(ntree, *link); - } - } -} - -static void flatten_group_do(bNodeTree *ntree, bNode *gnode) -{ - LinkNode *group_interface_nodes = nullptr; - bNodeTree *ngroup = (bNodeTree *)gnode->id; - - /* Add the nodes into the ntree */ - LISTBASE_FOREACH_MUTABLE (bNode *, node, &ngroup->nodes) { - /* Remove interface nodes. - * This also removes remaining links to and from interface nodes. - * We must delay removal since sockets will reference this node. see: #52092 */ - if (node->is_group_input() || node->is_group_output()) { - BLI_linklist_prepend(&group_interface_nodes, node); - } - /* migrate node */ - BLI_remlink(&ngroup->nodes, node); - BLI_addtail(&ntree->nodes, node); - blender::bke::node_unique_id(*ntree, *node); - /* ensure unique node name in the node tree */ - /* This is very slow and it has no use for GPU nodetree. (see #70609) */ - // blender::bke::node_unique_name(ntree, node); - } - ngroup->runtime->nodes_by_id.clear(); - - /* Save first and last link to iterate over flattened group links. */ - bNodeLink *glinks_first = static_cast(ntree->links.last); - - /* Add internal links to the ntree */ - LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ngroup->links) { - BLI_remlink(&ngroup->links, link); - BLI_addtail(&ntree->links, link); - } - - bNodeLink *glinks_last = static_cast(ntree->links.last); - - /* restore external links to and from the gnode */ - if (glinks_first != nullptr) { - /* input links */ - for (bNodeLink *link = glinks_first->next; link != glinks_last->next; link = link->next) { - if (link->fromnode->is_group_input()) { - const char *identifier = link->fromsock->identifier; - /* find external links to this input */ - for (bNodeLink *tlink = static_cast(ntree->links.first); - tlink != glinks_first->next; - tlink = tlink->next) - { - if (tlink->tonode == gnode && STREQ(tlink->tosock->identifier, identifier)) { - blender::bke::node_add_link( - *ntree, *tlink->fromnode, *tlink->fromsock, *link->tonode, *link->tosock); - } - } - } - } - /* Also iterate over the new links to cover passthrough links. */ - glinks_last = static_cast(ntree->links.last); - /* output links */ - for (bNodeLink *tlink = static_cast(ntree->links.first); - tlink != glinks_first->next; - tlink = tlink->next) - { - if (tlink->fromnode == gnode) { - const char *identifier = tlink->fromsock->identifier; - /* find internal links to this output */ - for (bNodeLink *link = glinks_first->next; link != glinks_last->next; link = link->next) { - /* only use active output node */ - if (link->tonode->is_group_output() && (link->tonode->flag & NODE_DO_OUTPUT)) { - if (STREQ(link->tosock->identifier, identifier)) { - blender::bke::node_add_link( - *ntree, *link->fromnode, *link->fromsock, *tlink->tonode, *tlink->tosock); - } - } - } - } - } - } - - while (group_interface_nodes) { - bNode *node = static_cast(BLI_linklist_pop(&group_interface_nodes)); - blender::bke::node_tree_free_local_node(*ntree, *node); - } - - BKE_ntree_update_tag_all(ntree); -} - -/* Flatten group to only have a simple single tree */ -static void ntree_shader_groups_flatten(bNodeTree *localtree) -{ - /* This is effectively recursive as the flattened groups will add - * nodes at the end of the list, which will also get evaluated. */ - for (bNode *node = static_cast(localtree->nodes.first), *node_next; node; - node = node_next) - { - if (node->is_group() && node->id != nullptr) { - flatten_group_do(localtree, node); - /* Continue even on new flattened nodes. */ - node_next = node->next; - /* delete the group instance and its localtree. */ - bNodeTree *ngroup = (bNodeTree *)node->id; - blender::bke::node_tree_free_local_node(*localtree, *node); - blender::bke::node_tree_free_tree(*ngroup); - BLI_assert(!ngroup->id.py_instance); /* Or call #BKE_libblock_free_data_py. */ - MEM_freeN(ngroup); - } - else { - node_next = node->next; - } - } - - BKE_ntree_update_after_single_tree_change(*G.main, *localtree); -} - struct branchIterData { bool (*node_filter)(const bNode *node); int node_count; @@ -1234,7 +940,8 @@ static void ntree_shader_pruned_unused(bNodeTree *ntree, bNode *output_node) LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) { if (node->runtime->tmp_flag == 0) { - blender::bke::node_tree_free_local_node(*ntree, *node); + blender::bke::node_unlink_node(*ntree, *node); + blender::bke::node_free_node(ntree, *node); changed = true; } } @@ -1249,10 +956,6 @@ void ntreeGPUMaterialNodes(bNodeTree *localtree, GPUMaterial *mat) bNodeTreeExec *exec; ntree_shader_unlink_script_nodes(localtree); - ntree_shader_groups_remove_muted_links(localtree); - ntree_shader_groups_expand_inputs(localtree); - ntree_shader_groups_flatten(localtree); - bNode *output = ntreeShaderOutputNode(localtree, SHD_OUTPUT_EEVEE); /* Tree is valid if it contains no undefined implicit socket type cast. */ diff --git a/tests/files/render/node_inlining/closures.blend b/tests/files/render/node_inlining/closures.blend new file mode 100644 index 00000000000..3645563a77e --- /dev/null +++ b/tests/files/render/node_inlining/closures.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0e5dd667df2682b3c712664759f0cf8c598545bbf9fa7ef9fd47dab418ec252 +size 1015820 diff --git a/tests/files/render/node_inlining/cycles_renders/closures.png b/tests/files/render/node_inlining/cycles_renders/closures.png new file mode 100644 index 00000000000..8f6c49b9913 --- /dev/null +++ b/tests/files/render/node_inlining/cycles_renders/closures.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d88b4c834eaa30c40d7e8481c052f9ca85883b06256d4fce0f4e394579bf640 +size 10133 diff --git a/tests/files/render/node_inlining/cycles_renders/repeat_zones.png b/tests/files/render/node_inlining/cycles_renders/repeat_zones.png new file mode 100644 index 00000000000..7d8855a9ca0 --- /dev/null +++ b/tests/files/render/node_inlining/cycles_renders/repeat_zones.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7557336ebbb9fd6da1b090517281e1883eb9cf4030f4dbd064b26e8e67833480 +size 5057 diff --git a/tests/files/render/node_inlining/eevee_renders/closures.png b/tests/files/render/node_inlining/eevee_renders/closures.png new file mode 100644 index 00000000000..0d1bc8782e2 --- /dev/null +++ b/tests/files/render/node_inlining/eevee_renders/closures.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f844b99ba90df6fd3cc6b17f30ca10469230c5f8a70df78e4a60652028c4816f +size 6161 diff --git a/tests/files/render/node_inlining/eevee_renders/repeat_zones.png b/tests/files/render/node_inlining/eevee_renders/repeat_zones.png new file mode 100644 index 00000000000..738914a509b --- /dev/null +++ b/tests/files/render/node_inlining/eevee_renders/repeat_zones.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b1399ee6daf3ff70b5911eb7ca9050545b188314b1ff87853762311d1f11cbc +size 2031 diff --git a/tests/files/render/node_inlining/repeat_zones.blend b/tests/files/render/node_inlining/repeat_zones.blend new file mode 100644 index 00000000000..b7d2b66e3db --- /dev/null +++ b/tests/files/render/node_inlining/repeat_zones.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6723d28bbbbb825f0c5f386091fe73d45281f7a980dec3143ef58b56180d535 +size 997745 diff --git a/tests/files/render/node_inlining/storm_hydra_renders/closures.png b/tests/files/render/node_inlining/storm_hydra_renders/closures.png new file mode 100644 index 00000000000..64ec85f5bd3 --- /dev/null +++ b/tests/files/render/node_inlining/storm_hydra_renders/closures.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:695da6ae0e366d58aad66e2ff83eff3fb13074141a819798ad47baf03ad3b879 +size 1195 diff --git a/tests/files/render/node_inlining/storm_hydra_renders/repeat_zones.png b/tests/files/render/node_inlining/storm_hydra_renders/repeat_zones.png new file mode 100644 index 00000000000..ba769beb952 --- /dev/null +++ b/tests/files/render/node_inlining/storm_hydra_renders/repeat_zones.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65ddc4905348061e88c82ea0ee3ce6df50d982c9928cdd1066d245ad270a0c0c +size 1104 diff --git a/tests/files/render/node_inlining/storm_usd_renders/closures.png b/tests/files/render/node_inlining/storm_usd_renders/closures.png new file mode 100644 index 00000000000..811c4f99d90 --- /dev/null +++ b/tests/files/render/node_inlining/storm_usd_renders/closures.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b71b38a59a9ddcedb4db7d4f7733c5ab70a587049811c227154331d1f8a57d3e +size 1195 diff --git a/tests/files/render/node_inlining/storm_usd_renders/repeat_zones.png b/tests/files/render/node_inlining/storm_usd_renders/repeat_zones.png new file mode 100644 index 00000000000..535132e30d1 --- /dev/null +++ b/tests/files/render/node_inlining/storm_usd_renders/repeat_zones.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:caba438137b2e70aa96e270735dabc39970a2a8ec1b9291ddda498cdd40aea74 +size 1104 diff --git a/tests/files/render/node_inlining/workbench_renders/closures.png b/tests/files/render/node_inlining/workbench_renders/closures.png new file mode 100644 index 00000000000..9056a1e185c --- /dev/null +++ b/tests/files/render/node_inlining/workbench_renders/closures.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0815c893a0bf54cddf19f1b50f2ace9a9fac1eaf9cf2471c31856307c51ec01 +size 1767 diff --git a/tests/files/render/node_inlining/workbench_renders/repeat_zones.png b/tests/files/render/node_inlining/workbench_renders/repeat_zones.png new file mode 100644 index 00000000000..1b99230b4bc --- /dev/null +++ b/tests/files/render/node_inlining/workbench_renders/repeat_zones.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8036b96683e9aab847bd5355530683bea3b0921010716e5f71c3e73205663d96 +size 1771 diff --git a/tests/files/render/shader/storm_hydra_renders/float_math.png b/tests/files/render/shader/storm_hydra_renders/float_math.png index b50bb6601e9..d008679fe55 100644 --- a/tests/files/render/shader/storm_hydra_renders/float_math.png +++ b/tests/files/render/shader/storm_hydra_renders/float_math.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62cceab7186b3f273d81c9ca0578c2df7d497d5053d70696736c3b045b1583dc -size 7777 +oid sha256:29875a9accf882ec45994732c7fe40156935d04d8e152f7a2c43cee0c8d6c7b1 +size 7858 diff --git a/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_0.5.png b/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_0.5.png index 79b6a22e170..1c4f2b8d344 100644 --- a/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_0.5.png +++ b/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a55b5dc63a1421adf8a39b9d1bceacd1ca26cd777afbf32d2917aae05a77eba -size 5753 +oid sha256:f392225babcd00f9806bb4a3d9c4044740b0dbc22335652d8d1ca3cf121ec3ae +size 5627 diff --git a/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_1.2.png b/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_1.2.png index 570c4410a5e..442bf39e3b2 100644 --- a/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_1.2.png +++ b/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_1.2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a79577e52c98ba84e918b1eba7e282ed598cdcbcf475eaf24eb83bc9937a2ef -size 5680 +oid sha256:5f908bd7613731b1dbab04297bc2aeeac482217af15fa5180b42d3157695bf6d +size 5631 diff --git a/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_2.0_extrapolate.png b/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_2.0_extrapolate.png index f4b9448f6bc..a2ebd0e3c21 100644 --- a/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_2.0_extrapolate.png +++ b/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_2.0_extrapolate.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bb7192f83a82ea4a4d9f254a55ba1b796514e383fdd77cecf2b802b22e38454 -size 5692 +oid sha256:95a0e0f90ab7437f97dd3dac560afbcad75cc1204bb525d846c743d92fcbed3e +size 5650 diff --git a/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_0.5.png b/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_0.5.png index 13a1708d18b..9d8029d521b 100644 --- a/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_0.5.png +++ b/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f862ac43f1d5b3c1a0ba553948d4244f95ab4139904a4ec83459320b311e9e87 -size 5700 +oid sha256:475b1eae58674ba25c4c505b159dfe17cdf52dc8fd510aeba5beaa85682fd271 +size 5631 diff --git a/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_1.2.png b/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_1.2.png index 4ab972369b7..f418b60490e 100644 --- a/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_1.2.png +++ b/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_1.2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67785046981cf99d2925dc34560f67bc6304576b83ecea4cd31b55c31124f9bf -size 5700 +oid sha256:c464c1fb11abc6d677f7f2790888d33b62268a84b6e485b37c020c5fcb940d5b +size 5635 diff --git a/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_2.0_extrapolate.png b/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_2.0_extrapolate.png index 15ecdc561eb..639773fa488 100644 --- a/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_2.0_extrapolate.png +++ b/tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_2.0_extrapolate.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:caa2f9cdfb8ef4e88526c4755391b500117380e4bf3656c2daf3a70dd0ffc4e1 -size 5712 +oid sha256:4f5325818370c17ee94bd27e777a9b04be97835ffae3886dfdd48d224d391722 +size 5654 diff --git a/tests/files/render/shader/storm_hydra_renders/vector_math.png b/tests/files/render/shader/storm_hydra_renders/vector_math.png index c7e7c84715b..050f3d64dbe 100644 --- a/tests/files/render/shader/storm_hydra_renders/vector_math.png +++ b/tests/files/render/shader/storm_hydra_renders/vector_math.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6475dcd7b827b3d4128bc14063fdc7b53b26ca80f0ed106a2d7fa6064f89245 -size 11176 +oid sha256:3b9925b4c5912275f879cec27ddeac180ce6a73cbe6445cd8dbc4656a634f157 +size 11172 diff --git a/tests/files/render/shader/storm_usd_renders/float_math.png b/tests/files/render/shader/storm_usd_renders/float_math.png index 64e6f32780e..d5eabe6c293 100644 --- a/tests/files/render/shader/storm_usd_renders/float_math.png +++ b/tests/files/render/shader/storm_usd_renders/float_math.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d05fea1f28c6a9725f6d083c5165ae4c327cf298d8eb1c78b383a9f3836a92e7 -size 7777 +oid sha256:ef310669af40f64afa4a47d946d901a0b1c1496909c5b3cb87f245fc1266049f +size 7858 diff --git a/tests/files/render/shader/storm_usd_renders/rgb_curves_input_0.5.png b/tests/files/render/shader/storm_usd_renders/rgb_curves_input_0.5.png index 25844d6c1ad..78f2014ad73 100644 --- a/tests/files/render/shader/storm_usd_renders/rgb_curves_input_0.5.png +++ b/tests/files/render/shader/storm_usd_renders/rgb_curves_input_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:195be60e8e0aa953971d7f9bfa4caf7e1003303a6df2707fc5e8c1088d47d3bf -size 5780 +oid sha256:8112a48b9daabdcaa78c55c9cf73ceb85dcae1ced79147c9e7b259817aa339b3 +size 5550 diff --git a/tests/files/render/shader/storm_usd_renders/rgb_curves_input_1.2.png b/tests/files/render/shader/storm_usd_renders/rgb_curves_input_1.2.png index 0f50d703088..2ed51bee893 100644 --- a/tests/files/render/shader/storm_usd_renders/rgb_curves_input_1.2.png +++ b/tests/files/render/shader/storm_usd_renders/rgb_curves_input_1.2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c2ccc2138db49d7bc0994af0d2cf016fac4f1e13c8e35f20b5dfb76838592cd -size 5707 +oid sha256:6a0fb21742db42bd19ad76aa1aa4022b59b95db365d21ef6b753b6b21b9a9e4c +size 5555 diff --git a/tests/files/render/shader/storm_usd_renders/rgb_curves_input_2.0_extrapolate.png b/tests/files/render/shader/storm_usd_renders/rgb_curves_input_2.0_extrapolate.png index 568878b5d8d..228b2db8a40 100644 --- a/tests/files/render/shader/storm_usd_renders/rgb_curves_input_2.0_extrapolate.png +++ b/tests/files/render/shader/storm_usd_renders/rgb_curves_input_2.0_extrapolate.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d315381055510fcd35e85f8e9b3b1db8f6f38c316bc374866a1e61ccd2f5ed03 -size 5719 +oid sha256:10b47f279c9635be86a601f08258bdccf3517bdefb061d5decc2514b45418ccc +size 5566 diff --git a/tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_0.5.png b/tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_0.5.png index 70f222a757c..58293139dcf 100644 --- a/tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_0.5.png +++ b/tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e2a76a990a29a1dd74e5af1202185afbd8743547ce772ad6dd29d91f4a34ec9 -size 5727 +oid sha256:3aac576f1e1b2c990b96d3ba5c59d6a8d5141c8a663915eb58f3a4350447bcf9 +size 5554 diff --git a/tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_1.2.png b/tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_1.2.png index bc49c618a27..6d7ea01e534 100644 --- a/tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_1.2.png +++ b/tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_1.2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b952221677f12394e05895c5ec44187737ca9c125626af5839320c0d59f157d -size 5727 +oid sha256:8aab65d4169eafd908edeaa234953d1383e1685b7571a8b5ee2bd576f4a59388 +size 5559 diff --git a/tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_2.0_extrapolate.png b/tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_2.0_extrapolate.png index e288b3d6546..f8a06855868 100644 --- a/tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_2.0_extrapolate.png +++ b/tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_2.0_extrapolate.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:183f0d6a68062d9af23e4efce0eee2dd23c5d9e02b0c481cae1992c7a11d6e16 -size 5739 +oid sha256:2948f138ca3bc01df678038544fff3e04d2d281cad70cd22b6ff7f7f1f9cdf30 +size 5570 diff --git a/tests/files/render/shader/storm_usd_renders/vector_math.png b/tests/files/render/shader/storm_usd_renders/vector_math.png index 7ac4f6ecd66..2fc3218f1d0 100644 --- a/tests/files/render/shader/storm_usd_renders/vector_math.png +++ b/tests/files/render/shader/storm_usd_renders/vector_math.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8374266054cdd3b32197f4be54358d4d31aea4ba5f9a09917c97f549ce26a6f -size 11176 +oid sha256:16c3abe854455b0748d9708cbdb3482033aa6298b7fae6ce4b538ea20068fcdd +size 11172 diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 5996818825e..c73b41c3fa5 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -681,6 +681,7 @@ if((WITH_CYCLES OR WITH_GPU_RENDER_TESTS) AND TEST_SRC_DIR_EXISTS) light_group light_linking mesh + node_inlining pointcloud principled_bsdf shader