Shader Nodes: improve preservation of repeat zones in shader nodes inlining

These are some cleanups and fixes extracted from #146068. Preserved repeat
zones are not used by any render engine yet.

Pull Request: https://projects.blender.org/blender/blender/pulls/146890
This commit is contained in:
Jacques Lucke
2025-09-27 21:02:29 +02:00
parent 3bed61b33c
commit ce85d4cbaf
2 changed files with 118 additions and 56 deletions

View File

@@ -13,6 +13,12 @@ namespace blender::nodes {
struct InlineShaderNodeTreeParams {
bool allow_preserving_repeat_zones = false;
/**
* In general, only a constant number of iterations per repeat zone is allowed, because otherwise
* it can't be inlined. However, if the render engine supports repeat zones natively, it could
* also support a dynamic number of iterations.
*/
bool dynamic_repeat_zone_iterations_is_error = true;
struct ErrorMessage {
/* In theory, more contextual information could be added here like the entire context path to

View File

@@ -256,7 +256,8 @@ class ShaderNodesInliner {
});
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->set_socket_value(
*src_node, *copied_node, *copied_socket, value_by_socket_.lookup(socket));
}
this->restore_zones_in_output_tree();
@@ -370,6 +371,13 @@ class ShaderNodesInliner {
const bke::bNodeTreeZone *from_zone = zones->get_zone_by_socket(*link.fromsock);
const ComputeContext *context = to_socket.context;
for (const bke::bNodeTreeZone *zone = to_zone; zone != from_zone; zone = zone->parent_zone) {
const bNode &zone_output_node = *zone->output_node();
if (zone_output_node.is_type("GeometryNodeRepeatOutput")) {
if (this->should_preserve_repeat_zone_node(zone_output_node)) {
/* Preserved repeat zones are embedded into their outer compute context. */
continue;
}
}
context = parent_zone_contexts_.lookup(context);
}
return context;
@@ -395,16 +403,20 @@ class ShaderNodesInliner {
return;
}
if (node->is_type("GeometryNodeRepeatOutput")) {
if (!this->should_preserve_repeat_zone_node(*node)) {
this->handle_output_socket__repeat_output(socket);
if (this->should_preserve_repeat_zone_node(*node)) {
this->handle_output_socket__preserved_repeat_output(socket);
return;
}
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);
if (this->should_preserve_repeat_zone_node(*node)) {
this->handle_output_socket__preserved_repeat_input(socket);
return;
}
this->handle_output_socket__repeat_input(socket);
return;
}
if (node->is_type("NodeClosureOutput")) {
this->handle_output_socket__closure_output(socket);
@@ -517,8 +529,9 @@ class ShaderNodesInliner {
if (!zone) {
return false;
}
const bNode *repeat_zone_input_node = zone->input_node();
const bNode *repeat_zone_output_node = zone->output_node();
if (!repeat_zone_output_node) {
if (!repeat_zone_input_node || !repeat_zone_output_node) {
return false;
}
const auto &storage = *static_cast<const NodeGeometryRepeatOutput *>(
@@ -561,8 +574,7 @@ class ShaderNodesInliner {
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")});
this->add_dynamic_repeat_zone_iterations_error(*repeat_input_node);
return;
}
const int iterations = std::get<int>(iterations_value_opt->value);
@@ -582,6 +594,51 @@ class ShaderNodesInliner {
this->forward_value_or_schedule(socket, origin_socket);
}
void handle_output_socket__preserved_repeat_output(const SocketInContext &socket)
{
const bNodeTree &tree = socket->owner_tree();
const NodeInContext repeat_output_node = socket.owner_node();
const bke::bNodeTreeZones &zones = *tree.zones();
const bke::bNodeTreeZone &zone = *zones.get_zone_by_node(repeat_output_node->identifier);
const bNode &repeat_input_node = *zone.input_node();
const EnsureInputsResult ensured_inputs = this->ensure_node_inputs(socket.owner_node());
if (ensured_inputs.has_missing_inputs) {
/* The node can only be evaluated if all inputs values are known. */
return;
}
const NodeInContext node = socket.owner_node();
bNode &copied_node = this->handle_output_socket__eval_copy_node(node);
PreservedZone &preserved_zone = copied_zone_by_zone_output_node_.lookup_or_add_default(
repeat_output_node);
preserved_zone.output_node = &copied_node;
/* Ensure that the repeat input node is created as well. */
this->schedule_socket({node.context, &repeat_input_node.output_socket(0)});
}
void handle_output_socket__preserved_repeat_input(const SocketInContext &socket)
{
const EnsureInputsResult ensured_inputs = this->ensure_node_inputs(socket.owner_node());
if (ensured_inputs.has_missing_inputs) {
/* The node can only be evaluated if all inputs values are known. */
return;
}
const bNodeTree &tree = socket->owner_tree();
const NodeInContext node = socket.owner_node();
bNode &copied_node = this->handle_output_socket__eval_copy_node(node);
const auto &storage = *static_cast<const NodeGeometryRepeatInput *>(node->storage);
const NodeInContext repeat_output_node{node.context, tree.node_by_id(storage.output_node_id)};
PreservedZone &preserved_zone = copied_zone_by_zone_output_node_.lookup_or_add_default(
repeat_output_node);
preserved_zone.input_node = &copied_node;
}
void add_dynamic_repeat_zone_iterations_error(const bNode &repeat_input_node)
{
params_.r_error_messages.append(
{&repeat_input_node, TIP_("Iterations input has to be a constant value")});
}
void handle_output_socket__repeat_input(const SocketInContext &socket)
{
const bNode &repeat_input_node = socket->owner_node();
@@ -800,29 +857,13 @@ class ShaderNodesInliner {
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) {
const EnsureInputsResult ensured_inputs = this->ensure_node_inputs(node);
if (ensured_inputs.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) {
if (node_type.build_multi_function && ensured_inputs.all_inputs_primitive) {
/* Do constant folding. */
this->handle_output_socket__eval_multi_function(node);
return;
@@ -831,6 +872,34 @@ class ShaderNodesInliner {
this->handle_output_socket__eval_copy_node(node);
}
struct EnsureInputsResult {
bool has_missing_inputs = false;
bool all_inputs_primitive = false;
};
EnsureInputsResult ensure_node_inputs(const NodeInContext &node)
{
EnsureInputsResult result;
result.has_missing_inputs = false;
result.all_inputs_primitive = true;
for (const bNodeSocket *input_socket : node->input_sockets()) {
if (!input_socket->is_available()) {
continue;
}
const SocketInContext input_socket_ctx = {node.context, input_socket};
const SocketValue *value = value_by_socket_.lookup_ptr(input_socket_ctx);
if (!value) {
this->schedule_socket(input_socket_ctx);
result.has_missing_inputs = true;
continue;
}
if (!value->to_primitive(*input_socket->typeinfo)) {
result.all_inputs_primitive = false;
}
}
return result;
}
void handle_output_socket__eval_multi_function(const NodeInContext &node)
{
NodeMultiFunctionBuilder builder{*node.node, node->owner_tree()};
@@ -879,7 +948,7 @@ class ShaderNodesInliner {
}
}
void handle_output_socket__eval_copy_node(const NodeInContext &node)
bNode &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
@@ -906,7 +975,7 @@ class ShaderNodesInliner {
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);
this->set_socket_value(*node, copied_node, dst_input_socket, value);
}
for (const bNodeSocket *src_output_socket : node->output_sockets()) {
if (!src_output_socket->is_available()) {
@@ -917,32 +986,7 @@ class ShaderNodesInliner {
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;
}
return copied_node;
}
/** Converts the given socket value if necessary. */
@@ -989,7 +1033,10 @@ class ShaderNodesInliner {
return SocketValue{FallbackValue{}};
}
void set_socket_value(bNode &dst_node, bNodeSocket &dst_socket, const SocketValue &value)
void set_socket_value(const bNode &original_node,
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)) {
@@ -1019,6 +1066,15 @@ class ShaderNodesInliner {
}
return;
}
if (params_.dynamic_repeat_zone_iterations_is_error) {
const bool is_iterations_input = dst_node.inputs.first == &dst_socket &&
dst_node.is_type("GeometryNodeRepeatInput");
if (is_iterations_input) {
this->add_dynamic_repeat_zone_iterations_error(original_node);
this->set_primitive_value_on_socket(dst_socket, PrimitiveSocketValue{0});
return;
}
}
if (std::get_if<InputSocketValue>(&value.value)) {
/* Cases were the input has a primitive value are handled above. */
return;