| … | … | @@ -131,6 +131,15 @@ static bool _bind_context_or_defer(ProcEvalContext &p_ctx, int p_node_idx, |
| 131 | 131 | return false; |
| 132 | 132 | } |
| 133 | 133 | |
| 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. | |
| 138 | static HashMap<String, double> &_lazy_ms() { | |
| 139 | static HashMap<String, double> acc; | |
| 140 | return acc; | |
| 141 | } | |
| 142 | ||
| 134 | 143 | SocketValue ProcEvalContext::materialize(const SocketValue &p_val, |
| 135 | 144 | const Ref<ProcGeometry> &p_geo) { |
| 136 | 145 | if (!p_val.is_lazy || graph == nullptr || p_val.lazy_node < 0) { |
| … | … | @@ -144,7 +153,14 @@ SocketValue ProcEvalContext::materialize(const SocketValue &p_val, |
| 144 | 153 | // Restoring the previous context keeps nesting honest. |
| 145 | 154 | Ref<ProcGeometry> prev = context_geometry; |
| 146 | 155 | 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 | } | |
| 148 | 164 | context_geometry = prev; |
| 149 | 165 | int slot = p_val.lazy_node * max_output_sockets + p_val.lazy_socket; |
| 150 | 166 | if (slot < 0 || slot >= output_values.size()) { |
| … | … | @@ -1568,6 +1584,16 @@ Ref<ProcGeometry> GraphExecutor::execute( |
| 1568 | 1584 | } |
| 1569 | 1585 | } |
| 1570 | 1586 | |
| 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 | ||
| 1571 | 1597 | // Free per-evaluation GPU buffers (pipelines persist in ExecutionPlan for reuse) |
| 1572 | 1598 | ctx.gpu_buffers.free_all(); |
| 1573 | 1599 |
| … | … | @@ -200,13 +200,19 @@ void PerlinNoiseNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 200 | 200 | PackedColorArray cols; |
| 201 | 201 | facs.resize(n); |
| 202 | 202 | 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) { | |
| 204 | 210 | float fac; |
| 205 | 211 | Color col; |
| 206 | 212 | 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 | }); | |
| 210 | 216 | p_ctx.write_float_field(p_node_idx, 0, facs, domain); |
| 211 | 217 | p_ctx.write_color_field(p_node_idx, 1, cols, domain); |
| 212 | 218 | } |
| … | … | @@ -1148,11 +1154,11 @@ void GradientTextureNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 1148 | 1154 | PackedFloat32Array fac; |
| 1149 | 1155 | col.resize(n); |
| 1150 | 1156 | fac.resize(n); |
| 1151 | for (int i = 0; i < n; i++) { | |
| 1157 | proc_parallel_for(n, [&](int i) { | |
| 1152 | 1158 | float r = _gradient(pv.vec_at(i), gradient_type); |
| 1153 | 1159 | col.ptrw()[i] = Color(r, r, r, 1.0f); |
| 1154 | 1160 | fac.ptrw()[i] = r; |
| 1155 | } | |
| 1161 | }); | |
| 1156 | 1162 | p_ctx.write_color_field(p_node_idx, 0, col, pv.domain); |
| 1157 | 1163 | p_ctx.write_float_field(p_node_idx, 1, fac, pv.domain); |
| 1158 | 1164 | } |
| … | … | @@ -1498,12 +1504,12 @@ void GradientNoiseNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 1498 | 1504 | PackedVector3Array s_arr; |
| 1499 | 1505 | h_arr.resize(n); |
| 1500 | 1506 | s_arr.resize(n); |
| 1501 | for (int i = 0; i < n; i++) { | |
| 1507 | proc_parallel_for(n, [&](int i) { | |
| 1502 | 1508 | Vector3 pos = pos_val.vec_at(i); |
| 1503 | 1509 | Vector3 r = fractal_noise_2d(Vector2(pos.x, pos.z), scale, detail, 2.0f, gain); |
| 1504 | 1510 | h_arr.ptrw()[i] = r.x; |
| 1505 | 1511 | s_arr.ptrw()[i] = Vector3(r.y, 0, r.z); |
| 1506 | } | |
| 1512 | }); | |
| 1507 | 1513 | p_ctx.write_float_field(p_node_idx, 0, h_arr); |
| 1508 | 1514 | p_ctx.write_vec_field(p_node_idx, 1, s_arr); |
| 1509 | 1515 | } else { |