| … | … | @@ -44,6 +44,54 @@ func _build() -> ProcNodeGraph: |
| 44 | 44 | return g |
| 45 | 45 | |
| 46 | 46 | |
| 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))) |
| 61 | func _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 | |
| 84 | func _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 | |
| 47 | 95 | func _bake(use_gpu: bool) -> PackedVector3Array: |
| 48 | 96 | var host := ProcGeometryNode.new() |
| 49 | 97 | host.use_gpu = use_gpu |
| … | … | @@ -88,5 +136,30 @@ func _init() -> void: |
| 88 | 136 | _ck("CPU and GPU agree", false, |
| 89 | 137 | "vertex counts differ: %d vs %d" % [cpu.size(), gpu.size()]) |
| 90 | 138 | |
| 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]) |
| 92 | 165 | quit(1 if _fail > 0 else 0) |