#ifndef PROC_NODES_PROC_NODE_H
#define PROC_NODES_PROC_NODE_H
#include "socket_value.h"
#include "../gpu/glsl_emit_context.h"
#include <godot_cpp/classes/resource.hpp>
#include <godot_cpp/variant/packed_int32_array.hpp>
#include <godot_cpp/variant/vector2.hpp>
namespace godot {
class ProcEvalContext;
class ProcNode : public Resource {
GDCLASS(ProcNode, Resource);
protected:
static void _bind_methods();
Vector<SocketDef> input_defs;
Vector<SocketDef> output_defs;
Vector2 graph_position;
// THE ID THE AUTHORING TOOL GAVE THIS NODE, -1 when it has none.
//
// GraphImporter builds a json-id -> graph-index map for connections and then
// THREW IT AWAY, so a consumer holding an authoring id had no way to reach
// the node. blender-bridge's parameter deltas carry exactly that id and were
// passing it straight to get_node() as though it were an index -- which is
// only correct while nothing is skipped. One unmapped node (SetMaterial)
// shifts every index after it, so a delta wrote the WRONG node, and an id
// past the end took the editor down with SIGSEGV.
int source_id = -1;
Vector<Variant> _constructor_defaults; // Captured lazily on first set_input_default
void _capture_constructor_defaults() {
if (_constructor_defaults.is_empty() && input_defs.size() > 0) {
for (int i = 0; i < input_defs.size(); i++) {
_constructor_defaults.push_back(input_defs[i].default_value);
}
}
}
public:
// Node identity
virtual StringName get_node_type() const { return "ProcNode"; }
virtual String get_node_title() const { return "Node"; }
virtual String get_node_category() const { return "General"; }
// Socket access
int get_input_count() const { return input_defs.size(); }
int get_output_count() const { return output_defs.size(); }
const SocketDef &get_input_def(int p_idx) const { return input_defs[p_idx]; }
bool is_input_multi(int p_idx) const {
return p_idx >= 0 && p_idx < input_defs.size() && input_defs[p_idx].multi;
}
const SocketDef &get_output_def(int p_idx) const { return output_defs[p_idx]; }
Variant get_input_default(int p_idx) const {
if (p_idx >= 0 && p_idx < input_defs.size()) return input_defs[p_idx].default_value;
return Variant();
}
String get_input_name(int p_idx) const {
if (p_idx >= 0 && p_idx < input_defs.size()) return String(input_defs[p_idx].name);
return "";
}
int get_input_type(int p_idx) const {
if (p_idx >= 0 && p_idx < input_defs.size()) return (int)input_defs[p_idx].type;
return -1;
}
// Outputs, for the socket audit. Only the input side was reachable from
// script, so an audit could not see that Gradient Texture declared one
// output where Blender's has two -- and a graph reading Fac read nothing.
String get_output_name(int p_idx) const {
if (p_idx >= 0 && p_idx < output_defs.size()) return String(output_defs[p_idx].name);
return "";
}
int get_output_type(int p_idx) const {
if (p_idx >= 0 && p_idx < output_defs.size()) return (int)output_defs[p_idx].type;
return -1;
}
// Graph editor position
Vector2 get_graph_position() const { return graph_position; }
void set_graph_position(const Vector2 &p_pos) { graph_position = p_pos; }
int get_source_id() const { return source_id; }
void set_source_id(int p_id) { source_id = p_id; }
// Set default value for an input socket (used by GraphImporter and serialization)
void set_input_default(int p_idx, const Variant &p_value) {
if (p_idx >= 0 && p_idx < input_defs.size()) {
_capture_constructor_defaults();
input_defs.ptrw()[p_idx].default_value = p_value;
}
}
// Check if an input default was modified from its constructor value
bool is_input_default_modified(int p_idx) const {
if (_constructor_defaults.is_empty()) {
return false;
}
if (p_idx < 0 || p_idx >= input_defs.size() || p_idx >= _constructor_defaults.size()) {
return false;
}
return input_defs[p_idx].default_value != _constructor_defaults[p_idx];
}
// Execution (called by GraphExecutor)
virtual void execute(ProcEvalContext &p_ctx, int p_node_idx) = 0;
// GPU support
virtual bool supports_gpu() const { return false; }
// p_ctx resolves this node's inputs to GLSL expressions: the producer's
// variable when a socket is connected, its default as a literal when it is
// not. Nodes written before it exists ignore it and emit what they always
// did -- see docs/glsl-emitter-design.md.
virtual String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const { return ""; }
// THE INPUT SOCKETS THIS NODE CARRIES IN THE BATCH PATH'S PUSH-CONSTANT
// BLOCK, in slot order. Float, Int and Bool take one slot; Vector takes
// three. Empty means the node has none -- which is every node that is not a
// GPU GEOMETRY node, because compile_batch is only ever reached through
// _is_gpu_geometry_node().
//
// This list is the ONLY statement of the mapping. GlslEmitContext::param()
// walks it to spell `pc.paramN`, and GraphExecutor walks it to pack the
// bytes, so the slot a node reads is the slot the executor fills BY
// CONSTRUCTION. It used to be stated twice, in the node's GLSL literal and
// in a per-type `if` in the executor, and they drifted: Set Position's
// packer read socket 1 -- which has been SELECTION since its sockets were
// matched to Blender's -- and called it the offset vector.
//
// A socket left out of this list cannot reach the batch shader at all, so
// GraphExecutor falls back to the CPU when one of them is connected rather
// than dispatching a shader that cannot see it.
virtual PackedInt32Array get_push_constant_sockets() const { return PackedInt32Array(); }
// TWO DIFFERENT REASONS TO EMIT NOTHING, and conflating them is what made
// the emitter drop nodes silently. A node with missing arithmetic must make
// emission refuse; a node that is STRUCTURALLY TRANSPARENT -- Group Input
// and Group Output carry the graph's boundary and no operation -- has
// nothing to emit and must not. Every bridged graph has both of them, so
// without this distinction the whole Blender path would refuse.
virtual bool gpu_transparent() const { return false; }
// FILE-SCOPE GLSL this node needs: constants, helper functions, buffer
// declarations. compile_glsl's output is spliced into the BODY of
// proc_density, and GLSL has no nested functions -- a node that emitted a
// helper there produced a shader that could not compile at all, silently,
// with the map falling back to whatever was there before. Anything that is
// not a statement belongs here.
virtual String compile_glsl_prelude(int p_node_idx) const { return ""; }
ProcNode() = default;
};
} // namespace godot
#endif // PROC_NODES_PROC_NODE_H