| … | … | @@ -174,6 +174,17 @@ SocketValue ProcEvalContext::materialize_on(const SocketValue &p_val, |
| 174 | 174 | return adapted; |
| 175 | 175 | } |
| 176 | 176 | |
| 177 | bool ProcEvalContext::is_output_needed(int p_node_idx, int p_socket_idx) const { | |
| 178 | if (output_needed.is_empty() || max_output_sockets <= 0) { | |
| 179 | return true; // no liveness information: behave as before | |
| 180 | } | |
| 181 | const int slot = p_node_idx * max_output_sockets + p_socket_idx; | |
| 182 | if (slot < 0 || slot >= output_needed.size()) { | |
| 183 | return true; | |
| 184 | } | |
| 185 | return output_needed[slot]; | |
| 186 | } | |
| 187 | ||
| 177 | 188 | bool ProcEvalContext::is_input_connected(int p_node_idx, int p_socket_idx) const { |
| 178 | 189 | if (graph == nullptr) { |
| 179 | 190 | return false; |
| … | … | @@ -1513,10 +1524,15 @@ Vector<bool> GraphExecutor::_compute_live_set(const ProcNodeGraph &p_graph, |
| 1513 | 1524 | for (int i = 0; i < count; i++) { |
| 1514 | 1525 | live.write[i] = false; |
| 1515 | 1526 | } |
| 1527 | p_ctx.output_needed.resize(count * p_ctx.max_output_sockets); | |
| 1528 | for (int i = 0; i < p_ctx.output_needed.size(); i++) { | |
| 1529 | p_ctx.output_needed.write[i] = false; | |
| 1530 | } | |
| 1516 | 1531 | if (p_plan.output_node < 0 || p_plan.output_node >= count) { |
| 1517 | 1532 | for (int i = 0; i < count; i++) { |
| 1518 | 1533 | live.write[i] = true; |
| 1519 | 1534 | } |
| 1535 | p_ctx.output_needed.clear(); // no information: is_output_needed says yes | |
| 1520 | 1536 | return live; |
| 1521 | 1537 | } |
| 1522 | 1538 | |
| … | … | @@ -1588,7 +1604,18 @@ Vector<bool> GraphExecutor::_compute_live_set(const ProcNodeGraph &p_graph, |
| 1588 | 1604 | continue; // the branch this Switch does not take |
| 1589 | 1605 | } |
| 1590 | 1606 | const int src = p_graph.get_input_source_node(idx, s); |
| 1591 | if (src < 0 || src >= count || live[src]) { | |
| 1607 | if (src < 0 || src >= count) { | |
| 1608 | continue; | |
| 1609 | } | |
| 1610 | // Which SOCKET of the producer this edge reads. Recorded even when | |
| 1611 | // the producer is already live, because a second consumer may want | |
| 1612 | // a different port. | |
| 1613 | const int ssock = p_graph.get_input_source_socket(idx, s); | |
| 1614 | const int slot = src * p_ctx.max_output_sockets + ssock; | |
| 1615 | if (slot >= 0 && slot < p_ctx.output_needed.size()) { | |
| 1616 | p_ctx.output_needed.write[slot] = true; | |
| 1617 | } | |
| 1618 | if (live[src]) { | |
| 1592 | 1619 | continue; |
| 1593 | 1620 | } |
| 1594 | 1621 | live.write[src] = true; |
| … | … | @@ -65,6 +65,25 @@ public: |
| 65 | 65 | // A default cannot express that, so the node has to ask. |
| 66 | 66 | bool is_input_connected(int p_node_idx, int p_socket_idx) const; |
| 67 | 67 | |
| 68 | // ── DOES ANYONE READ THIS OUTPUT? ─────────────────────────────────────── | |
| 69 | // | |
| 70 | // Nodes wrote every output whether or not the graph read it. Measured on | |
| 71 | // the bridged voronoi mountain, where EVERY node reads exactly one port: | |
| 72 | // Voronoi Texture writes five arrays over 2.58M elements -- a 41 MB Colour, | |
| 73 | // a 31 MB position and two more -- for one float anybody wanted. Noise | |
| 74 | // Texture and Gradient Texture each write a 41 MB Colour nobody reads. | |
| 75 | // ~175 MB of materialisation an evaluation, plus the arithmetic behind it. | |
| 76 | // | |
| 77 | // Blender does not have this problem because a field is a FUNCTION and | |
| 78 | // nothing is materialised until a consumer asks over its own domain. This | |
| 79 | // is the same answer at socket granularity: skip what has no reader. | |
| 80 | // | |
| 81 | // Empty means "no information" -- a graph evaluated without a liveness pass | |
| 82 | // gets true for everything, so the old behaviour is what an unanalysed | |
| 83 | // graph still does. | |
| 84 | bool is_output_needed(int p_node_idx, int p_socket_idx) const; | |
| 85 | Vector<bool> output_needed; // node * max_output_sockets + socket | |
| 86 | ||
| 68 | 87 | // Redeem a context field against a specific geometry. A value that is not |
| 69 | 88 | // lazy is returned unchanged, so callers need not care which they have. |
| 70 | 89 | SocketValue materialize(const SocketValue &p_val, const Ref<ProcGeometry> &p_geo); |
| … | … | @@ -204,14 +204,18 @@ void PerlinNoiseNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 204 | 204 | // and this did two of them per element. Measured on the bridged mountain, |
| 205 | 205 | // NoiseTexture was 9.8 s of an 11.4 s value cone -- the largest single node |
| 206 | 206 | // in it once Voronoi was threaded. |
| 207 | float *facs_p = facs.ptrw(); | |
| 208 | Color *cols_p = cols.ptrw(); | |
| 207 | const bool need_fac = p_ctx.is_output_needed(p_node_idx, 0); | |
| 208 | const bool need_col = p_ctx.is_output_needed(p_node_idx, 1); | |
| 209 | if (!need_fac) { facs.resize(0); } | |
| 210 | if (!need_col) { cols.resize(0); } | |
| 211 | float *facs_p = need_fac ? facs.ptrw() : nullptr; | |
| 212 | Color *cols_p = need_col ? cols.ptrw() : nullptr; | |
| 209 | 213 | proc_parallel_for(n, [&](int e) { |
| 210 | 214 | float fac; |
| 211 | 215 | Color col; |
| 212 | 216 | at(e, fac, col); |
| 213 | facs_p[e] = fac; | |
| 214 | cols_p[e] = col; | |
| 217 | if (facs_p) facs_p[e] = fac; | |
| 218 | if (cols_p) cols_p[e] = col; | |
| 215 | 219 | }); |
| 216 | 220 | p_ctx.write_float_field(p_node_idx, 0, facs, domain); |
| 217 | 221 | p_ctx.write_color_field(p_node_idx, 1, cols, domain); |
| … | … | @@ -658,11 +662,26 @@ void VoronoiTextureNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 658 | 662 | // ⛔ HOIST THE WRITE POINTERS. ptrw() is a copy-on-write check, and this |
| 659 | 663 | // wrote through five of them PER ELEMENT -- 12.9 million CoW checks on the |
| 660 | 664 | // bridged mountain's 2.58M vertices, for five stores. |
| 661 | float *dist_p = is_field ? dist_a.ptrw() : nullptr; | |
| 662 | Color *col_p = is_field ? col_a.ptrw() : nullptr; | |
| 663 | Vector3 *pos_p = is_field ? pos_a.ptrw() : nullptr; | |
| 664 | float *w_p = is_field ? w_a.ptrw() : nullptr; | |
| 665 | float *rad_p = is_field ? rad_a.ptrw() : nullptr; | |
| 665 | // Only the ports something reads. Voronoi has five, and a graph reading one | |
| 666 | // of them still paid for a 41 MB Colour and a 31 MB position over 2.58M | |
| 667 | // elements. | |
| 668 | const bool need_dist = p_ctx.is_output_needed(p_node_idx, 0); | |
| 669 | const bool need_col = p_ctx.is_output_needed(p_node_idx, 1); | |
| 670 | const bool need_pos = p_ctx.is_output_needed(p_node_idx, 2); | |
| 671 | const bool need_w = p_ctx.is_output_needed(p_node_idx, 3); | |
| 672 | const bool need_rad = p_ctx.is_output_needed(p_node_idx, 4); | |
| 673 | if (is_field) { | |
| 674 | if (!need_dist) { dist_a.resize(0); } | |
| 675 | if (!need_col) { col_a.resize(0); } | |
| 676 | if (!need_pos) { pos_a.resize(0); } | |
| 677 | if (!need_w) { w_a.resize(0); } | |
| 678 | if (!need_rad) { rad_a.resize(0); } | |
| 679 | } | |
| 680 | float *dist_p = (is_field && need_dist) ? dist_a.ptrw() : nullptr; | |
| 681 | Color *col_p = (is_field && need_col) ? col_a.ptrw() : nullptr; | |
| 682 | Vector3 *pos_p = (is_field && need_pos) ? pos_a.ptrw() : nullptr; | |
| 683 | float *w_p = (is_field && need_w) ? w_a.ptrw() : nullptr; | |
| 684 | float *rad_p = (is_field && need_rad) ? rad_a.ptrw() : nullptr; | |
| 666 | 685 | |
| 667 | 686 | // Per element and independent: every iteration reads its own inputs and |
| 668 | 687 | // writes its own slot. The scalar path is n == 1 and stays serial. |
| … | … | @@ -751,11 +770,11 @@ void VoronoiTextureNode::execute(ProcEvalContext &p_ctx, int p_node_idx) { |
| 751 | 770 | Color c(color.x, color.y, color.z, 1.0f); |
| 752 | 771 | Vector3 p3(position.x, position.y, position.z); |
| 753 | 772 | if (is_field) { |
| 754 | dist_p[e] = dist; | |
| 755 | col_p[e] = c; | |
| 756 | pos_p[e] = p3; | |
| 757 | w_p[e] = position.w; | |
| 758 | rad_p[e] = radius; | |
| 773 | if (dist_p) dist_p[e] = dist; | |
| 774 | if (col_p) col_p[e] = c; | |
| 775 | if (pos_p) pos_p[e] = p3; | |
| 776 | if (w_p) w_p[e] = position.w; | |
| 777 | if (rad_p) rad_p[e] = radius; | |
| 759 | 778 | } else { |
| 760 | 779 | s_dist = dist; |
| 761 | 780 | s_col = c; |