Shader Nodes: support repeat zones, closures and bundles
This adds a function that can turn an existing `bNodeTree` into an inlined one. The new node tree has all node groups, repeat zones, closures and bundles inlined. So it's just a flat tree that ideally can be consumed easily by render engines. As part of the process, it also does constant folding. The goal is to support more advanced features from geometry nodes (repeat zones, etc.) in shader nodes which the evaluator is more limited because it has to be able to run on the GPU. Creating an inlined `bNodeTree` is likely the most direct way to get but may also be limiting in the future. Since this is a fairly local change, it's likely still worth it to support these features in all render engines without having to make their evaluators significantly more complex. Some limitations apply here that do not apply in Geometry Nodes. For example, the iterations count in a repeat zone has to be a constant after constant folding. There is also a `Test Inlining Shader Nodes` operator that creates the inlined tree and creates a group node for it. This is just for testing purposes. #145811 will make this functionality available to the Python API as well so that external renderers can use it too.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>();
|
||||
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,
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class ComputeContextCache {
|
||||
Map<std::pair<const ComputeContext *, int>, const ModifierComputeContext *>
|
||||
modifier_contexts_cache_;
|
||||
Map<const ComputeContext *, const OperatorComputeContext *> operator_contexts_cache_;
|
||||
Map<const ComputeContext *, const ShaderComputeContext *> shader_contexts_cache_;
|
||||
Map<std::pair<const ComputeContext *, int32_t>, const GroupNodeComputeContext *>
|
||||
group_node_contexts_cache_;
|
||||
Map<std::pair<const ComputeContext *, int32_t>, 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<int> unique_identifier = std::nullopt);
|
||||
bNode *node_add_static_node(const bContext *C, bNodeTree &ntree, int type);
|
||||
|
||||
/**
|
||||
|
||||
@@ -207,6 +207,13 @@ class bNodeTreeRuntime : NonCopyable, NonMovable {
|
||||
*/
|
||||
MultiValueMap<NodeLinkKey, NodeLinkError> link_errors;
|
||||
|
||||
/**
|
||||
* Error messages for shading nodes. Those don't have more contextual information yet. Maps
|
||||
* #bNode::identifier to error messages.
|
||||
*/
|
||||
Map<int32_t, VectorSet<std::string>> 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.
|
||||
|
||||
@@ -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<OperatorComputeContext>(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<ShaderComputeContext>(parent, tree); });
|
||||
}
|
||||
|
||||
const GroupNodeComputeContext &ComputeContextCache::for_group_node(const ComputeContext *parent,
|
||||
const int32_t node_id,
|
||||
const bNodeTree *tree)
|
||||
|
||||
@@ -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<int> unique_identifier)
|
||||
{
|
||||
bNode *node = MEM_callocN<bNode>(__func__);
|
||||
node->runtime = MEM_new<bNodeRuntime>(__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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -155,6 +155,8 @@ struct TreeDrawContext {
|
||||
*/
|
||||
Array<Vector<NodeExtraInfoRow>> extra_info_rows_per_node;
|
||||
|
||||
Map<int32_t, VectorSet<std::string>> shader_node_errors;
|
||||
|
||||
~TreeDrawContext()
|
||||
{
|
||||
for (MutableSpan<NodeExtraInfoRow> 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<std::string> *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<std::chrono::nanoseconds> 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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<const ComputeContext *> 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<ed::space_node::ObjectAndModifier> 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<ed::space_node::ObjectAndModifier> 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,
|
||||
|
||||
@@ -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<Error> 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,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#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<GPUMaterial>(__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,
|
||||
|
||||
@@ -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
|
||||
|
||||
31
source/blender/nodes/NOD_shader_nodes_inline.hh
Normal file
31
source/blender/nodes/NOD_shader_nodes_inline.hh
Normal file
@@ -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<ErrorMessage> r_error_messages;
|
||||
};
|
||||
|
||||
bool inline_shader_node_tree(const bNodeTree &src_tree,
|
||||
bNodeTree &dst_tree,
|
||||
InlineShaderNodeTreeParams ¶ms);
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<NodeGeometryRepeatOutput>(__func__);
|
||||
|
||||
data->next_identifier = 0;
|
||||
|
||||
data->items = MEM_calloc_arrayN<NodeRepeatItem>(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<NodeRepeatItem>(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;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "BLO_read_write.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "shader/node_shader_util.hh"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
@@ -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;
|
||||
|
||||
1217
source/blender/nodes/intern/shader_nodes_inline.cc
Normal file
1217
source/blender/nodes/intern/shader_nodes_inline.cc
Normal file
@@ -0,0 +1,1217 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <variant>
|
||||
|
||||
#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<BundleSocketValue>;
|
||||
|
||||
struct FallbackValue {};
|
||||
|
||||
struct NodeAndSocket {
|
||||
bNode *node = nullptr;
|
||||
bNodeSocket *socket = nullptr;
|
||||
};
|
||||
|
||||
struct PrimitiveSocketValue {
|
||||
std::variant<int, float, bool, ColorGeometry4f, float3> value;
|
||||
|
||||
const void *buffer() const
|
||||
{
|
||||
return std::visit([](auto &&value) -> const void * { return &value; }, value);
|
||||
}
|
||||
|
||||
void *buffer()
|
||||
{
|
||||
return const_cast<void *>(const_cast<const PrimitiveSocketValue *>(this)->buffer());
|
||||
}
|
||||
|
||||
static PrimitiveSocketValue from_value(const GPointer value)
|
||||
{
|
||||
const CPPType &type = *value.type();
|
||||
if (type.is<int>()) {
|
||||
return {*static_cast<const int *>(value.get())};
|
||||
}
|
||||
if (type.is<float>()) {
|
||||
return {*static_cast<const float *>(value.get())};
|
||||
}
|
||||
if (type.is<bool>()) {
|
||||
return {*static_cast<const bool *>(value.get())};
|
||||
}
|
||||
if (type.is<ColorGeometry4f>()) {
|
||||
return {*static_cast<const ColorGeometry4f *>(value.get())};
|
||||
}
|
||||
if (type.is<float3>()) {
|
||||
return {*static_cast<const float3 *>(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<FallbackValue,
|
||||
LinkedSocketValue,
|
||||
InputSocketValue,
|
||||
PrimitiveSocketValue,
|
||||
ClosureZoneValue,
|
||||
BundleSocketValuePtr>
|
||||
value;
|
||||
|
||||
/** Try to get the value as a primitive value. */
|
||||
std::optional<PrimitiveSocketValue> to_primitive(const bke::bNodeSocketType &type) const
|
||||
{
|
||||
if (const auto *primitive_value = std::get_if<PrimitiveSocketValue>(&this->value)) {
|
||||
return *primitive_value;
|
||||
}
|
||||
if (const auto *input_socket_value = std::get_if<InputSocketValue>(&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<bNodeSocketValueFloat>()->value};
|
||||
case SOCK_INT:
|
||||
return PrimitiveSocketValue{socket.default_value_typed<bNodeSocketValueInt>()->value};
|
||||
case SOCK_BOOLEAN:
|
||||
return PrimitiveSocketValue{
|
||||
socket.default_value_typed<bNodeSocketValueBoolean>()->value};
|
||||
case SOCK_VECTOR:
|
||||
return PrimitiveSocketValue{
|
||||
float3(socket.default_value_typed<bNodeSocketValueVector>()->value)};
|
||||
case SOCK_RGBA:
|
||||
return PrimitiveSocketValue{
|
||||
ColorGeometry4f(socket.default_value_typed<bNodeSocketValueRGBA>()->value)};
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
if (std::get_if<FallbackValue>(&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<Item> 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<SocketInContext, SocketValue> value_by_socket_;
|
||||
/**
|
||||
* Remember zone nodes that have been copied to the destination so that they can be connected
|
||||
* again in the end.
|
||||
*/
|
||||
Map<NodeInContext, PreservedZone> copied_zone_by_zone_output_node_;
|
||||
/** Sockets that still have to be evaluated. */
|
||||
Stack<SocketInContext> 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<SocketInContext> 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<NodeInContext, bNode *> 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<const bNodeSocket *, bNodeSocket *> 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<bNodeSocket *>(
|
||||
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<SocketInContext> find_final_output_sockets() const
|
||||
{
|
||||
Vector<SocketInContext> 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<const bNodeTree *>(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<const bke::GroupNodeComputeContext *>(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<const NodeGeometryRepeatOutput *>(
|
||||
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<PrimitiveSocketValue> 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<int>(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<const bke::RepeatZoneComputeContext *>(
|
||||
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<ClosureZoneValue>(
|
||||
&closure_input_value->value);
|
||||
if (!closure_zone_value) {
|
||||
this->store_socket_value_fallback(socket);
|
||||
return;
|
||||
}
|
||||
const auto *evaluate_closure_storage = static_cast<const NodeEvaluateClosure *>(
|
||||
evaluate_closure_node->storage);
|
||||
const bNode &closure_output_node = *closure_zone_value->zone->output_node();
|
||||
const auto &closure_storage = *static_cast<const NodeClosureOutput *>(
|
||||
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<const bke::EvaluateClosureComputeContext *>(
|
||||
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<const NodeClosureOutput *>(
|
||||
closure_output_node.storage);
|
||||
const auto &eval_closure_storage = *static_cast<const NodeEvaluateClosure *>(
|
||||
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<const NodeCombineBundle *>(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<BundleSocketValue>();
|
||||
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<const NodeSeparateBundle *>(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<BundleSocketValuePtr>(&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<void *> 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<const bNodeSocket *, bNodeSocket *> 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<StringRefNull>(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<LinkedSocketValue>(&src_value.value)) {
|
||||
return src_value;
|
||||
}
|
||||
const std::optional<PrimitiveSocketValue> 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<ColorGeometry4f>(), src_buffer, &color);
|
||||
bNodeSocket *output_socket = static_cast<bNodeSocket *>(color_node->outputs.first);
|
||||
auto *socket_storage = static_cast<bNodeSocketValueRGBA *>(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<InputSocketValue>(&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<PrimitiveSocketValue> 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<InputSocketValue>(&value.value)) {
|
||||
/* Cases were the input has a primitive value are handled above. */
|
||||
return;
|
||||
}
|
||||
if (std::get_if<FallbackValue>(&value.value)) {
|
||||
/* Cases were the input has a primitive fallback value are handled above. */
|
||||
return;
|
||||
}
|
||||
if (std::get_if<BundleSocketValuePtr>(&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<ClosureZoneValue>(&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<LinkedSocketValue>(&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<float>(&value.value)) {
|
||||
bNode *node = this->add_node("ShaderNodeValue");
|
||||
bNodeSocket *socket = static_cast<bNodeSocket *>(node->outputs.first);
|
||||
socket->default_value_typed<bNodeSocketValueFloat>()->value = *value_float;
|
||||
return {node, socket};
|
||||
}
|
||||
if (const int *value_int = std::get_if<int>(&value.value)) {
|
||||
bNode *node = this->add_node("ShaderNodeValue");
|
||||
bNodeSocket *socket = static_cast<bNodeSocket *>(node->outputs.first);
|
||||
socket->default_value_typed<bNodeSocketValueFloat>()->value = *value_int;
|
||||
return {node, socket};
|
||||
}
|
||||
if (const bool *value_bool = std::get_if<bool>(&value.value)) {
|
||||
bNode *node = this->add_node("ShaderNodeValue");
|
||||
bNodeSocket *socket = static_cast<bNodeSocket *>(node->outputs.first);
|
||||
socket->default_value_typed<bNodeSocketValueFloat>()->value = *value_bool;
|
||||
return {node, socket};
|
||||
}
|
||||
if (const float3 *value_float3 = std::get_if<float3>(&value.value)) {
|
||||
bNode *node = this->add_node("ShaderNodeCombineXYZ");
|
||||
bNodeSocket *output_socket = static_cast<bNodeSocket *>(node->outputs.first);
|
||||
bNodeSocket *input_x = static_cast<bNodeSocket *>(node->inputs.first);
|
||||
bNodeSocket *input_y = input_x->next;
|
||||
bNodeSocket *input_z = input_y->next;
|
||||
input_x->default_value_typed<bNodeSocketValueFloat>()->value = value_float3->x;
|
||||
input_y->default_value_typed<bNodeSocketValueFloat>()->value = value_float3->y;
|
||||
input_z->default_value_typed<bNodeSocketValueFloat>()->value = value_float3->z;
|
||||
return {node, output_socket};
|
||||
}
|
||||
if (const ColorGeometry4f *value_color = std::get_if<ColorGeometry4f>(&value.value)) {
|
||||
bNode *node = this->add_node("ShaderNodeRGB");
|
||||
bNodeSocket *output_socket = static_cast<bNodeSocket *>(node->outputs.first);
|
||||
auto *socket_storage = static_cast<bNodeSocketValueRGBA *>(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<bNodeSocketValueFloat>()->value = std::get<float>(value.value);
|
||||
break;
|
||||
}
|
||||
case SOCK_INT: {
|
||||
socket.default_value_typed<bNodeSocketValueInt>()->value = std::get<int>(value.value);
|
||||
break;
|
||||
}
|
||||
case SOCK_BOOLEAN: {
|
||||
socket.default_value_typed<bNodeSocketValueBoolean>()->value = std::get<bool>(value.value);
|
||||
break;
|
||||
}
|
||||
case SOCK_VECTOR: {
|
||||
copy_v3_v3(socket.default_value_typed<bNodeSocketValueVector>()->value,
|
||||
std::get<float3>(value.value));
|
||||
break;
|
||||
}
|
||||
case SOCK_RGBA: {
|
||||
copy_v4_v4(socket.default_value_typed<bNodeSocketValueRGBA>()->value,
|
||||
std::get<ColorGeometry4f>(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<int, int> num_by_depth;
|
||||
Map<bNode *, int> 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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<bNodeSocket *>(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<bNodeSocketValueVector *>(socket->default_value);
|
||||
dst_rgba = static_cast<bNodeSocketValueRGBA *>(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<bNodeSocketValueRGBA *>(socket->default_value);
|
||||
dst_rgba = static_cast<bNodeSocketValueRGBA *>(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<bNodeSocketValueBoolean *>(socket->default_value);
|
||||
dst_float = static_cast<bNodeSocketValueFloat *>(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<bNodeSocketValueInt *>(socket->default_value);
|
||||
dst_float = static_cast<bNodeSocketValueFloat *>(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<bNodeSocketValueFloat *>(socket->default_value);
|
||||
dst_float = static_cast<bNodeSocketValueFloat *>(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<bNodeSocket *>(tmp->inputs.first));
|
||||
blender::bke::node_add_link(*localtree,
|
||||
*tmp,
|
||||
*static_cast<bNodeSocket *>(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<bNodeSocket *>(tmp->inputs.first);
|
||||
bNodeSocket *dot_input2 = static_cast<bNodeSocket *>(dot_input1->next);
|
||||
bNodeSocketValueVector *input2_socket_value = static_cast<bNodeSocketValueVector *>(
|
||||
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<bNodeSocket *>(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<bNodeTree *>(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<bNodeLink *>(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<bNodeLink *>(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<bNodeLink *>(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<bNodeLink *>(ntree->links.last);
|
||||
/* output links */
|
||||
for (bNodeLink *tlink = static_cast<bNodeLink *>(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<bNode *>(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<bNode *>(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. */
|
||||
|
||||
BIN
tests/files/render/node_inlining/closures.blend
(Stored with Git LFS)
Normal file
BIN
tests/files/render/node_inlining/closures.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/node_inlining/cycles_renders/closures.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/node_inlining/cycles_renders/closures.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/node_inlining/cycles_renders/repeat_zones.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/node_inlining/cycles_renders/repeat_zones.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/node_inlining/eevee_renders/closures.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/node_inlining/eevee_renders/closures.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/node_inlining/eevee_renders/repeat_zones.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/node_inlining/eevee_renders/repeat_zones.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/node_inlining/repeat_zones.blend
(Stored with Git LFS)
Normal file
BIN
tests/files/render/node_inlining/repeat_zones.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/node_inlining/storm_hydra_renders/closures.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/node_inlining/storm_hydra_renders/closures.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/node_inlining/storm_hydra_renders/repeat_zones.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/node_inlining/storm_hydra_renders/repeat_zones.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/node_inlining/storm_usd_renders/closures.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/node_inlining/storm_usd_renders/closures.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/node_inlining/storm_usd_renders/repeat_zones.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/node_inlining/storm_usd_renders/repeat_zones.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/node_inlining/workbench_renders/closures.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/node_inlining/workbench_renders/closures.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/node_inlining/workbench_renders/repeat_zones.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/node_inlining/workbench_renders/repeat_zones.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/shader/storm_hydra_renders/float_math.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_hydra_renders/float_math.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_hydra_renders/rgb_curves_input_0.5.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_hydra_renders/rgb_curves_input_0.5.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_hydra_renders/rgb_curves_input_1.2.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_hydra_renders/rgb_curves_input_1.2.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_hydra_renders/rgb_curves_input_2.0_extrapolate.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_hydra_renders/rgb_curves_input_2.0_extrapolate.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_0.5.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_0.5.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_1.2.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_1.2.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_2.0_extrapolate.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_hydra_renders/rgb_curves_input_neg_2.0_extrapolate.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_hydra_renders/vector_math.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_hydra_renders/vector_math.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_usd_renders/float_math.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_usd_renders/float_math.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_usd_renders/rgb_curves_input_0.5.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_usd_renders/rgb_curves_input_0.5.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_usd_renders/rgb_curves_input_1.2.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_usd_renders/rgb_curves_input_1.2.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_usd_renders/rgb_curves_input_2.0_extrapolate.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_usd_renders/rgb_curves_input_2.0_extrapolate.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_0.5.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_0.5.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_1.2.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_1.2.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_2.0_extrapolate.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_usd_renders/rgb_curves_input_neg_2.0_extrapolate.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/shader/storm_usd_renders/vector_math.png
(Stored with Git LFS)
BIN
tests/files/render/shader/storm_usd_renders/vector_math.png
(Stored with Git LFS)
Binary file not shown.
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user