OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes commits / 82f9985

fields: thread the noise nodes -- 74 s -> 8.3 s end to end
Mesh bit-identical, as at every step: 2,582,018 verts, x -20..20 y -2..23.765 z -20..20, sum(z)=1792386.7043, sumsq(z)=28382638.9998. SubdivideMesh 1.7 s -> 1.2 s SetPosition 17.8 s -> 2.6 s MeshToSDFGrid 53.7 s -> 3.6 s GridToMesh 0.5 s -> 0.4 s whole bake 74.0 s -> 8.3 s (Blender, same graph: 2.29 s) MEASURED FIRST THIS TIME. The value cone is deferred and runs from materialize(), so every node in it is charged to whichever geometry node asked -- "CPU node [SetPosition]: 11.4 s" is the whole cone and names nothing. A per-node accumulator in the lazy path said where to look: NoiseTexture 9848 ms <- the one to thread MapRange 800 ms VoronoiTexture 759 ms <- already threaded, and it shows VectorMath 685 ms ⚠ Those are INCLUSIVE: a nested materialize charges the parent its child's time, which is why they sum to 89 s against 11.4 s of wall. Useful for ranking, not for adding up. Math's 65.7 s is thirteen Math nodes each nesting the subtree beneath it, not 65 seconds of arithmetic. NoiseTexture threaded with proc_parallel_for and its two ptrw() calls hoisted out of the loop: 9848 ms -> 1097 ms, 9x. GradientTexture and GradientNoise threaded the same way. Set Position, which is charged the cone, 11.4 s -> 2.6 s. The remaining shape of the cone is Switch 2.5 s and Math 13.3 s inclusive, both of which are now mostly the subtree beneath them rather than their own work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
82f9985c4165bd5f602e8288b1843949f85cc661
by plankatron <fleeky@gmail.com> · 2026-09-08T18:00:07.000Z

Changed files (2) +41 -9

src/graph/graph_executor.cpp +27 -1
……@@ -131,6 +131,15 @@ static bool _bind_context_or_defer(ProcEvalContext &p_ctx, int p_node_idx,
131131 return false;
132132}
133133
134// Per-node time spent in the LAZY path. The value cone is deferred and then run
135// from materialize(), so every one of its nodes is charged to whichever geometry
136// node happened to ask -- "CPU node [SetPosition]: 11.4 s" is the whole cone,
137// and says nothing about which node in it to thread.
138static HashMap<String, double> &_lazy_ms() {
139 static HashMap<String, double> acc;
140 return acc;
141}
142
134143SocketValue ProcEvalContext::materialize(const SocketValue &p_val,
135144 const Ref<ProcGeometry> &p_geo) {
136145 if (!p_val.is_lazy || graph == nullptr || p_val.lazy_node < 0) {
……@@ -144,7 +153,14 @@ SocketValue ProcEvalContext::materialize(const SocketValue &p_val,
144153 // Restoring the previous context keeps nesting honest.
145154 Ref<ProcGeometry> prev = context_geometry;
146155 context_geometry = p_geo;
147 producer->execute(*this, p_val.lazy_node);
156 {
157 const uint64_t _t0 = Time::get_singleton()->get_ticks_usec();
158 producer->execute(*this, p_val.lazy_node);
159 const uint64_t _t1 = Time::get_singleton()->get_ticks_usec();
160 HashMap<String, double> &acc = _lazy_ms();
161 const String k = String(producer->get_node_type());
162 acc[k] = (acc.has(k) ? acc[k] : 0.0) + (double)(_t1 - _t0) / 1000.0;
163 }
148164 context_geometry = prev;
149165 int slot = p_val.lazy_node * max_output_sockets + p_val.lazy_socket;
150166 if (slot < 0 || slot >= output_values.size()) {
……@@ -1568,6 +1584,16 @@ Ref<ProcGeometry> GraphExecutor::execute(
15681584 }
15691585 }
15701586
1587 {
1588 HashMap<String, double> &acc = _lazy_ms();
1589 for (const KeyValue<String, double> &kv : acc) {
1590 if (kv.value > 0.5) {
1591 UtilityFunctions::print(" LAZY node [", kv.key, "]: ", kv.value, "ms");
1592 }
1593 }
1594 acc.clear();
1595 }
1596
15711597 // Free per-evaluation GPU buffers (pipelines persist in ExecutionPlan for reuse)
15721598 ctx.gpu_buffers.free_all();
15731599
src/nodes/noise_nodes.cpp +14 -8
……@@ -200,13 +200,19 @@ void PerlinNoiseNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
200200 PackedColorArray cols;
201201 facs.resize(n);
202202 cols.resize(n);
203 for (int e = 0; e < n; e++) {
203 // Threaded, and the write pointers hoisted: ptrw() is a copy-on-write check
204 // and this did two of them per element. Measured on the bridged mountain,
205 // NoiseTexture was 9.8 s of an 11.4 s value cone -- the largest single node
206 // in it once Voronoi was threaded.
207 float *facs_p = facs.ptrw();
208 Color *cols_p = cols.ptrw();
209 proc_parallel_for(n, [&](int e) {
204210 float fac;
205211 Color col;
206212 at(e, fac, col);
207 facs.ptrw()[e] = fac;
208 cols.ptrw()[e] = col;
209 }
213 facs_p[e] = fac;
214 cols_p[e] = col;
215 });
210216 p_ctx.write_float_field(p_node_idx, 0, facs, domain);
211217 p_ctx.write_color_field(p_node_idx, 1, cols, domain);
212218}
……@@ -1148,11 +1154,11 @@ void GradientTextureNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
11481154 PackedFloat32Array fac;
11491155 col.resize(n);
11501156 fac.resize(n);
1151 for (int i = 0; i < n; i++) {
1157 proc_parallel_for(n, [&](int i) {
11521158 float r = _gradient(pv.vec_at(i), gradient_type);
11531159 col.ptrw()[i] = Color(r, r, r, 1.0f);
11541160 fac.ptrw()[i] = r;
1155 }
1161 });
11561162 p_ctx.write_color_field(p_node_idx, 0, col, pv.domain);
11571163 p_ctx.write_float_field(p_node_idx, 1, fac, pv.domain);
11581164}
……@@ -1498,12 +1504,12 @@ void GradientNoiseNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
14981504 PackedVector3Array s_arr;
14991505 h_arr.resize(n);
15001506 s_arr.resize(n);
1501 for (int i = 0; i < n; i++) {
1507 proc_parallel_for(n, [&](int i) {
15021508 Vector3 pos = pos_val.vec_at(i);
15031509 Vector3 r = fractal_noise_2d(Vector2(pos.x, pos.z), scale, detail, 2.0f, gain);
15041510 h_arr.ptrw()[i] = r.x;
15051511 s_arr.ptrw()[i] = Vector3(r.y, 0, r.z);
1506 }
1512 });
15071513 p_ctx.write_float_field(p_node_idx, 0, h_arr);
15081514 p_ctx.write_vec_field(p_node_idx, 1, s_arr);
15091515 } else {