OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / gpu / glsl_emit_context.h

133 lines · c ref:
#ifndef PROC_NODES_GLSL_EMIT_CONTEXT_H
#define PROC_NODES_GLSL_EMIT_CONTEXT_H

#include "../graph/socket_value.h"

#include <godot_cpp/variant/string.hpp>

namespace godot {

class ProcNodeGraph;

// HOW A NODE'S GLSL GETS ITS INPUTS.
//
// The emitter used to be a concatenator: it appended each node's snippet in
// graph-index order and replaced `pc.paramN` with THAT NODE'S OWN socket
// defaults. `grep -c connections gpu_compiler.cpp` was 0 -- the graph's edges
// were never read -- so values moved between nodes only where two of them
// agreed on a hard-coded local name (gn_height, gn_slope, en_erosion). A node
// with inputs from anywhere else could not be expressed at all, which is why
// nine node types have no GLSL and writing their arithmetic would have produced
// nodes that compute on their own defaults and are silently constant.
//
// This is the missing half. A node asks the context for an input and gets an
// EXPRESSION: the producer's variable when the socket is connected, the
// socket's own default as a literal when it is not.
//
//   in(node, socket)       "n3_s0"  or  "0.150000000"
//   out(node, socket)      "n7_s0"        the variable to assign
//   declare(node, socket)  "float n7_s0"  that variable's declaration
//
// GEOMETRY IS NOT A VALUE HERE. A density graph is a displacement pipeline:
// nodes read and move the shared `pos`, and the density is how far pos.y ended
// up from the query height. That channel stays implicit, because it is what
// every shipping terrain graph is built on. Only non-geometry sockets get
// variables.
struct GlslEmitContext {
	const ProcNodeGraph *graph = nullptr;

	// Graph node indices emitted into THIS shader. A connected socket whose
	// producer is in here resolves to that producer's local; one whose producer
	// is not has to keep coming through the push-constant block, because the
	// local it would name does not exist in this translation unit.
	Vector<int> emitted_nodes;
	bool is_emitted(int p_node_idx) const {
		for (int i = 0; i < emitted_nodes.size(); i++) {
			if (emitted_nodes[i] == p_node_idx) {
				return true;
			}
		}
		return false;
	}

	// Wrap integer (octave/detail) parameters in min(N, pn_max_octaves). The
	// density path passes true; the batch path does not have that uniform.
	bool clamp_octaves = false;

	// WHICH PATH IS BEING EMITTED, and it is not cosmetic.
	//
	// compile_batch produces a compute shader with a push-constant block that
	// GraphExecutor refills on every dispatch, so a scalar a node reads from a
	// socket must arrive through `pc.paramN` -- the pipeline is cached in the
	// ExecutionPlan and set_input_default() emits no `changed`, so a literal
	// baked into that shader would freeze at the value it had when the plan was
	// built. compile_to_function produces a standalone `float proc_density`
	// with NO push-constant block at all, so the same `pc.paramN` there is a
	// reference to something that does not exist.
	//
	// The two used to be reconciled by compile_to_function TEXTUALLY replacing
	// `pc.paramN` with socket N's default. That is a different convention from
	// the batch path's, where N is a push-constant SLOT the executor packs by
	// hand, and the two collided on every node that used both. Measured
	// 2026-09-06 on the emitted density text:
	//
	//   Displace     every argument shifted one socket, and `* pc.param0`
	//                became `* 0.0` (socket 0 is Geometry, which has no
	//                literal) -- the displacement multiplied by zero
	//   SimplexNoise the same shift, plus `pc.param3` left VERBATIM because
	//                the node has only three sockets, in a function where `pc`
	//                is not declared: the shader cannot compile
	//   SetPosition  `pos += vec3(0.0, 0.0, 0.0)`
	//
	// So the choice is made once, here, and `param()` below is the only place a
	// node has to know about it.
	bool push_constants = false;

	// The expression for an input socket. Connected -> the producer's variable,
	// coerced if the types differ; unconnected -> the socket default as a
	// literal.
	String in(int p_node_idx, int p_socket) const;

	// An input socket that the BATCH path carries in the push-constant block.
	//
	//   push_constants  ->  "pc.param2", or "vec3(pc.param0, pc.param1, pc.param2)"
	//   otherwise       ->  in(node, socket), the same as any other input
	//
	// The slot is computed by walking the node's own get_push_constant_sockets()
	// and summing the widths, and GraphExecutor packs by walking the SAME list.
	// One list, so the slot a node reads and the slot the executor fills cannot
	// drift -- which they had, on Set Position, for as long as its sockets have
	// matched Blender's (see graph_executor.cpp).
	//
	// A socket the node does not declare falls through to in(): a node with no
	// map at all -- Simplex Noise is one, it is not a geometry node and never
	// reaches compile_batch -- behaves exactly as if this did not exist.
	String param(int p_node_idx, int p_socket) const;

	// The variable an output socket is written to, and its declaration.
	String out(int p_node_idx, int p_socket) const;
	String declare(int p_node_idx, int p_socket) const;

	// GLSL spelling of a socket type. String and Geometry have none -- a node
	// carrying either cannot be GPU-capable, and asking returns "" so a caller
	// cannot quietly emit a broken type name.
	static String type_name(SocketType p_type);

	// A literal for a socket's default value, at the precision the shader needs.
	// NINE digits: four is fine for a frequency and destroys anything small --
	// a y-scale of 5/21000 = 0.000238 truncated to 0.0002 is a 16% error in the
	// vertical mapping, which moved a terrain surface ~1770 m.
	String literal(int p_node_idx, int p_socket) const;

	// The same formatter, for a value that is not on an INPUT socket. Group
	// Input's parameters live on its OUTPUTS -- execute() writes them from
	// output_defs[i].default_value -- and spelling them a second time here is
	// how the two paths would come to disagree about a number neither of them
	// computes.
	String literal_value(SocketType p_type, const Variant &p_value) const;
};

} // namespace godot

#endif // PROC_NODES_GLSL_EMIT_CONTEXT_H