OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes commits / 0346f6c

glsl: White Noise comes back on, and it is the hash and nothing else
First of the five texture nodes off the GLSL debt list, and the cheapest, because there is nothing above the hash in it: Blender's White Noise Texture is hash_float_to_float for the Value and hash_float_to_float3 for the Colour, keyed on noise_dimensions, and that is the entire node. supports_gpu() went false -> true, and the comment that used to sit there -- "the GLSL is NOT this function" -- is gone rather than reworded. It was true of a hand-written copy, and the way to stop having to say it was to delete the copy, which blender_hash.inc did. compile_glsl now emits calls into the same text execute() runs through the C++ shim. op parity 658 -> 678/678, the 20 new cases being 4 dimensions x 5 inputs golden 12/12, byte-identical (no shipping graph uses this node) hash parity 1056/1056 blender 8/8 white noise ALL FOUR DIMENSIONS ARE GATED, because each keys a DIFFERENT hash arity -- 1D takes W alone, 2D drops z, 4D appends W -- and a node emitting the 3D form for every one of them would look perfectly plausible in a diff and be wrong three times out of four. Checked by mutation: collapsing the 2D case to the 3D form fails exactly the five d2 cases and nothing else, 673/678. Also checked that the new cases reach the node at all rather than passing trivially -- the emitted text for each dimension names a different arity, the inputs arrive as connection variables (n1_s0, n2_s0) rather than as the node's own defaults, and the prelude is present in the shader. The Colour output is emitted but gated elsewhere: hash_float_to_float3 is covered exactly, all 1056 cases, by tests/test_hash_parity.cpp, which is a better instrument for a vec4 than a float density socket. blender_hash.inc gained a whole-file #ifndef guard. Every texture node that stands on the hash will emit it as a prelude and a prelude is emitted per node; GpuCompiler dedups by string, but Math's helpers already record why that is not enough when one prelude contains another. GLSL has a preprocessor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0346f6c8bcf8dba0023e987001a315a7921c6d7c
by plankatron <fleeky@gmail.com> · 2026-09-05T23:12:34.000Z

Changed files (4) +99 -4

demo/glsl_op_gate_dump.gd +43 -0
……@@ -188,6 +188,49 @@ func _init() -> void:
188188 PackedStringArray()),
189189 })
190190
191 # --- White Noise ------------------------------------------------------
192 # The first texture node back on the GPU, and the one with nothing above
193 # the hash: Blender's White Noise IS hash_float_to_float, so this compares
194 # the dual-compiled blender_hash.inc through the node that exposes it most
195 # directly. All four noise_dimensions, because each keys a DIFFERENT hash
196 # arity -- 1D takes W alone, 2D drops z, 4D appends W -- and a node that
197 # emitted the 3D form for all of them would still look perfectly plausible.
198 #
199 # The Value output only. The Colour is hash_float_to_float3 of the same
200 # key, and that family is gated exactly, all 1056 cases of it, by
201 # tests/test_hash_parity.cpp -- which is a better instrument for it than a
202 # float socket.
203 var wn_inputs := [
204 [Vector3(0.0, 0.0, 0.0), 0.0],
205 [Vector3(1.0, 2.0, 3.0), 0.5],
206 [Vector3(-7.25, 0.5, 1e7), -1.5],
207 [Vector3(1e-7, -0.001, 65536.0), 3.14159265],
208 [Vector3(0.5, 0.5, 0.5), 0.0],
209 ]
210 for dims in [1, 2, 3, 4]:
211 for wi in wn_inputs:
212 var g5 := ProcNodeGraph.new()
213 var wn := WhiteNoiseNode.new()
214 wn.set("noise_dimensions", dims)
215 var wn_idx := g5.add_node(wn)
216 var vn := VectorNode.new()
217 vn.set("value", wi[0])
218 g5.connect_sockets(g5.add_node(vn), 0, wn_idx, 0)
219 var wv := ValueNode.new()
220 wv.set("value", wi[1])
221 g5.connect_sockets(g5.add_node(wv), 0, wn_idx, 1)
222 var go5 := GroupOutputNode.new()
223 go5.set_interface([{"name": "Density", "type": 0}])
224 var go5_idx := g5.add_node(go5)
225 g5.connect_sockets(wn_idx, 0, go5_idx, 0)
226 cases.append({
227 "node": "WhiteNoise.d%d" % dims, "op": dims,
228 "inputs": [wi[0].x, wi[0].y, wi[1]],
229 "cpu": float(g5.evaluate_socket(wn_idx, 0)),
230 "glsl": g5.compile_to_glsl_function("proc_density", false,
231 PackedStringArray()),
232 })
233
191234 # A DIRECTORY, not JSON: stage 2 is a standalone C++ program with no JSON
192235 # parser, and adding one to read our own test data would be more code than
193236 # the gate. One manifest line per case, one .glsl file per case.
src/nodes/noise_nodes.cpp +32 -0
……@@ -10,6 +10,7 @@
1010#include "../graph/graph_executor.h"
1111#include "../graph/proc_node_graph.h"
1212#include <cmath>
13#include "../gpu/blender_hash_glsl.gen.h"
1314
1415namespace godot {
1516
……@@ -1021,6 +1022,37 @@ void WhiteNoiseNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
10211022 p_ctx.write_color_field(p_node_idx, 1, cols, domain);
10221023}
10231024
1025String WhiteNoiseNode::compile_glsl_prelude(int p_node_idx) const {
1026 // The hash layer itself, guarded, so several texture nodes can each ask
1027 // for it. This is the GENERATED text -- the same characters execute()
1028 // compiles through blender_noise_cpp_shim.h -- not a GLSL rendering of it.
1029 return String(BLENDER_HASH_GLSL);
1030}
1031
1032String WhiteNoiseNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
1033 // Mirrors execute() case for case. Blender's White Noise is
1034 // hash_float_to_float for the Value and hash_float_to_float3 for the
1035 // Colour, keyed on noise_dimensions, and that is the whole node.
1036 const String v = p_ctx.in(p_node_idx, 0);
1037 const String w = p_ctx.in(p_node_idx, 1);
1038
1039 // Parenthesised: `in` returns an EXPRESSION, which may be a producer's
1040 // variable or a literal like vec3(0.0, 0.0, 0.0), and `.x` binds tighter
1041 // than anything inside it.
1042 String key;
1043 switch (noise_dimensions) {
1044 case 1: key = w; break;
1045 case 2: key = "vec2((" + v + ").x, (" + v + ").y)"; break;
1046 case 4: key = "vec4(" + v + ", " + w + ")"; break;
1047 default: key = v; break;
1048 }
1049
1050 // Both outputs, always, as every other multi-output node does. The alpha
1051 // is 1.0 because the CPU writes Color(c.x, c.y, c.z, 1.0f).
1052 return p_ctx.declare(p_node_idx, 0) + " = bn_hash_float_to_float(" + key + ");\n\t" +
1053 p_ctx.declare(p_node_idx, 1) + " = vec4(bn_hash_float_to_float3(" + key + "), 1.0);";
1054}
1055
10241056// --- GradientTextureNode ---
10251057
10261058void GradientTextureNode::_bind_methods() {
src/nodes/noise_nodes.h +10 -4
……@@ -196,10 +196,16 @@ public:
196196 StringName get_node_type() const override { return "WhiteNoise"; }
197197 String get_node_title() const override { return "White Noise"; }
198198 String get_node_category() const override { return "Texture"; }
199 // The GLSL is NOT this function. Claiming GPU support would let a batch
200 // evaluate a different hash from the CPU path, silently -- the exact drift
201 // the shared erosion filter exists to prevent. Off until it is ported.
202 bool supports_gpu() const override { return false; }
199 // ON since 2026-09-05, and this is the first of the five texture nodes to
200 // come back, because it is the one with nothing above the hash. There is
201 // no second implementation to drift from: compile_glsl emits calls into
202 // blender_hash.inc, which is the same text execute() runs through the C++
203 // shim. What used to be here -- "the GLSL is NOT this function" -- was
204 // true of a hand-written copy, and the way to stop saying it was to
205 // remove the copy rather than to write a better one.
206 bool supports_gpu() const override { return true; }
207 String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
208 String compile_glsl_prelude(int p_node_idx) const override;
203209
204210 void set_noise_dimensions(int p_d) { noise_dimensions = p_d; }
205211 int get_noise_dimensions() const { return noise_dimensions; }
src/shared/blender_hash.inc +14 -0
……@@ -45,6 +45,18 @@
4545// That is why bn_int_to_float_01 below writes `float(1)` and not `1.0`.
4646// ============================================================================
4747
48// GUARDED AS A WHOLE, because every texture node that stands on this hash
49// emits it as a prelude and a prelude is emitted per node. GpuCompiler drops
50// one it has already emitted by string comparison, but Math's helpers record
51// why that is not enough on its own: when one prelude CONTAINS another,
52// string-dedup either drops them or defines them twice depending on which
53// node came first. GLSL has a preprocessor; let it do the work.
54//
55// Harmless on the C++ side, which includes this exactly once through
56// blender_noise_cpp_shim.h.
57#ifndef PN_BLENDER_HASH
58#define PN_BLENDER_HASH
59
4860// The C++ side defines this as `inline`; GLSL leaves it empty. blender_noise.h
4961// reaches more than one translation unit, so without it the second one is a
5062// multiple-definition link error -- the trap erosion_cpp_shim.h already has,
……@@ -299,3 +311,5 @@ BN_FN vec3 bn_hash_int_to_float3(ivec4 k) {
299311 vec4 h = bn_hash_int_to_float4(k);
300312 return vec3(h.x, h.y, h.z);
301313}
314
315#endif // PN_BLENDER_HASH