OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes commits / aac88dc

volume: a volume graph can compile now -- ProcVolume carries real GLSL
Reversing the conclusion of the two commits before this one. I read "nothing constructs a ProcVolume" as a reason to document the node and leave it off. The opposite is true: nothing constructs one BECAUSE it could not, and building them is the point -- an authored cloud formation obscuring a floating island above the mountain, on the GPU. WHY IT COULD NOT. VolumeToMeshNode emits no GLSL, and in proc_node_graph.cpp `!supports_gpu()` is a REFUSAL, not a skip. So a graph ending in the node it was designed to end in could not be compiled to a density function AT ALL. That is the "volume node GLSL connectivity" the dc-terrain-nubis notes have listed as missing since March, and the reason ProcVolume -- which has carried a `glsl_source` field with the signature `float proc_density(vec3 pos)` since it was written -- has never held one. THREE CHANGES, and they are small because the pieces were all there. * gpu_transparent() -> true. The node NAMES a density, it does not compute one, which is the same category as Group Input and Group Output. Transparent is a skip; the refusal was for missing arithmetic, and there is none missing here. supports_gpu() stays false, correctly: it emits nothing. * find_density_output() accepts its Density input as well as a Group Output float. Both spellings say "this float is my density" -- one through the graph's interface, one at an authored terminal -- and a volume graph should not need a Group Output to be compilable. One place to change, because the rule was hoisted into one function an hour ago. * execute() fills the ProcVolume from the graph instead of instantiating an empty one, and errors if the compile refused rather than handing back a volume that renders nothing. var vt := graph.compile_to_glsl_function(...) -> 469 chars, ending `return n2_s0;` -- the SDF chain, no Group Output in the graph. WHAT THIS IS FOR, and what is still missing. dc-terrain-nubis Layer 3 is artist-placed local effects through Godot FogVolume + a `shader_type fog` material. The emitted proc_density is exactly what such a shader needs to drive DENSITY. NOT DONE HERE: the fog shader template, and anything that attaches a ProcVolume to a FogVolume. This commit makes the density exist; nothing renders it yet. ⚠ VolumeToMesh is a bad name for it. It is not meshed -- it is raymarched. The name is load-bearing in graph_importer.cpp's factory, so renaming it breaks any graph that names it. Not renamed; recorded. --- TWO GATE PROBLEMS FOUND WHILE DOING THIS. ⛔ A CHECK THAT THROWS WAS SILENTLY DROPPED. glsl_golden_gate.gd reported "N/N passed" where N was however many checks happened to run. Asserting on gpu_transparent(), which was not bound to script, errored -- and the gate printed "28/28 passed" and exited 0 with that check simply absent. The count moved and nothing went red. EXPECTED_CHECKS now pins it: 29 ran, 29 expected, and setting it to 30 takes the gate to 28/29. gpu_transparent() is bound too -- it and supports_gpu() are a pair, and only one of them was askable, so a gate could not tell a refusal from a skip. ⚠ CASE B HAS LOST ITS SUBJECT FOR THE THIRD TIME. "A node with no GLSL must refuse" used Math, then Map Range, then VolumeToMesh -- each retired when that node got an implementation, which is what just happened again. It is Join Geometry now: a density function is a float of a position, there is no geometry in it to join, so this one cannot be finished into legitimacy by anybody. glsl_golden_gate 27/27 -> 29/29, emitted-op parity 770/770, golden map_dc_erosion_density byte-identical, all three targets build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
aac88dc2008da1089489ac5d225734265f64285a
by plankatron <fleeky@gmail.com> · 2026-09-06T15:30:51.000Z

Changed files (5) +105 -60

demo/glsl_golden_gate.gd +51 -9
……@@ -323,9 +323,29 @@ func _volume_density_via_group_output() -> void:
323323 # VolumeToMesh stays GPU-off, and a graph containing it still REFUSES --
324324 # case B depends on that, so assert the property rather than leave it
325325 # implied by one other case.
326 # M. AN AUTHORED VOLUME COMPILES. This is the thing VolumeToMesh is FOR and
327 # it has never worked: the node emits nothing, and `!supports_gpu()` is a
328 # REFUSAL, so a graph ending in it could not be compiled to GLSL at all.
329 # It is structurally transparent now -- it names the density rather than
330 # computing one -- so the chain emits and the terminal carries it.
331 var vg := ProcNodeGraph.new()
332 var vs1 := vg.add_node(SphereSdfNode.new())
333 var vs2 := vg.add_node(SphereSdfNode.new())
334 var vbo := vg.add_node(VolumeBooleanNode.new())
335 vg.connect_sockets(vs1, 0, vbo, 0)
336 vg.connect_sockets(vs2, 0, vbo, 1)
337 var vtm_idx := vg.add_node(VolumeToMeshNode.new())
338 vg.connect_sockets(vbo, 0, vtm_idx, 0)
339 var vt: String = vg.compile_to_glsl_function("proc_density", false, PackedStringArray())
340 _check("volume graph emits at all", not vt.is_empty(),
341 "%d chars" % vt.length() if not vt.is_empty() else "REFUSED -- the gap this closes")
342 _check("VolumeToMesh names the density",
343 vt.find("return n%d_s0;" % vbo) != -1,
344 "returns the SDF chain, with no Group Output in the graph")
326345 var vtm := VolumeToMeshNode.new()
327 _check("VolumeToMesh stays GPU-off", not vtm.supports_gpu(),
328 "supports_gpu() false -- it hands on geometry, it names no float")
346 _check("VolumeToMesh emits nothing itself",
347 not vtm.supports_gpu() and vtm.gpu_transparent(),
348 "transparent, not refused: it names a density, it does not compute one")
329349
330350
331351func _init() -> void:
……@@ -353,14 +373,19 @@ func _init() -> void:
353373 "erosion call gone, gradient noise call kept")
354374
355375 # B. A node with no GLSL must make emission FAIL, not vanish from the shader.
356 # VolumeToMesh, and the choice matters. This case used Math, then Map Range,
357 # and each time that node got an implementation the case stopped testing
358 # refusal and started testing emission -- it went red twice for being
359 # OBSOLETE rather than wrong. VolumeToMesh's output is a Geometry socket,
360 # which has no GLSL spelling at all, so it cannot acquire one by someone
361 # doing the next item on the list.
376 # JOIN GEOMETRY, and the choice matters. This case used Math, then Map
377 # Range, then VolumeToMesh, and each time that node got an implementation
378 # the case stopped testing refusal and started testing emission -- it has
379 # now gone obsolete THREE times rather than wrong. VolumeToMesh was retired
380 # from this role deliberately: it became a legitimate terminal that names a
381 # volume's density, which is exactly the "someone did the next item on the
382 # list" this case keeps losing its subject to.
383 #
384 # Join Geometry combines GEOMETRIES. A density function is a float of a
385 # position -- there is no geometry in it to join -- so this one cannot
386 # acquire a GLSL implementation by anybody finishing anything.
362387 var g2 := _erosion_graph()
363 g2.add_node(VolumeToMeshNode.new())
388 g2.add_node(JoinGeometryNode.new())
364389 var with_math: String = g2.compile_to_glsl_function("proc_density", true, empty)
365390 _check("a node with no GLSL refuses", with_math.is_empty(),
366391 "emitted %d chars (a silent drop is the bug)" % with_math.length()
……@@ -476,5 +501,22 @@ func _init() -> void:
476501 _batch_still_uses_push_constants()
477502 _volume_density_via_group_output()
478503
504 # ⛔ HOW MANY CHECKS SHOULD HAVE RUN.
505 #
506 # Without this the gate reports "N/N passed" where N is however many checks
507 # happened to execute -- so a check that THROWS is silently dropped rather
508 # than failed. Measured 2026-09-06: asserting on gpu_transparent(), which
509 # was not bound to script, errored, vanished from the run, and the gate
510 # printed "28/28 passed" and exited 0. The count moved and nothing went red.
511 #
512 # Bumping this by hand when a case is added is the point: an intentional
513 # change is a one-line edit, an accidental drop is a failure.
514 const EXPECTED_CHECKS := 29
515 if _ran != EXPECTED_CHECKS:
516 _fail += 1
517 print(" %-34s FAIL %d checks ran, expected %d -- a check was DROPPED, "
518 % ["all checks ran", _ran, EXPECTED_CHECKS]
519 + "probably by throwing. Look for SCRIPT ERROR above.")
520
479521 print("\n%d/%d passed" % [_ran - _fail, _ran])
480522 quit(1 if _fail > 0 else 0)
src/graph/proc_node.cpp +5 -0
……@@ -6,6 +6,11 @@ void ProcNode::_bind_methods() {
66 ClassDB::bind_method(D_METHOD("get_node_type"), &ProcNode::get_node_type);
77 ClassDB::bind_method(D_METHOD("get_node_title"), &ProcNode::get_node_title);
88 ClassDB::bind_method(D_METHOD("supports_gpu"), &ProcNode::supports_gpu);
9 // Bound because the two are a PAIR and only one was askable. A node can
10 // emit nothing for two different reasons -- missing arithmetic, which must
11 // refuse, or structural transparency, which must skip -- and a gate that
12 // can see supports_gpu() but not gpu_transparent() cannot tell them apart.
13 ClassDB::bind_method(D_METHOD("gpu_transparent"), &ProcNode::gpu_transparent);
914 // Bound so glsl_golden_gate.gd can assert that the slot a node's GLSL reads
1015 // is the slot GraphExecutor packs -- the drift this map exists to end is
1116 // only visible if the gate can see the map.
src/graph/proc_node_graph.cpp +10 -1
……@@ -465,7 +465,16 @@ String ProcNodeGraph::compile_to_glsl_function(const String &p_function_name,
465465bool ProcNodeGraph::find_density_output(int &r_node, int &r_socket) const {
466466 for (int i = 0; i < get_node_count(); i++) {
467467 Ref<ProcNode> n = get_node(i);
468 if (n.is_null() || n->get_node_type() != StringName("GroupOutput")) {
468 // TWO WAYS TO SAY IT, one rule. A Group Output float is the graph
469 // declaring its result through its interface; a Volume to Mesh's
470 // Density input is an authored VOLUME declaring the same thing at a
471 // terminal. Both are "this float is my density", and a volume graph
472 // should not have to carry a Group Output to be compilable.
473 if (n.is_null()) {
474 continue;
475 }
476 const StringName ty = n->get_node_type();
477 if (ty != StringName("GroupOutput") && ty != StringName("VolumeToMesh")) {
469478 continue;
470479 }
471480 for (int s = 0; s < n->get_input_count(); s++) {
src/nodes/volume_nodes.cpp +24 -13
……@@ -1,5 +1,8 @@
11#include "volume_nodes.h"
22
3#include "../graph/graph_executor.h"
4#include "../graph/proc_node_graph.h"
5
36#include <godot_cpp/variant/utility_functions.hpp>
47#include "../graph/graph_executor.h"
58
……@@ -94,24 +97,32 @@ VolumeToMeshNode::VolumeToMeshNode() {
9497}
9598
9699void VolumeToMeshNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
97 // ⛔ THIS IS A PLACEHOLDER AND IT FAILS SILENTLY. It writes an EMPTY
98 // ProcVolume on an empty ProcGeometry -- no mesh, no density, no message --
99 // so a graph ending here bakes to nothing and looks like a graph that did
100 // not run. That is the actual debt behind this node, not its
101 // supports_gpu() == false (see the header).
100 // FILL THE VOLUME. This used to write an EMPTY ProcVolume on an empty
101 // ProcGeometry -- no mesh, no density, no message -- so a graph ending here
102 // baked to nothing and looked like a graph that never ran.
102103 //
103 // Say so. The working way to express a volume's density is an explicit
104 // Group Output float, which _density_output_socket reads and the emitter
105 // returns; this node is not that mechanism.
106 UtilityFunctions::push_warning(
107 "proc-nodes: Volume to Mesh is a PLACEHOLDER -- it produces an empty "
108 "geometry with an empty volume, so this graph will bake to nothing. "
109 "To give a graph a density, wire the SDF chain into a Group Output "
110 "FLOAT socket instead; that is what the emitter returns.");
104 // ProcVolume has carried `glsl_source` with the signature
105 // `float proc_density(vec3 pos)` since it was written; nothing ever put one
106 // in it. The graph can emit exactly that, and now does.
111107 Ref<ProcGeometry> geo;
112108 geo.instantiate();
113109 Ref<ProcVolume> vol;
114110 vol.instantiate();
111 if (p_ctx.graph != nullptr) {
112 vol->glsl_source = p_ctx.graph->compile_to_glsl_function(
113 "proc_density", false, PackedStringArray());
114 if (vol->glsl_source.is_empty()) {
115 // The emitter has already said which node refused. Say what it
116 // costs here, the way dc-terrain's _recompile_glsl does: an empty
117 // volume is a cloud that does not render, and silence about it is
118 // what made this node look finished for as long as it did.
119 UtilityFunctions::push_error(
120 "proc-nodes: Volume to Mesh could not compile this graph to a "
121 "density function, so its ProcVolume carries no GLSL and will "
122 "render nothing. See the emission error above for the node "
123 "that refused.");
124 }
125 }
115126 geo->set_volume(vol);
116127 p_ctx.write_geometry(p_node_idx, 0, geo);
117128}
src/nodes/volume_nodes.h +15 -37
……@@ -83,46 +83,24 @@ public:
8383
8484 void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
8585
86 // ⛔ FALSE ON PURPOSE, AND THE REASON WAS RE-CHECKED 2026-09-06 (hyper-games
87 // #65 NEXT 5, which asked whether this last supports_gpu() == false has a
88 // reason or is the next debt). It has one, and half of what stood here was
89 // already out of date:
86 // ⛔ THIS NODE NAMES THE DENSITY. It does not compute one.
9087 //
91 // 1. Its output is a GEOMETRY socket, which has no GLSL spelling.
88 // It emits nothing, so supports_gpu() stays false -- but it is
89 // STRUCTURALLY TRANSPARENT, the same category as Group Input and Group
90 // Output: it carries the graph's boundary, not an operation. Without that
91 // distinction `!supports_gpu()` is a REFUSAL (proc_node_graph.cpp), so a
92 // graph ending in this node could not be compiled to GLSL AT ALL -- which
93 // is the "volume node GLSL connectivity" the dc-terrain-nubis notes list as
94 // missing, and the reason nothing has ever built a ProcVolume.
9295 //
93 // 2. ⚠ "the density function has no way to return a node's value" is NO
94 // LONGER TRUE. _density_output_socket (proc_node_graph.cpp) picks an
95 // explicit Group Output FLOAT, and gpu_compiler emits `return n2_s0;`
96 // for it. Measured: an SDF chain wired into a Group Output float emits
97 // 486 characters returning the chain, with no VolumeToMesh anywhere.
98 // So a volume graph CAN say what its density is -- through a mechanism
99 // this node is not, and cannot become, because it hands on geometry
100 // rather than naming a float.
101 //
102 // 3. NOTHING BUILDS A VOLUME THROUGH IT -- but it IS REACHABLE, and the
103 // first version of this note said "no consumers", which was wrong. It
104 // came from a grep truncated at six lines, and the line it cut off was
105 // the one that matters:
106 //
107 // graph_importer.cpp:161 the FACTORY. Any imported or serialized
108 // graph naming "VolumeToMesh" instantiates
109 // one, and then bakes to nothing.
110 //
111 // What is true: no graph ON DISK uses it -- searched .tres, .tscn and
112 // .json across proc-nodes, dc-terrain and hyper-tps, zero hits -- and
113 // nothing constructs a meaningful ProcVolume anywhere. So it is
114 // unused, not unreachable, and the reachable path is exactly the one
115 // that fails silently. That makes execute()'s warning below load
116 // bearing rather than decorative.
117 //
118 // blender-bridge does NOT map it, deliberately, and says why
119 // (geometry_serializer.py:151): "ours takes a Density FIELD, Blender's
120 // takes a volume GEOMETRY". They are different operations that share a
121 // name, so a Blender graph can never produce this node.
122 //
123 // Turning this true would require it to emit something, and there is
124 // nothing for it to emit. The debt here is not the flag -- see execute().
96 // Its Density input is the graph saying what its density IS, exactly as a
97 // Group Output float does, and find_density_output() reads both. So an
98 // authored volume -- a cloud formation, a fog body, an SDF blob -- compiles
99 // to `float proc_density(vec3 pos)` and rides in the ProcVolume that
100 // execute() now fills, for a GPU consumer (a FogVolume fog shader, or
101 // dc-terrain) to raymarch. It is not meshed, despite the name.
125102 bool supports_gpu() const override { return false; }
103 bool gpu_transparent() const override { return true; }
126104 String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
127105};
128106