OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes commits / 903973d

gate: the shape gpu_batch_gate could not test -- a producer outside the batch
The gate compared CPU against GPU, which is the right assertion, and passed while the bridged mountain came out flat. Its graph is FULLY FUSED: every producer sits inside the batch, so there is nothing outside for a node to misread. Right assertion, wrong shape -- the third time in this session an oracle passed because of what it did not contain. A Switch has no supports_gpu() override, so _is_gpu_value_node rejects it and it can never join a batch. That is exactly where the mountain's height sat. Its consumer, Combine XYZ, IS fusable, so the batch ends up reading a producer that is not in the shader, and GlslEmitContext::in() falls back to the socket's LITERAL -- which compiles, and computes something else. MeshGrid -> SetPosition(Offset <- CombineXYZ(Z <- Switch(true, 0.0, 5.0))) DRILLED against the bug it was written for: refusal removed 7/8 CombineXYZ "gpu (fused)", SetPosition "gpu", worst |dp| = 5.0 -- exactly the Switch's True value refusal restored 8/8 both "cpu", worst |dp| = 0.0 It also asserts the CPU path displaces at all (max |z| = 7.0 on a 4x4 grid, 2 of thickness plus the 5.0 offset), so the comparison cannot pass by both halves doing nothing. glsl_golden_gate 32/32 sdf_grid_gate 3/3 volume_mesh_gate 4/4 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
903973dec0b8d831fed1d14cdef15445fd7b9c10
by plankatron <fleeky@gmail.com> · 2026-09-06T21:19:14.000Z

Changed files (1) +74 -1

demo/gpu_batch_gate.gd +74 -1
……@@ -44,6 +44,54 @@ func _build() -> ProcNodeGraph:
4444 return g
4545
4646
47## ⛔ THE SHAPE THE FIRST GRAPH CANNOT TEST.
48##
49## _build()'s chain is fully fused, so every producer is inside the batch and
50## there is nothing outside for a node to misread. That is the right assertion
51## on the wrong shape, and it passed while the bridged mountain came out flat.
52##
53## A Switch has no supports_gpu() override, so _is_gpu_value_node rejects it and
54## it can never join a batch -- which is exactly where the mountain's height sat.
55## Its consumer, Combine XYZ, IS fusable, so the batch ends up reading a producer
56## that is not in the shader. GlslEmitContext::in() falls back to the socket's
57## LITERAL there, which compiles and computes something else: offset 0 instead
58## of 5, no error anywhere.
59##
60## MeshGrid -> SetPosition(Offset <- CombineXYZ(Z <- Switch(true, 0.0, 5.0)))
61func _build_outside_producer() -> ProcNodeGraph:
62 var g := ProcNodeGraph.new()
63 var grid := g.add_node(MeshGridNode.new())
64 g.get_node(grid).set_input_default(0, 4.0)
65 g.get_node(grid).set_input_default(1, 4.0)
66 g.get_node(grid).set_input_default(2, 8)
67 g.get_node(grid).set_input_default(3, 8)
68
69 var sw := g.add_node(SwitchNode.new())
70 g.get_node(sw).set("input_type", 0) # Float, as Blender's was
71 g.get_node(sw).set_input_default(0, true) # take the True branch
72 g.get_node(sw).set_input_default(1, 0.0) # False
73 g.get_node(sw).set_input_default(2, 5.0) # True -- the displacement
74
75 var comb := g.add_node(CombineXYZNode.new())
76 g.connect_sockets(sw, 0, comb, 2) # Z
77
78 var sp := g.add_node(SetPositionNode.new())
79 g.connect_sockets(grid, 0, sp, 0)
80 g.connect_sockets(comb, 0, sp, 3) # Offset
81 return g
82
83
84func _bake_graph(g: ProcNodeGraph, use_gpu: bool) -> PackedVector3Array:
85 var host := ProcGeometryNode.new()
86 host.use_gpu = use_gpu
87 host.set_graph(g)
88 get_root().add_child(host)
89 var m: ArrayMesh = host.bake_mesh()
90 if m == null or m.get_surface_count() == 0:
91 return PackedVector3Array()
92 return m.surface_get_arrays(0)[Mesh.ARRAY_VERTEX]
93
94
4795func _bake(use_gpu: bool) -> PackedVector3Array:
4896 var host := ProcGeometryNode.new()
4997 host.use_gpu = use_gpu
……@@ -88,5 +136,30 @@ func _init() -> void:
88136 _ck("CPU and GPU agree", false,
89137 "vertex counts differ: %d vs %d" % [cpu.size(), gpu.size()])
90138
91 print("\n%d/%d passed" % [6 - _fail, 6])
139 # ── A PRODUCER OUTSIDE THE BATCH ────────────────────────────────────────
140 var og := _build_outside_producer()
141 var ocpu := _bake_graph(og, false)
142 var ogpu := _bake_graph(og, true)
143 var orep: Dictionary = ProcGeometryNode.get_last_exec_report()
144 print(" outside-producer report: %s" % orep)
145
146 var cpu_lift := 0.0
147 for p in ocpu:
148 cpu_lift = max(cpu_lift, absf(p.z))
149 _ck("the outside-producer graph displaces at all", cpu_lift > 4.9,
150 "CPU max |z| = " + String.num(cpu_lift, 4) + ", Switch True is 5.0")
151
152 if ocpu.size() == ogpu.size() and ocpu.size() > 0:
153 var oworst := 0.0
154 for i in range(ocpu.size()):
155 oworst = max(oworst, (ocpu[i] - ogpu[i]).length())
156 # ⛔ THE ASSERTION THAT WAS MISSING. Without the batch refusal the GPU
157 # reads Combine XYZ's Z as its literal 0.0 and this is 5.0.
158 _ck("CPU and GPU agree when a producer is outside the batch",
159 oworst < EPS, "worst |dp| = " + String.num(oworst, 9))
160 else:
161 _ck("CPU and GPU agree when a producer is outside the batch", false,
162 "vertex counts differ: %d vs %d" % [ocpu.size(), ogpu.size()])
163
164 print("\n%d/%d passed" % [8 - _fail, 8])
92165 quit(1 if _fail > 0 else 0)