| … | … | @@ -304,6 +304,52 @@ func _init() -> void: |
| 304 | 304 | PackedStringArray()), |
| 305 | 305 | }) |
| 306 | 306 | |
| 307 | # --- Noise Texture ---------------------------------------------------- | |
| 308 | # DELIBERATELY FEW. This gate emits one function per case and a perlin case | |
| 309 | # carries an octave loop, which is how its sibling came to OOM a 31 GB | |
| 310 | # machine. The ARITHMETIC is gated exhaustively next door by | |
| 311 | # tests/test_shared_noise_parity.cpp (2496 cases, data-driven); what is | |
| 312 | # checked HERE is the thing only this gate can see -- that the emitter | |
| 313 | # wires the sockets and picks the dimension the node is set to. | |
| 314 | # | |
| 315 | # All four dimensions, because each builds a different coordinate from | |
| 316 | # Vector and W and calls a different per-dimension function, and each of | |
| 317 | # those has its own scale constant. | |
| 318 | for dims in [1, 2, 3, 4]: | |
| 319 | for ntype in [1, 3]: | |
| 320 | var g7 := ProcNodeGraph.new() | |
| 321 | var pn := PerlinNoiseNode.new() | |
| 322 | pn.set("noise_dimensions", dims) | |
| 323 | pn.set("noise_type", ntype) | |
| 324 | pn.set("normalize", true) | |
| 325 | var pn_idx := g7.add_node(pn) | |
| 326 | var vn2 := VectorNode.new() | |
| 327 | vn2.set("value", Vector3(1.5, -2.25, 0.75)) | |
| 328 | g7.connect_sockets(g7.add_node(vn2), 0, pn_idx, 0) | |
| 329 | var wv2 := ValueNode.new() | |
| 330 | wv2.set("value", 0.5) | |
| 331 | g7.connect_sockets(g7.add_node(wv2), 0, pn_idx, 1) | |
| 332 | var sc := ValueNode.new() | |
| 333 | sc.set("value", 3.0) | |
| 334 | g7.connect_sockets(g7.add_node(sc), 0, pn_idx, 2) | |
| 335 | var dt := ValueNode.new() | |
| 336 | dt.set("value", 2.5) # fractional: takes the extra-octave branch | |
| 337 | g7.connect_sockets(g7.add_node(dt), 0, pn_idx, 3) | |
| 338 | var ds := ValueNode.new() | |
| 339 | ds.set("value", 0.4) # non-zero distortion | |
| 340 | g7.connect_sockets(g7.add_node(ds), 0, pn_idx, 8) | |
| 341 | var go7 := GroupOutputNode.new() | |
| 342 | go7.set_interface([{"name": "Density", "type": 0}]) | |
| 343 | var go7_idx := g7.add_node(go7) | |
| 344 | g7.connect_sockets(pn_idx, 0, go7_idx, 0) | |
| 345 | cases.append({ | |
| 346 | "node": "NoiseTexture.d%d.t%d" % [dims, ntype], "op": dims, | |
| 347 | "inputs": [3.0, 2.5, 0.4], | |
| 348 | "cpu": float(g7.evaluate_socket(pn_idx, 0)), | |
| 349 | "glsl": g7.compile_to_glsl_function("proc_density", false, | |
| 350 | PackedStringArray()), | |
| 351 | }) | |
| 352 | ||
| 307 | 353 | # A DIRECTORY, not JSON: stage 2 is a standalone C++ program with no JSON |
| 308 | 354 | # parser, and adding one to read our own test data would be more code than |
| 309 | 355 | # the gate. One manifest line per case, one .glsl file per case. |
| … | … | @@ -11,6 +11,7 @@ |
| 11 | 11 | #include "../graph/proc_node_graph.h" |
| 12 | 12 | #include <cmath> |
| 13 | 13 | #include "../gpu/blender_hash_glsl.gen.h" |
| 14 | #include "../gpu/blender_perlin_glsl.gen.h" | |
| 14 | 15 | |
| 15 | 16 | namespace godot { |
| 16 | 17 | |
| … | … | @@ -427,6 +428,63 @@ PerlinNoiseNode::PerlinNoiseNode() { |
| 427 | 428 | // in grey, and the seeds differ per dimension. Ours wrote grey, so every |
| 428 | 429 | // Color-driven graph across the bridge was wrong even where the Factor was |
| 429 | 430 | // exact -- which it was, for 3D FBM, and only for 3D FBM. |
| 431 | String PerlinNoiseNode::compile_glsl_prelude(int p_node_idx) const { | |
| 432 | // BOTH, in dependency order: perlin's lattice gradients ARE the hash, and | |
| 433 | // GLSL has no #include to say so. Each carries its own #ifndef, so a graph | |
| 434 | // holding this node and a White Noise node emits the hash once. | |
| 435 | return String(BLENDER_HASH_GLSL) + String(BLENDER_PERLIN_GLSL); | |
| 436 | } | |
| 437 | ||
| 438 | String PerlinNoiseNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const { | |
| 439 | // Mirrors execute() case for case: node_shader_tex_noise.cc's dispatch | |
| 440 | // over blender_perlin.inc. The dimension picks BOTH the coordinate built | |
| 441 | // from Vector and W and the per-dimension function -- 1D takes W alone | |
| 442 | // scaled, 2D drops z, 4D appends W -- and each has its own scale constant | |
| 443 | // inside perlin_signed, so a dimension mix-up is a scale error and not | |
| 444 | // merely different noise. | |
| 445 | const String v = p_ctx.in(p_node_idx, 0); | |
| 446 | const String w = p_ctx.in(p_node_idx, 1); | |
| 447 | const String scale = p_ctx.in(p_node_idx, 2); | |
| 448 | const String args = String(", ") + p_ctx.in(p_node_idx, 3) + String(", ") + | |
| 449 | p_ctx.in(p_node_idx, 4) + String(", ") + p_ctx.in(p_node_idx, 5) + String(", ") + | |
| 450 | p_ctx.in(p_node_idx, 6) + String(", ") + p_ctx.in(p_node_idx, 7) + String(", ") + | |
| 451 | p_ctx.in(p_node_idx, 8) + String(", ") + itos(noise_type) + String(", ") + | |
| 452 | String(normalize ? "true" : "false") + String(")"); | |
| 453 | ||
| 454 | // The coordinate is named, not inlined: the Factor and the Colour both | |
| 455 | // need it, and Blender's scale is applied to the coordinate rather than | |
| 456 | // inside the noise. | |
| 457 | const String pv = String("pn") + itos(p_node_idx) + String("_p"); | |
| 458 | String decl_p; | |
| 459 | String fn; | |
| 460 | switch (noise_dimensions) { | |
| 461 | case 1: | |
| 462 | decl_p = String("float ") + pv + String(" = (") + w + String(") * (") + scale + | |
| 463 | String(");"); | |
| 464 | fn = "bp_perlin_fractal_distorted_1("; | |
| 465 | break; | |
| 466 | case 2: | |
| 467 | decl_p = String("vec2 ") + pv + String(" = vec2((") + v + String(").x, (") + v + | |
| 468 | String(").y) * (") + scale + String(");"); | |
| 469 | fn = "bp_perlin_fractal_distorted_2("; | |
| 470 | break; | |
| 471 | case 4: | |
| 472 | decl_p = String("vec4 ") + pv + String(" = vec4(") + v + String(", ") + w + | |
| 473 | String(") * (") + scale + String(");"); | |
| 474 | fn = "bp_perlin_fractal_distorted_4("; | |
| 475 | break; | |
| 476 | default: | |
| 477 | decl_p = String("vec3 ") + pv + String(" = (") + v + String(") * (") + scale + | |
| 478 | String(");"); | |
| 479 | fn = "bp_perlin_fractal_distorted_3("; | |
| 480 | break; | |
| 481 | } | |
| 482 | ||
| 483 | return decl_p + String("\n\t") + p_ctx.declare(p_node_idx, 0) + String(" = ") + fn + pv + | |
| 484 | args + String(";\n\t") + p_ctx.declare(p_node_idx, 1) + | |
| 485 | String(" = vec4(bp_perlin_float3_fractal_distorted(") + pv + args + String(", 1.0);"); | |
| 486 | } | |
| 487 | ||
| 430 | 488 | void PerlinNoiseNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 431 | 489 | SocketValue in[9]; |
| 432 | 490 | bool is_field = false; |
| … | … | @@ -59,10 +59,14 @@ public: |
| 59 | 59 | StringName get_node_type() const override { return "NoiseTexture"; } |
| 60 | 60 | String get_node_title() const override { return "Noise Texture"; } |
| 61 | 61 | String get_node_category() const override { return "Texture"; } |
| 62 | // The GLSL is the OLD arithmetic -- a different lattice, no per-dimension | |
| 63 | // scale constant, no distortion. Off until it is ported, so a batch cannot | |
| 64 | // silently evaluate a different function from the CPU path. | |
| 65 | bool supports_gpu() const override { return false; } | |
| 62 | // ON since 2026-09-05. It used to emit the OLD arithmetic -- a different | |
| 63 | // lattice, no per-dimension scale constant, no distortion -- from a | |
| 64 | // hand-written GLSL Perlin that nothing compared against this node's C++. | |
| 65 | // There is no second implementation now: compile_glsl calls into | |
| 66 | // blender_perlin.inc, the same text execute() runs through the shim. | |
| 67 | bool supports_gpu() const override { return true; } | |
| 68 | String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override; | |
| 69 | String compile_glsl_prelude(int p_node_idx) const override; | |
| 66 | 70 | |
| 67 | 71 | void set_noise_dimensions(int p_dim) { noise_dimensions = CLAMP(p_dim, 1, 4); } |
| 68 | 72 | int get_noise_dimensions() const { return noise_dimensions; } |
| … | … | @@ -40,6 +40,7 @@ |
| 40 | 40 | // ============================================================================ |
| 41 | 41 | |
| 42 | 42 | #include <vulkan/vulkan.h> |
| 43 | #include <sys/resource.h> | |
| 43 | 44 | |
| 44 | 45 | #include <cmath> |
| 45 | 46 | #include <cstdio> |
| … | … | @@ -57,8 +58,35 @@ |
| 57 | 58 | static const double TOL_REL = 1e-5; |
| 58 | 59 | static const double TOL_ABS = 1e-6; |
| 59 | 60 | |
| 61 | // ⚠ THIS GATE EMITS ONE FUNCTION PER CASE, so its shader grows with the case | |
| 62 | // list. That is fine for cheap arithmetic and NOT fine for anything with an | |
| 63 | // octave loop in it: the sibling gate, built the same way, twice took a 31 GB | |
| 64 | // machine to global OOM once perlin cases were added to it | |
| 65 | // (tests/test_shared_noise_parity.cpp, which is now data-driven and does not). | |
| 66 | // Keep the expensive families over there and only enough cases here to show | |
| 67 | // the EMITTER wires a node correctly. The cap makes a mistake fail alone. | |
| 68 | static const rlim_t MEM_LIMIT_BYTES = (rlim_t)4 * 1024 * 1024 * 1024; | |
| 69 | ||
| 70 | static void cap_own_memory() { | |
| 71 | struct rlimit rl; | |
| 72 | if (getrlimit(RLIMIT_AS, &rl) != 0) { return; } | |
| 73 | if (rl.rlim_cur != RLIM_INFINITY && rl.rlim_cur <= MEM_LIMIT_BYTES) { return; } | |
| 74 | rl.rlim_cur = (rl.rlim_max == RLIM_INFINITY || rl.rlim_max > MEM_LIMIT_BYTES) | |
| 75 | ? MEM_LIMIT_BYTES : rl.rlim_max; | |
| 76 | if (setrlimit(RLIMIT_AS, &rl) != 0) { | |
| 77 | fprintf(stderr, "WARNING: setrlimit failed; running with NO memory cap\n"); | |
| 78 | } | |
| 79 | } | |
| 80 | ||
| 60 | 81 | static void die(const char *what, VkResult r) { |
| 61 | 82 | fprintf(stderr, "FATAL: %s -> VkResult %d\n", what, (int)r); |
| 83 | struct rlimit rl; | |
| 84 | if (getrlimit(RLIMIT_AS, &rl) == 0 && rl.rlim_cur != RLIM_INFINITY) { | |
| 85 | fprintf(stderr, " Capped at %.1f GiB of address space, deliberately.\n" | |
| 86 | " If it is being hit, the case list has outgrown a gate that\n" | |
| 87 | " emits one function per case.\n", | |
| 88 | (double)rl.rlim_cur / (1024.0 * 1024.0 * 1024.0)); | |
| 89 | } | |
| 62 | 90 | exit(1); |
| 63 | 91 | } |
| 64 | 92 | #define VK(x) do { VkResult _r = (x); if (_r != VK_SUCCESS) die(#x, _r); } while (0) |
| … | … | @@ -184,6 +212,7 @@ static void replace_all(std::string &s, const std::string &from, const std::stri |
| 184 | 212 | } |
| 185 | 213 | |
| 186 | 214 | int main(int argc, char **argv) { |
| 215 | cap_own_memory(); | |
| 187 | 216 | const std::string dir = (argc > 1) ? argv[1] : "/tmp/proc_op_gate"; |
| 188 | 217 | |
| 189 | 218 | std::vector<Case> cases; |