diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 62de4261c0bd..1dee1b8ce5b7 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -33,6 +33,7 @@ #include "core/templates/rb_map.h" #include "core/templates/vmap.h" #include "core/variant/variant_utility.h" +#include "scene/resources/visual_shader_group.h" #include "servers/rendering/shader_types.h" #include "visual_shader_nodes.h" #include "visual_shader_particle_nodes.h" @@ -205,239 +206,826 @@ void ShaderGraph::_get_property_list(List *p_list) const { p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "nodes/connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } -bool ShaderGraph::_check_reroute_subgraph(int p_target_port_type, int p_reroute_node, List *r_visited_reroute_nodes) const { - // BFS to check whether connecting to the given subgraph (rooted at p_reroute_node) is valid. - List queue; - queue.push_back(p_reroute_node); - if (r_visited_reroute_nodes != nullptr) { - r_visited_reroute_nodes->push_back(p_reroute_node); - } - while (!queue.is_empty()) { - int current_node_id = queue.front()->get(); - ShaderGraph::Node current_node = nodes[current_node_id]; - queue.pop_front(); - for (const int &next_node_id : current_node.next_connected_nodes) { - Ref next_vsnode = nodes[next_node_id].node; - if (next_vsnode.is_valid()) { - queue.push_back(next_node_id); - if (r_visited_reroute_nodes != nullptr) { - r_visited_reroute_nodes->push_back(next_node_id); - } - continue; - } - // Check whether all ports connected with the reroute node are compatible. - for (const ShaderGraph::Connection &c : connections) { - VisualShaderNode::PortType to_port_type = nodes[next_node_id].node->get_input_port_type(c.to_port); - if (c.from_node == current_node_id && - c.to_node == next_node_id && - !is_port_types_compatible(p_target_port_type, to_port_type)) { - return false; - } - } - } - } - return true; -} - -void ShaderGraph::add_node(const Ref &p_node, const Vector2 &p_position, int p_id) { - ERR_FAIL_COND(p_node.is_null()); - ERR_FAIL_COND(p_id < 2); // Reserved for the output node. - ERR_FAIL_COND(nodes.has(p_id)); - - ShaderGraph::Node n; - n.node = p_node; - n.position = p_position; - - Ref parameter = n.node; - if (parameter.is_valid()) { - String valid_name = validate_parameter_name(parameter->get_parameter_name(), parameter); - parameter->set_parameter_name(valid_name); - } - - Ref custom = n.node; - if (custom.is_valid()) { - custom->update_ports(); - } - - nodes[p_id] = n; - - n.node->connect_changed(callable_mp(this, &ShaderGraph::_node_changed)); - emit_signal("graph_changed"); -} - -void ShaderGraph::set_node_position(int p_id, const Vector2 &p_position) { - ERR_FAIL_COND(!nodes.has(p_id)); - nodes[p_id].position = p_position; -} - -Vector2 ShaderGraph::get_node_position(int p_id) const { - ERR_FAIL_COND_V(!nodes.has(p_id), Vector2()); - return nodes[p_id].position; -} - -Ref ShaderGraph::get_node(int p_id) const { - if (!nodes.has(p_id)) { - return Ref(); - } - ERR_FAIL_COND_V(!nodes.has(p_id), Ref()); - return nodes[p_id].node; -} +// TODO: Refactor (simplify, rename and change comment style) +Error ShaderGraph::_write_node( + StringBuilder *p_global_code, + StringBuilder *p_global_code_per_node, + HashMap *p_global_code_per_func, + StringBuilder &r_code, Vector &r_def_tex_params, + const VMap::Element *> &p_input_connections, + const VMap::Element *> &p_output_connections, + int p_node, + HashSet &r_processed, + bool p_for_preview, + HashSet &r_classes, + Type p_type, + Shader::Mode p_mode) const { + const Ref vsnode = nodes[p_node].node; -Vector ShaderGraph::get_node_ids() const { - Vector ret; - for (const KeyValue &E : nodes) { - ret.push_back(E.key); + if (vsnode->is_disabled()) { + r_code += "// " + vsnode->get_caption() + ":" + itos(p_node) + "\n"; + r_code += " // Node is disabled and code is not generated.\n"; + return OK; } - return ret; -} + // Check inputs recursively first. + int input_count = vsnode->get_input_port_count(); + for (int i = 0; i < input_count; i++) { + ConnectionKey ck; + ck.node = p_node; + ck.port = i; -int ShaderGraph::get_valid_node_id() const { - return nodes.size() ? MAX(2, nodes.back()->key() + 1) : 2; -} + if (p_input_connections.has(ck)) { + int from_node = p_input_connections[ck]->get().from_node; + if (r_processed.has(from_node)) { + continue; + } -int ShaderGraph::find_node_id(const Ref &p_node) const { - for (const KeyValue &E : nodes) { - if (E.value.node == p_node) { - return E.key; + Error err = _write_node(p_global_code, p_global_code_per_node, p_global_code_per_func, r_code, r_def_tex_params, p_input_connections, p_output_connections, from_node, r_processed, p_for_preview, r_classes, p_type, p_mode); + if (err) { + return err; + } } } - return NODE_ID_INVALID; -} + // Then this node. -void ShaderGraph::remove_node(int p_id) { - ERR_FAIL_COND(!nodes.has(p_id)); + Vector params = vsnode->get_default_texture_parameters((VisualShader::Type)p_type, p_node); + for (int i = 0; i < params.size(); i++) { + r_def_tex_params.push_back(params[i]); + } - nodes.erase(p_id); + Ref input = vsnode; + bool skip_global = input.is_valid() && p_for_preview; - for (List::Element *E = connections.front(); E;) { - List::Element *N = E->next(); - const ShaderGraph::Connection &connection = E->get(); - if (connection.from_node == p_id || connection.to_node == p_id) { - if (connection.from_node == p_id) { - nodes[connection.to_node].prev_connected_nodes.erase(p_id); - nodes[connection.to_node].node->set_input_port_connected(connection.to_port, false); - } else if (connection.to_node == p_id) { - nodes[connection.from_node].next_connected_nodes.erase(p_id); - nodes[connection.from_node].node->set_output_port_connected(connection.from_port, false); + if (!skip_global) { + Ref parameter = vsnode; + if (!parameter.is_valid() || !parameter->is_global_code_generated()) { + if (p_global_code) { + *p_global_code += vsnode->generate_global(p_mode, (VisualShader::Type)p_type, p_node); } - connections.erase(E); } - E = N; - } - - emit_signal("graph_changed"); -} - -void ShaderGraph::replace_node(int p_id, const StringName &p_new_class) { - ERR_FAIL_COND(!nodes.has(p_id)); - - if (nodes[p_id].node->get_class_name() == p_new_class) { - return; - } - VisualShaderNode *vsn = Object::cast_to(ClassDB::instantiate(p_new_class)); - VisualShaderNode *prev_vsn = nodes[p_id].node.ptr(); - // Update connection data. - for (int i = 0; i < vsn->get_output_port_count(); i++) { - if (i < prev_vsn->get_output_port_count()) { - if (prev_vsn->is_output_port_connected(i)) { - vsn->set_output_port_connected(i, true); + String class_name = vsnode->get_class_name(); + if (class_name == "VisualShaderNodeCustom") { + class_name = vsnode->get_script_instance()->get_script()->get_path(); + } + if (!r_classes.has(class_name)) { + if (p_global_code_per_node) { + *p_global_code_per_node += vsnode->generate_global_per_node(p_mode, p_node); } - - if (prev_vsn->is_output_port_expandable(i) && prev_vsn->_is_output_port_expanded(i) && vsn->is_output_port_expandable(i)) { - vsn->_set_output_port_expanded(i, true); - - int component_count = 0; - switch (prev_vsn->get_output_port_type(i)) { - case VisualShaderNode::PORT_TYPE_VECTOR_2D: - component_count = 2; - break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: - component_count = 3; - break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: - component_count = 4; - break; - default: - break; + for (int i = 0; i < VisualShader::TYPE_MAX; i++) { + if (p_global_code_per_func) { + (*p_global_code_per_func)[Type(i)] += vsnode->generate_global_per_func(p_mode, VisualShader::Type(i), p_node); } + } + r_classes.insert(class_name); + } - for (int j = 0; j < component_count; j++) { - int sub_port = i + 1 + j; - - if (prev_vsn->is_output_port_connected(sub_port)) { - vsn->set_output_port_connected(sub_port, true); - } + // Generate node group functions only once globally. + Ref group = vsnode; + if (group.is_valid()) { + // TODO: Use UID for group function names. + if (!r_classes.has("GROUP_" + group->get_group()->get_group_name())) { + if (p_global_code_per_node) { + *p_global_code_per_node += group->generate_group_function(p_mode, (VisualShader::Type)p_type, p_node); } - - i += component_count; + r_classes.insert("GROUP_" + group->get_group()->get_group_name()); } - } else { - break; } } - nodes[p_id].node = Ref(vsn); - - emit_signal("graph_changed"); -} - -bool ShaderGraph::are_nodes_connected(int p_from_node, int p_from_port, int p_to_node, int p_to_port) const { - for (const ShaderGraph::Connection &E : connections) { - if (E.from_node == p_from_node && E.from_port == p_from_port && E.to_node == p_to_node && E.to_port == p_to_port) { - return true; - } + if (!vsnode->is_code_generated()) { // Just generate globals and ignore locals. + r_processed.insert(p_node); + return OK; } - return false; -} - -bool ShaderGraph::is_nodes_connected_relatively(int p_node, int p_target) const { - bool result = false; - - const ShaderGraph::Node &node = nodes[p_node]; - - for (const int &E : node.prev_connected_nodes) { - if (E == p_target) { - return true; - } + String node_name = "// " + vsnode->get_caption() + ":" + itos(p_node) + "\n"; + String node_code; + Vector input_vars; - result = is_nodes_connected_relatively(E, p_target); - if (result) { - break; - } - } - return result; -} + input_vars.resize(vsnode->get_input_port_count()); + String *inputs = input_vars.ptrw(); -bool ShaderGraph::can_connect_nodes(int p_from_node, int p_from_port, int p_to_node, int p_to_port) const { - if (!nodes.has(p_from_node)) { - return false; - } + for (int i = 0; i < input_count; i++) { + ConnectionKey ck; + ck.node = p_node; + ck.port = i; - if (p_from_node == p_to_node) { - return false; - } + if (p_input_connections.has(ck)) { + // Connected to something, use that output. + int from_node = p_input_connections[ck]->get().from_node; - if (p_from_port < 0 || p_from_port >= nodes[p_from_node].node->get_expanded_output_port_count()) { - return false; - } + if (nodes[from_node].node->is_disabled()) { + continue; + } - if (!nodes.has(p_to_node)) { - return false; - } + int from_port = p_input_connections[ck]->get().from_port; - if (p_to_port < 0 || p_to_port >= nodes[p_to_node].node->get_input_port_count()) { - return false; - } + VisualShaderNode::PortType in_type = vsnode->get_input_port_type(i); + VisualShaderNode::PortType out_type = nodes[from_node].node->get_output_port_type(from_port); - VisualShaderNode::PortType from_port_type = nodes[p_from_node].node->get_output_port_type(p_from_port); - VisualShaderNode::PortType to_port_type = nodes[p_to_node].node->get_input_port_type(p_to_port); + String src_var = "n_out" + itos(from_node) + "p" + itos(from_port); - Ref to_node_reroute = nodes[p_to_node].node; - if (to_node_reroute.is_valid()) { + if (in_type == VisualShaderNode::PORT_TYPE_SAMPLER && out_type == VisualShaderNode::PORT_TYPE_SAMPLER) { + VisualShaderNode *ptr = const_cast(nodes[from_node].node.ptr()); + // FIXME: This needs to be refactored at some point. + if (ptr->has_method("get_input_real_name")) { + inputs[i] = ptr->call("get_input_real_name"); + } else if (ptr->has_method("get_parameter_name")) { + inputs[i] = ptr->call("get_parameter_name"); + } else { + Ref reroute = nodes[from_node].node; + if (reroute.is_valid()) { + inputs[i] = get_reroute_parameter_name(from_node); + } else { + inputs[i] = ""; + } + } + } else if (in_type == out_type) { + inputs[i] = src_var; + } else { + switch (in_type) { + case VisualShaderNode::PORT_TYPE_SCALAR: { + switch (out_type) { + case VisualShaderNode::PORT_TYPE_SCALAR_INT: { + inputs[i] = "float(" + src_var + ")"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { + inputs[i] = "float(" + src_var + ")"; + } break; + case VisualShaderNode::PORT_TYPE_BOOLEAN: { + inputs[i] = "(" + src_var + " ? 1.0 : 0.0)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + inputs[i] = src_var + ".x"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + inputs[i] = src_var + ".x"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + inputs[i] = src_var + ".x"; + } break; + default: + break; + } + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_INT: { + switch (out_type) { + case VisualShaderNode::PORT_TYPE_SCALAR: { + inputs[i] = "int(" + src_var + ")"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { + inputs[i] = "int(" + src_var + ")"; + } break; + case VisualShaderNode::PORT_TYPE_BOOLEAN: { + inputs[i] = "(" + src_var + " ? 1 : 0)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + inputs[i] = "int(" + src_var + ".x)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + inputs[i] = "int(" + src_var + ".x)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + inputs[i] = "int(" + src_var + ".x)"; + } break; + default: + break; + } + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { + switch (out_type) { + case VisualShaderNode::PORT_TYPE_SCALAR: { + inputs[i] = "uint(" + src_var + ")"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_INT: { + inputs[i] = "uint(" + src_var + ")"; + } break; + case VisualShaderNode::PORT_TYPE_BOOLEAN: { + inputs[i] = "(" + src_var + " ? 1u : 0u)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + inputs[i] = "uint(" + src_var + ".x)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + inputs[i] = "uint(" + src_var + ".x)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + inputs[i] = "uint(" + src_var + ".x)"; + } break; + default: + break; + } + } break; + case VisualShaderNode::PORT_TYPE_BOOLEAN: { + switch (out_type) { + case VisualShaderNode::PORT_TYPE_SCALAR: { + inputs[i] = src_var + " > 0.0 ? true : false"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_INT: { + inputs[i] = src_var + " > 0 ? true : false"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { + inputs[i] = src_var + " > 0u ? true : false"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + inputs[i] = "all(bvec2(" + src_var + "))"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + inputs[i] = "all(bvec3(" + src_var + "))"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + inputs[i] = "all(bvec4(" + src_var + "))"; + } break; + default: + break; + } + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + switch (out_type) { + case VisualShaderNode::PORT_TYPE_SCALAR: { + inputs[i] = "vec2(" + src_var + ")"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_INT: { + inputs[i] = "vec2(float(" + src_var + "))"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { + inputs[i] = "vec2(float(" + src_var + "))"; + } break; + case VisualShaderNode::PORT_TYPE_BOOLEAN: { + inputs[i] = "vec2(" + src_var + " ? 1.0 : 0.0)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + inputs[i] = "vec2(" + src_var + ".xy)"; + } break; + default: + break; + } + } break; + + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + switch (out_type) { + case VisualShaderNode::PORT_TYPE_SCALAR: { + inputs[i] = "vec3(" + src_var + ")"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_INT: { + inputs[i] = "vec3(float(" + src_var + "))"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { + inputs[i] = "vec3(float(" + src_var + "))"; + } break; + case VisualShaderNode::PORT_TYPE_BOOLEAN: { + inputs[i] = "vec3(" + src_var + " ? 1.0 : 0.0)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + inputs[i] = "vec3(" + src_var + ", 0.0)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + inputs[i] = "vec3(" + src_var + ".xyz)"; + } break; + default: + break; + } + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + switch (out_type) { + case VisualShaderNode::PORT_TYPE_SCALAR: { + inputs[i] = "vec4(" + src_var + ")"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_INT: { + inputs[i] = "vec4(float(" + src_var + "))"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { + inputs[i] = "vec4(float(" + src_var + "))"; + } break; + case VisualShaderNode::PORT_TYPE_BOOLEAN: { + inputs[i] = "vec4(" + src_var + " ? 1.0 : 0.0)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + inputs[i] = "vec4(" + src_var + ", 0.0, 0.0)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + inputs[i] = "vec4(" + src_var + ", 0.0)"; + } break; + default: + break; + } + } break; + default: + break; + } + } + } else { + if (!vsnode->is_generate_input_var(i)) { + continue; + } + + Variant defval = vsnode->get_input_port_default_value(i); + if (defval.get_type() == Variant::FLOAT) { + float val = defval; + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); + node_code += " float " + inputs[i] + " = " + vformat("%.5f", val) + ";\n"; + } else if (defval.get_type() == Variant::INT) { + int val = defval; + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); + if (vsnode->get_input_port_type(i) == VisualShaderNode::PORT_TYPE_SCALAR_UINT) { + node_code += " uint " + inputs[i] + " = " + itos(val) + "u;\n"; + } else { + node_code += " int " + inputs[i] + " = " + itos(val) + ";\n"; + } + } else if (defval.get_type() == Variant::BOOL) { + bool val = defval; + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); + node_code += " bool " + inputs[i] + " = " + (val ? "true" : "false") + ";\n"; + } else if (defval.get_type() == Variant::VECTOR2) { + Vector2 val = defval; + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); + node_code += " vec2 " + inputs[i] + " = " + vformat("vec2(%.5f, %.5f);\n", val.x, val.y); + } else if (defval.get_type() == Variant::VECTOR3) { + Vector3 val = defval; + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); + node_code += " vec3 " + inputs[i] + " = " + vformat("vec3(%.5f, %.5f, %.5f);\n", val.x, val.y, val.z); + } else if (defval.get_type() == Variant::VECTOR4) { + Vector4 val = defval; + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); + node_code += " vec4 " + inputs[i] + " = " + vformat("vec4(%.5f, %.5f, %.5f, %.5f);\n", val.x, val.y, val.z, val.w); + } else if (defval.get_type() == Variant::QUATERNION) { + Quaternion val = defval; + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); + node_code += " vec4 " + inputs[i] + " = " + vformat("vec4(%.5f, %.5f, %.5f, %.5f);\n", val.x, val.y, val.z, val.w); + } else if (defval.get_type() == Variant::TRANSFORM3D) { + Transform3D val = defval; + val.basis.transpose(); + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); + Array values; + for (int j = 0; j < 3; j++) { + values.push_back(val.basis[j].x); + values.push_back(val.basis[j].y); + values.push_back(val.basis[j].z); + } + values.push_back(val.origin.x); + values.push_back(val.origin.y); + values.push_back(val.origin.z); + bool err = false; + node_code += " mat4 " + inputs[i] + " = " + String("mat4(vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 1.0));\n").sprintf(values, &err); + } else { + // TODO: Cleanup + // Will go empty, node is expected to know what it is doing at this point and handle it. + } + } + } + + int output_count = vsnode->get_output_port_count(); + int initial_output_count = output_count; + + HashMap expanded_output_ports; + + for (int i = 0; i < initial_output_count; i++) { + bool expanded = false; + + if (vsnode->is_output_port_expandable(i) && vsnode->_is_output_port_expanded(i)) { + expanded = true; + + switch (vsnode->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + output_count += 2; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + output_count += 3; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + output_count += 4; + } break; + default: + break; + } + } + expanded_output_ports.insert(i, expanded); + } + + Vector output_vars; + output_vars.resize(output_count); + String *outputs = output_vars.ptrw(); + + if (vsnode->is_simple_decl()) { // Less code to generate for some simple_decl nodes. + for (int i = 0, j = 0; i < initial_output_count; i++, j++) { + String var_name = "n_out" + itos(p_node) + "p" + itos(j); + switch (vsnode->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_SCALAR: + outputs[i] = "float " + var_name; + break; + case VisualShaderNode::PORT_TYPE_SCALAR_INT: + outputs[i] = "int " + var_name; + break; + case VisualShaderNode::PORT_TYPE_SCALAR_UINT: + outputs[i] = "uint " + var_name; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: + outputs[i] = "vec2 " + var_name; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: + outputs[i] = "vec3 " + var_name; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: + outputs[i] = "vec4 " + var_name; + break; + case VisualShaderNode::PORT_TYPE_BOOLEAN: + outputs[i] = "bool " + var_name; + break; + case VisualShaderNode::PORT_TYPE_TRANSFORM: + outputs[i] = "mat4 " + var_name; + break; + default: + break; + } + if (expanded_output_ports[i]) { + switch (vsnode->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + j += 2; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + j += 3; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + j += 4; + } break; + default: + break; + } + } + } + + } else { + for (int i = 0, j = 0; i < initial_output_count; i++, j++) { + outputs[i] = "n_out" + itos(p_node) + "p" + itos(j); + switch (vsnode->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_SCALAR: + r_code += " float " + outputs[i] + ";\n"; + break; + case VisualShaderNode::PORT_TYPE_SCALAR_INT: + r_code += " int " + outputs[i] + ";\n"; + break; + case VisualShaderNode::PORT_TYPE_SCALAR_UINT: + r_code += " uint " + outputs[i] + ";\n"; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: + r_code += " vec2 " + outputs[i] + ";\n"; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: + r_code += " vec3 " + outputs[i] + ";\n"; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: + r_code += " vec4 " + outputs[i] + ";\n"; + break; + case VisualShaderNode::PORT_TYPE_BOOLEAN: + r_code += " bool " + outputs[i] + ";\n"; + break; + case VisualShaderNode::PORT_TYPE_TRANSFORM: + r_code += " mat4 " + outputs[i] + ";\n"; + break; + default: + break; + } + if (expanded_output_ports[i]) { + switch (vsnode->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + j += 2; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + j += 3; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + j += 4; + } break; + default: + break; + } + } + } + } + + node_code += vsnode->generate_code(p_mode, (VisualShader::Type)p_type, p_node, inputs, outputs, p_for_preview); + if (!node_code.is_empty()) { + r_code += node_name; + r_code += node_code; + } + + for (int i = 0; i < output_count; i++) { + if (expanded_output_ports[i]) { + switch (vsnode->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + if (vsnode->is_output_port_connected(i + 1) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component + String r = "n_out" + itos(p_node) + "p" + itos(i + 1); + r_code += " float " + r + " = n_out" + itos(p_node) + "p" + itos(i) + ".r;\n"; + outputs[i + 1] = r; + } + + if (vsnode->is_output_port_connected(i + 2) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component + String g = "n_out" + itos(p_node) + "p" + itos(i + 2); + r_code += " float " + g + " = n_out" + itos(p_node) + "p" + itos(i) + ".g;\n"; + outputs[i + 2] = g; + } + + i += 2; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + if (vsnode->is_output_port_connected(i + 1) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component + String r = "n_out" + itos(p_node) + "p" + itos(i + 1); + r_code += " float " + r + " = n_out" + itos(p_node) + "p" + itos(i) + ".r;\n"; + outputs[i + 1] = r; + } + + if (vsnode->is_output_port_connected(i + 2) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component + String g = "n_out" + itos(p_node) + "p" + itos(i + 2); + r_code += " float " + g + " = n_out" + itos(p_node) + "p" + itos(i) + ".g;\n"; + outputs[i + 2] = g; + } + + if (vsnode->is_output_port_connected(i + 3) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 3))) { // blue-component + String b = "n_out" + itos(p_node) + "p" + itos(i + 3); + r_code += " float " + b + " = n_out" + itos(p_node) + "p" + itos(i) + ".b;\n"; + outputs[i + 3] = b; + } + + i += 3; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + if (vsnode->is_output_port_connected(i + 1) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component + String r = "n_out" + itos(p_node) + "p" + itos(i + 1); + r_code += " float " + r + " = n_out" + itos(p_node) + "p" + itos(i) + ".r;\n"; + outputs[i + 1] = r; + } + + if (vsnode->is_output_port_connected(i + 2) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component + String g = "n_out" + itos(p_node) + "p" + itos(i + 2); + r_code += " float " + g + " = n_out" + itos(p_node) + "p" + itos(i) + ".g;\n"; + outputs[i + 2] = g; + } + + if (vsnode->is_output_port_connected(i + 3) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 3))) { // blue-component + String b = "n_out" + itos(p_node) + "p" + itos(i + 3); + r_code += " float " + b + " = n_out" + itos(p_node) + "p" + itos(i) + ".b;\n"; + outputs[i + 3] = b; + } + + if (vsnode->is_output_port_connected(i + 4) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 4))) { // alpha-component + String a = "n_out" + itos(p_node) + "p" + itos(i + 4); + r_code += " float " + a + " = n_out" + itos(p_node) + "p" + itos(i) + ".a;\n"; + outputs[i + 4] = a; + } + + i += 4; + } break; + default: + break; + } + } + } + + if (!node_code.is_empty()) { + r_code += "\n\n"; + } + + r_processed.insert(p_node); + + return OK; +} + +bool ShaderGraph::_check_reroute_subgraph(int p_target_port_type, int p_reroute_node, List *r_visited_reroute_nodes) const { + // BFS to check whether connecting to the given subgraph (rooted at p_reroute_node) is valid. + List queue; + queue.push_back(p_reroute_node); + if (r_visited_reroute_nodes != nullptr) { + r_visited_reroute_nodes->push_back(p_reroute_node); + } + while (!queue.is_empty()) { + int current_node_id = queue.front()->get(); + ShaderGraph::Node current_node = nodes[current_node_id]; + queue.pop_front(); + for (const int &next_node_id : current_node.next_connected_nodes) { + Ref next_vsnode = nodes[next_node_id].node; + if (next_vsnode.is_valid()) { + queue.push_back(next_node_id); + if (r_visited_reroute_nodes != nullptr) { + r_visited_reroute_nodes->push_back(next_node_id); + } + continue; + } + // Check whether all ports connected with the reroute node are compatible. + for (const ShaderGraph::Connection &c : connections) { + VisualShaderNode::PortType to_port_type = nodes[next_node_id].node->get_input_port_type(c.to_port); + if (c.from_node == current_node_id && + c.to_node == next_node_id && + !is_port_types_compatible(p_target_port_type, to_port_type)) { + return false; + } + } + } + } + return true; +} + +void ShaderGraph::add_node(const Ref &p_node, const Vector2 &p_position, int p_id) { + ERR_FAIL_COND(p_node.is_null()); + ERR_FAIL_COND(p_id < 2); // Reserved for the output node. + ERR_FAIL_COND(nodes.has(p_id)); + + ShaderGraph::Node n; + n.node = p_node; + n.position = p_position; + + Ref parameter = n.node; + if (parameter.is_valid()) { + String valid_name = validate_parameter_name(parameter->get_parameter_name(), parameter); + parameter->set_parameter_name(valid_name); + } + + Ref custom = n.node; + if (custom.is_valid()) { + custom->update_ports(); + } + + nodes[p_id] = n; + + n.node->connect_changed(callable_mp(this, &ShaderGraph::_node_changed)); + emit_signal("graph_changed"); +} + +void ShaderGraph::set_node_position(int p_id, const Vector2 &p_position) { + ERR_FAIL_COND(!nodes.has(p_id)); + nodes[p_id].position = p_position; +} + +Vector2 ShaderGraph::get_node_position(int p_id) const { + ERR_FAIL_COND_V(!nodes.has(p_id), Vector2()); + return nodes[p_id].position; +} + +Ref ShaderGraph::get_node(int p_id) const { + if (!nodes.has(p_id)) { + return Ref(); + } + ERR_FAIL_COND_V(!nodes.has(p_id), Ref()); + return nodes[p_id].node; +} + +Vector ShaderGraph::get_node_ids() const { + Vector ret; + for (const KeyValue &E : nodes) { + ret.push_back(E.key); + } + + return ret; +} + +int ShaderGraph::get_valid_node_id() const { + return nodes.size() ? MAX(2, nodes.back()->key() + 1) : 2; +} + +int ShaderGraph::find_node_id(const Ref &p_node) const { + for (const KeyValue &E : nodes) { + if (E.value.node == p_node) { + return E.key; + } + } + + return NODE_ID_INVALID; +} + +void ShaderGraph::remove_node(int p_id) { + ERR_FAIL_COND(!nodes.has(p_id)); + + nodes.erase(p_id); + + for (List::Element *E = connections.front(); E;) { + List::Element *N = E->next(); + const ShaderGraph::Connection &connection = E->get(); + if (connection.from_node == p_id || connection.to_node == p_id) { + if (connection.from_node == p_id) { + nodes[connection.to_node].prev_connected_nodes.erase(p_id); + nodes[connection.to_node].node->set_input_port_connected(connection.to_port, false); + } else if (connection.to_node == p_id) { + nodes[connection.from_node].next_connected_nodes.erase(p_id); + nodes[connection.from_node].node->set_output_port_connected(connection.from_port, false); + } + connections.erase(E); + } + E = N; + } + + emit_signal("graph_changed"); +} + +void ShaderGraph::replace_node(int p_id, const StringName &p_new_class) { + ERR_FAIL_COND(!nodes.has(p_id)); + + if (nodes[p_id].node->get_class_name() == p_new_class) { + return; + } + VisualShaderNode *vsn = Object::cast_to(ClassDB::instantiate(p_new_class)); + VisualShaderNode *prev_vsn = nodes[p_id].node.ptr(); + + // Update connection data. + for (int i = 0; i < vsn->get_output_port_count(); i++) { + if (i < prev_vsn->get_output_port_count()) { + if (prev_vsn->is_output_port_connected(i)) { + vsn->set_output_port_connected(i, true); + } + + if (prev_vsn->is_output_port_expandable(i) && prev_vsn->_is_output_port_expanded(i) && vsn->is_output_port_expandable(i)) { + vsn->_set_output_port_expanded(i, true); + + int component_count = 0; + switch (prev_vsn->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: + component_count = 2; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: + component_count = 3; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: + component_count = 4; + break; + default: + break; + } + + for (int j = 0; j < component_count; j++) { + int sub_port = i + 1 + j; + + if (prev_vsn->is_output_port_connected(sub_port)) { + vsn->set_output_port_connected(sub_port, true); + } + } + + i += component_count; + } + } else { + break; + } + } + + nodes[p_id].node = Ref(vsn); + + emit_signal("graph_changed"); +} + +bool ShaderGraph::are_nodes_connected(int p_from_node, int p_from_port, int p_to_node, int p_to_port) const { + for (const ShaderGraph::Connection &E : connections) { + if (E.from_node == p_from_node && E.from_port == p_from_port && E.to_node == p_to_node && E.to_port == p_to_port) { + return true; + } + } + + return false; +} + +bool ShaderGraph::is_nodes_connected_relatively(int p_node, int p_target) const { + bool result = false; + + const ShaderGraph::Node &node = nodes[p_node]; + + for (const int &E : node.prev_connected_nodes) { + if (E == p_target) { + return true; + } + + result = is_nodes_connected_relatively(E, p_target); + if (result) { + break; + } + } + return result; +} + +bool ShaderGraph::can_connect_nodes(int p_from_node, int p_from_port, int p_to_node, int p_to_port) const { + if (!nodes.has(p_from_node)) { + return false; + } + + if (p_from_node == p_to_node) { + return false; + } + + if (p_from_port < 0 || p_from_port >= nodes[p_from_node].node->get_expanded_output_port_count()) { + return false; + } + + if (!nodes.has(p_to_node)) { + return false; + } + + if (p_to_port < 0 || p_to_port >= nodes[p_to_node].node->get_input_port_count()) { + return false; + } + + VisualShaderNode::PortType from_port_type = nodes[p_from_node].node->get_output_port_type(p_from_port); + VisualShaderNode::PortType to_port_type = nodes[p_to_node].node->get_input_port_type(p_to_port); + + Ref to_node_reroute = nodes[p_to_node].node; + if (to_node_reroute.is_valid()) { if (!_check_reroute_subgraph(from_port_type, p_to_node)) { return false; } @@ -526,9 +1114,11 @@ void ShaderGraph::disconnect_nodes(int p_from_node, int p_from_port, int p_to_no void ShaderGraph::connect_nodes_forced(int p_from_node, int p_from_port, int p_to_node, int p_to_port) { ERR_FAIL_COND(!nodes.has(p_from_node)); - ERR_FAIL_INDEX(p_from_port, nodes[p_from_node].node->get_expanded_output_port_count()); + // ERR_FAIL_INDEX(p_from_port, nodes[p_from_node].node->get_expanded_output_port_count()); ERR_FAIL_COND(!nodes.has(p_to_node)); - ERR_FAIL_INDEX(p_to_port, nodes[p_to_node].node->get_input_port_count()); + // ERR_FAIL_INDEX(p_to_port, nodes[p_to_node].node->get_input_port_count()); + // TODO: The above two checks need to be disabled because the group input/output nodes won't have their group set until the whole graph is loaded. + // TODO: A solution would be to cache the input/output ports in the group input/output nodes. for (const ShaderGraph::Connection &E : connections) { if (E.from_node == p_from_node && E.from_port == p_from_port && E.to_node == p_to_node && E.to_port == p_to_port) { @@ -662,6 +1252,32 @@ String ShaderGraph::validate_parameter_name(const String &p_name, const Refcan_connect_nodes(p_from_node, p_from_port, p_to_node, p_to_port); } -bool VisualShader::is_port_types_compatible(int p_a, int p_b) const { - const Ref g = graph[0]; // Currently, this is independent of the type so we can use an arbitrary graph. - return g->is_port_types_compatible(p_a, p_b); -} - -void VisualShader::attach_node_to_frame(Type p_type, int p_node, int p_frame) { - ERR_FAIL_INDEX(p_type, TYPE_MAX); - ERR_FAIL_COND(p_frame < 0); - Ref g = graph[p_type]; - g->attach_node_to_frame(p_node, p_frame); -} - -void VisualShader::detach_node_from_frame(Type p_type, int p_node) { - ERR_FAIL_INDEX(p_type, TYPE_MAX); - Ref g = graph[p_type]; - g->detach_node_from_frame(p_node); -} - -String VisualShader::get_reroute_parameter_name(Type p_type, int p_reroute_node) const { - ERR_FAIL_INDEX_V(p_type, TYPE_MAX, ""); - const Ref g = graph[p_type]; - return g->get_reroute_parameter_name(p_reroute_node); -} - -void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { - ERR_FAIL_INDEX(p_type, TYPE_MAX); - Ref g = graph[p_type]; - g->connect_nodes_forced(p_from_node, p_from_port, p_to_node, p_to_port); - _queue_update(); -} - -Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { - ERR_FAIL_INDEX_V(p_type, TYPE_MAX, ERR_CANT_CONNECT); - Ref g = graph[p_type]; - const Error error = g->connect_nodes(p_from_node, p_from_port, p_to_node, p_to_port); - _queue_update(); - return error; -} - -void VisualShader::disconnect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { - ERR_FAIL_INDEX(p_type, TYPE_MAX); - Ref g = graph[p_type]; - g->disconnect_nodes(p_from_node, p_from_port, p_to_node, p_to_port); - _queue_update(); -} - -TypedArray VisualShader::_get_node_connections(Type p_type) const { - ERR_FAIL_INDEX_V(p_type, TYPE_MAX, Array()); - const Ref g = graph[p_type]; - - TypedArray ret; - for (const ShaderGraph::Connection &E : g->connections) { - Dictionary d; - d["from_node"] = E.from_node; - d["from_port"] = E.from_port; - d["to_node"] = E.to_node; - d["to_port"] = E.to_port; - ret.push_back(d); - } - - return ret; -} - -void VisualShader::get_node_connections(Type p_type, List *r_connections) const { - ERR_FAIL_INDEX(p_type, TYPE_MAX); - const Ref g = graph[p_type]; - g->get_node_connections(r_connections); -} - -void VisualShader::set_mode(Mode p_mode) { - ERR_FAIL_INDEX_MSG(p_mode, Mode::MODE_MAX, vformat("Invalid shader mode: %d.", p_mode)); - - if (shader_mode == p_mode) { - return; - } - - //erase input/output connections - modes.clear(); - flags.clear(); - shader_mode = p_mode; - for (int i = 0; i < TYPE_MAX; i++) { - for (KeyValue &E : graph[i]->nodes) { - Ref input = E.value.node; - if (input.is_valid()) { - input->shader_mode = shader_mode; - //input->input_index = 0; - } - } - - Ref output = graph[i]->nodes[ShaderGraph::NODE_ID_OUTPUT].node; - output->shader_mode = shader_mode; - - // clear connections since they are no longer valid - for (List::Element *E = graph[i]->connections.front(); E;) { - bool keep = true; - - List::Element *N = E->next(); - - int from = E->get().from_node; - int to = E->get().to_node; - - if (!graph[i]->nodes.has(from)) { - keep = false; - } else { - Ref from_node = graph[i]->nodes[from].node; - if (from_node->is_class("VisualShaderNodeOutput") || from_node->is_class("VisualShaderNodeInput")) { - keep = false; - } - } - - if (!graph[i]->nodes.has(to)) { - keep = false; - } else { - Ref to_node = graph[i]->nodes[to].node; - if (to_node->is_class("VisualShaderNodeOutput") || to_node->is_class("VisualShaderNodeInput")) { - keep = false; - } - } - - if (!keep) { - graph[i]->connections.erase(E); - } - E = N; - } - } - - _queue_update(); - notify_property_list_changed(); -} - -Shader::Mode VisualShader::get_mode() const { - return shader_mode; -} - -bool VisualShader::is_text_shader() const { - return false; -} - -String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port, Vector &default_tex_params) const { - Ref node = get_node(p_type, p_node); - ERR_FAIL_COND_V(!node.is_valid(), String()); - ERR_FAIL_COND_V(p_port < 0 || p_port >= node->get_expanded_output_port_count(), String()); - ERR_FAIL_COND_V(node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_TRANSFORM, String()); - - StringBuilder global_code; - StringBuilder global_code_per_node; - HashMap global_code_per_func; - StringBuilder shader_code; - HashSet classes; - - global_code += String() + "shader_type canvas_item;\n"; - - String global_expressions; - for (int i = 0, index = 0; i < TYPE_MAX; i++) { - for (const KeyValue &E : graph[i]->nodes) { - Ref global_expression = E.value.node; - if (global_expression.is_valid()) { - String expr = ""; - expr += "// " + global_expression->get_caption() + ":" + itos(index++) + "\n"; - expr += global_expression->generate_global(get_mode(), Type(i), -1); - expr = expr.replace("\n", "\n "); - expr += "\n"; - global_expressions += expr; - } - } - } - - global_code += "\n"; - global_code += global_expressions; - - //make it faster to go around through shader - VMap::Element *> input_connections; - VMap::Element *> output_connections; - - for (const List::Element *E = graph[p_type]->connections.front(); E; E = E->next()) { - ConnectionKey from_key; - from_key.node = E->get().from_node; - from_key.port = E->get().from_port; - - output_connections.insert(from_key, E); - - ConnectionKey to_key; - to_key.node = E->get().to_node; - to_key.port = E->get().to_port; - - input_connections.insert(to_key, E); - } - - shader_code += "\nvoid fragment() {\n"; - - HashSet processed; - Error err = _write_node(p_type, &global_code, &global_code_per_node, &global_code_per_func, shader_code, default_tex_params, input_connections, output_connections, p_node, processed, true, classes); - ERR_FAIL_COND_V(err != OK, String()); - - switch (node->get_output_port_type(p_port)) { - case VisualShaderNode::PORT_TYPE_SCALAR: { - shader_code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + ");\n"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_INT: { - shader_code += " COLOR.rgb = vec3(float(n_out" + itos(p_node) + "p" + itos(p_port) + "));\n"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { - shader_code += " COLOR.rgb = vec3(float(n_out" + itos(p_node) + "p" + itos(p_port) + "));\n"; - } break; - case VisualShaderNode::PORT_TYPE_BOOLEAN: { - shader_code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + " ? 1.0 : 0.0);\n"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - shader_code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + ", 0.0);\n"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - shader_code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - shader_code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ".xyz;\n"; - } break; - default: { - shader_code += " COLOR.rgb = vec3(0.0);\n"; - } break; - } - - shader_code += "}\n"; - - //set code secretly - global_code += "\n\n"; - String final_code = global_code; - final_code += global_code_per_node; - final_code += shader_code; - return final_code; -} - -String VisualShader::validate_port_name(const String &p_port_name, VisualShaderNode *p_node, int p_port_id, bool p_output) const { - const Ref g = graph[0]; - return g->validate_port_name(p_port_name, p_node, p_port_id, p_output); +bool VisualShader::is_port_types_compatible(int p_a, int p_b) const { + const Ref g = graph[0]; // Currently, this is independent of the type so we can use an arbitrary graph. + return g->is_port_types_compatible(p_a, p_b); } -String VisualShader::validate_parameter_name(const String &p_name, const Ref &p_parameter) const { - String param_name = p_name; //validate name first - while (param_name.length() && !is_ascii_alphabet_char(param_name[0])) { - param_name = param_name.substr(1, param_name.length() - 1); - } - if (!param_name.is_empty()) { - String valid_name; - - for (int i = 0; i < param_name.length(); i++) { - if (is_ascii_identifier_char(param_name[i])) { - valid_name += String::chr(param_name[i]); - } else if (param_name[i] == ' ') { - valid_name += "_"; - } - } - - param_name = valid_name; - } +void VisualShader::attach_node_to_frame(Type p_type, int p_node, int p_frame) { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + ERR_FAIL_COND(p_frame < 0); + Ref g = graph[p_type]; + g->attach_node_to_frame(p_node, p_frame); +} - if (param_name.is_empty()) { - param_name = p_parameter->get_caption(); - } +void VisualShader::detach_node_from_frame(Type p_type, int p_node) { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + Ref g = graph[p_type]; + g->detach_node_from_frame(p_node); +} - int attempt = 1; +String VisualShader::get_reroute_parameter_name(Type p_type, int p_reroute_node) const { + ERR_FAIL_INDEX_V(p_type, TYPE_MAX, ""); + const Ref g = graph[p_type]; + return g->get_reroute_parameter_name(p_reroute_node); +} - while (true) { - bool exists = false; - for (int i = 0; i < TYPE_MAX; i++) { - for (const KeyValue &E : graph[i]->nodes) { - Ref node = E.value.node; - if (node == p_parameter) { //do not test on self - continue; - } - if (node.is_valid() && node->get_parameter_name() == param_name) { - exists = true; - break; - } - } - if (exists) { - break; - } - } +void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + Ref g = graph[p_type]; + g->connect_nodes_forced(p_from_node, p_from_port, p_to_node, p_to_port); + _queue_update(); +} - if (exists) { - //remove numbers, put new and try again - attempt++; - while (param_name.length() && is_digit(param_name[param_name.length() - 1])) { - param_name = param_name.substr(0, param_name.length() - 1); - } - ERR_FAIL_COND_V(param_name.is_empty(), String()); - param_name += itos(attempt); - } else { - break; - } - } +Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { + ERR_FAIL_INDEX_V(p_type, TYPE_MAX, ERR_CANT_CONNECT); + Ref g = graph[p_type]; + const Error error = g->connect_nodes(p_from_node, p_from_port, p_to_node, p_to_port); + _queue_update(); + return error; +} - return param_name; +void VisualShader::disconnect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + Ref g = graph[p_type]; + g->disconnect_nodes(p_from_node, p_from_port, p_to_node, p_to_port); + _queue_update(); } -static const char *type_string[VisualShader::TYPE_MAX] = { - "vertex", - "fragment", - "light", - "start", - "process", - "collide", - "start_custom", - "process_custom", - "sky", - "fog", -}; +TypedArray VisualShader::_get_node_connections(Type p_type) const { + ERR_FAIL_INDEX_V(p_type, TYPE_MAX, Array()); + const Ref g = graph[p_type]; -bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { - String prop_name = p_name; - if (prop_name == "mode") { - set_mode(Shader::Mode(int(p_value))); - return true; - } else if (prop_name.begins_with("flags/")) { - StringName flag = prop_name.get_slicec('/', 1); - bool enable = p_value; - if (enable) { - flags.insert(flag); - } else { - flags.erase(flag); - } - _queue_update(); - return true; - } else if (prop_name.begins_with("modes/")) { - String mode_name = prop_name.get_slicec('/', 1); - int value = p_value; - if (value == 0) { - modes.erase(mode_name); //means it's default anyway, so don't store it - } else { - modes[mode_name] = value; - } - _queue_update(); - return true; - } else if (prop_name.begins_with("varyings/")) { - String var_name = prop_name.get_slicec('/', 1); - Varying value = Varying(); - value.name = var_name; - if (value.from_string(p_value) && !varyings.has(var_name)) { - varyings[var_name] = value; - varyings_list.push_back(value); - } - _queue_update(); - return true; - } -#ifdef TOOLS_ENABLED - else if (prop_name.begins_with("preview_params/") && Engine::get_singleton()->is_editor_hint()) { - String param_name = prop_name.get_slicec('/', 1); - Variant value = VariantUtilityFunctions::str_to_var(p_value); - preview_params[param_name] = value; - return true; + TypedArray ret; + for (const ShaderGraph::Connection &E : g->connections) { + Dictionary d; + d["from_node"] = E.from_node; + d["from_port"] = E.from_port; + d["to_node"] = E.to_node; + d["to_port"] = E.to_port; + ret.push_back(d); } -#endif - else if (prop_name.begins_with("nodes/")) { - String typestr = prop_name.get_slicec('/', 1); - Type type = TYPE_VERTEX; - for (int i = 0; i < TYPE_MAX; i++) { - if (typestr == type_string[i]) { - type = Type(i); - break; - } - } - - String index = prop_name.get_slicec('/', 2); - if (index == "connections") { - Vector conns = p_value; - if (conns.size() % 4 == 0) { - for (int i = 0; i < conns.size(); i += 4) { - connect_nodes_forced(type, conns[i + 0], conns[i + 1], conns[i + 2], conns[i + 3]); - } - } - return true; - } - int id = index.to_int(); - String what = prop_name.get_slicec('/', 3); + return ret; +} - if (what == "node") { - add_node(type, p_value, Vector2(), id); - return true; - } else if (what == "position") { - set_node_position(type, id, p_value); - return true; - } else if (what == "size") { - VisualShaderNodeResizableBase *resizable_vn = Object::cast_to(get_node(type, id).ptr()); - if (resizable_vn) { - resizable_vn->set_size(p_value); - return true; - } - } else if (what == "input_ports") { - VisualShaderNodeGroupBase *group_vn = Object::cast_to(get_node(type, id).ptr()); - if (group_vn) { - group_vn->set_inputs(p_value); - return true; - } - } else if (what == "output_ports") { - VisualShaderNodeGroupBase *group_vn = Object::cast_to(get_node(type, id).ptr()); - if (group_vn) { - group_vn->set_outputs(p_value); - return true; - } - } else if (what == "expression") { - VisualShaderNodeExpression *expression_vn = Object::cast_to(get_node(type, id).ptr()); - if (expression_vn) { - expression_vn->set_expression(p_value); - return true; - } - } - } - return false; +void VisualShader::get_node_connections(Type p_type, List *r_connections) const { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + const Ref g = graph[p_type]; + g->get_node_connections(r_connections); } -bool VisualShader::_get(const StringName &p_name, Variant &r_ret) const { - String prop_name = p_name; - if (prop_name == "mode") { - r_ret = get_mode(); - return true; - } else if (prop_name.begins_with("flags/")) { - StringName flag = prop_name.get_slicec('/', 1); - r_ret = flags.has(flag); - return true; - } else if (prop_name.begins_with("modes/")) { - String mode_name = prop_name.get_slicec('/', 1); - if (modes.has(mode_name)) { - r_ret = modes[mode_name]; - } else { - r_ret = 0; - } - return true; - } else if (prop_name.begins_with("varyings/")) { - String var_name = prop_name.get_slicec('/', 1); - if (varyings.has(var_name)) { - r_ret = varyings[var_name].to_string(); - } else { - r_ret = String(); - } - return true; - } -#ifdef TOOLS_ENABLED - else if (prop_name.begins_with("preview_params/") && Engine::get_singleton()->is_editor_hint()) { - String param_name = prop_name.get_slicec('/', 1); - if (preview_params.has(param_name)) { - r_ret = VariantUtilityFunctions::var_to_str(preview_params[param_name]); - } else { - r_ret = String(); - } - return true; +void VisualShader::set_mode(Mode p_mode) { + ERR_FAIL_INDEX_MSG(p_mode, Mode::MODE_MAX, vformat("Invalid shader mode: %d.", p_mode)); + + if (shader_mode == p_mode) { + return; } -#endif // TOOLS_ENABLED - else if (prop_name.begins_with("nodes/")) { - String typestr = prop_name.get_slicec('/', 1); - Type type = TYPE_VERTEX; - for (int i = 0; i < TYPE_MAX; i++) { - if (typestr == type_string[i]) { - type = Type(i); - break; + + //erase input/output connections + modes.clear(); + flags.clear(); + shader_mode = p_mode; + for (int i = 0; i < TYPE_MAX; i++) { + for (KeyValue &E : graph[i]->nodes) { + Ref input = E.value.node; + if (input.is_valid()) { + input->shader_mode = shader_mode; + //input->input_index = 0; } } - String index = prop_name.get_slicec('/', 2); - if (index == "connections") { - Vector conns; - for (const ShaderGraph::Connection &E : graph[type]->connections) { - conns.push_back(E.from_node); - conns.push_back(E.from_port); - conns.push_back(E.to_node); - conns.push_back(E.to_port); - } + Ref output = graph[i]->nodes[ShaderGraph::NODE_ID_OUTPUT].node; + output->shader_mode = shader_mode; - r_ret = conns; - return true; - } + // clear connections since they are no longer valid + for (List::Element *E = graph[i]->connections.front(); E;) { + bool keep = true; - int id = index.to_int(); - String what = prop_name.get_slicec('/', 3); + List::Element *N = E->next(); - if (what == "node") { - r_ret = get_node(type, id); - return true; - } else if (what == "position") { - r_ret = get_node_position(type, id); - return true; - } else if (what == "size") { - VisualShaderNodeResizableBase *resizable_vn = Object::cast_to(get_node(type, id).ptr()); - if (resizable_vn) { - r_ret = resizable_vn->get_size(); - return true; - } - } else if (what == "input_ports") { - VisualShaderNodeGroupBase *group_vn = Object::cast_to(get_node(type, id).ptr()); - if (group_vn) { - r_ret = group_vn->get_inputs(); - return true; + int from = E->get().from_node; + int to = E->get().to_node; + + if (!graph[i]->nodes.has(from)) { + keep = false; + } else { + Ref from_node = graph[i]->nodes[from].node; + if (from_node->is_class("VisualShaderNodeOutput") || from_node->is_class("VisualShaderNodeInput")) { + keep = false; + } } - } else if (what == "output_ports") { - VisualShaderNodeGroupBase *group_vn = Object::cast_to(get_node(type, id).ptr()); - if (group_vn) { - r_ret = group_vn->get_outputs(); - return true; + + if (!graph[i]->nodes.has(to)) { + keep = false; + } else { + Ref to_node = graph[i]->nodes[to].node; + if (to_node->is_class("VisualShaderNodeOutput") || to_node->is_class("VisualShaderNodeInput")) { + keep = false; + } } - } else if (what == "expression") { - VisualShaderNodeExpression *expression_vn = Object::cast_to(get_node(type, id).ptr()); - if (expression_vn) { - r_ret = expression_vn->get_expression(); - return true; + + if (!keep) { + graph[i]->connections.erase(E); } + E = N; } } - return false; -} -void VisualShader::reset_state() { - // TODO: Everything needs to be cleared here. Evaluate this or implement it. - emit_changed(); + _queue_update(); + notify_property_list_changed(); } -void VisualShader::_get_property_list(List *p_list) const { - //mode - p_list->push_back(PropertyInfo(Variant::INT, PNAME("mode"), PROPERTY_HINT_ENUM, "Spatial,CanvasItem,Particles,Sky,Fog")); - //render modes - - HashMap blend_mode_enums; - HashSet toggles; +Shader::Mode VisualShader::get_mode() const { + return shader_mode; +} - const Vector &rmodes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader_mode)); +bool VisualShader::is_text_shader() const { + return false; +} - for (int i = 0; i < rmodes.size(); i++) { - const ShaderLanguage::ModeInfo &info = rmodes[i]; +String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port, Vector &default_tex_params) const { + Ref node = get_node(p_type, p_node); + ERR_FAIL_COND_V(!node.is_valid(), String()); + ERR_FAIL_COND_V(p_port < 0 || p_port >= node->get_expanded_output_port_count(), String()); + ERR_FAIL_COND_V(node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_TRANSFORM, String()); - if (!info.options.is_empty()) { - const String begin = String(info.name); + StringBuilder global_code; + StringBuilder global_code_per_node; + HashMap global_code_per_func; + StringBuilder shader_code; + HashSet classes; - for (int j = 0; j < info.options.size(); j++) { - const String option = String(info.options[j]).capitalize(); + global_code += String() + "shader_type canvas_item;\n"; - if (!blend_mode_enums.has(begin)) { - blend_mode_enums[begin] = option; - } else { - blend_mode_enums[begin] += "," + option; - } + String global_expressions; + for (int i = 0, index = 0; i < TYPE_MAX; i++) { + for (const KeyValue &E : graph[i]->nodes) { + Ref global_expression = E.value.node; + if (global_expression.is_valid()) { + String expr = ""; + expr += "// " + global_expression->get_caption() + ":" + itos(index++) + "\n"; + expr += global_expression->generate_global(get_mode(), Type(i), -1); + expr = expr.replace("\n", "\n "); + expr += "\n"; + global_expressions += expr; } - } else { - toggles.insert(String(info.name)); } } - for (const KeyValue &E : blend_mode_enums) { - p_list->push_back(PropertyInfo(Variant::INT, vformat("%s/%s", PNAME("modes"), E.key), PROPERTY_HINT_ENUM, E.value)); - } + global_code += "\n"; + global_code += global_expressions; - for (const String &E : toggles) { - p_list->push_back(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("flags"), E))); - } + //make it faster to go around through shader + VMap::Element *> input_connections; + VMap::Element *> output_connections; - for (const KeyValue &E : varyings) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", PNAME("varyings"), E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - } + for (const List::Element *E = graph[p_type]->connections.front(); E; E = E->next()) { + ShaderGraph::ConnectionKey from_key; + from_key.node = E->get().from_node; + from_key.port = E->get().from_port; -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - for (const KeyValue &E : preview_params) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", PNAME("preview_params"), E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - } + output_connections.insert(from_key, E); + + ShaderGraph::ConnectionKey to_key; + to_key.node = E->get().to_node; + to_key.port = E->get().to_port; + + input_connections.insert(to_key, E); } -#endif // TOOLS_ENABLED - for (int i = 0; i < TYPE_MAX; i++) { - for (const KeyValue &E : graph[i]->nodes) { - String prop_name = "nodes/"; - prop_name += type_string[i]; - prop_name += "/" + itos(E.key); + shader_code += "\nvoid fragment() {\n"; - if (E.key != ShaderGraph::NODE_ID_OUTPUT) { - p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_ALWAYS_DUPLICATE)); - } - p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + HashSet processed; + Error err = _write_node((ShaderGraph::Type)p_type, &global_code, &global_code_per_node, &global_code_per_func, shader_code, default_tex_params, input_connections, output_connections, p_node, processed, true, classes); + ERR_FAIL_COND_V(err != OK, String()); - if (Object::cast_to(E.value.node.ptr()) != nullptr) { - p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/input_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/output_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - } - if (Object::cast_to(E.value.node.ptr()) != nullptr) { - p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/expression", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - } - } - p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "nodes/" + String(type_string[i]) + "/connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + switch (node->get_output_port_type(p_port)) { + case VisualShaderNode::PORT_TYPE_SCALAR: { + shader_code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + ");\n"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_INT: { + shader_code += " COLOR.rgb = vec3(float(n_out" + itos(p_node) + "p" + itos(p_port) + "));\n"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { + shader_code += " COLOR.rgb = vec3(float(n_out" + itos(p_node) + "p" + itos(p_port) + "));\n"; + } break; + case VisualShaderNode::PORT_TYPE_BOOLEAN: { + shader_code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + " ? 1.0 : 0.0);\n"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + shader_code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + ", 0.0);\n"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + shader_code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + shader_code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ".xyz;\n"; + } break; + default: { + shader_code += " COLOR.rgb = vec3(0.0);\n"; + } break; } -} -// TODO: Refactor (simplify, rename and change comment style) -Error VisualShader::_write_node(Type type, StringBuilder *p_global_code, StringBuilder *p_global_code_per_node, HashMap *p_global_code_per_func, StringBuilder &r_code, Vector &r_def_tex_params, const VMap::Element *> &p_input_connections, const VMap::Element *> &p_output_connections, int p_node, HashSet &r_processed, bool p_for_preview, HashSet &r_classes) const { - const Ref vsnode = graph[type]->nodes[p_node].node; + shader_code += "}\n"; - if (vsnode->is_disabled()) { - r_code += "// " + vsnode->get_caption() + ":" + itos(p_node) + "\n"; - r_code += " // Node is disabled and code is not generated.\n"; - return OK; - } + //set code secretly + global_code += "\n\n"; + String final_code = global_code; + final_code += global_code_per_node; + final_code += shader_code; + return final_code; +} - //check inputs recursively first - int input_count = vsnode->get_input_port_count(); - for (int i = 0; i < input_count; i++) { - ConnectionKey ck; - ck.node = p_node; - ck.port = i; +String VisualShader::validate_port_name(const String &p_port_name, VisualShaderNode *p_node, int p_port_id, bool p_output) const { + const Ref g = graph[0]; + return g->validate_port_name(p_port_name, p_node, p_port_id, p_output); +} - if (p_input_connections.has(ck)) { - int from_node = p_input_connections[ck]->get().from_node; - if (r_processed.has(from_node)) { - continue; - } +String VisualShader::validate_parameter_name(const String &p_name, const Ref &p_parameter) const { + String param_name = p_name; //validate name first + while (param_name.length() && !is_ascii_alphabet_char(param_name[0])) { + param_name = param_name.substr(1, param_name.length() - 1); + } + if (!param_name.is_empty()) { + String valid_name; - Error err = _write_node(type, p_global_code, p_global_code_per_node, p_global_code_per_func, r_code, r_def_tex_params, p_input_connections, p_output_connections, from_node, r_processed, p_for_preview, r_classes); - if (err) { - return err; + for (int i = 0; i < param_name.length(); i++) { + if (is_ascii_identifier_char(param_name[i])) { + valid_name += String::chr(param_name[i]); + } else if (param_name[i] == ' ') { + valid_name += "_"; } } - } - // then this node + param_name = valid_name; + } - Vector params = vsnode->get_default_texture_parameters(type, p_node); - for (int i = 0; i < params.size(); i++) { - r_def_tex_params.push_back(params[i]); + if (param_name.is_empty()) { + param_name = p_parameter->get_caption(); } - Ref input = vsnode; - bool skip_global = input.is_valid() && p_for_preview; + int attempt = 1; - if (!skip_global) { - Ref parameter = vsnode; - if (!parameter.is_valid() || !parameter->is_global_code_generated()) { - if (p_global_code) { - *p_global_code += vsnode->generate_global(get_mode(), type, p_node); + while (true) { + bool exists = false; + for (int i = 0; i < TYPE_MAX; i++) { + for (const KeyValue &E : graph[i]->nodes) { + Ref node = E.value.node; + if (node == p_parameter) { //do not test on self + continue; + } + if (node.is_valid() && node->get_parameter_name() == param_name) { + exists = true; + break; + } + } + if (exists) { + break; } } - String class_name = vsnode->get_class_name(); - if (class_name == "VisualShaderNodeCustom") { - class_name = vsnode->get_script_instance()->get_script()->get_path(); - } - if (!r_classes.has(class_name)) { - if (p_global_code_per_node) { - *p_global_code_per_node += vsnode->generate_global_per_node(get_mode(), p_node); - } - for (int i = 0; i < TYPE_MAX; i++) { - if (p_global_code_per_func) { - (*p_global_code_per_func)[Type(i)] += vsnode->generate_global_per_func(get_mode(), Type(i), p_node); - } + if (exists) { + //remove numbers, put new and try again + attempt++; + while (param_name.length() && is_digit(param_name[param_name.length() - 1])) { + param_name = param_name.substr(0, param_name.length() - 1); } - r_classes.insert(class_name); + ERR_FAIL_COND_V(param_name.is_empty(), String()); + param_name += itos(attempt); + } else { + break; } } - if (!vsnode->is_code_generated()) { // just generate globals and ignore locals - r_processed.insert(p_node); - return OK; - } - - String node_name = "// " + vsnode->get_caption() + ":" + itos(p_node) + "\n"; - String node_code; - Vector input_vars; - - input_vars.resize(vsnode->get_input_port_count()); - String *inputs = input_vars.ptrw(); - - for (int i = 0; i < input_count; i++) { - ConnectionKey ck; - ck.node = p_node; - ck.port = i; - - if (p_input_connections.has(ck)) { - //connected to something, use that output - int from_node = p_input_connections[ck]->get().from_node; - - if (graph[type]->nodes[from_node].node->is_disabled()) { - continue; - } - - int from_port = p_input_connections[ck]->get().from_port; - - VisualShaderNode::PortType in_type = vsnode->get_input_port_type(i); - VisualShaderNode::PortType out_type = graph[type]->nodes[from_node].node->get_output_port_type(from_port); + return param_name; +} - String src_var = "n_out" + itos(from_node) + "p" + itos(from_port); +static const char *type_string[VisualShader::TYPE_MAX] = { + "vertex", + "fragment", + "light", + "start", + "process", + "collide", + "start_custom", + "process_custom", + "sky", + "fog", +}; - if (in_type == VisualShaderNode::PORT_TYPE_SAMPLER && out_type == VisualShaderNode::PORT_TYPE_SAMPLER) { - VisualShaderNode *ptr = const_cast(graph[type]->nodes[from_node].node.ptr()); - // FIXME: This needs to be refactored at some point. - if (ptr->has_method("get_input_real_name")) { - inputs[i] = ptr->call("get_input_real_name"); - } else if (ptr->has_method("get_parameter_name")) { - inputs[i] = ptr->call("get_parameter_name"); - } else { - Ref reroute = graph[type]->nodes[from_node].node; - if (reroute.is_valid()) { - inputs[i] = get_reroute_parameter_name(type, from_node); - } else { - inputs[i] = ""; - } - } - } else if (in_type == out_type) { - inputs[i] = src_var; - } else { - switch (in_type) { - case VisualShaderNode::PORT_TYPE_SCALAR: { - switch (out_type) { - case VisualShaderNode::PORT_TYPE_SCALAR_INT: { - inputs[i] = "float(" + src_var + ")"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { - inputs[i] = "float(" + src_var + ")"; - } break; - case VisualShaderNode::PORT_TYPE_BOOLEAN: { - inputs[i] = "(" + src_var + " ? 1.0 : 0.0)"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - inputs[i] = src_var + ".x"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - inputs[i] = src_var + ".x"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - inputs[i] = src_var + ".x"; - } break; - default: - break; - } - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_INT: { - switch (out_type) { - case VisualShaderNode::PORT_TYPE_SCALAR: { - inputs[i] = "int(" + src_var + ")"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { - inputs[i] = "int(" + src_var + ")"; - } break; - case VisualShaderNode::PORT_TYPE_BOOLEAN: { - inputs[i] = "(" + src_var + " ? 1 : 0)"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - inputs[i] = "int(" + src_var + ".x)"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - inputs[i] = "int(" + src_var + ".x)"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - inputs[i] = "int(" + src_var + ".x)"; - } break; - default: - break; - } - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { - switch (out_type) { - case VisualShaderNode::PORT_TYPE_SCALAR: { - inputs[i] = "uint(" + src_var + ")"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_INT: { - inputs[i] = "uint(" + src_var + ")"; - } break; - case VisualShaderNode::PORT_TYPE_BOOLEAN: { - inputs[i] = "(" + src_var + " ? 1u : 0u)"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - inputs[i] = "uint(" + src_var + ".x)"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - inputs[i] = "uint(" + src_var + ".x)"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - inputs[i] = "uint(" + src_var + ".x)"; - } break; - default: - break; - } - } break; - case VisualShaderNode::PORT_TYPE_BOOLEAN: { - switch (out_type) { - case VisualShaderNode::PORT_TYPE_SCALAR: { - inputs[i] = src_var + " > 0.0 ? true : false"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_INT: { - inputs[i] = src_var + " > 0 ? true : false"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { - inputs[i] = src_var + " > 0u ? true : false"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - inputs[i] = "all(bvec2(" + src_var + "))"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - inputs[i] = "all(bvec3(" + src_var + "))"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - inputs[i] = "all(bvec4(" + src_var + "))"; - } break; - default: - break; - } - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - switch (out_type) { - case VisualShaderNode::PORT_TYPE_SCALAR: { - inputs[i] = "vec2(" + src_var + ")"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_INT: { - inputs[i] = "vec2(float(" + src_var + "))"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { - inputs[i] = "vec2(float(" + src_var + "))"; - } break; - case VisualShaderNode::PORT_TYPE_BOOLEAN: { - inputs[i] = "vec2(" + src_var + " ? 1.0 : 0.0)"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - inputs[i] = "vec2(" + src_var + ".xy)"; - } break; - default: - break; - } - } break; +bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { + String prop_name = p_name; + if (prop_name == "mode") { + set_mode(Shader::Mode(int(p_value))); + return true; + } else if (prop_name.begins_with("flags/")) { + StringName flag = prop_name.get_slicec('/', 1); + bool enable = p_value; + if (enable) { + flags.insert(flag); + } else { + flags.erase(flag); + } + _queue_update(); + return true; + } else if (prop_name.begins_with("modes/")) { + String mode_name = prop_name.get_slicec('/', 1); + int value = p_value; + if (value == 0) { + modes.erase(mode_name); //means it's default anyway, so don't store it + } else { + modes[mode_name] = value; + } + _queue_update(); + return true; + } else if (prop_name.begins_with("varyings/")) { + String var_name = prop_name.get_slicec('/', 1); + Varying value = Varying(); + value.name = var_name; + if (value.from_string(p_value) && !varyings.has(var_name)) { + varyings[var_name] = value; + varyings_list.push_back(value); + } + _queue_update(); + return true; + } +#ifdef TOOLS_ENABLED + else if (prop_name.begins_with("preview_params/") && Engine::get_singleton()->is_editor_hint()) { + String param_name = prop_name.get_slicec('/', 1); + Variant value = VariantUtilityFunctions::str_to_var(p_value); + preview_params[param_name] = value; + return true; + } +#endif + else if (prop_name.begins_with("nodes/")) { + String typestr = prop_name.get_slicec('/', 1); + Type type = TYPE_VERTEX; + for (int i = 0; i < TYPE_MAX; i++) { + if (typestr == type_string[i]) { + type = Type(i); + break; + } + } - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - switch (out_type) { - case VisualShaderNode::PORT_TYPE_SCALAR: { - inputs[i] = "vec3(" + src_var + ")"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_INT: { - inputs[i] = "vec3(float(" + src_var + "))"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { - inputs[i] = "vec3(float(" + src_var + "))"; - } break; - case VisualShaderNode::PORT_TYPE_BOOLEAN: { - inputs[i] = "vec3(" + src_var + " ? 1.0 : 0.0)"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - inputs[i] = "vec3(" + src_var + ", 0.0)"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - inputs[i] = "vec3(" + src_var + ".xyz)"; - } break; - default: - break; - } - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - switch (out_type) { - case VisualShaderNode::PORT_TYPE_SCALAR: { - inputs[i] = "vec4(" + src_var + ")"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_INT: { - inputs[i] = "vec4(float(" + src_var + "))"; - } break; - case VisualShaderNode::PORT_TYPE_SCALAR_UINT: { - inputs[i] = "vec4(float(" + src_var + "))"; - } break; - case VisualShaderNode::PORT_TYPE_BOOLEAN: { - inputs[i] = "vec4(" + src_var + " ? 1.0 : 0.0)"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - inputs[i] = "vec4(" + src_var + ", 0.0, 0.0)"; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - inputs[i] = "vec4(" + src_var + ", 0.0)"; - } break; - default: - break; - } - } break; - default: - break; + String index = prop_name.get_slicec('/', 2); + if (index == "connections") { + Vector conns = p_value; + if (conns.size() % 4 == 0) { + for (int i = 0; i < conns.size(); i += 4) { + connect_nodes_forced(type, conns[i + 0], conns[i + 1], conns[i + 2], conns[i + 3]); } } + return true; + } + + int id = index.to_int(); + String what = prop_name.get_slicec('/', 3); + + if (what == "node") { + add_node(type, p_value, Vector2(), id); + return true; + } else if (what == "position") { + set_node_position(type, id, p_value); + return true; + } else if (what == "size") { + VisualShaderNodeResizableBase *resizable_vn = Object::cast_to(get_node(type, id).ptr()); + if (resizable_vn) { + resizable_vn->set_size(p_value); + return true; + } + } else if (what == "input_ports") { + VisualShaderNodeGroupBase *group_vn = Object::cast_to(get_node(type, id).ptr()); + if (group_vn) { + group_vn->set_inputs(p_value); + return true; + } + } else if (what == "output_ports") { + VisualShaderNodeGroupBase *group_vn = Object::cast_to(get_node(type, id).ptr()); + if (group_vn) { + group_vn->set_outputs(p_value); + return true; + } + } else if (what == "expression") { + VisualShaderNodeExpression *expression_vn = Object::cast_to(get_node(type, id).ptr()); + if (expression_vn) { + expression_vn->set_expression(p_value); + return true; + } + } + } + return false; +} + +bool VisualShader::_get(const StringName &p_name, Variant &r_ret) const { + String prop_name = p_name; + if (prop_name == "mode") { + r_ret = get_mode(); + return true; + } else if (prop_name.begins_with("flags/")) { + StringName flag = prop_name.get_slicec('/', 1); + r_ret = flags.has(flag); + return true; + } else if (prop_name.begins_with("modes/")) { + String mode_name = prop_name.get_slicec('/', 1); + if (modes.has(mode_name)) { + r_ret = modes[mode_name]; } else { - if (!vsnode->is_generate_input_var(i)) { - continue; + r_ret = 0; + } + return true; + } else if (prop_name.begins_with("varyings/")) { + String var_name = prop_name.get_slicec('/', 1); + if (varyings.has(var_name)) { + r_ret = varyings[var_name].to_string(); + } else { + r_ret = String(); + } + return true; + } +#ifdef TOOLS_ENABLED + else if (prop_name.begins_with("preview_params/") && Engine::get_singleton()->is_editor_hint()) { + String param_name = prop_name.get_slicec('/', 1); + if (preview_params.has(param_name)) { + r_ret = VariantUtilityFunctions::var_to_str(preview_params[param_name]); + } else { + r_ret = String(); + } + return true; + } +#endif // TOOLS_ENABLED + else if (prop_name.begins_with("nodes/")) { + String typestr = prop_name.get_slicec('/', 1); + Type type = TYPE_VERTEX; + for (int i = 0; i < TYPE_MAX; i++) { + if (typestr == type_string[i]) { + type = Type(i); + break; } + } - Variant defval = vsnode->get_input_port_default_value(i); - if (defval.get_type() == Variant::FLOAT) { - float val = defval; - inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); - node_code += " float " + inputs[i] + " = " + vformat("%.5f", val) + ";\n"; - } else if (defval.get_type() == Variant::INT) { - int val = defval; - inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); - if (vsnode->get_input_port_type(i) == VisualShaderNode::PORT_TYPE_SCALAR_UINT) { - node_code += " uint " + inputs[i] + " = " + itos(val) + "u;\n"; - } else { - node_code += " int " + inputs[i] + " = " + itos(val) + ";\n"; - } - } else if (defval.get_type() == Variant::BOOL) { - bool val = defval; - inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); - node_code += " bool " + inputs[i] + " = " + (val ? "true" : "false") + ";\n"; - } else if (defval.get_type() == Variant::VECTOR2) { - Vector2 val = defval; - inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); - node_code += " vec2 " + inputs[i] + " = " + vformat("vec2(%.5f, %.5f);\n", val.x, val.y); - } else if (defval.get_type() == Variant::VECTOR3) { - Vector3 val = defval; - inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); - node_code += " vec3 " + inputs[i] + " = " + vformat("vec3(%.5f, %.5f, %.5f);\n", val.x, val.y, val.z); - } else if (defval.get_type() == Variant::VECTOR4) { - Vector4 val = defval; - inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); - node_code += " vec4 " + inputs[i] + " = " + vformat("vec4(%.5f, %.5f, %.5f, %.5f);\n", val.x, val.y, val.z, val.w); - } else if (defval.get_type() == Variant::QUATERNION) { - Quaternion val = defval; - inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); - node_code += " vec4 " + inputs[i] + " = " + vformat("vec4(%.5f, %.5f, %.5f, %.5f);\n", val.x, val.y, val.z, val.w); - } else if (defval.get_type() == Variant::TRANSFORM3D) { - Transform3D val = defval; - val.basis.transpose(); - inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); - Array values; - for (int j = 0; j < 3; j++) { - values.push_back(val.basis[j].x); - values.push_back(val.basis[j].y); - values.push_back(val.basis[j].z); - } - values.push_back(val.origin.x); - values.push_back(val.origin.y); - values.push_back(val.origin.z); - bool err = false; - node_code += " mat4 " + inputs[i] + " = " + String("mat4(vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 1.0));\n").sprintf(values, &err); - } else { - // TODO: Cleanup - //will go empty, node is expected to know what it is doing at this point and handle it + String index = prop_name.get_slicec('/', 2); + if (index == "connections") { + Vector conns; + for (const ShaderGraph::Connection &E : graph[type]->connections) { + conns.push_back(E.from_node); + conns.push_back(E.from_port); + conns.push_back(E.to_node); + conns.push_back(E.to_port); + } + + r_ret = conns; + return true; + } + + int id = index.to_int(); + String what = prop_name.get_slicec('/', 3); + + if (what == "node") { + r_ret = get_node(type, id); + return true; + } else if (what == "position") { + r_ret = get_node_position(type, id); + return true; + } else if (what == "size") { + VisualShaderNodeResizableBase *resizable_vn = Object::cast_to(get_node(type, id).ptr()); + if (resizable_vn) { + r_ret = resizable_vn->get_size(); + return true; + } + } else if (what == "input_ports") { + VisualShaderNodeGroupBase *group_vn = Object::cast_to(get_node(type, id).ptr()); + if (group_vn) { + r_ret = group_vn->get_inputs(); + return true; + } + } else if (what == "output_ports") { + VisualShaderNodeGroupBase *group_vn = Object::cast_to(get_node(type, id).ptr()); + if (group_vn) { + r_ret = group_vn->get_outputs(); + return true; + } + } else if (what == "expression") { + VisualShaderNodeExpression *expression_vn = Object::cast_to(get_node(type, id).ptr()); + if (expression_vn) { + r_ret = expression_vn->get_expression(); + return true; } } } + return false; +} - int output_count = vsnode->get_output_port_count(); - int initial_output_count = output_count; +void VisualShader::reset_state() { + // TODO: Everything needs to be cleared here. Evaluate this or implement it. + emit_changed(); +} - HashMap expanded_output_ports; +void VisualShader::_get_property_list(List *p_list) const { + //mode + p_list->push_back(PropertyInfo(Variant::INT, PNAME("mode"), PROPERTY_HINT_ENUM, "Spatial,CanvasItem,Particles,Sky,Fog")); + //render modes - for (int i = 0; i < initial_output_count; i++) { - bool expanded = false; + HashMap blend_mode_enums; + HashSet toggles; - if (vsnode->is_output_port_expandable(i) && vsnode->_is_output_port_expanded(i)) { - expanded = true; + const Vector &rmodes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader_mode)); - switch (vsnode->get_output_port_type(i)) { - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - output_count += 2; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - output_count += 3; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - output_count += 4; - } break; - default: - break; - } - } - expanded_output_ports.insert(i, expanded); - } + for (int i = 0; i < rmodes.size(); i++) { + const ShaderLanguage::ModeInfo &info = rmodes[i]; - Vector output_vars; - output_vars.resize(output_count); - String *outputs = output_vars.ptrw(); + if (!info.options.is_empty()) { + const String begin = String(info.name); - if (vsnode->is_simple_decl()) { // less code to generate for some simple_decl nodes - for (int i = 0, j = 0; i < initial_output_count; i++, j++) { - String var_name = "n_out" + itos(p_node) + "p" + itos(j); - switch (vsnode->get_output_port_type(i)) { - case VisualShaderNode::PORT_TYPE_SCALAR: - outputs[i] = "float " + var_name; - break; - case VisualShaderNode::PORT_TYPE_SCALAR_INT: - outputs[i] = "int " + var_name; - break; - case VisualShaderNode::PORT_TYPE_SCALAR_UINT: - outputs[i] = "uint " + var_name; - break; - case VisualShaderNode::PORT_TYPE_VECTOR_2D: - outputs[i] = "vec2 " + var_name; - break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: - outputs[i] = "vec3 " + var_name; - break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: - outputs[i] = "vec4 " + var_name; - break; - case VisualShaderNode::PORT_TYPE_BOOLEAN: - outputs[i] = "bool " + var_name; - break; - case VisualShaderNode::PORT_TYPE_TRANSFORM: - outputs[i] = "mat4 " + var_name; - break; - default: - break; - } - if (expanded_output_ports[i]) { - switch (vsnode->get_output_port_type(i)) { - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - j += 2; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - j += 3; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - j += 4; - } break; - default: - break; - } - } - } + for (int j = 0; j < info.options.size(); j++) { + const String option = String(info.options[j]).capitalize(); - } else { - for (int i = 0, j = 0; i < initial_output_count; i++, j++) { - outputs[i] = "n_out" + itos(p_node) + "p" + itos(j); - switch (vsnode->get_output_port_type(i)) { - case VisualShaderNode::PORT_TYPE_SCALAR: - r_code += " float " + outputs[i] + ";\n"; - break; - case VisualShaderNode::PORT_TYPE_SCALAR_INT: - r_code += " int " + outputs[i] + ";\n"; - break; - case VisualShaderNode::PORT_TYPE_SCALAR_UINT: - r_code += " uint " + outputs[i] + ";\n"; - break; - case VisualShaderNode::PORT_TYPE_VECTOR_2D: - r_code += " vec2 " + outputs[i] + ";\n"; - break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: - r_code += " vec3 " + outputs[i] + ";\n"; - break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: - r_code += " vec4 " + outputs[i] + ";\n"; - break; - case VisualShaderNode::PORT_TYPE_BOOLEAN: - r_code += " bool " + outputs[i] + ";\n"; - break; - case VisualShaderNode::PORT_TYPE_TRANSFORM: - r_code += " mat4 " + outputs[i] + ";\n"; - break; - default: - break; - } - if (expanded_output_ports[i]) { - switch (vsnode->get_output_port_type(i)) { - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - j += 2; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - j += 3; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - j += 4; - } break; - default: - break; + if (!blend_mode_enums.has(begin)) { + blend_mode_enums[begin] = option; + } else { + blend_mode_enums[begin] += "," + option; } } + } else { + toggles.insert(String(info.name)); } } - node_code += vsnode->generate_code(get_mode(), type, p_node, inputs, outputs, p_for_preview); - if (!node_code.is_empty()) { - r_code += node_name; - r_code += node_code; + for (const KeyValue &E : blend_mode_enums) { + p_list->push_back(PropertyInfo(Variant::INT, vformat("%s/%s", PNAME("modes"), E.key), PROPERTY_HINT_ENUM, E.value)); } - for (int i = 0; i < output_count; i++) { - if (expanded_output_ports[i]) { - switch (vsnode->get_output_port_type(i)) { - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - if (vsnode->is_output_port_connected(i + 1) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component - String r = "n_out" + itos(p_node) + "p" + itos(i + 1); - r_code += " float " + r + " = n_out" + itos(p_node) + "p" + itos(i) + ".r;\n"; - outputs[i + 1] = r; - } - - if (vsnode->is_output_port_connected(i + 2) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component - String g = "n_out" + itos(p_node) + "p" + itos(i + 2); - r_code += " float " + g + " = n_out" + itos(p_node) + "p" + itos(i) + ".g;\n"; - outputs[i + 2] = g; - } - - i += 2; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - if (vsnode->is_output_port_connected(i + 1) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component - String r = "n_out" + itos(p_node) + "p" + itos(i + 1); - r_code += " float " + r + " = n_out" + itos(p_node) + "p" + itos(i) + ".r;\n"; - outputs[i + 1] = r; - } - - if (vsnode->is_output_port_connected(i + 2) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component - String g = "n_out" + itos(p_node) + "p" + itos(i + 2); - r_code += " float " + g + " = n_out" + itos(p_node) + "p" + itos(i) + ".g;\n"; - outputs[i + 2] = g; - } - - if (vsnode->is_output_port_connected(i + 3) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 3))) { // blue-component - String b = "n_out" + itos(p_node) + "p" + itos(i + 3); - r_code += " float " + b + " = n_out" + itos(p_node) + "p" + itos(i) + ".b;\n"; - outputs[i + 3] = b; - } + for (const String &E : toggles) { + p_list->push_back(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("flags"), E))); + } - i += 3; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - if (vsnode->is_output_port_connected(i + 1) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component - String r = "n_out" + itos(p_node) + "p" + itos(i + 1); - r_code += " float " + r + " = n_out" + itos(p_node) + "p" + itos(i) + ".r;\n"; - outputs[i + 1] = r; - } + for (const KeyValue &E : varyings) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", PNAME("varyings"), E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + } - if (vsnode->is_output_port_connected(i + 2) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component - String g = "n_out" + itos(p_node) + "p" + itos(i + 2); - r_code += " float " + g + " = n_out" + itos(p_node) + "p" + itos(i) + ".g;\n"; - outputs[i + 2] = g; - } +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + for (const KeyValue &E : preview_params) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", PNAME("preview_params"), E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + } + } +#endif // TOOLS_ENABLED - if (vsnode->is_output_port_connected(i + 3) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 3))) { // blue-component - String b = "n_out" + itos(p_node) + "p" + itos(i + 3); - r_code += " float " + b + " = n_out" + itos(p_node) + "p" + itos(i) + ".b;\n"; - outputs[i + 3] = b; - } + for (int i = 0; i < TYPE_MAX; i++) { + for (const KeyValue &E : graph[i]->nodes) { + String prop_name = "nodes/"; + prop_name += type_string[i]; + prop_name += "/" + itos(E.key); - if (vsnode->is_output_port_connected(i + 4) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 4))) { // alpha-component - String a = "n_out" + itos(p_node) + "p" + itos(i + 4); - r_code += " float " + a + " = n_out" + itos(p_node) + "p" + itos(i) + ".a;\n"; - outputs[i + 4] = a; - } + if (E.key != ShaderGraph::NODE_ID_OUTPUT) { + p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_ALWAYS_DUPLICATE)); + } + p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - i += 4; - } break; - default: - break; + if (Object::cast_to(E.value.node.ptr()) != nullptr) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/input_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/output_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + } + if (Object::cast_to(E.value.node.ptr()) != nullptr) { + p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/expression", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } } + p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "nodes/" + String(type_string[i]) + "/connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } +} - if (!node_code.is_empty()) { - r_code += "\n\n"; - } - - r_processed.insert(p_node); - - return OK; +Error VisualShader::_write_node( + ShaderGraph::Type p_type, + StringBuilder *p_global_code, + StringBuilder *p_global_code_per_node, + HashMap *p_global_code_per_func, + StringBuilder &r_code, + Vector &r_def_tex_params, + const VMap::Element *> &p_input_connections, + const VMap::Element *> &p_output_connections, + int p_node, + HashSet &r_processed, + bool p_for_preview, + HashSet &r_classes) const { + return graph[p_type]->_write_node(p_global_code, p_global_code_per_node, p_global_code_per_func, r_code, r_def_tex_params, p_input_connections, p_output_connections, p_node, r_processed, p_for_preview, r_classes, p_type, get_mode()); } bool VisualShader::has_func_name(RenderingServer::ShaderMode p_mode, const String &p_func_name) const { @@ -2813,7 +2885,7 @@ void VisualShader::_update_shader() const { StringBuilder global_code; StringBuilder global_code_per_node; - HashMap global_code_per_func; + HashMap global_code_per_func; StringBuilder shader_code; Vector default_tex_params; HashSet classes; @@ -2977,8 +3049,8 @@ void VisualShader::_update_shader() const { } //make it faster to go around through shader - VMap::Element *> input_connections; - VMap::Element *> output_connections; + VMap::Element *> input_connections; + VMap::Element *> output_connections; StringBuilder func_code; HashSet processed; @@ -3039,13 +3111,13 @@ void VisualShader::_update_shader() const { } for (const List::Element *E = graph[i]->connections.front(); E; E = E->next()) { - ConnectionKey from_key; + ShaderGraph::ConnectionKey from_key; from_key.node = E->get().from_node; from_key.port = E->get().from_port; output_connections.insert(from_key, E); - ConnectionKey to_key; + ShaderGraph::ConnectionKey to_key; to_key.node = E->get().to_node; to_key.port = E->get().to_port; @@ -3066,19 +3138,19 @@ void VisualShader::_update_shader() const { } insertion_pos.insert(i, shader_code.get_string_length() + func_code.get_string_length()); - Error err = _write_node(Type(i), &global_code, &global_code_per_node, &global_code_per_func, func_code, default_tex_params, input_connections, output_connections, ShaderGraph::NODE_ID_OUTPUT, processed, false, classes); + Error err = _write_node(ShaderGraph::Type(i), &global_code, &global_code_per_node, &global_code_per_func, func_code, default_tex_params, input_connections, output_connections, ShaderGraph::NODE_ID_OUTPUT, processed, false, classes); ERR_FAIL_COND(err != OK); if (varying_setters.has(i)) { for (int &E : varying_setters[i]) { - err = _write_node(Type(i), &global_code, nullptr, nullptr, func_code, default_tex_params, input_connections, output_connections, E, processed, false, classes); + err = _write_node(ShaderGraph::Type(i), &global_code, nullptr, nullptr, func_code, default_tex_params, input_connections, output_connections, E, processed, false, classes); ERR_FAIL_COND(err != OK); } } if (emitters.has(i)) { for (int &E : emitters[i]) { - err = _write_node(Type(i), &global_code, &global_code_per_node, &global_code_per_func, func_code, default_tex_params, input_connections, output_connections, E, processed, false, classes); + err = _write_node(ShaderGraph::Type(i), &global_code, &global_code_per_node, &global_code_per_func, func_code, default_tex_params, input_connections, output_connections, E, processed, false, classes); ERR_FAIL_COND(err != OK); } } @@ -3217,7 +3289,7 @@ void VisualShader::_update_shader() const { if (!has_func_name(RenderingServer::ShaderMode(shader_mode), func_name[i])) { continue; } - String func_code = global_code_per_func[Type(i)].as_string(); + String func_code = global_code_per_func[ShaderGraph::Type(i)].as_string(); if (empty_funcs.has(Type(i)) && !func_code.is_empty()) { func_code = vformat("%s%s%s", String("\nvoid " + String(func_name[i]) + "() {\n"), func_code, "}\n"); } @@ -3325,7 +3397,7 @@ VisualShader::VisualShader() { dirty.set(); for (int i = 0; i < TYPE_MAX; i++) { graph[i].instantiate(); - graph[i]->connect("graph_changed",callable_mp(this, &VisualShader::_queue_update)); + graph[i]->connect("graph_changed", callable_mp(this, &VisualShader::_queue_update)); if (i > (int)TYPE_LIGHT && i < (int)TYPE_SKY) { Ref output; output.instantiate(); diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 0c9a1fab748a..6ff682de0eec 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -45,6 +45,23 @@ class ShaderGraph : public RefCounted { friend class VisualShaderGroup; // For _get,_set and _get_property_list. public: + // TODO: Unify this eventually, but for now this is too much work. + // TODO: Rename to ShaderFunction/ShaderStage and make it an enum class. + // Keep in sync with VisualShader::Type. + enum Type { + TYPE_VERTEX, + TYPE_FRAGMENT, + TYPE_LIGHT, + TYPE_START, + TYPE_PROCESS, + TYPE_COLLIDE, + TYPE_START_CUSTOM, + TYPE_PROCESS_CUSTOM, + TYPE_SKY, + TYPE_FOG, + TYPE_MAX + }; + struct Node { // TODO: Rename to vsnode; Ref node; @@ -60,6 +77,17 @@ class ShaderGraph : public RefCounted { int to_port = 0; }; + union ConnectionKey { + struct { + uint64_t node : 32; + uint64_t port : 32; + }; + uint64_t key = 0; + bool operator<(const ConnectionKey &p_key) const { + return key < p_key.key; + } + }; + struct DefaultTextureParam { StringName name; List> params; @@ -76,6 +104,7 @@ class ShaderGraph : public RefCounted { List connections; // TODO: Evaluate whether this should be a LocalVector. void _node_changed(); + protected: static void _bind_methods(); @@ -84,7 +113,24 @@ class ShaderGraph : public RefCounted { void _get_property_list(List *p_list) const; public: + Error _write_node( + StringBuilder *p_global_code, + StringBuilder *p_global_code_per_node, + HashMap *p_global_code_per_func, + StringBuilder &r_code, + Vector &r_def_tex_params, + const VMap::Element *> &p_input_connections, + const VMap::Element *> &p_output_connections, + int p_node, + HashSet &r_processed, + bool p_for_preview, + HashSet &r_classes, + Type p_type = TYPE_MAX, // Only used for VisualShader. + Shader::Mode p_mode = Shader::MODE_MAX // Only used for VisualShader. + ) const; + bool _check_reroute_subgraph(int p_target_port_type, int p_reroute_node, List *r_visited_reroute_nodes = nullptr) const; + void add_node(const Ref &p_node, const Vector2 &p_position, int p_id); void set_node_position(int p_id, const Vector2 &p_position); Vector2 get_node_position(int p_id) const; @@ -226,19 +272,20 @@ class VisualShader : public Shader { mutable SafeFlag dirty; void _queue_update(); - // TODO: Move up. - union ConnectionKey { - struct { - uint64_t node : 32; - uint64_t port : 32; - }; - uint64_t key = 0; - bool operator<(const ConnectionKey &p_key) const { - return key < p_key.key; - } - }; - - Error _write_node(Type p_type, StringBuilder *p_global_code, StringBuilder *p_global_code_per_node, HashMap *p_global_code_per_func, StringBuilder &r_code, Vector &r_def_tex_params, const VMap::Element *> &p_input_connections, const VMap::Element *> &p_output_connections, int p_node, HashSet &r_processed, bool p_for_preview, HashSet &r_classes) const; + Error _write_node( + ShaderGraph::Type p_type, + StringBuilder *p_global_code, + StringBuilder *p_global_code_per_node, + HashMap *p_global_code_per_func, + StringBuilder &r_code, Vector &r_def_tex_params, + const VMap::Element *> &p_input_connections, + const VMap::Element *> &p_output_connections, + int p_node, + HashSet &r_processed, + bool p_for_preview, + HashSet &r_classes) const; void _input_type_changed(Type p_type, int p_id); // TODO: Check why we need this method. At least rename it (underscore). @@ -386,6 +433,8 @@ class VisualShaderNode : public Resource { static void _bind_methods(); public: + static String port_type_to_shader_string(PortType p_type); + bool is_simple_decl() const; virtual String get_caption() const = 0; @@ -940,8 +989,6 @@ class VisualShaderNodeGroupBase : public VisualShaderNodeResizableBase { VisualShaderNodeGroupBase(); }; - - class VisualShaderNodeExpression : public VisualShaderNodeGroupBase { GDCLASS(VisualShaderNodeExpression, VisualShaderNodeGroupBase); @@ -1080,8 +1127,6 @@ class VisualShaderNodeVaryingGetter : public VisualShaderNodeVarying { VisualShaderNodeVaryingGetter(); }; - - extern String make_unique_id(VisualShader::Type p_type, int p_id, const String &p_name); #endif // VISUAL_SHADER_H diff --git a/scene/resources/visual_shader_group.cpp b/scene/resources/visual_shader_group.cpp index b9ba4f497909..16182374a39a 100644 --- a/scene/resources/visual_shader_group.cpp +++ b/scene/resources/visual_shader_group.cpp @@ -1,10 +1,81 @@ #include "visual_shader_group.h" +#include "core/error/error_macros.h" +#include "core/object/callable_method_pointer.h" +#include "core/string/ustring.h" +#include "core/templates/hash_set.h" +#include "core/templates/vmap.h" #include "editor/plugins/visual_shader_editor_plugin.h" #include "scene/gui/box_container.h" +#include "scene/gui/graph_node.h" #include "scene/gui/item_list.h" #include "scene/gui/line_edit.h" #include "scene/gui/option_button.h" +#include "visual_shader_particle_nodes.h" + +String VisualShaderGroup::_validate_port_name(const String &p_port_name, int p_port_id, bool p_output) const { + String port_name = p_port_name; + + if (port_name.is_empty()) { + return String(); + } + + while (port_name.length() && !is_ascii_alphabet_char(port_name[0])) { + port_name = port_name.substr(1, port_name.length() - 1); + } + + if (!port_name.is_empty()) { + String valid_name; + + for (int i = 0; i < port_name.length(); i++) { + if (is_ascii_identifier_char(port_name[i])) { + valid_name += String::chr(port_name[i]); + } else if (port_name[i] == ' ') { + valid_name += "_"; + } + } + + port_name = valid_name; + } else { + return String(); + } + + List input_names; + List output_names; + + for (int i = 0; i < get_input_ports().size(); i++) { + if (!p_output && i == p_port_id) { + continue; + } + if (port_name == get_input_port(i).name) { + return String(); + } + } + for (int i = 0; i < get_output_ports().size(); i++) { + if (p_output && i == p_port_id) { + continue; + } + if (port_name == get_output_port(i).name) { + return String(); + } + } + + return port_name; +} + +String VisualShaderGroup::_validate_group_name(const String &p_name) const { + String valid_name; + + for (int i = 0; i < p_name.length(); i++) { + if (is_ascii_identifier_char(p_name[i])) { + valid_name += String::chr(p_name[i]); + } else if (p_name[i] == ' ') { + valid_name += "_"; + } + } + + return valid_name; +} void VisualShaderGroup::_bind_methods() { // TODO: Bind setters/getters for input/output ports. @@ -41,6 +112,120 @@ void VisualShaderGroup::_bind_methods() { ADD_PROPERTY_DEFAULT("group_name", "Node group"); } +void VisualShaderGroup::_queue_update() { + if (dirty.is_set()) { + return; + } + + dirty.set(); + callable_mp(this, &VisualShaderGroup::_update_group).call_deferred(); +} + +void VisualShaderGroup::_update_group() { + if (!dirty.is_set()) { + return; + } + + dirty.clear(); + + // TODO: Update group. + + StringBuilder global_code_builder; + StringBuilder global_code_per_node_builder; + HashMap global_code_per_func_builder; + StringBuilder code_builder; + Vector default_tex_params; + // static const char *shader_mode_str[Shader::MODE_MAX] = { "spatial", "canvas_item", "particles", "sky", "fog" }; + + HashSet classes; + HashMap insertion_pos; + + String global_expressions; + HashSet used_parameter_names; + List parameters; + List emitters; + HashMap> varying_setters; + + // Preprocess nodes. + int index = 0; + for (const KeyValue &E : graph->nodes) { + Ref global_expression = E.value.node; + if (global_expression.is_valid()) { + String expr = ""; + expr += "// " + global_expression->get_caption() + ":" + itos(index++) + "\n"; + expr += global_expression->generate_global(Shader::MODE_MAX, VisualShader::TYPE_MAX, -1); + expr = expr.replace("\n", "\n "); + expr += "\n"; + global_expressions += expr; + } + Ref parameter_ref = E.value.node; + if (parameter_ref.is_valid()) { + used_parameter_names.insert(parameter_ref->get_parameter_name()); + } + Ref parameter = E.value.node; + if (parameter.is_valid()) { + parameters.push_back(parameter.ptr()); + } + Ref emit_particle = E.value.node; + if (emit_particle.is_valid()) { + emitters.push_back(E.key); + } + } + + // TODO: Forbid paramters. + // int idx = 0 + // for (List::Iterator itr = parameters.begin(); itr != parameters.end(); ++itr, ++idx) { + // VisualShaderNodeParameter *parameter = *itr; + // if (used_parameter_names.has(parameter->get_parameter_name())) { + // global_code += parameter->generate_global(get_mode(), Type(idx), -1); + // const_cast(parameter)->set_global_code_generated(true); + // } else { + // const_cast(parameter)->set_global_code_generated(false); + // } + // } + HashMap code_map; + HashSet empty_funcs; + VMap::Element *> input_connections; + VMap::Element *> output_connections; + + StringBuilder group_code; + HashSet processed; + + for (const List::Element *E = graph->connections.front(); E; E = E->next()) { + ShaderGraph::ConnectionKey from_key; + from_key.node = E->get().from_node; + from_key.port = E->get().from_port; + + output_connections.insert(from_key, E); + + ShaderGraph::ConnectionKey to_key; + to_key.node = E->get().to_node; + to_key.port = E->get().to_port; + + input_connections.insert(to_key, E); + } + + Error err = graph->_write_node(&global_code_builder, &global_code_per_node_builder, &global_code_per_func_builder, group_code, default_tex_params, input_connections, output_connections, NODE_ID_GROUP_OUTPUT, processed, false, classes); + ERR_FAIL_COND(err != OK); + + // TODO: Figure out why this needs to be separately. + for (int &E : emitters) { + err = graph->_write_node(&global_code_builder, &global_code_per_node_builder, &global_code_per_func_builder, group_code, default_tex_params, input_connections, output_connections, E, processed, false, classes); + ERR_FAIL_COND(err != OK); + } + + // TODO: Use concept of previous code to determine whether to fire the changed signal? + + code_builder += "// Group content: " + group_name + "\n"; + code_builder += group_code; + + global_code_builder.append(global_code_per_node_builder); + global_code_builder.append(global_expressions); + global_code = global_code_builder.as_string(); + // TODO: Insert global code per func + code = code_builder.as_string(); +} + bool VisualShaderGroup::_set(const StringName &p_name, const Variant &p_value) { if (p_name == "input_ports") { input_ports.clear(); @@ -108,12 +293,34 @@ Ref VisualShaderGroup::get_graph() const { return graph; } +String VisualShaderGroup::get_code() { + if (dirty.is_set()) { + _update_group(); + } + return code; +} + +String VisualShaderGroup::get_global_code() { + if (dirty.is_set()) { + _update_group(); + } + return global_code; +} + +String VisualShaderGroup::get_unique_name() const { + String name = group_name; + name = name.replace(" ", "_"); + return name; +} + void VisualShaderGroup::set_group_name(const String &p_name) { - if (group_name == p_name) { + const String valid_name = _validate_group_name(p_name); + + if (group_name == p_name || valid_name.is_empty()) { return; } - group_name = p_name; + group_name = p_name; // Don't use valid_name here, since we want to keep the original name. emit_changed(); } @@ -122,16 +329,32 @@ String VisualShaderGroup::get_group_name() const { } void VisualShaderGroup::add_input_port(int p_id, VisualShaderNode::PortType p_type, const String &p_name) { - input_ports[p_id] = Port{ p_type, p_name }; + const String valid_name = _validate_port_name(p_name, p_id, false); + + if (valid_name.is_empty()) { + return; + } + + input_ports[p_id] = Port{ p_type, valid_name }; emit_changed(); } void VisualShaderGroup::set_input_port_name(int p_id, const String &p_name) { - input_ports[p_id].name = p_name; + ERR_FAIL_COND(!input_ports.has(p_id)); + + const String valid_name = _validate_port_name(p_name, p_id, false); + + if (valid_name.is_empty()) { + return; + } + + input_ports[p_id].name = valid_name; emit_changed(); } void VisualShaderGroup::set_input_port_type(int p_id, VisualShaderNode::PortType p_type) { + ERR_FAIL_COND(!input_ports.has(p_id)); + input_ports[p_id].type = p_type; emit_changed(); } @@ -154,16 +377,32 @@ void VisualShaderGroup::remove_input_port(int p_id) { } void VisualShaderGroup::add_output_port(int p_id, VisualShaderNode::PortType p_type, const String &p_name) { - output_ports[p_id] = Port{ p_type, p_name }; + const String valid_name = _validate_port_name(p_name, p_id, true); + + if (valid_name.is_empty()) { + return; + } + + output_ports[p_id] = Port{ p_type, valid_name }; emit_changed(); } void VisualShaderGroup::set_output_port_name(int p_id, const String &p_name) { - output_ports[p_id].name = p_name; + ERR_FAIL_COND(!output_ports.has(p_id)); + + const String valid_name = _validate_port_name(p_name, p_id, true); + + if (valid_name.is_empty()) { + return; + } + + output_ports[p_id].name = valid_name; emit_changed(); } void VisualShaderGroup::set_output_port_type(int p_id, VisualShaderNode::PortType p_type) { + ERR_FAIL_COND(!output_ports.has(p_id)); + output_ports[p_id].type = p_type; emit_changed(); } @@ -266,7 +505,10 @@ void VisualShaderGroup::get_node_connections(List *r_co } VisualShaderGroup::VisualShaderGroup() { + dirty.set(); + graph.instantiate(); + graph->connect("graph_changed", callable_mp(this, &VisualShaderGroup::_queue_update)); Ref input_node; input_node.instantiate(); @@ -280,7 +522,7 @@ VisualShaderGroup::VisualShaderGroup() { graph->nodes[NODE_ID_GROUP_OUTPUT].node = output_node; graph->nodes[NODE_ID_GROUP_OUTPUT].position = Vector2(400, 150); - group_name == TTR("Node group"); + group_name = TTR("Node group"); } ////////////// Group @@ -371,9 +613,93 @@ Ref VisualShaderNodeGroup::get_group() const { return group; } +void VisualShaderNodeGroup::set_shader_type(ShaderGraph::Type p_type) { + shader_type = p_type; +} + +void VisualShaderNodeGroup::set_shader_mode(Shader::Mode p_mode) { + shader_mode = p_mode; +} + String VisualShaderNodeGroup::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { - // TODO: Implement. - return String(); + if (group.is_null()) { + return String(); + } + // TODO:Validate name and append unique id. + + // Generate the code for the group. + String code = String("/* Group: ") + group->get_group_name() + " */\n"; + + const String valid_group_name = group->_validate_group_name(group->get_group_name()); + ERR_FAIL_COND_V(valid_group_name.is_empty(), ""); + code += "group_" + valid_group_name + "("; + + const Vector input_ports = group->get_input_ports(); + int param_idx = 0; + for (int i = 0; i < input_ports.size(); i++) { + if (i > 0) { + code += ","; + } + code += p_input_vars[i]; + param_idx++; + } + + const Vector output_ports = group->get_output_ports(); + for (int i = 0; i < output_ports.size(); i++) { + if (param_idx > 0) { + code += ","; + } + code += p_output_vars[i]; + param_idx++; + } + + code += ");\n"; + + return code; +} + +String VisualShaderNodeGroup::generate_group_function(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + if (group.is_null()) { + return String(); + } + + // Generate a global function for the group. + String code = String("/* Group: ") + group->get_group_name() + " */\n"; + + code += group->get_global_code(); + + // TODO: Don't use the type and id for the function name. + const String valid_group_name = group->_validate_group_name(group->get_group_name()); + ERR_FAIL_COND_V(valid_group_name.is_empty(), ""); + + code += "void group_" + valid_group_name + "("; + + // Add all inputs/outputs as function parameters. + const Vector input_ports = group->get_input_ports(); + for (int i = 0; i < input_ports.size(); i++) { + if (i == 0) { + code += "in "; + } else { + code += ", in "; + } + code += VisualShaderNode::port_type_to_shader_string(input_ports[i].type) + " "; + code += input_ports[i].name; + } + + const Vector output_ports = group->get_output_ports(); + for (int i = 0; i < output_ports.size(); i++) { + code += ", out "; + code += VisualShaderNode::port_type_to_shader_string(output_ports[i].type) + " "; + code += output_ports[i].name; + } + + code += ") {\n"; + + // Add the code for the group. + code += group->get_code(); + + code += "}\n"; + return code; } bool VisualShaderNodeGroup::is_output_port_expandable(int p_port) const { @@ -444,8 +770,13 @@ String VisualShaderNodeGroupInput::get_caption() const { } String VisualShaderNodeGroupInput::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { - // TODO: Implement. - return String(); + ERR_FAIL_NULL_V(group, ""); + + String code = String("/* Group input: ") + group->get_group_name() + " */\n"; + for (int i = 0; i < group->get_input_ports().size(); i++) { + code += p_output_vars[i] + " = " + group->get_input_port(i).name + ";\n"; + } + return code; } Vector VisualShaderNodeGroupInput::get_editable_properties() const { @@ -511,7 +842,16 @@ String VisualShaderNodeGroupOutput::get_caption() const { } String VisualShaderNodeGroupOutput::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { - return String(); + ERR_FAIL_NULL_V(group, String()); + + String code = String("/* Group output: ") + group->get_group_name() + " */\n"; + for (int i = 0; i < group->get_output_ports().size(); i++) { + if (p_input_vars[i].is_empty()) { + continue; + }; + code += group->get_output_port(i).name + " = " + p_input_vars[i] + ";\n"; + } + return code; } VisualShaderNodeGroupOutput::VisualShaderNodeGroupOutput() { @@ -522,7 +862,16 @@ void VisualShaderGroupPortsDialog::_add_port() { // Add a new port to the group. const VisualShaderNode::PortType port_type = VisualShaderNode::PORT_TYPE_SCALAR; - const String port_name = "new_port"; + String port_name = edit_inputs ? "new_in_port" : "new_out_port"; + + // Find a new valid name for the port. + int port_idx = 2; + String port_name_numerated = port_name; + while (group->_validate_port_name(port_name_numerated,-1, !edit_inputs).is_empty()) { + port_name_numerated = port_name + itos(port_idx); + } + port_name = port_name_numerated; + if (edit_inputs) { group->add_input_port(group->get_input_ports().size(), port_type, port_name); } else { @@ -555,6 +904,7 @@ void VisualShaderGroupPortsDialog::_update_editor_for_port(int p_idx) { port_type_optbtn->set_visible(true); // Update the controls in the editor area of the dialog. + ERR_FAIL_INDEX(p_idx, edit_inputs ? group->get_input_ports().size() : group->get_output_ports().size()); const VisualShaderGroup::Port port = edit_inputs ? group->get_input_port(p_idx) : group->get_output_port(p_idx); name_edit->set_text(port.name); @@ -592,6 +942,7 @@ void VisualShaderGroupPortsDialog::_on_port_item_selected(int p_index) { void VisualShaderGroupPortsDialog::_on_port_name_changed(const String &p_name) { ERR_FAIL_NULL(group); + ERR_FAIL_COND(port_item_list->get_selected_items().size() != 1); // Update the port name in the group. const int port_idx = port_item_list->get_selected_items()[0]; @@ -607,6 +958,7 @@ void VisualShaderGroupPortsDialog::_on_port_name_changed(const String &p_name) { void VisualShaderGroupPortsDialog::_on_port_type_changed(int p_idx) { ERR_FAIL_NULL(group); + ERR_FAIL_COND(port_item_list->get_selected_items().size() != 1); // Update the port type in the group. const int port_idx = port_item_list->get_selected_items()[0]; @@ -621,7 +973,23 @@ void VisualShaderGroupPortsDialog::_on_port_type_changed(int p_idx) { port_item_list->set_item_icon_modulate(port_idx, port_colors[p_idx]); } +void VisualShaderGroupPortsDialog::_on_dialog_about_to_popup() { + ERR_FAIL_NULL(group); + if (port_item_list->get_item_count() == 0) { + _update_editor_for_port(-1); + return; + } + + if (port_item_list->get_selected_items().size() != 1) { + port_item_list->select(0); + } + _update_editor_for_port(port_item_list->get_selected_items()[0]); +} + void VisualShaderGroupPortsDialog::set_dialog_mode(bool p_edit_inputs) { + if (edit_inputs == p_edit_inputs) { + return; + } edit_inputs = p_edit_inputs; } @@ -639,11 +1007,12 @@ void VisualShaderGroupPortsDialog::set_group(VisualShaderGroup *p_group) { port_item_list->add_item(ports[i].name, port_icon); port_item_list->set_item_icon_modulate(i, port_colors[ports[i].type]); } - - port_item_list->select(0); } VisualShaderGroupPortsDialog::VisualShaderGroupPortsDialog() { + connect(SNAME("about_to_popup"), callable_mp(this, &VisualShaderGroupPortsDialog::_on_dialog_about_to_popup)); + set_title("Edit group ports"); + VBoxContainer *vbc = memnew(VBoxContainer); add_child(vbc); diff --git a/scene/resources/visual_shader_group.h b/scene/resources/visual_shader_group.h index d833cf5a49f4..fa2bb143d1eb 100644 --- a/scene/resources/visual_shader_group.h +++ b/scene/resources/visual_shader_group.h @@ -7,6 +7,9 @@ class VisualShaderGroup : public Resource { GDCLASS(VisualShaderGroup, Resource); + inline static int NODE_ID_GROUP_INPUT = 0; + inline static int NODE_ID_GROUP_OUTPUT = 1; + public: struct Port { VisualShaderNode::PortType type = VisualShaderNode::PortType::PORT_TYPE_MAX; @@ -22,22 +25,33 @@ class VisualShaderGroup : public Resource { Ref graph; - inline static int NODE_ID_GROUP_INPUT = 0; - inline static int NODE_ID_GROUP_OUTPUT = 1; + mutable SafeFlag dirty; + String code; + String global_code; protected: static void _bind_methods(); + void _queue_update(); + void _update_group(); + bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List *p_list) const; public: Ref get_graph() const; + String get_code(); + String get_global_code(); + String get_unique_name() const; void set_group_name(const String &p_name); String get_group_name() const; + // TODO: Make private? + String _validate_port_name(const String &p_port_name, int p_port_id, bool p_output) const; + String _validate_group_name(const String &p_name) const; + void add_input_port(int p_id, VisualShaderNode::PortType p_type, const String &p_name); void set_input_port_name(int p_id, const String &p_name); void set_input_port_type(int p_id, VisualShaderNode::PortType p_type); @@ -87,7 +101,7 @@ class VisualShaderGroup : public Resource { // TODO: Implement? String generate_preview_shader(int p_node, int p_port, Vector &r_default_tex_params) const; - String validate_port_name(const String &p_port_name, VisualShaderNode *p_node, int p_port_id, bool p_output) const; + // String validate_port_name(const String &p_port_name, VisualShaderNode *p_node, int p_port_id, bool p_output) const; // TODO: Implement? String validate_parameter_name(const String &p_name, const Ref &p_parameter) const; @@ -99,6 +113,10 @@ class VisualShaderNodeGroup : public VisualShaderNode { Ref group; + // For validation. + ShaderGraph::Type shader_type = ShaderGraph::Type::TYPE_MAX; // TYPE_MAX when used in a VisualShaderGroup itself. + Shader::Mode shader_mode = Shader::Mode::MODE_MAX; // MODE_MAX when used in a VisualShaderGroup itself. + void _emit_changed(); protected: @@ -122,7 +140,12 @@ class VisualShaderNodeGroup : public VisualShaderNode { void set_group(const Ref &p_group); Ref get_group() const; + void set_shader_type(ShaderGraph::Type p_type); + void set_shader_mode(Shader::Mode p_mode); + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + // virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + String generate_group_function(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; virtual bool is_output_port_expandable(int p_port) const override; virtual Category get_category() const override { return CATEGORY_SPECIAL; } @@ -189,7 +212,7 @@ class VisualShaderNodeGroupOutput : public VisualShaderNode { GDCLASS(VisualShaderNodeGroupOutput, VisualShaderNode); // TODO: Possibly dangerous, but it is necessary for now since we don't have a proper weak reference. - VisualShaderGroup *group; + VisualShaderGroup *group = nullptr; // struct Port { // PortType type = PortType::PORT_TYPE_MAX; @@ -249,6 +272,7 @@ class VisualShaderGroupPortsDialog : public AcceptDialog { void _on_port_item_selected(int p_idx); void _on_port_name_changed(const String &p_name); void _on_port_type_changed(int p_idx); + void _on_dialog_about_to_popup(); // TODO: Update graph on exit. public: