| … | … | @@ -231,6 +231,79 @@ func _init() -> void: |
| 231 | 231 | PackedStringArray()), |
| 232 | 232 | }) |
| 233 | 233 | |
| 234 | # --- Random Value ----------------------------------------------------- | |
| 235 | # All FOUR data types, because they are four different functions wearing | |
| 236 | # one node: the socket LIST changes (boolean drops Min and Max for a | |
| 237 | # single Probability, so ID and Seed move from 2/3 to 1/2) and the hash | |
| 238 | # ARGUMENT ORDER changes (float and vector hash (seed, id); int and | |
| 239 | # boolean hash (id, seed)). Getting either wrong still produces | |
| 240 | # well-distributed random numbers, which is exactly why it needs a gate | |
| 241 | # and not a reading. | |
| 242 | var rv_ids := [[0, 0], [1, 0], [0, 1], [7, 13], [-3, 99]] | |
| 243 | for rv_type in [10, 11, 48, 50]: | |
| 244 | for ids in rv_ids: | |
| 245 | var comps := 3 if rv_type == 48 else 1 | |
| 246 | for comp in range(comps): | |
| 247 | var g6 := ProcNodeGraph.new() | |
| 248 | var rv := RandomValueNode.new() | |
| 249 | rv.set("data_type", rv_type) | |
| 250 | var rv_idx := g6.add_node(rv) | |
| 251 | var is_bool: bool = rv_type == 50 | |
| 252 | # Min/Max, or Probability alone. | |
| 253 | if is_bool: | |
| 254 | var pn := ValueNode.new() | |
| 255 | pn.set("value", 0.5) | |
| 256 | g6.connect_sockets(g6.add_node(pn), 0, rv_idx, 0) | |
| 257 | elif rv_type == 48: | |
| 258 | var mn := VectorNode.new() | |
| 259 | mn.set("value", Vector3(-1.0, 0.0, 2.5)) | |
| 260 | g6.connect_sockets(g6.add_node(mn), 0, rv_idx, 0) | |
| 261 | var mx := VectorNode.new() | |
| 262 | mx.set("value", Vector3(1.0, 10.0, 3.5)) | |
| 263 | g6.connect_sockets(g6.add_node(mx), 0, rv_idx, 1) | |
| 264 | elif rv_type == 11: | |
| 265 | var mn2 := IntegerNode.new() | |
| 266 | mn2.set("value", -5) | |
| 267 | g6.connect_sockets(g6.add_node(mn2), 0, rv_idx, 0) | |
| 268 | var mx2 := IntegerNode.new() | |
| 269 | mx2.set("value", 42) | |
| 270 | g6.connect_sockets(g6.add_node(mx2), 0, rv_idx, 1) | |
| 271 | else: | |
| 272 | var mn3 := ValueNode.new() | |
| 273 | mn3.set("value", -2.5) | |
| 274 | g6.connect_sockets(g6.add_node(mn3), 0, rv_idx, 0) | |
| 275 | var mx3 := ValueNode.new() | |
| 276 | mx3.set("value", 7.5) | |
| 277 | g6.connect_sockets(g6.add_node(mx3), 0, rv_idx, 1) | |
| 278 | # ID and Seed, wherever this data type keeps them. | |
| 279 | var id_sock := 1 if is_bool else 2 | |
| 280 | var idn := IntegerNode.new() | |
| 281 | idn.set("value", ids[0]) | |
| 282 | g6.connect_sockets(g6.add_node(idn), 0, rv_idx, id_sock) | |
| 283 | var sdn := IntegerNode.new() | |
| 284 | sdn.set("value", ids[1]) | |
| 285 | g6.connect_sockets(g6.add_node(sdn), 0, rv_idx, id_sock + 1) | |
| 286 | ||
| 287 | var rsrc := rv_idx | |
| 288 | var rsock := 0 | |
| 289 | if rv_type == 48: | |
| 290 | var sep3 := SeparateXYZNode.new() | |
| 291 | var sep3_idx := g6.add_node(sep3) | |
| 292 | g6.connect_sockets(rv_idx, 0, sep3_idx, 0) | |
| 293 | rsrc = sep3_idx | |
| 294 | rsock = comp | |
| 295 | var go6 := GroupOutputNode.new() | |
| 296 | go6.set_interface([{"name": "Density", "type": 0}]) | |
| 297 | var go6_idx := g6.add_node(go6) | |
| 298 | g6.connect_sockets(rsrc, rsock, go6_idx, 0) | |
| 299 | cases.append({ | |
| 300 | "node": "RandomValue.t%d" % rv_type, "op": comp, | |
| 301 | "inputs": [float(ids[0]), float(ids[1]), 0.0], | |
| 302 | "cpu": float(g6.evaluate_socket(rsrc, rsock)), | |
| 303 | "glsl": g6.compile_to_glsl_function("proc_density", false, | |
| 304 | PackedStringArray()), | |
| 305 | }) | |
| 306 | ||
| 234 | 307 | # A DIRECTORY, not JSON: stage 2 is a standalone C++ program with no JSON |
| 235 | 308 | # parser, and adding one to read our own test data would be more code than |
| 236 | 309 | # the gate. One manifest line per case, one .glsl file per case. |
| … | … | @@ -3,6 +3,7 @@ |
| 3 | 3 | #include "../shared/blender_noise.h" |
| 4 | 4 | #include <godot_cpp/variant/utility_functions.hpp> |
| 5 | 5 | #include <cmath> |
| 6 | #include "../gpu/blender_hash_glsl.gen.h" | |
| 6 | 7 | |
| 7 | 8 | namespace godot { |
| 8 | 9 | |
| … | … | @@ -517,6 +518,81 @@ void RandomValueNode::set_data_type(int p_type) { |
| 517 | 518 | output_defs = out; |
| 518 | 519 | } |
| 519 | 520 | |
| 521 | String RandomValueNode::compile_glsl_prelude(int p_node_idx) const { | |
| 522 | return String(BLENDER_HASH_GLSL); | |
| 523 | } | |
| 524 | ||
| 525 | String RandomValueNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const { | |
| 526 | // Mirrors execute() case for case, including the two things about this | |
| 527 | // node that cannot be guessed: | |
| 528 | // | |
| 529 | // 1. BOOLEAN HAS THREE INPUTS, so ID and Seed sit at 1 and 2 rather | |
| 530 | // than 2 and 3. | |
| 531 | // 2. THE HASH ARGUMENT ORDER IS NOT THE SAME FOR ALL FOUR TYPES. Float | |
| 532 | // and vector hash (seed, id); int and boolean hash (id, seed). Read | |
| 533 | // off node_fn_random_value.cc, not inferred -- one order agrees with | |
| 534 | // Blender on half the types and looks plausible on the rest. | |
| 535 | // | |
| 536 | // ⚠ EVERY LITERAL AFTER A NAMED String IS WRAPPED IN String(). A named | |
| 537 | // `const String` plus a bare "literal" is an AMBIGUOUS overload in | |
| 538 | // godot-cpp -- the char array converts equally well to String and to | |
| 539 | // StringName -- while the prvalue `p_ctx.declare(...) + " = "` that every | |
| 540 | // other node in this tree happens to use does not. Same expression, and | |
| 541 | // the only difference is whether the left side was given a name. | |
| 542 | const bool is_bool = (data_type == 50); | |
| 543 | const String id = "uint(" + p_ctx.in(p_node_idx, is_bool ? 1 : 2) + ")"; | |
| 544 | const String seed = "uint(" + p_ctx.in(p_node_idx, is_bool ? 2 : 3) + ")"; | |
| 545 | const String a = "(" + p_ctx.in(p_node_idx, 0) + ")"; | |
| 546 | const String decl = p_ctx.declare(p_node_idx, 0); | |
| 547 | ||
| 548 | switch (data_type) { | |
| 549 | case 10: { // FLOAT | |
| 550 | const String b = "(" + p_ctx.in(p_node_idx, 1) + ")"; | |
| 551 | return decl + String(" = bn_hash_to_float(") + seed + String(", ") + id + | |
| 552 | String(") * (") + b + String(" - ") + a + String(") + ") + a + String(";"); | |
| 553 | } | |
| 554 | case 48: { // FLOAT_VECTOR -- three hashes, one per component. | |
| 555 | const String b = "(" + p_ctx.in(p_node_idx, 1) + ")"; | |
| 556 | String e = "vec3("; | |
| 557 | const char *c[3] = { "x", "y", "z" }; | |
| 558 | for (int i = 0; i < 3; i++) { | |
| 559 | if (i > 0) { | |
| 560 | e += ", "; | |
| 561 | } | |
| 562 | const String comp = String(".") + c[i]; | |
| 563 | e += String("bn_hash_to_float(") + seed + String(", ") + id + String(", ") + | |
| 564 | itos(i) + String("u) * (") + b + comp + String(" - ") + a + comp + | |
| 565 | String(") + ") + a + comp; | |
| 566 | } | |
| 567 | return decl + String(" = ") + e + String(");"); | |
| 568 | } | |
| 569 | case 11: { // INT | |
| 570 | const String b = "(" + p_ctx.in(p_node_idx, 1) + ")"; | |
| 571 | const String pre = String("rv") + itos(p_node_idx); | |
| 572 | // UNSIGNED throughout, so the whole 32-bit span fits and the range | |
| 573 | // wraps to 0 for INT_MIN..INT_MAX, where a modulo would divide by | |
| 574 | // zero. Blender SWAPS rather than erroring when Min > Max. | |
| 575 | return String("int ") + pre + String("_mn = min(") + a + String(", ") + b + | |
| 576 | String(");\n\tint ") + pre + String("_mx = max(") + a + String(", ") + b + | |
| 577 | String(");\n\tuint ") + pre + String("_h = bn_hash(") + id + String(", ") + | |
| 578 | seed + String(");\n\tuint ") + pre + String("_r = uint(") + pre + | |
| 579 | String("_mx) - uint(") + pre + String("_mn) + 1u;\n\t") + decl + | |
| 580 | String(" = int(uint(") + pre + String("_mn) + ((") + pre + | |
| 581 | String("_r == 0u) ? ") + pre + String("_h : (") + pre + String("_h % ") + | |
| 582 | pre + String("_r)));"); | |
| 583 | } | |
| 584 | case 50: // BOOLEAN | |
| 585 | // `<=`, not `<`, so a probability of exactly 0 can still be true | |
| 586 | // when the hash is exactly 0. | |
| 587 | return decl + String(" = bn_hash_to_float(") + id + String(", ") + seed + | |
| 588 | String(") <= ") + a + String(";"); | |
| 589 | default: | |
| 590 | // set_data_type refuses anything else, so this is unreachable | |
| 591 | // rather than a silent fallback. | |
| 592 | return String(); | |
| 593 | } | |
| 594 | } | |
| 595 | ||
| 520 | 596 | void RandomValueNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 521 | 597 | // BOOLEAN HAS THREE INPUTS, so ID and Seed are not where they are for the |
| 522 | 598 | // other three. Retyping the sockets without moving them would have ID |
| … | … | @@ -134,12 +134,15 @@ public: |
| 134 | 134 | StringName get_node_type() const override { return "RandomValue"; } |
| 135 | 135 | String get_node_title() const override { return "Random Value"; } |
| 136 | 136 | String get_node_category() const override { return "Utility"; } |
| 137 | // The GLSL claim was false: supports_gpu() returned true and there is no | |
| 138 | // compile_glsl override at all, so the GPU path never emitted this node's | |
| 139 | // arithmetic. Now that the CPU side is Blender's hash it would be a | |
| 140 | // different function again. Off until it is ported, as MathNode and | |
| 141 | // VectorMathNode are. | |
| 142 | bool supports_gpu() const override { return false; } | |
| 137 | // ON since 2026-09-05. This node's history is the reason the rest of the | |
| 138 | // list was audited: supports_gpu() used to return true with NO | |
| 139 | // compile_glsl override at all, so the GPU path emitted none of its | |
| 140 | // arithmetic and said nothing. It is true again now because there is | |
| 141 | // something behind it -- calls into blender_hash.inc, the same text | |
| 142 | // execute() runs. | |
| 143 | bool supports_gpu() const override { return true; } | |
| 144 | String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override; | |
| 145 | String compile_glsl_prelude(int p_node_idx) const override; | |
| 143 | 146 | |
| 144 | 147 | // Rebuilds the socket LIST, not just its types. |
| 145 | 148 | void set_data_type(int p_type); |