1
1

Compare commits

...

291 Commits

Author SHA1 Message Date
011a1f3335 fix 2022-09-08 17:21:48 +02:00
9007489c51 comments 2022-09-08 16:53:43 +02:00
079b38f8de comments 2022-09-08 16:37:35 +02:00
3d1244c134 comments 2022-09-08 16:07:02 +02:00
4ce0711e67 comments 2022-09-08 15:43:16 +02:00
a0e1d123fc comments 2022-09-08 15:34:17 +02:00
6c0ec45daf cleanup 2022-09-08 13:19:57 +02:00
91a57c9cc2 cleanup 2022-09-08 13:18:30 +02:00
90edc4472e Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-09-08 12:55:44 +02:00
3f9f9f0348 cleanup 2022-09-08 12:39:06 +02:00
04c91471e0 cleanup 2022-09-08 12:23:00 +02:00
e356fe95fd cleanup 2022-09-08 12:17:05 +02:00
6e09d25657 bring back attribute search in node editor 2022-09-08 12:12:23 +02:00
a77491a6ae bring back used named attribute logging in modifier 2022-09-08 11:41:56 +02:00
a8a71d6b63 bring back used named attributes overlay 2022-09-08 11:39:36 +02:00
bf97ce6418 cleanup 2022-09-08 11:06:13 +02:00
69ab1bd73c cleanup 2022-09-08 11:01:37 +02:00
e599bb1793 cleanup 2022-09-08 10:54:44 +02:00
198f8c209f cleanup 2022-09-08 10:43:58 +02:00
2f1d60a481 improve compute context docs 2022-09-08 10:35:43 +02:00
1c37a515bc Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-09-08 09:50:40 +02:00
8429f01d8c fix attribute search 2022-09-07 17:29:44 +02:00
3aba2a3f9e Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-09-07 16:15:15 +02:00
b07660a2b9 cleanup 2022-09-07 12:53:52 +02:00
1b6d1fb1d6 move context stacks to separate header 2022-09-07 12:49:50 +02:00
f9434dbe59 use fewer allocators 2022-09-07 12:33:36 +02:00
aaf8c0d9d7 cleanup 2022-09-07 12:14:26 +02:00
ee3049e508 support logging field in viewer again 2022-09-07 12:10:08 +02:00
091e7fb735 support viewer geometry again 2022-09-07 11:44:08 +02:00
0b75698519 actually log data in viewer node 2022-09-07 11:21:35 +02:00
cdd045d269 cleanup logging 2022-09-07 11:06:24 +02:00
679a4d218b Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-09-07 10:55:06 +02:00
35caf245dd progress 2022-09-07 10:21:32 +02:00
45d36410a7 Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-09-07 09:12:15 +02:00
7b71ab3fe0 progress 2022-09-06 20:06:44 +02:00
d98c7a195f add lazy function for viewer node 2022-09-06 19:45:17 +02:00
a347508dcc simplify naming 2022-09-06 19:28:13 +02:00
cc08debc0c make nodes with side effects work 2022-09-06 19:24:13 +02:00
4e9515d23d gather side effect nodes 2022-09-06 18:18:14 +02:00
e7c95b8fde cleanup + add initial side effect provider 2022-09-06 18:12:59 +02:00
c36d8516b0 cleanup 2022-09-06 17:46:13 +02:00
2b9e3e4e5a Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-09-06 17:37:20 +02:00
46f0b0798a make logging optional 2022-09-04 12:49:07 +02:00
3548c5acfe cleanup 2022-09-04 12:40:00 +02:00
68c797c49e gather sockets to preview 2022-09-04 12:39:36 +02:00
dd5c704ee1 add utility context stack builder 2022-09-04 12:07:54 +02:00
9e2ff5b11a show more detailed socket inspection 2022-09-04 11:58:11 +02:00
a99fddf98e log more information of values 2022-09-04 11:44:18 +02:00
d73e2d5612 log less unnecessary data 2022-09-04 11:13:59 +02:00
d71d0e402f add logger to lazy function graph executor 2022-09-02 17:50:30 +02:00
8629bd863a log converted values 2022-09-02 15:44:59 +02:00
686d90a824 try find socket with logged value 2022-09-02 15:26:54 +02:00
f197490901 Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-09-02 14:46:53 +02:00
d38cc2fc53 Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-09-01 20:38:20 +02:00
80d6565b93 fix missing lazy function update with animation 2022-09-01 19:58:49 +02:00
75e658e6a0 Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-09-01 19:25:54 +02:00
4cfa580a68 fix multi input socket with muted link 2022-09-01 19:23:33 +02:00
db13aa8e43 refactor lazy function graph generation 2022-09-01 18:56:19 +02:00
d6639cfd00 cleanup naming 2022-09-01 12:57:12 +02:00
6ee52e2e8c Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-09-01 12:50:57 +02:00
a2a8b9e82f fixes 2022-08-31 15:58:20 +02:00
ad55174715 Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-08-31 15:45:58 +02:00
7d3f3c6fd7 fixes 2022-08-31 13:02:54 +02:00
280c039908 Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-08-31 12:34:03 +02:00
8ecd241502 improve text 2022-08-24 13:48:43 +02:00
4f1de2be9d cleanup 2022-08-24 13:33:52 +02:00
9f4db143f3 Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-08-24 13:17:38 +02:00
231cafa911 socket value logging progress 2022-08-23 13:39:23 +02:00
5523d90d9c progress 2022-08-23 13:06:18 +02:00
4d94c1a4e7 progress 2022-08-23 12:55:11 +02:00
8e2b11badf fixes 2022-08-23 12:26:54 +02:00
5dbbfc8cc3 Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-08-23 12:26:50 +02:00
cb091658a9 improve naming 2022-07-18 23:13:28 +02:00
f3a63be474 improve naming 2022-07-18 22:57:09 +02:00
98b5005787 fix 2022-07-18 22:33:07 +02:00
bf808496f8 fix 2022-07-18 22:22:16 +02:00
050a9f5f0d Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-07-18 22:20:30 +02:00
f5652ce6b2 show node timings 2022-06-30 23:31:15 +02:00
58bd9230a8 initial node timings support 2022-06-30 23:19:47 +02:00
8f710b104d cleanup logger naming 2022-06-30 22:27:09 +02:00
69aa085cd8 Merge branch 'master' into temp-geometry-nodes-evaluator-refactor 2022-06-30 22:16:27 +02:00
dbef967a7b progress 2022-06-26 14:56:56 +02:00
c2e101facb progress 2022-06-26 14:30:58 +02:00
f4164f27ab progress 2022-06-26 14:28:18 +02:00
ff11ece7a1 progress 2022-06-26 14:26:32 +02:00
d9fc777ce2 show warnings 2022-06-26 14:17:29 +02:00
a74097a150 cleanup 2022-06-26 14:07:06 +02:00
30ba454cbe cleanup 2022-06-26 13:58:39 +02:00
7adefbc04e progress 2022-06-26 13:44:57 +02:00
2c69f123ca cleanup 2022-06-26 12:46:02 +02:00
aaf8b97666 fixes 2022-06-26 12:44:18 +02:00
b6b2e91132 cleanup 2022-06-26 12:22:43 +02:00
0f9bfe501b cleanup 2022-06-26 12:08:57 +02:00
668f34d346 reduce redundant work 2022-06-26 11:58:32 +02:00
b570994b0e show node warnings again 2022-06-26 11:37:21 +02:00
cec0db55df cleanup 2022-06-26 10:29:24 +02:00
4c16d01222 Merge branch 'master' into lazy-function 2022-06-26 10:23:55 +02:00
460c970cab fix warning 2022-06-10 17:42:39 +02:00
14163d9ce0 fix 2022-06-10 17:24:06 +02:00
d4110b2728 Merge branch 'master' into lazy-function 2022-06-10 17:15:45 +02:00
7a5eee9710 Merge branch 'master' into lazy-function 2022-06-10 09:56:48 +02:00
9b598f0ab2 fix 2022-06-09 15:38:38 +02:00
1287fbe88b fix 2022-06-09 14:02:43 +02:00
464428b002 Merge branch 'master' into lazy-function 2022-06-09 14:02:26 +02:00
090555898c initial value logging 2022-06-01 11:02:04 +02:00
f1cd0ed6b8 remove old geometry nodes logger 2022-06-01 10:46:18 +02:00
aa0b2990f6 progress 2022-06-01 10:22:11 +02:00
9c23df35e4 progress 2022-06-01 10:09:22 +02:00
ad52352e6f Merge branch 'master' into lazy-function 2022-05-31 20:56:35 +02:00
67f46a8c05 pass context stack 2022-05-30 18:16:26 +02:00
b29e17bc89 fix 2022-05-30 17:59:01 +02:00
b10e74d85e cleanup 2022-05-30 17:47:21 +02:00
cef9625a2e split params from context 2022-05-30 17:25:03 +02:00
0c6d11c1be simplify naming 2022-05-30 16:50:32 +02:00
4f7ca91df1 simplify naming 2022-05-30 16:41:40 +02:00
45efd45ee0 simplify naming 2022-05-30 16:41:07 +02:00
48d97f9abb cleanup 2022-05-30 16:40:05 +02:00
0b1c529be3 simplify 2022-05-30 16:36:35 +02:00
0ff96301e9 Merge branch 'master' into lazy-function 2022-05-30 16:28:37 +02:00
ba163fcc30 progress 2022-05-30 16:26:59 +02:00
f3a412290c remove xxhash for now 2022-05-30 15:57:03 +02:00
04e2bf544a Merge branch 'master' into lazy-function 2022-05-30 15:34:26 +02:00
1f10276019 Merge branch 'master' into lazy-function 2022-05-30 12:16:49 +02:00
f54ba94974 progress 2022-05-29 22:23:22 +02:00
cffc2dcae9 progress 2022-05-29 13:43:51 +02:00
48e709fd84 progress 2022-05-29 13:24:02 +02:00
e8693f14a6 progress 2022-05-29 13:13:43 +02:00
d73b216e6a fix 2022-05-29 12:16:01 +02:00
a2ad563cba initial context stack 2022-05-29 12:15:26 +02:00
f5454f4a5f Merge branch 'master' into lazy-function 2022-05-28 22:10:21 +02:00
3a6d61b019 Merge branch 'master' into lazy-function 2022-05-26 13:12:37 +02:00
8b97f6414d support inlining node groups 2022-05-26 11:58:30 +02:00
28af4afa3d separate mapping from resourcse 2022-05-26 10:44:36 +02:00
73f68712d1 disable print 2022-05-26 10:31:42 +02:00
cafd20f998 store output attributes again 2022-05-26 10:26:38 +02:00
7a89bd9ad5 cleanup 2022-05-26 10:14:40 +02:00
c83b2e04ac fix 2022-05-25 20:30:30 +02:00
1468dcb614 initial support for implicit inputs 2022-05-25 19:23:23 +02:00
781454b645 support muted links 2022-05-25 18:58:24 +02:00
9181b14dc7 Merge branch 'master' into lazy-function 2022-05-25 18:54:41 +02:00
f3bb76b9c1 fixes 2022-05-22 14:05:12 +02:00
1ccb353295 fix 2022-05-22 13:34:49 +02:00
2c0d794dc4 fix 2022-05-22 12:51:27 +02:00
955f78d289 fixes 2022-05-22 12:31:28 +02:00
aa4bdf42dd fixes 2022-05-22 12:14:30 +02:00
d1ad588611 fix 2022-05-22 12:06:27 +02:00
a3f9d535d0 Merge branch 'master' into lazy-function 2022-05-22 12:02:06 +02:00
a51c9a3126 initial support for muted nodes 2022-05-22 11:56:15 +02:00
0bb0a6e7b6 Merge branch 'master' into lazy-function 2022-05-22 10:17:59 +02:00
3dabd0f705 better dot output 2022-05-21 22:22:25 +02:00
9f12236be1 cleanup 2022-05-21 22:05:11 +02:00
470f63db41 cleanup 2022-05-21 22:00:36 +02:00
2f3119a06b cleanup 2022-05-21 21:58:58 +02:00
3058283f71 cleanup 2022-05-21 21:55:38 +02:00
23abe292bd cleanup 2022-05-21 21:54:44 +02:00
20d0c51dd7 only allow dummy sockets as graph inputs and outputs 2022-05-21 21:53:14 +02:00
ed2e265f37 cleanup 2022-05-21 21:20:23 +02:00
88d035ddd3 cleanup 2022-05-21 21:06:11 +02:00
e8520c9949 progress 2022-05-21 21:00:03 +02:00
5209621e41 progress 2022-05-21 20:52:48 +02:00
103b1209b5 comments 2022-05-21 20:38:05 +02:00
c1888b372b fix 2022-05-21 15:29:12 +02:00
9d37ae1679 pass inputs from modifier 2022-05-21 15:20:19 +02:00
c7076c5185 fixes 2022-05-21 14:59:33 +02:00
93b2206945 fixes 2022-05-21 14:52:14 +02:00
978f69fb2f initial node group execution 2022-05-21 14:15:37 +02:00
10f691efb7 support lazy geometry nodes again 2022-05-21 13:55:37 +02:00
ee0589d8e2 setup default values 2022-05-21 13:49:03 +02:00
5df06ea72f progress 2022-05-21 13:21:24 +02:00
2c8af68857 create dummy socket map 2022-05-21 12:54:45 +02:00
604e74dc8b initial group node support 2022-05-21 12:46:35 +02:00
b673fe34fa progress 2022-05-21 12:30:19 +02:00
c1b5ced6ba support reroute 2022-05-21 12:22:35 +02:00
f758100275 support group input and output 2022-05-21 12:14:58 +02:00
52951fefeb progress 2022-05-21 11:53:53 +02:00
e303aad37c refactor getting multi functions for nodes 2022-05-21 11:26:13 +02:00
c15a768a9a progress 2022-05-21 11:23:59 +02:00
25bf08b337 add dummy node support 2022-05-21 11:08:01 +02:00
51e026786a implement actual type conversion 2022-05-21 10:42:40 +02:00
73d19fe9f7 update geo node params 2022-05-21 09:06:55 +02:00
649bdd77e0 remove old evaluator 2022-05-21 08:36:24 +02:00
dc75ea3c3f cleanup 2022-05-20 21:45:50 +02:00
73bb083747 Merge branch 'master' into lazy-function 2022-05-20 21:33:38 +02:00
09e37da0db remove dnode from params provider 2022-05-19 17:08:34 +02:00
a5f79ecf59 remove logger from params 2022-05-19 17:04:26 +02:00
c4c23b6f8a define geo nodes user data 2022-05-19 17:03:01 +02:00
1ce9c68e54 support passing user data through lazy functions 2022-05-19 17:00:02 +02:00
274c98fe50 cleanup 2022-05-19 16:51:06 +02:00
a27245f093 ignore frame nodes 2022-05-19 16:22:58 +02:00
8277bf8c5d insert conversion nodes 2022-05-19 16:19:03 +02:00
6c291c79ad handle multi-input 2022-05-19 15:48:08 +02:00
d0473dea53 Merge branch 'master' into lazy-function 2022-05-19 15:13:26 +02:00
e65f31a8c4 initial function generation from node tree 2022-05-19 11:08:27 +02:00
b9fc7939f8 make node chains 2022-05-19 09:35:35 +02:00
c2ec3f2a44 avoid using task pool when there is no parallel work 2022-05-19 09:16:37 +02:00
1cb24cfe17 use node indices instead of map 2022-05-19 08:56:32 +02:00
97b5a838fa improve naming 2022-05-18 22:39:59 +02:00
6c27edbc26 more cleanup 2022-05-18 22:36:25 +02:00
5b921ece53 remove sgraph 2022-05-18 22:34:07 +02:00
974e334b16 bypass task pool in many cases 2022-05-18 22:19:04 +02:00
63f885e00d pass along current task 2022-05-18 22:07:36 +02:00
d5b3e5fca9 progress 2022-05-18 21:55:14 +02:00
b9dfdb7400 progress 2022-05-18 21:29:32 +02:00
28edae5bac progress 2022-05-18 21:13:33 +02:00
2a8ccf463a fix 2022-05-18 20:16:12 +02:00
9f1d871138 progress 2022-05-18 20:11:46 +02:00
a5a7b19128 cleanup 2022-05-18 19:48:00 +02:00
95d26d80ae progress 2022-05-18 19:41:34 +02:00
32e30a4263 progress 2022-05-18 19:30:16 +02:00
3ee490b4de progress 2022-05-18 19:27:12 +02:00
29587d2cb0 progrss 2022-05-18 19:21:37 +02:00
fd919e18ae Merge branch 'master' into sgraph 2022-05-18 19:19:30 +02:00
d06a4cb4a9 progress 2022-05-18 12:05:43 +02:00
62ece09597 progress 2022-05-18 09:49:43 +02:00
80a02e54b8 progress 2022-05-18 09:42:26 +02:00
f02fdf0ff1 progress 2022-05-18 09:40:02 +02:00
457f9aa832 progress 2022-05-17 21:18:24 +02:00
c445bd7486 progress 2022-05-17 21:12:16 +02:00
48dc18ff2c progress 2022-05-17 20:54:05 +02:00
d6ebcc7619 progress 2022-05-17 20:52:46 +02:00
9896169f9a progress 2022-05-17 20:50:24 +02:00
964fb14ba4 progress 2022-05-17 20:11:00 +02:00
c8caff0216 progress 2022-05-17 20:03:42 +02:00
ccec45a2db progress 2022-05-17 19:47:08 +02:00
8b6ece9fd2 cleanup 2022-05-17 19:38:49 +02:00
8b822977b8 initial eager evaluation 2022-05-17 19:30:56 +02:00
c83224de85 progress 2022-05-17 15:18:20 +02:00
679ef9a083 progress 2022-05-17 15:01:02 +02:00
5797238694 progress 2022-05-17 14:33:01 +02:00
c8ea1f1b4b progress 2022-05-17 14:22:12 +02:00
20396034c1 progress 2022-05-17 14:19:37 +02:00
f43f912431 progress 2022-05-17 14:07:36 +02:00
a6d33079f3 progress 2022-05-17 13:53:43 +02:00
77b1cf9b68 initial lazy function 2022-05-17 13:23:21 +02:00
3356a58507 Merge branch 'master' into sgraph 2022-05-17 12:12:12 +02:00
80a590420c progress 2022-05-14 11:38:33 +02:00
dcf1c2ef9a cleanup 2022-05-14 09:02:54 +02:00
0fdd7e59df Merge branch 'master' into sgraph 2022-05-14 08:52:35 +02:00
0eef8f3747 progress 2022-05-13 17:01:22 +02:00
645d2643f9 progress 2022-05-13 16:56:00 +02:00
11a8dc2ef6 remove code that won't be used 2022-05-13 15:13:02 +02:00
1443cef855 Merge branch 'master' into sgraph 2022-05-13 15:04:48 +02:00
0d0d7c9b4c fix 2022-03-22 11:53:09 +01:00
002d849be2 progress 2022-03-22 11:48:07 +01:00
528ba923bf fix 2022-03-22 11:34:33 +01:00
3595abb204 Merge branch 'master' into sgraph 2022-03-22 11:33:03 +01:00
be856674e9 progress 2022-02-13 18:17:44 +01:00
20acbad7c3 progress 2022-02-13 17:54:59 +01:00
d2b0ae4f9d progress 2022-02-13 17:39:43 +01:00
7cd63301d3 progress 2022-02-13 13:15:53 +01:00
08016656a3 progress 2022-02-13 13:09:43 +01:00
2c2d3b7bdc progress 2022-02-13 12:57:47 +01:00
395df6766f progress 2022-02-13 12:51:22 +01:00
1690446c5e cleanup 2022-02-13 12:41:59 +01:00
a14fc38933 progress 2022-02-13 12:27:42 +01:00
fb443dd0bc cleanup 2022-02-13 12:06:08 +01:00
6058a1ffa7 progress 2022-02-13 11:58:13 +01:00
b031f1ae56 progress 2022-02-13 11:52:18 +01:00
d3c8885793 progress 2022-02-13 11:41:31 +01:00
9a65a061c5 progress 2022-02-13 11:32:14 +01:00
42f2c6a0bb progress 2022-02-13 11:18:08 +01:00
db425bd997 progress 2022-02-13 11:06:20 +01:00
735b7e171e progress 2022-02-13 11:03:56 +01:00
7e871183b7 progress 2022-02-12 14:51:20 +01:00
e2b4b17d2e fixes 2022-02-12 13:37:34 +01:00
642a461491 progress 2022-02-12 13:15:38 +01:00
cf8e500a64 progress 2022-02-12 12:41:29 +01:00
ccd1d65fe8 simplify templating 2022-02-12 12:30:27 +01:00
65917a1461 progress 2022-02-12 12:09:00 +01:00
5388195757 progress 2022-02-12 11:46:32 +01:00
2257a71154 Merge branch 'master' into sgraph 2022-02-12 11:25:20 +01:00
df4e9a0f29 progress 2022-02-07 17:25:54 +01:00
54e622c3b0 cleanup 2022-02-07 17:24:16 +01:00
754ed19656 progress 2022-02-07 17:23:23 +01:00
588d14a66d progress 2022-02-07 16:42:29 +01:00
7c159fef17 progress 2022-02-07 16:35:35 +01:00
8f7effb585 Merge branch 'master' into sgraph 2022-02-07 15:23:29 +01:00
1e29c82a5c progress 2022-02-07 13:54:43 +01:00
63e16a6ee2 comment 2022-02-06 14:19:51 +01:00
244c7f6ad2 check sgraph validity 2022-02-06 14:11:07 +01:00
9f1751914d add adapter for derived tree 2022-02-06 13:19:55 +01:00
1147c0f40d cleanup 2022-02-06 13:03:41 +01:00
96b2216cfc cleanup naming 2022-02-06 12:53:26 +01:00
c1c46bc60c enable test 2022-02-06 12:49:59 +01:00
2807625ee5 cleanup 2022-02-06 12:47:13 +01:00
04592a81b0 refactor sgraph 2022-02-06 12:46:09 +01:00
afb6cea2cf more cleanup 2022-02-06 11:46:50 +01:00
0f1302d281 use "sgraph" name 2022-02-06 11:40:52 +01:00
db85b09c70 logical tree ref adapter 2022-02-04 15:19:20 +01:00
12a037c0d1 tree ref adapter 2022-02-04 15:03:22 +01:00
9a6259264f initial node graph 2022-02-04 14:43:59 +01:00
54 changed files with 5821 additions and 3585 deletions

View File

@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/**
* This file implements some specific compute contexts for concepts in Blender.
*/
#include "BLI_compute_context.hh"
namespace blender::bke {
class ModifierComputeContext : public ComputeContext {
private:
static constexpr const char *s_static_type = "MODIFIER";
std::string modifier_name_;
public:
ModifierComputeContext(const ComputeContext *parent, std::string modifier_name);
private:
void print_current_in_line(std::ostream &stream) const override;
};
class NodeGroupComputeContext : public ComputeContext {
private:
static constexpr const char *s_static_type = "NODE_GROUP";
std::string node_name_;
public:
NodeGroupComputeContext(const ComputeContext *parent, std::string node_name);
StringRefNull node_name() const;
private:
void print_current_in_line(std::ostream &stream) const override;
};
} // namespace blender::bke

View File

@@ -21,6 +21,7 @@ struct bNodeType;
namespace blender::nodes {
struct FieldInferencingInterface;
class NodeDeclaration;
struct GeometryNodesLazyFunctionGraphInfo;
} // namespace blender::nodes
namespace blender::bke {
@@ -48,6 +49,15 @@ class bNodeTreeRuntime : NonCopyable, NonMovable {
/** Information about how inputs and outputs of the node group interact with fields. */
std::unique_ptr<nodes::FieldInferencingInterface> field_inferencing_interface;
/**
* For geometry nodes, a lazy function graph with some additional info is cached. This is used to
* evaluate the node group. Caching it here allows us to reuse the preprocessed node tree in case
* its used multiple times.
*/
std::mutex geometry_nodes_lazy_function_graph_info_mutex;
std::unique_ptr<nodes::GeometryNodesLazyFunctionGraphInfo>
geometry_nodes_lazy_function_graph_info;
/**
* Protects access to all topology cache variables below. This is necessary so that the cache can
* be updated on a const #bNodeTree.
@@ -148,6 +158,8 @@ class bNodeRuntime : NonCopyable, NonMovable {
namespace node_tree_runtime {
void handle_node_tree_output_changed(bNodeTree &tree_cow);
class AllowUsingOutdatedInfo : NonCopyable, NonMovable {
private:
const bNodeTree &tree_;
@@ -413,7 +425,6 @@ inline blender::Span<const bNodeLink *> bNode::internal_links_span() const
inline const blender::nodes::NodeDeclaration *bNode::declaration() const
{
BLI_assert(this->runtime->declaration != nullptr);
return this->runtime->declaration;
}

View File

@@ -98,6 +98,7 @@ set(SRC
intern/collision.c
intern/colorband.c
intern/colortools.c
intern/compute_contexts.cc
intern/constraint.c
intern/context.c
intern/crazyspace.cc
@@ -352,6 +353,7 @@ set(SRC
BKE_collision.h
BKE_colorband.h
BKE_colortools.h
BKE_compute_contexts.hh
BKE_constraint.h
BKE_context.h
BKE_crazyspace.h

View File

@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_compute_contexts.hh"
namespace blender::bke {
ModifierComputeContext::ModifierComputeContext(const ComputeContext *parent,
std::string modifier_name)
: ComputeContext(s_static_type, parent), modifier_name_(std::move(modifier_name))
{
hash_.mix_in(s_static_type, strlen(s_static_type));
hash_.mix_in(modifier_name_.data(), modifier_name_.size());
}
void ModifierComputeContext::print_current_in_line(std::ostream &stream) const
{
stream << "Modifier: " << modifier_name_;
}
NodeGroupComputeContext::NodeGroupComputeContext(const ComputeContext *parent,
std::string node_name)
: ComputeContext(s_static_type, parent), node_name_(std::move(node_name))
{
hash_.mix_in(s_static_type, strlen(s_static_type));
hash_.mix_in(node_name_.data(), node_name_.size());
}
StringRefNull NodeGroupComputeContext::node_name() const
{
return node_name_;
}
void NodeGroupComputeContext::print_current_in_line(std::ostream &stream) const
{
stream << "Node: " << node_name_;
}
} // namespace blender::bke

View File

@@ -71,6 +71,7 @@
#include "NOD_composite.h"
#include "NOD_function.h"
#include "NOD_geometry.h"
#include "NOD_geometry_nodes_to_lazy_function_graph.hh"
#include "NOD_node_declaration.hh"
#include "NOD_shader.h"
#include "NOD_socket.h"

View File

@@ -10,8 +10,22 @@
#include "BLI_task.hh"
#include "BLI_timeit.hh"
#include "NOD_geometry_nodes_to_lazy_function_graph.hh"
namespace blender::bke::node_tree_runtime {
void handle_node_tree_output_changed(bNodeTree &tree_cow)
{
if (tree_cow.type == NTREE_GEOMETRY) {
/* Rebuild geometry nodes lazy function graph. */
{
std::lock_guard lock{tree_cow.runtime->geometry_nodes_lazy_function_graph_info_mutex};
tree_cow.runtime->geometry_nodes_lazy_function_graph_info.reset();
}
blender::nodes::ensure_geometry_nodes_lazy_function_graph(tree_cow);
}
}
static void double_checked_lock(std::mutex &mutex, bool &data_is_dirty, FunctionRef<void()> fn)
{
if (!data_is_dirty) {

View File

@@ -0,0 +1,173 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* When logging computed values, we generally want to know where the value was computed. For
* example, geometry nodes logs socket values so that they can be displayed in the ui. For that we
* can combine the logged value with a `ComputeContext`, which identifies the place where the value
* was computed.
*
* This is not a trivial problem because e.g. just storing storing a pointer to the socket a value
* belongs to is not enough. That's because the same socket may correspond to many different values
* when the socket is used in a node group that is used multiple times. In this case, not only does
* the socket has to be stored but also the entire nested node group path that led to the
* evaluation of the socket.
*
* Storing the entire "context path" for every logged value is not feasible, because that path can
* become quite long. So that would need much more memory, more compute overhead and makes it
* complicated to compare if two contexts are the same. If the identifier for a compute context
* would have a variable size, it would also be much harder to create a map from context to values.
*
* The solution implemented below uses the following key ideas:
* - Every compute context can be hashed to a unique fixed size value (`ComputeContextHash`). While
* technically there could be hash collisions, the hashing algorithm has to be chosen to make
* that practically impossible. This way an entire context path, possibly consisting of many
* nested contexts, is represented by a single value that can be stored easily.
* - A nested compute context is build as singly linked list, where every compute context has a
* pointer to the parent compute context. Note that a link in the other direction is not possible
* because the same parent compute context may be used by many different children which possibly
* run on different threads.
*/
#include "BLI_array.hh"
#include "BLI_linear_allocator.hh"
#include "BLI_stack.hh"
#include "BLI_string_ref.hh"
namespace blender {
/**
* A hash that uniquely identifies a specific (non-fixed-size) compute context. The hash has to
* have enough bits to make collisions practically impossible.
*/
struct ComputeContextHash {
static constexpr int64_t HashSizeInBytes = 16;
uint64_t v1 = 0;
uint64_t v2 = 0;
uint64_t hash() const
{
return v1;
}
friend bool operator==(const ComputeContextHash &a, const ComputeContextHash &b)
{
return a.v1 == b.v1 && a.v2 == b.v2;
}
void mix_in(const void *data, int64_t len);
friend std::ostream &operator<<(std::ostream &stream, const ComputeContextHash &hash);
};
static_assert(sizeof(ComputeContextHash) == ComputeContextHash::HashSizeInBytes);
/**
* Identifies the context in which a computation happens. This context can be used to identify
* values logged during the computation. For more details, see the comment at the top of the file.
*
* This class should be subclassed to implement specific contexts.
*/
class ComputeContext {
private:
/**
* Only used for debugging currently.
*/
const char *static_type_;
/**
* Pointer to the context that this context is child of. That allows nesting compute contexts.
*/
const ComputeContext *parent_ = nullptr;
protected:
/**
* The hash that uniquely identifies this context. It's a combined hash of this context as well
* as all the parent contexts.
*/
ComputeContextHash hash_;
public:
ComputeContext(const char *static_type, const ComputeContext *parent)
: static_type_(static_type), parent_(parent)
{
if (parent != nullptr) {
hash_ = parent_->hash_;
}
}
virtual ~ComputeContext() = default;
const ComputeContextHash &hash() const
{
return hash_;
}
const char *static_type() const
{
return static_type_;
}
const ComputeContext *parent() const
{
return parent_;
}
/**
* Print the entire nested context stack.
*/
void print_stack(std::ostream &stream, StringRef name) const;
/**
* Print information about this specific context. This has to be implemented by each subclass.
*/
virtual void print_current_in_line(std::ostream &stream) const = 0;
friend std::ostream &operator<<(std::ostream &stream, const ComputeContext &compute_context);
};
/**
* Utility class to build a context stack in one place. This is typically used to get the hash that
* corresponds to a specific nested compute context, in order to look up corresponding logged
* values.
*/
class ComputeContextBuilder {
private:
LinearAllocator<> allocator_;
Stack<destruct_ptr<ComputeContext>> contexts_;
public:
bool is_empty() const
{
return contexts_.is_empty();
}
const ComputeContext *current() const
{
if (contexts_.is_empty()) {
return nullptr;
}
return contexts_.peek().get();
}
const ComputeContextHash hash() const
{
BLI_assert(!contexts_.is_empty());
return this->current()->hash();
}
template<typename T, typename... Args> void push(Args &&...args)
{
const ComputeContext *current = this->current();
destruct_ptr<T> context = allocator_.construct<T>(current, std::forward<Args>(args)...);
contexts_.push(std::move(context));
}
void pop()
{
contexts_.pop();
}
};
} // namespace blender

View File

@@ -114,6 +114,14 @@ template<typename Key, typename Value> class MultiValueMap {
return {};
}
/**
* Get the number of keys.
*/
int64_t size() const
{
return map_.size();
}
/**
* NOTE: This signature will change when the implementation changes.
*/

View File

@@ -53,6 +53,7 @@ set(SRC
intern/bitmap_draw_2d.c
intern/boxpack_2d.c
intern/buffer.c
intern/compute_context.cc
intern/convexhull_2d.c
intern/cpp_type.cc
intern/delaunay_2d.cc
@@ -180,6 +181,7 @@ set(SRC
BLI_compiler_attrs.h
BLI_compiler_compat.h
BLI_compiler_typecheck.h
BLI_compute_context.hh
BLI_console.h
BLI_convexhull_2d.h
BLI_cpp_type.hh

View File

@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_compute_context.hh"
#include "BLI_hash_md5.h"
namespace blender {
void ComputeContextHash::mix_in(const void *data, int64_t len)
{
DynamicStackBuffer<> buffer_owner(HashSizeInBytes + len, 8);
char *buffer = static_cast<char *>(buffer_owner.buffer());
memcpy(buffer, this, HashSizeInBytes);
memcpy(buffer + HashSizeInBytes, data, len);
BLI_hash_md5_buffer(buffer, HashSizeInBytes + len, this);
}
std::ostream &operator<<(std::ostream &stream, const ComputeContextHash &hash)
{
std::stringstream ss;
ss << "0x" << std::hex << hash.v1 << hash.v2;
stream << ss.str();
return stream;
}
void ComputeContext::print_stack(std::ostream &stream, StringRef name) const
{
Stack<const ComputeContext *> stack;
for (const ComputeContext *current = this; current; current = current->parent_) {
stack.push(current);
}
stream << "Context Stack: " << name << "\n";
while (!stack.is_empty()) {
const ComputeContext *current = stack.pop();
stream << "-> ";
current->print_current_in_line(stream);
const ComputeContextHash &current_hash = current->hash_;
stream << " \t(hash: " << current_hash << ")\n";
}
}
std::ostream &operator<<(std::ostream &stream, const ComputeContext &compute_context)
{
compute_context.print_stack(stream, "");
return stream;
}
} // namespace blender

View File

@@ -26,3 +26,4 @@ BLI_CPP_TYPE_MAKE(ColorGeometry4f, blender::ColorGeometry4f, CPPTypeFlags::Basic
BLI_CPP_TYPE_MAKE(ColorGeometry4b, blender::ColorGeometry4b, CPPTypeFlags::BasicType)
BLI_CPP_TYPE_MAKE(string, std::string, CPPTypeFlags::BasicType)
BLI_CPP_TYPE_MAKE(StringVector, blender::Vector<std::string>, CPPTypeFlags::None)

View File

@@ -1741,7 +1741,14 @@ void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree)
/* Animation, */
build_animdata(&ntree->id);
/* Output update. */
add_operation_node(&ntree->id, NodeType::NTREE_OUTPUT, OperationCode::NTREE_OUTPUT);
ID *id_cow = get_cow_id(&ntree->id);
add_operation_node(&ntree->id,
NodeType::NTREE_OUTPUT,
OperationCode::NTREE_OUTPUT,
[id_cow](::Depsgraph * /*depsgraph*/) {
bNodeTree *ntree_cow = reinterpret_cast<bNodeTree *>(id_cow);
bke::node_tree_runtime::handle_node_tree_output_changed(*ntree_cow);
});
/* nodetree's nodes... */
LISTBASE_FOREACH (bNode *, bnode, &ntree->nodes) {
build_idproperties(bnode->prop);

View File

@@ -13,7 +13,7 @@
#include "UI_resources.h"
namespace blender::nodes::geometry_nodes_eval_log {
namespace blender::nodes::geo_eval_log {
struct GeometryAttributeInfo;
}
@@ -44,12 +44,11 @@ void context_path_add_generic(Vector<ContextPathItem> &path,
void template_breadcrumbs(uiLayout &layout, Span<ContextPathItem> context_path);
void attribute_search_add_items(
StringRefNull str,
bool can_create_attribute,
Span<const nodes::geometry_nodes_eval_log::GeometryAttributeInfo *> infos,
uiSearchItems *items,
bool is_first);
void attribute_search_add_items(StringRefNull str,
bool can_create_attribute,
Span<const nodes::geo_eval_log::GeometryAttributeInfo *> infos,
uiSearchItems *items,
bool is_first);
} // namespace blender::ui

View File

@@ -14,13 +14,15 @@
#include "BLT_translation.h"
#include "NOD_geometry_nodes_eval_log.hh"
#include "BKE_attribute.hh"
#include "NOD_geometry_nodes_log.hh"
#include "UI_interface.h"
#include "UI_interface.hh"
#include "UI_resources.h"
using blender::nodes::geometry_nodes_eval_log::GeometryAttributeInfo;
using blender::nodes::geo_eval_log::GeometryAttributeInfo;
namespace blender::ui {

View File

@@ -13,6 +13,7 @@
#include "DNA_light_types.h"
#include "DNA_linestyle_types.h"
#include "DNA_material_types.h"
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
@@ -29,11 +30,13 @@
#include "BLT_translation.h"
#include "BKE_compute_contexts.hh"
#include "BKE_context.h"
#include "BKE_idtype.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_node.h"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.h"
#include "BKE_object.h"
@@ -65,7 +68,8 @@
#include "RNA_access.h"
#include "RNA_prototypes.h"
#include "NOD_geometry_nodes_eval_log.hh"
#include "NOD_geometry_exec.hh"
#include "NOD_geometry_nodes_log.hh"
#include "NOD_node_declaration.hh"
#include "NOD_socket_declarations_geometry.hh"
@@ -74,10 +78,18 @@
#include "node_intern.hh" /* own include */
namespace geo_log = blender::nodes::geo_eval_log;
using blender::GPointer;
using blender::Vector;
using blender::fn::GField;
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
using geo_log::eNamedAttrUsage;
using geo_log::GeoModifierLog;
using geo_log::GeoNodeLog;
using geo_log::GeoTreeLog;
using geo_log::GeoTreeLogger;
using geo_log::NamedAttributeUsage;
using geo_log::NodeWarning;
using geo_log::NodeWarningType;
extern "C" {
/* XXX interface.h */
@@ -85,6 +97,17 @@ extern void ui_draw_dropshadow(
const rctf *rct, float radius, float aspect, float alpha, int select);
}
/**
* This is passed to many functions which draw the node editor.
*/
struct TreeDrawContext {
/**
* Geometry nodes logs various data during execution. The logged data that corresponds to the
* currently drawn node tree can be retrieved from the log below.
*/
GeoTreeLog *geo_tree_log = nullptr;
};
float ED_node_grid_size()
{
return U.widget_unit;
@@ -157,6 +180,12 @@ void ED_node_tag_update_id(ID *id)
namespace blender::ed::space_node {
static void node_socket_add_tooltip_in_node_editor(TreeDrawContext *UNUSED(tree_draw_ctx),
const bNodeTree *ntree,
const bNode *node,
const bNodeSocket *sock,
uiLayout *layout);
static bool compare_nodes(const bNode *a, const bNode *b)
{
/* These tell if either the node or any of the parent nodes is selected.
@@ -313,7 +342,11 @@ float2 node_from_view(const bNode &node, const float2 &co)
/**
* Based on settings and sockets in node, set drawing rect info.
*/
static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node, uiBlock &block)
static void node_update_basis(const bContext &C,
TreeDrawContext &tree_draw_ctx,
bNodeTree &ntree,
bNode &node,
uiBlock &block)
{
PointerRNA nodeptr;
RNA_pointer_create(&ntree.id, &RNA_Node, &node, &nodeptr);
@@ -374,7 +407,7 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node,
const char *socket_label = nodeSocketLabel(socket);
socket->typeinfo->draw((bContext *)&C, row, &sockptr, &nodeptr, IFACE_(socket_label));
node_socket_add_tooltip(ntree, node, *socket, *row);
node_socket_add_tooltip_in_node_editor(&tree_draw_ctx, &ntree, &node, socket, row);
UI_block_align_end(&block);
UI_block_layout_resolve(&block, nullptr, &buty);
@@ -506,7 +539,7 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node,
const char *socket_label = nodeSocketLabel(socket);
socket->typeinfo->draw((bContext *)&C, row, &sockptr, &nodeptr, IFACE_(socket_label));
node_socket_add_tooltip(ntree, node, *socket, *row);
node_socket_add_tooltip_in_node_editor(&tree_draw_ctx, &ntree, &node, socket, row);
UI_block_align_end(&block);
UI_block_layout_resolve(&block, nullptr, &buty);
@@ -823,25 +856,16 @@ static void create_inspection_string_for_generic_value(const GPointer value, std
}
}
static void create_inspection_string_for_gfield(const geo_log::GFieldValueLog &value_log,
std::stringstream &ss)
static void create_inspection_string_for_field_info(const geo_log::FieldInfoLog &value_log,
std::stringstream &ss)
{
const CPPType &type = value_log.type();
const GField &field = value_log.field();
const Span<std::string> input_tooltips = value_log.input_tooltips();
const CPPType &type = value_log.type;
const Span<std::string> input_tooltips = value_log.input_tooltips;
if (input_tooltips.is_empty()) {
if (field) {
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
blender::fn::evaluate_constant_field(field, buffer);
create_inspection_string_for_generic_value({type, buffer}, ss);
type.destruct(buffer);
}
else {
/* Constant values should always be logged. */
BLI_assert_unreachable();
ss << "Value has not been logged";
}
/* Should have been logged as constant value. */
BLI_assert_unreachable();
ss << "Value has not been logged";
}
else {
if (type.is<int>()) {
@@ -874,11 +898,11 @@ static void create_inspection_string_for_gfield(const geo_log::GFieldValueLog &v
}
}
static void create_inspection_string_for_geometry(const geo_log::GeometryValueLog &value_log,
std::stringstream &ss,
const nodes::decl::Geometry *geometry)
static void create_inspection_string_for_geometry_info(const geo_log::GeometryInfoLog &value_log,
std::stringstream &ss,
const nodes::decl::Geometry *socket_decl)
{
Span<GeometryComponentType> component_types = value_log.component_types();
Span<GeometryComponentType> component_types = value_log.component_types;
if (component_types.is_empty()) {
ss << TIP_("Empty Geometry");
return;
@@ -895,7 +919,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
const char *line_end = (type == component_types.last()) ? "" : ".\n";
switch (type) {
case GEO_COMPONENT_TYPE_MESH: {
const geo_log::GeometryValueLog::MeshInfo &mesh_info = *value_log.mesh_info;
const geo_log::GeometryInfoLog::MeshInfo &mesh_info = *value_log.mesh_info;
char line[256];
BLI_snprintf(line,
sizeof(line),
@@ -907,7 +931,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
break;
}
case GEO_COMPONENT_TYPE_POINT_CLOUD: {
const geo_log::GeometryValueLog::PointCloudInfo &pointcloud_info =
const geo_log::GeometryInfoLog::PointCloudInfo &pointcloud_info =
*value_log.pointcloud_info;
char line[256];
BLI_snprintf(line,
@@ -918,7 +942,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
break;
}
case GEO_COMPONENT_TYPE_CURVE: {
const geo_log::GeometryValueLog::CurveInfo &curve_info = *value_log.curve_info;
const geo_log::GeometryInfoLog::CurveInfo &curve_info = *value_log.curve_info;
char line[256];
BLI_snprintf(line,
sizeof(line),
@@ -928,7 +952,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
break;
}
case GEO_COMPONENT_TYPE_INSTANCES: {
const geo_log::GeometryValueLog::InstancesInfo &instances_info = *value_log.instances_info;
const geo_log::GeometryInfoLog::InstancesInfo &instances_info = *value_log.instances_info;
char line[256];
BLI_snprintf(line,
sizeof(line),
@@ -943,7 +967,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
}
case GEO_COMPONENT_TYPE_EDIT: {
if (value_log.edit_data_info.has_value()) {
const geo_log::GeometryValueLog::EditDataInfo &edit_info = *value_log.edit_data_info;
const geo_log::GeometryInfoLog::EditDataInfo &edit_info = *value_log.edit_data_info;
char line[256];
BLI_snprintf(line,
sizeof(line),
@@ -959,11 +983,11 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
/* If the geometry declaration is null, as is the case for input to group output,
* or it is an output socket don't show supported types. */
if (geometry == nullptr || geometry->in_out() == SOCK_OUT) {
if (socket_decl == nullptr || socket_decl->in_out() == SOCK_OUT) {
return;
}
Span<GeometryComponentType> supported_types = geometry->supported_types();
Span<GeometryComponentType> supported_types = socket_decl->supported_types();
if (supported_types.is_empty()) {
ss << ".\n\n" << TIP_("Supported: All Types");
return;
@@ -1000,118 +1024,130 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
}
}
static std::optional<std::string> create_socket_inspection_string(const bContext &C,
const bNode &node,
static std::optional<std::string> create_socket_inspection_string(TreeDrawContext &tree_draw_ctx,
const bNodeSocket &socket)
{
const SpaceNode *snode = CTX_wm_space_node(&C);
if (snode == nullptr) {
return {};
};
const geo_log::SocketLog *socket_log = geo_log::ModifierLog::find_socket_by_node_editor_context(
*snode, node, socket);
if (socket_log == nullptr) {
return {};
}
const geo_log::ValueLog *value_log = socket_log->value();
using namespace blender::nodes::geo_eval_log;
tree_draw_ctx.geo_tree_log->ensure_socket_values();
ValueLog *value_log = tree_draw_ctx.geo_tree_log->find_socket_value_log(socket);
if (value_log == nullptr) {
return {};
return std::nullopt;
}
std::stringstream ss;
if (const geo_log::GenericValueLog *generic_value_log =
dynamic_cast<const geo_log::GenericValueLog *>(value_log)) {
create_inspection_string_for_generic_value(generic_value_log->value(), ss);
create_inspection_string_for_generic_value(generic_value_log->value, ss);
}
if (const geo_log::GFieldValueLog *gfield_value_log =
dynamic_cast<const geo_log::GFieldValueLog *>(value_log)) {
create_inspection_string_for_gfield(*gfield_value_log, ss);
else if (const geo_log::FieldInfoLog *gfield_value_log =
dynamic_cast<const geo_log::FieldInfoLog *>(value_log)) {
create_inspection_string_for_field_info(*gfield_value_log, ss);
}
else if (const geo_log::GeometryValueLog *geo_value_log =
dynamic_cast<const geo_log::GeometryValueLog *>(value_log)) {
create_inspection_string_for_geometry(
else if (const geo_log::GeometryInfoLog *geo_value_log =
dynamic_cast<const geo_log::GeometryInfoLog *>(value_log)) {
create_inspection_string_for_geometry_info(
*geo_value_log,
ss,
dynamic_cast<const nodes::decl::Geometry *>(socket.runtime->declaration));
}
return ss.str();
std::string str = ss.str();
if (str.empty()) {
return std::nullopt;
}
return str;
}
static bool node_socket_has_tooltip(const bNodeTree &ntree, const bNodeSocket &socket)
static bool node_socket_has_tooltip(const bNodeTree *ntree, const bNodeSocket *socket)
{
if (ntree.type == NTREE_GEOMETRY) {
if (ntree->type == NTREE_GEOMETRY) {
return true;
}
if (socket.runtime->declaration != nullptr) {
const blender::nodes::SocketDeclaration &socket_decl = *socket.runtime->declaration;
if (socket->runtime->declaration != nullptr) {
const nodes::SocketDeclaration &socket_decl = *socket->runtime->declaration;
return !socket_decl.description().is_empty();
}
return false;
}
static char *node_socket_get_tooltip(const bContext &C,
const bNodeTree &ntree,
const bNode &node,
const bNodeSocket &socket)
static char *node_socket_get_tooltip(const bContext *C,
const bNodeTree *ntree,
const bNode *UNUSED(node),
const bNodeSocket *socket)
{
SpaceNode *snode = CTX_wm_space_node(C);
TreeDrawContext tree_draw_ctx;
if (snode != nullptr) {
if (ntree->type == NTREE_GEOMETRY) {
tree_draw_ctx.geo_tree_log =
nodes::geo_eval_log::GeoModifierLog::get_tree_log_for_node_editor(*snode);
}
}
std::stringstream output;
if (socket.runtime->declaration != nullptr) {
const blender::nodes::SocketDeclaration &socket_decl = *socket.runtime->declaration;
if (socket->runtime->declaration != nullptr) {
const blender::nodes::SocketDeclaration &socket_decl = *socket->runtime->declaration;
blender::StringRef description = socket_decl.description();
if (!description.is_empty()) {
output << TIP_(description.data());
}
}
if (ntree.type == NTREE_GEOMETRY) {
if (ntree->type == NTREE_GEOMETRY && tree_draw_ctx.geo_tree_log != nullptr) {
if (!output.str().empty()) {
output << ".\n\n";
}
std::optional<std::string> socket_inspection_str = create_socket_inspection_string(
C, node, socket);
tree_draw_ctx, *socket);
if (socket_inspection_str.has_value()) {
output << *socket_inspection_str;
}
else {
output << TIP_("The socket value has not been computed yet");
output << TIP_("Unknown socket value");
}
}
if (output.str().empty()) {
output << nodeSocketLabel(&socket);
output << nodeSocketLabel(socket);
}
return BLI_strdup(output.str().c_str());
}
static void node_socket_add_tooltip_in_node_editor(TreeDrawContext *UNUSED(tree_draw_ctx),
const bNodeTree *ntree,
const bNode *node,
const bNodeSocket *sock,
uiLayout *layout)
{
if (!node_socket_has_tooltip(ntree, sock)) {
return;
}
SocketTooltipData *data = MEM_cnew<SocketTooltipData>(__func__);
data->ntree = ntree;
data->node = node;
data->socket = sock;
uiLayoutSetTooltipFunc(
layout,
[](bContext *C, void *argN, const char *UNUSED(tip)) {
SocketTooltipData *data = static_cast<SocketTooltipData *>(argN);
return node_socket_get_tooltip(C, data->ntree, data->node, data->socket);
},
data,
MEM_dupallocN,
MEM_freeN);
}
void node_socket_add_tooltip(const bNodeTree &ntree,
const bNode &node,
const bNodeSocket &sock,
uiLayout &layout)
{
if (!node_socket_has_tooltip(ntree, sock)) {
return;
}
SocketTooltipData *data = MEM_new<SocketTooltipData>(__func__);
data->ntree = &ntree;
data->node = &node;
data->socket = &sock;
uiLayoutSetTooltipFunc(
&layout,
[](bContext *C, void *argN, const char *UNUSED(tip)) {
const SocketTooltipData *data = static_cast<SocketTooltipData *>(argN);
return node_socket_get_tooltip(*C, *data->ntree, *data->node, *data->socket);
},
data,
MEM_dupallocN,
MEM_freeN);
node_socket_add_tooltip_in_node_editor(nullptr, &ntree, &node, &sock, &layout);
}
static void node_socket_draw_nested(const bContext &C,
@@ -1146,7 +1182,7 @@ static void node_socket_draw_nested(const bContext &C,
size_id,
outline_col_id);
if (!node_socket_has_tooltip(ntree, sock)) {
if (!node_socket_has_tooltip(&ntree, &sock)) {
return;
}
@@ -1178,7 +1214,7 @@ static void node_socket_draw_nested(const bContext &C,
but,
[](bContext *C, void *argN, const char *UNUSED(tip)) {
SocketTooltipData *data = (SocketTooltipData *)argN;
return node_socket_get_tooltip(*C, *data->ntree, *data->node, *data->socket);
return node_socket_get_tooltip(C, data->ntree, data->node, data->socket);
},
data,
MEM_freeN);
@@ -1537,14 +1573,14 @@ static void node_draw_sockets(const View2D &v2d,
}
}
static int node_error_type_to_icon(const geo_log::NodeWarningType type)
static int node_error_type_to_icon(const NodeWarningType type)
{
switch (type) {
case geo_log::NodeWarningType::Error:
case NodeWarningType::Error:
return ICON_ERROR;
case geo_log::NodeWarningType::Warning:
case NodeWarningType::Warning:
return ICON_ERROR;
case geo_log::NodeWarningType::Info:
case NodeWarningType::Info:
return ICON_INFO;
}
@@ -1552,14 +1588,14 @@ static int node_error_type_to_icon(const geo_log::NodeWarningType type)
return ICON_ERROR;
}
static uint8_t node_error_type_priority(const geo_log::NodeWarningType type)
static uint8_t node_error_type_priority(const NodeWarningType type)
{
switch (type) {
case geo_log::NodeWarningType::Error:
case NodeWarningType::Error:
return 3;
case geo_log::NodeWarningType::Warning:
case NodeWarningType::Warning:
return 2;
case geo_log::NodeWarningType::Info:
case NodeWarningType::Info:
return 1;
}
@@ -1567,11 +1603,11 @@ static uint8_t node_error_type_priority(const geo_log::NodeWarningType type)
return 0;
}
static geo_log::NodeWarningType node_error_highest_priority(Span<geo_log::NodeWarning> warnings)
static NodeWarningType node_error_highest_priority(Span<NodeWarning> warnings)
{
uint8_t highest_priority = 0;
geo_log::NodeWarningType highest_priority_type = geo_log::NodeWarningType::Info;
for (const geo_log::NodeWarning &warning : warnings) {
NodeWarningType highest_priority_type = NodeWarningType::Info;
for (const NodeWarning &warning : warnings) {
const uint8_t priority = node_error_type_priority(warning.type);
if (priority > highest_priority) {
highest_priority = priority;
@@ -1582,7 +1618,7 @@ static geo_log::NodeWarningType node_error_highest_priority(Span<geo_log::NodeWa
}
struct NodeErrorsTooltipData {
Span<geo_log::NodeWarning> warnings;
Span<NodeWarning> warnings;
};
static char *node_errors_tooltip_fn(bContext *UNUSED(C), void *argN, const char *UNUSED(tip))
@@ -1591,7 +1627,7 @@ static char *node_errors_tooltip_fn(bContext *UNUSED(C), void *argN, const char
std::string complete_string;
for (const geo_log::NodeWarning &warning : data.warnings.drop_back(1)) {
for (const NodeWarning &warning : data.warnings.drop_back(1)) {
complete_string += warning.message;
/* Adding the period is not ideal for multi-line messages, but it is consistent
* with other tooltip implementations in Blender, so it is added here. */
@@ -1607,28 +1643,27 @@ static char *node_errors_tooltip_fn(bContext *UNUSED(C), void *argN, const char
#define NODE_HEADER_ICON_SIZE (0.8f * U.widget_unit)
static void node_add_error_message_button(
const bContext &C, bNode &node, uiBlock &block, const rctf &rect, float &icon_offset)
static void node_add_error_message_button(TreeDrawContext &tree_draw_ctx,
bNode &node,
uiBlock &block,
const rctf &rect,
float &icon_offset)
{
SpaceNode *snode = CTX_wm_space_node(&C);
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(*snode,
node);
if (node_log == nullptr) {
return;
Span<NodeWarning> warnings;
if (tree_draw_ctx.geo_tree_log) {
GeoNodeLog *node_log = tree_draw_ctx.geo_tree_log->nodes.lookup_ptr(node.name);
if (node_log != nullptr) {
warnings = node_log->warnings;
}
}
Span<geo_log::NodeWarning> warnings = node_log->warnings();
if (warnings.is_empty()) {
return;
}
NodeErrorsTooltipData *tooltip_data = (NodeErrorsTooltipData *)MEM_mallocN(
sizeof(NodeErrorsTooltipData), __func__);
const NodeWarningType display_type = node_error_highest_priority(warnings);
NodeErrorsTooltipData *tooltip_data = MEM_new<NodeErrorsTooltipData>(__func__);
tooltip_data->warnings = warnings;
const geo_log::NodeWarningType display_type = node_error_highest_priority(warnings);
icon_offset -= NODE_HEADER_ICON_SIZE;
UI_block_emboss_set(&block, UI_EMBOSS_NONE);
uiBut *but = uiDefIconBut(&block,
@@ -1645,90 +1680,70 @@ static void node_add_error_message_button(
0,
0,
nullptr);
UI_but_func_tooltip_set(but, node_errors_tooltip_fn, tooltip_data, MEM_freeN);
UI_but_func_tooltip_set(but, node_errors_tooltip_fn, tooltip_data, [](void *arg) {
MEM_delete(static_cast<NodeErrorsTooltipData *>(arg));
});
UI_block_emboss_set(&block, UI_EMBOSS);
}
static void get_exec_time_other_nodes(const bNode &node,
const SpaceNode &snode,
std::chrono::microseconds &exec_time,
int &node_count)
static std::optional<std::chrono::nanoseconds> node_get_execution_time(
TreeDrawContext &tree_draw_ctx, const bNodeTree &ntree, const bNode &node)
{
if (node.type == NODE_GROUP) {
const geo_log::TreeLog *root_tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context(
snode);
if (root_tree_log == nullptr) {
return;
}
const geo_log::TreeLog *tree_log = root_tree_log->lookup_child_log(node.name);
if (tree_log == nullptr) {
return;
}
tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) {
exec_time += node_log.execution_time();
node_count++;
});
const GeoTreeLog *tree_log = tree_draw_ctx.geo_tree_log;
if (tree_log == nullptr) {
return std::nullopt;
}
else {
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(
snode, node);
if (node_log) {
exec_time += node_log->execution_time();
node_count++;
}
}
}
static std::chrono::microseconds node_get_execution_time(const bNodeTree &ntree,
const bNode &node,
const SpaceNode &snode,
int &node_count)
{
std::chrono::microseconds exec_time = std::chrono::microseconds::zero();
if (node.type == NODE_GROUP_OUTPUT) {
const geo_log::TreeLog *tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context(
snode);
if (tree_log == nullptr) {
return exec_time;
}
tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) {
exec_time += node_log.execution_time();
node_count++;
});
return tree_log->run_time_sum;
}
else if (node.type == NODE_FRAME) {
if (node.type == NODE_FRAME) {
/* Could be cached in the future if this recursive code turns out to be slow. */
std::chrono::nanoseconds run_time{0};
bool found_node = false;
LISTBASE_FOREACH (bNode *, tnode, &ntree.nodes) {
if (tnode->parent != &node) {
continue;
}
if (tnode->type == NODE_FRAME) {
exec_time += node_get_execution_time(ntree, *tnode, snode, node_count);
std::optional<std::chrono::nanoseconds> sub_frame_run_time = node_get_execution_time(
tree_draw_ctx, ntree, *tnode);
if (sub_frame_run_time.has_value()) {
run_time += *sub_frame_run_time;
found_node = true;
}
}
else {
get_exec_time_other_nodes(*tnode, snode, exec_time, node_count);
if (const GeoNodeLog *node_log = tree_log->nodes.lookup_ptr_as(tnode->name)) {
found_node = true;
run_time += node_log->run_time;
}
}
}
if (found_node) {
return run_time;
}
return std::nullopt;
}
else {
get_exec_time_other_nodes(node, snode, exec_time, node_count);
if (const GeoNodeLog *node_log = tree_log->nodes.lookup_ptr(node.name)) {
return node_log->run_time;
}
return exec_time;
return std::nullopt;
}
static std::string node_get_execution_time_label(const SpaceNode &snode, const bNode &node)
static std::string node_get_execution_time_label(TreeDrawContext &tree_draw_ctx,
const SpaceNode &snode,
const bNode &node)
{
int node_count = 0;
std::chrono::microseconds exec_time = node_get_execution_time(
*snode.edittree, node, snode, node_count);
const std::optional<std::chrono::nanoseconds> exec_time = node_get_execution_time(
tree_draw_ctx, *snode.edittree, node);
if (node_count == 0) {
if (!exec_time.has_value()) {
return std::string("");
}
uint64_t exec_time_us = exec_time.count();
const uint64_t exec_time_us =
std::chrono::duration_cast<std::chrono::microseconds>(*exec_time).count();
/* Don't show time if execution time is 0 microseconds. */
if (exec_time_us == 0) {
@@ -1763,7 +1778,7 @@ struct NodeExtraInfoRow {
};
struct NamedAttributeTooltipArg {
Map<std::string, eNamedAttrUsage> usage_by_attribute;
Map<std::string, NamedAttributeUsage> usage_by_attribute;
};
static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char *UNUSED(tip))
@@ -1775,7 +1790,7 @@ static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char
struct NameWithUsage {
StringRefNull name;
eNamedAttrUsage usage;
NamedAttributeUsage usage;
};
Vector<NameWithUsage> sorted_used_attribute;
@@ -1790,16 +1805,16 @@ static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char
for (const NameWithUsage &attribute : sorted_used_attribute) {
const StringRefNull name = attribute.name;
const eNamedAttrUsage usage = attribute.usage;
const NamedAttributeUsage usage = attribute.usage;
ss << " \u2022 \"" << name << "\": ";
Vector<std::string> usages;
if ((usage & eNamedAttrUsage::Read) != eNamedAttrUsage::None) {
if ((usage & NamedAttributeUsage::Read) != NamedAttributeUsage::None) {
usages.append(TIP_("read"));
}
if ((usage & eNamedAttrUsage::Write) != eNamedAttrUsage::None) {
if ((usage & NamedAttributeUsage::Write) != NamedAttributeUsage::None) {
usages.append(TIP_("write"));
}
if ((usage & eNamedAttrUsage::Remove) != eNamedAttrUsage::None) {
if ((usage & NamedAttributeUsage::Remove) != NamedAttributeUsage::None) {
usages.append(TIP_("remove"));
}
for (const int i : usages.index_range()) {
@@ -1817,7 +1832,7 @@ static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char
}
static NodeExtraInfoRow row_from_used_named_attribute(
const Map<std::string, eNamedAttrUsage> &usage_by_attribute_name)
const Map<std::string, NamedAttributeUsage> &usage_by_attribute_name)
{
const int attributes_num = usage_by_attribute_name.size();
@@ -1831,32 +1846,11 @@ static NodeExtraInfoRow row_from_used_named_attribute(
return row;
}
static std::optional<NodeExtraInfoRow> node_get_accessed_attributes_row(const SpaceNode &snode,
const bNode &node)
static std::optional<NodeExtraInfoRow> node_get_accessed_attributes_row(
TreeDrawContext &tree_draw_ctx, const bNode &node)
{
if (node.type == NODE_GROUP) {
const geo_log::TreeLog *root_tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context(
snode);
if (root_tree_log == nullptr) {
return std::nullopt;
}
const geo_log::TreeLog *tree_log = root_tree_log->lookup_child_log(node.name);
if (tree_log == nullptr) {
return std::nullopt;
}
Map<std::string, eNamedAttrUsage> usage_by_attribute;
tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) {
for (const geo_log::UsedNamedAttribute &used_attribute : node_log.used_named_attributes()) {
usage_by_attribute.lookup_or_add_as(used_attribute.name,
used_attribute.usage) |= used_attribute.usage;
}
});
if (usage_by_attribute.is_empty()) {
return std::nullopt;
}
return row_from_used_named_attribute(usage_by_attribute);
if (tree_draw_ctx.geo_tree_log == nullptr) {
return std::nullopt;
}
if (ELEM(node.type,
GEO_NODE_STORE_NAMED_ATTRIBUTE,
@@ -1865,31 +1859,26 @@ static std::optional<NodeExtraInfoRow> node_get_accessed_attributes_row(const Sp
/* Only show the overlay when the name is passed in from somewhere else. */
LISTBASE_FOREACH (bNodeSocket *, socket, &node.inputs) {
if (STREQ(socket->name, "Name")) {
if ((socket->flag & SOCK_IN_USE) == 0) {
if (!socket->is_directly_linked()) {
return std::nullopt;
}
}
}
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(
snode, node.name);
if (node_log == nullptr) {
return std::nullopt;
}
Map<std::string, eNamedAttrUsage> usage_by_attribute;
for (const geo_log::UsedNamedAttribute &used_attribute : node_log->used_named_attributes()) {
usage_by_attribute.lookup_or_add_as(used_attribute.name,
used_attribute.usage) |= used_attribute.usage;
}
if (usage_by_attribute.is_empty()) {
return std::nullopt;
}
return row_from_used_named_attribute(usage_by_attribute);
}
return std::nullopt;
tree_draw_ctx.geo_tree_log->ensure_used_named_attributes();
GeoNodeLog *node_log = tree_draw_ctx.geo_tree_log->nodes.lookup_ptr(node.name);
if (node_log == nullptr) {
return std::nullopt;
}
if (node_log->used_named_attributes.is_empty()) {
return std::nullopt;
}
return row_from_used_named_attribute(node_log->used_named_attributes);
}
static Vector<NodeExtraInfoRow> node_get_extra_info(const SpaceNode &snode, const bNode &node)
static Vector<NodeExtraInfoRow> node_get_extra_info(TreeDrawContext &tree_draw_ctx,
const SpaceNode &snode,
const bNode &node)
{
Vector<NodeExtraInfoRow> rows;
if (!(snode.overlay.flag & SN_OVERLAY_SHOW_OVERLAYS)) {
@@ -1898,7 +1887,8 @@ static Vector<NodeExtraInfoRow> node_get_extra_info(const SpaceNode &snode, cons
if (snode.overlay.flag & SN_OVERLAY_SHOW_NAMED_ATTRIBUTES &&
snode.edittree->type == NTREE_GEOMETRY) {
if (std::optional<NodeExtraInfoRow> row = node_get_accessed_attributes_row(snode, node)) {
if (std::optional<NodeExtraInfoRow> row = node_get_accessed_attributes_row(tree_draw_ctx,
node)) {
rows.append(std::move(*row));
}
}
@@ -1907,7 +1897,7 @@ static Vector<NodeExtraInfoRow> node_get_extra_info(const SpaceNode &snode, cons
(ELEM(node.typeinfo->nclass, NODE_CLASS_GEOMETRY, NODE_CLASS_GROUP, NODE_CLASS_ATTRIBUTE) ||
ELEM(node.type, NODE_FRAME, NODE_GROUP_OUTPUT))) {
NodeExtraInfoRow row;
row.text = node_get_execution_time_label(snode, node);
row.text = node_get_execution_time_label(tree_draw_ctx, snode, node);
if (!row.text.empty()) {
row.tooltip = TIP_(
"The execution time from the node tree's latest evaluation. For frame and group nodes, "
@@ -1916,16 +1906,6 @@ static Vector<NodeExtraInfoRow> node_get_extra_info(const SpaceNode &snode, cons
rows.append(std::move(row));
}
}
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(snode,
node);
if (node_log != nullptr) {
for (const std::string &message : node_log->debug_messages()) {
NodeExtraInfoRow row;
row.text = message;
row.icon = ICON_INFO;
rows.append(std::move(row));
}
}
return rows;
}
@@ -1988,9 +1968,12 @@ static void node_draw_extra_info_row(const bNode &node,
}
}
static void node_draw_extra_info_panel(const SpaceNode &snode, const bNode &node, uiBlock &block)
static void node_draw_extra_info_panel(TreeDrawContext &tree_draw_ctx,
const SpaceNode &snode,
const bNode &node,
uiBlock &block)
{
Vector<NodeExtraInfoRow> extra_info_rows = node_get_extra_info(snode, node);
Vector<NodeExtraInfoRow> extra_info_rows = node_get_extra_info(tree_draw_ctx, snode, node);
if (extra_info_rows.size() == 0) {
return;
@@ -2046,6 +2029,7 @@ static void node_draw_extra_info_panel(const SpaceNode &snode, const bNode &node
}
static void node_draw_basis(const bContext &C,
TreeDrawContext &tree_draw_ctx,
const View2D &v2d,
const SpaceNode &snode,
bNodeTree &ntree,
@@ -2070,7 +2054,7 @@ static void node_draw_basis(const bContext &C,
GPU_line_width(1.0f);
node_draw_extra_info_panel(snode, node, block);
node_draw_extra_info_panel(tree_draw_ctx, snode, node, block);
/* Header. */
{
@@ -2165,7 +2149,7 @@ static void node_draw_basis(const bContext &C,
UI_block_emboss_set(&block, UI_EMBOSS);
}
node_add_error_message_button(C, node, block, rct, iconofs);
node_add_error_message_button(tree_draw_ctx, node, block, rct, iconofs);
/* Title. */
if (node.flag & SELECT) {
@@ -2338,6 +2322,7 @@ static void node_draw_basis(const bContext &C,
}
static void node_draw_hidden(const bContext &C,
TreeDrawContext &tree_draw_ctx,
const View2D &v2d,
const SpaceNode &snode,
bNodeTree &ntree,
@@ -2353,7 +2338,7 @@ static void node_draw_hidden(const bContext &C,
const int color_id = node_get_colorid(node);
node_draw_extra_info_panel(snode, node, block);
node_draw_extra_info_panel(tree_draw_ctx, snode, node, block);
/* Shadow. */
node_draw_shadow(snode, node, hiddenrad, 1.0f);
@@ -2668,6 +2653,7 @@ static void reroute_node_prepare_for_draw(bNode &node)
}
static void node_update_nodetree(const bContext &C,
TreeDrawContext &tree_draw_ctx,
bNodeTree &ntree,
Span<bNode *> nodes,
Span<uiBlock *> blocks)
@@ -2694,7 +2680,7 @@ static void node_update_nodetree(const bContext &C,
node_update_hidden(node, block);
}
else {
node_update_basis(C, ntree, node, block);
node_update_basis(C, tree_draw_ctx, ntree, node, block);
}
}
}
@@ -2795,6 +2781,7 @@ static void frame_node_draw_label(const bNodeTree &ntree,
}
static void frame_node_draw(const bContext &C,
TreeDrawContext &tree_draw_ctx,
const ARegion &region,
const SpaceNode &snode,
bNodeTree &ntree,
@@ -2841,7 +2828,7 @@ static void frame_node_draw(const bContext &C,
/* label and text */
frame_node_draw_label(ntree, node, snode);
node_draw_extra_info_panel(snode, node, block);
node_draw_extra_info_panel(tree_draw_ctx, snode, node, block);
UI_block_end(&C, &block);
UI_block_draw(&C, &block);
@@ -2895,6 +2882,7 @@ static void reroute_node_draw(
}
static void node_draw(const bContext &C,
TreeDrawContext &tree_draw_ctx,
ARegion &region,
const SpaceNode &snode,
bNodeTree &ntree,
@@ -2903,7 +2891,7 @@ static void node_draw(const bContext &C,
bNodeInstanceKey key)
{
if (node.type == NODE_FRAME) {
frame_node_draw(C, region, snode, ntree, node, block);
frame_node_draw(C, tree_draw_ctx, region, snode, ntree, node, block);
}
else if (node.type == NODE_REROUTE) {
reroute_node_draw(C, region, ntree, node, block);
@@ -2911,10 +2899,10 @@ static void node_draw(const bContext &C,
else {
const View2D &v2d = region.v2d;
if (node.flag & NODE_HIDDEN) {
node_draw_hidden(C, v2d, snode, ntree, node, block);
node_draw_hidden(C, tree_draw_ctx, v2d, snode, ntree, node, block);
}
else {
node_draw_basis(C, v2d, snode, ntree, node, block, key);
node_draw_basis(C, tree_draw_ctx, v2d, snode, ntree, node, block, key);
}
}
}
@@ -2922,6 +2910,7 @@ static void node_draw(const bContext &C,
#define USE_DRAW_TOT_UPDATE
static void node_draw_nodetree(const bContext &C,
TreeDrawContext &tree_draw_ctx,
ARegion &region,
SpaceNode &snode,
bNodeTree &ntree,
@@ -2946,7 +2935,7 @@ static void node_draw_nodetree(const bContext &C,
}
bNodeInstanceKey key = BKE_node_instance_key(parent_key, &ntree, nodes[i]);
node_draw(C, region, snode, ntree, *nodes[i], *blocks[i], key);
node_draw(C, tree_draw_ctx, region, snode, ntree, *nodes[i], *blocks[i], key);
}
/* Node lines. */
@@ -2976,7 +2965,7 @@ static void node_draw_nodetree(const bContext &C,
}
bNodeInstanceKey key = BKE_node_instance_key(parent_key, &ntree, nodes[i]);
node_draw(C, region, snode, ntree, *nodes[i], *blocks[i], key);
node_draw(C, tree_draw_ctx, region, snode, ntree, *nodes[i], *blocks[i], key);
}
}
@@ -3035,8 +3024,18 @@ static void draw_nodetree(const bContext &C,
Array<uiBlock *> blocks = node_uiblocks_init(C, nodes);
node_update_nodetree(C, ntree, nodes, blocks);
node_draw_nodetree(C, region, *snode, ntree, nodes, blocks, parent_key);
TreeDrawContext tree_draw_ctx;
if (ntree.type == NTREE_GEOMETRY) {
tree_draw_ctx.geo_tree_log = nodes::geo_eval_log::GeoModifierLog::get_tree_log_for_node_editor(
*snode);
if (tree_draw_ctx.geo_tree_log != nullptr) {
tree_draw_ctx.geo_tree_log->ensure_node_warnings();
tree_draw_ctx.geo_tree_log->ensure_node_run_time();
}
}
node_update_nodetree(C, tree_draw_ctx, ntree, nodes, blocks);
node_draw_nodetree(C, tree_draw_ctx, region, *snode, ntree, nodes, blocks, parent_key);
}
/**

View File

@@ -14,6 +14,7 @@
#include "DNA_space_types.h"
#include "BKE_context.h"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.h"
#include "BKE_object.h"
@@ -30,12 +31,11 @@
#include "UI_interface.hh"
#include "UI_resources.h"
#include "NOD_geometry_nodes_eval_log.hh"
#include "NOD_geometry_nodes_log.hh"
#include "node_intern.hh"
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
using geo_log::GeometryAttributeInfo;
using blender::nodes::geo_eval_log::GeometryAttributeInfo;
namespace blender::ed::space_node {
@@ -50,6 +50,8 @@ BLI_STATIC_ASSERT(std::is_trivially_destructible_v<AttributeSearchData>, "");
static Vector<const GeometryAttributeInfo *> get_attribute_info_from_context(
const bContext &C, AttributeSearchData &data)
{
using namespace nodes::geo_eval_log;
SpaceNode *snode = CTX_wm_space_node(&C);
if (!snode) {
BLI_assert_unreachable();
@@ -65,41 +67,48 @@ static Vector<const GeometryAttributeInfo *> get_attribute_info_from_context(
BLI_assert_unreachable();
return {};
}
GeoTreeLog *tree_log = GeoModifierLog::get_tree_log_for_node_editor(*snode);
if (tree_log == nullptr) {
return {};
}
tree_log->ensure_socket_values();
/* For the attribute input node, collect attribute information from all nodes in the group. */
if (node->type == GEO_NODE_INPUT_NAMED_ATTRIBUTE) {
const geo_log::TreeLog *tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context(
*snode);
if (tree_log == nullptr) {
return {};
}
tree_log->ensure_existing_attributes();
Vector<const GeometryAttributeInfo *> attributes;
Set<StringRef> names;
tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) {
for (const geo_log::SocketLog &socket_log : node_log.input_logs()) {
const geo_log::ValueLog *value_log = socket_log.value();
if (const geo_log::GeometryValueLog *geo_value_log =
dynamic_cast<const geo_log::GeometryValueLog *>(value_log)) {
for (const GeometryAttributeInfo &attribute : geo_value_log->attributes()) {
if (bke::allow_procedural_attribute_access(attribute.name)) {
if (names.add(attribute.name)) {
attributes.append(&attribute);
}
}
}
}
for (const GeometryAttributeInfo *attribute : tree_log->existing_attributes) {
if (bke::allow_procedural_attribute_access(attribute->name)) {
attributes.append(attribute);
}
});
}
return attributes;
}
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(
*snode, data.node_name);
GeoNodeLog *node_log = tree_log->nodes.lookup_ptr(node->name);
if (node_log == nullptr) {
return {};
}
return node_log->lookup_available_attributes();
Set<StringRef> names;
Vector<const GeometryAttributeInfo *> attributes;
for (const bNodeSocket *input_socket : node->input_sockets()) {
if (input_socket->type != SOCK_GEOMETRY) {
continue;
}
const ValueLog *value_log = tree_log->find_socket_value_log(*input_socket);
if (value_log == nullptr) {
continue;
}
if (const GeometryInfoLog *geo_log = dynamic_cast<const GeometryInfoLog *>(value_log)) {
for (const GeometryAttributeInfo &attribute : geo_log->attributes) {
if (bke::allow_procedural_attribute_access(attribute.name)) {
if (names.add(attribute.name)) {
attributes.append(&attribute);
}
}
}
}
}
return attributes;
}
static void attribute_search_update_fn(

View File

@@ -4,6 +4,7 @@
#include "BLI_virtual_array.hh"
#include "BKE_attribute.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_context.h"
#include "BKE_curves.hh"
#include "BKE_editmesh.h"
@@ -26,7 +27,8 @@
#include "ED_curves_sculpt.h"
#include "ED_spreadsheet.h"
#include "NOD_geometry_nodes_eval_log.hh"
#include "NOD_geometry_nodes_log.hh"
#include "NOD_geometry_nodes_to_lazy_function_graph.hh"
#include "BLT_translation.h"
@@ -40,8 +42,8 @@
#include "spreadsheet_data_source_geometry.hh"
#include "spreadsheet_intern.hh"
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
using blender::fn::GField;
using blender::nodes::geo_eval_log::ViewerNodeLog;
namespace blender::ed::spreadsheet {
@@ -465,19 +467,10 @@ GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspread
}
}
else {
const geo_log::NodeLog *node_log =
geo_log::ModifierLog::find_node_by_spreadsheet_editor_context(*sspreadsheet);
if (node_log != nullptr) {
for (const geo_log::SocketLog &input_log : node_log->input_logs()) {
if (const geo_log::GeometryValueLog *geo_value_log =
dynamic_cast<const geo_log::GeometryValueLog *>(input_log.value())) {
const GeometrySet *full_geometry = geo_value_log->full_geometry();
if (full_geometry != nullptr) {
geometry_set = *full_geometry;
break;
}
}
}
if (const ViewerNodeLog *viewer_log =
nodes::geo_eval_log::GeoModifierLog::find_viewer_node_log_for_spreadsheet(
*sspreadsheet)) {
geometry_set = viewer_log->geometry;
}
}
}
@@ -495,27 +488,11 @@ static void find_fields_to_evaluate(const SpaceSpreadsheet *sspreadsheet,
/* No viewer is currently referenced by the context path. */
return;
}
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_spreadsheet_editor_context(
*sspreadsheet);
if (node_log == nullptr) {
return;
}
for (const geo_log::SocketLog &socket_log : node_log->input_logs()) {
const geo_log::ValueLog *value_log = socket_log.value();
if (value_log == nullptr) {
continue;
}
if (const geo_log::GFieldValueLog *field_value_log =
dynamic_cast<const geo_log::GFieldValueLog *>(value_log)) {
const GField &field = field_value_log->field();
if (field) {
r_fields.add("Viewer", std::move(field));
}
}
if (const geo_log::GenericValueLog *generic_value_log =
dynamic_cast<const geo_log::GenericValueLog *>(value_log)) {
GPointer value = generic_value_log->value();
r_fields.add("Viewer", fn::make_constant_field(*value.type(), value.get()));
if (const ViewerNodeLog *viewer_log =
nodes::geo_eval_log::GeoModifierLog::find_viewer_node_log_for_spreadsheet(
*sspreadsheet)) {
if (viewer_log->field) {
r_fields.add("Viewer", viewer_log->field);
}
}
}

View File

@@ -13,6 +13,10 @@ set(INC_SYS
set(SRC
intern/cpp_types.cc
intern/field.cc
intern/lazy_function.cc
intern/lazy_function_execute.cc
intern/lazy_function_graph.cc
intern/lazy_function_graph_executor.cc
intern/multi_function.cc
intern/multi_function_builder.cc
intern/multi_function_params.cc
@@ -23,6 +27,10 @@ set(SRC
FN_field.hh
FN_field_cpp_type.hh
FN_lazy_function.hh
FN_lazy_function_execute.hh
FN_lazy_function_graph.hh
FN_lazy_function_graph_executor.hh
FN_multi_function.hh
FN_multi_function_builder.hh
FN_multi_function_context.hh
@@ -61,6 +69,7 @@ blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
set(TEST_SRC
tests/FN_field_test.cc
tests/FN_lazy_function_test.cc
tests/FN_multi_function_procedure_test.cc
tests/FN_multi_function_test.cc

View File

@@ -565,6 +565,17 @@ template<typename T> struct ValueOrField {
}
return this->value;
}
friend std::ostream &operator<<(std::ostream &stream, const ValueOrField<T> &value_or_field)
{
if (value_or_field.field) {
stream << "ValueOrField<T>";
}
else {
stream << value_or_field.value;
}
return stream;
}
};
/** \} */

View File

@@ -59,7 +59,7 @@ class ValueOrFieldCPPType : public CPPType {
public:
template<typename T>
ValueOrFieldCPPType(FieldCPPTypeParam<ValueOrField<T>> /* unused */, StringRef debug_name)
: CPPType(CPPTypeParam<ValueOrField<T>, CPPTypeFlags::None>(), debug_name),
: CPPType(CPPTypeParam<ValueOrField<T>, CPPTypeFlags::Printable>(), debug_name),
base_type_(CPPType::get<T>())
{
construct_from_value_ = [](void *dst, const void *value_or_field) {

View File

@@ -0,0 +1,384 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup fn
*
* A `LazyFunction` encapsulates a computation which has inputs, outputs and potentially side
* effects. Most importantly, a `LazyFunction` supports lazyness in its inputs and outputs:
* - Only outputs that are actually used have to be computed.
* - Inputs can be requested lazily based on which outputs are used or what side effects the
* function has.
*
* A lazy-function that uses lazyness may be executed more than once. The most common example is
* the geometry nodes switch node. Depending on a condition input, it decides which one of the
* other inputs is actually used. From the perspective of the switch node, its execution works as
* follows:
* 1. The switch node is first executed. It sees that the output is used. Now it requests the
* condition input from the caller and exits.
* 2. Once the caller is able to provide the condition input the switch node is executed again.
* This time it retrieves the condition and requests one of the other inputs. Then the node
* exits again, giving back control to the caller.
* 3. When the caller computed the second requested input the switch node executes a last time.
* This time it retrieves the new input and forwards it to the output.
*
* In some sense, a lazy-function can be thought of like a state machine. Every time it is
* executed, it advances its state until all required outputs are ready.
*
* The lazy-function interface is designed to support composition of many such functions into a new
* lazy-functions. All while keeping the lazyness working. For example, in geometry nodes a switch
* node in a node group should still be able to decide whether a node in the parent group will be
* executed or not. This is essential to avoid doing unnecessary work.
*
* The lazy-function system consists of multiple core components:
* - The interface of a lazy-function itself including its calling convention.
* - A graph data structure that allows composing many lazy-functions by connecting their inputs
* and outputs.
* - An executor that allows multi-threaded execution or such a graph.
*/
#include "BLI_cpp_type.hh"
#include "BLI_generic_pointer.hh"
#include "BLI_linear_allocator.hh"
#include "BLI_vector.hh"
namespace blender::fn::lazy_function {
enum class ValueUsage {
/**
* The value is definitely used and therefore has to be computed.
*/
Used,
/**
* It's unknown whether this value will be used or not. Computing it is ok but the result may be
* discarded.
*/
Maybe,
/**
* The value will definitely not be used. It can still be computed but the result will be
* discarded in all cases.
*/
Unused,
};
class LazyFunction;
/**
* This allows passing arbitrary data into a lazy-function during execution. For that, #UserData
* has to be subclassed. This mainly exists because it's more type safe than passing a `void *`
* with no type information attached.
*
* Some lazy-functions may expect to find a certain type of user data when executed.
*/
class UserData {
public:
virtual ~UserData() = default;
};
/**
* Passed to the lazy-function when it is executed.
*/
struct Context {
/**
* If the lazy-function has some state (which only makes sense when it is executed more than once
* to finish its job), the state is stored here. This points to memory returned from
* #LazyFunction::init_storage.
*/
void *storage;
/**
* Custom user data that can be used in the function.
*/
UserData *user_data;
};
/**
* Defines the calling convention for a lazy-function. During execution, a lazy-function retrieves
* its inputs and sets the outputs through #Params.
*/
class Params {
public:
/**
* The lazy-function this #Params has been prepared for.
*/
const LazyFunction &fn_;
public:
Params(const LazyFunction &fn);
/**
* Get a pointer to an input value if the value is available already. Otherwise null is returned.
*
* The #LazyFunction must leave returned object in an initialized state, but can move from it.
*/
void *try_get_input_data_ptr(int index) const;
/**
* Same as #try_get_input_data_ptr, but if the data is not yet available, request it. This makes
* sure that the data will be available in a future execution of the #LazyFunction.
*/
void *try_get_input_data_ptr_or_request(int index);
/**
* Get a pointer to where the output value should be stored.
* The value at the pointer is in an uninitialized state at first.
* The #LazyFunction is responsible for initializing the value.
* After the output has been initialized to its final value, #output_set has to be called.
*/
void *get_output_data_ptr(int index);
/**
* Call this after the output value is initialized. After this is called, the value must not be
* touched anymore. It may be moved or destructed immediatly.
*/
void output_set(int index);
/**
* Allows the #MultiFunction to check whether an output was computed already without keeping
* track of it itself.
*/
bool output_was_set(int index) const;
/**
* Can be used to detect which outputs have to be computed.
*/
ValueUsage get_output_usage(int index) const;
/**
* Tell the caller of the #LazyFunction that a specific input will definitely not be used.
* Only an input that was not #ValueUsage::Used can become unused.
*/
void set_input_unused(int index);
/**
* Typed utility methods that wrap the methods above.
*/
template<typename T> T extract_input(int index);
template<typename T> const T &get_input(int index);
template<typename T> T *try_get_input_data_ptr_or_request(int index);
template<typename T> void set_output(int index, T &&value);
/**
* Utility to initialize all outputs that haven't been set yet.
*/
void set_default_remaining_outputs();
private:
/**
* Methods that need to be implemented by subclasses. Those are separate from the non-virtual
* methods above to make it easy to insert additional debugging logic on top of the
* implementations.
*/
virtual void *try_get_input_data_ptr_impl(int index) const = 0;
virtual void *try_get_input_data_ptr_or_request_impl(int index) = 0;
virtual void *get_output_data_ptr_impl(int index) = 0;
virtual void output_set_impl(int index) = 0;
virtual bool output_was_set_impl(int index) const = 0;
virtual ValueUsage get_output_usage_impl(int index) const = 0;
virtual void set_input_unused_impl(int index) = 0;
};
/**
* Describes an input of a #LazyFunction.
*/
struct Input {
/**
* Name used for debugging purposes. The string has to be static or has to be owned by something
* else.
*/
const char *debug_name;
/**
* Data type of this input.
*/
const CPPType *type;
/**
* Can be used to indicate a caller or this function if this input is used statically before
* executing it the first time. This is technically not needed but can improve efficiency because
* a round-trip through the `execute` method can be avoided.
*
* When this is #ValueUsage::Used, the caller has to ensure that the input is definitely
* available when the #execute method is first called. The #execute method does not have to check
* whether the value is actually available.
*/
ValueUsage usage;
Input(const char *debug_name, const CPPType &type, const ValueUsage usage = ValueUsage::Used)
: debug_name(debug_name), type(&type), usage(usage)
{
}
};
struct Output {
/**
* Name used for debugging purposes. The string has to be static or has to be owned by something
* else.
*/
const char *debug_name;
/**
* Data type of this output.
*/
const CPPType *type = nullptr;
Output(const char *debug_name, const CPPType &type) : debug_name(debug_name), type(&type)
{
}
};
/**
* A function that can compute outputs and request inputs lazily. For more details see the comment
* at the top of the file.
*/
class LazyFunction {
protected:
const char *debug_name_ = "<unknown>";
Vector<Input> inputs_;
Vector<Output> outputs_;
public:
virtual ~LazyFunction() = default;
/**
* Get a name of the function or an input or output. This is mainly used for debugging.
* These are virtual functions because the names are often not used outside of debugging
* workflows. This way the names are only generated when they are actually needed.
*/
virtual std::string name() const;
virtual std::string input_name(int index) const;
virtual std::string output_name(int index) const;
/**
* Allocates storage for this function. The storage will be passed to every call to #execute.
* If the function does not keep track of any state, this does not have to be implemented.
*/
virtual void *init_storage(LinearAllocator<> &allocator) const;
/**
* Destruct the storage created in #init_storage.
*/
virtual void destruct_storage(void *storage) const;
/**
* Inputs of the function.
*/
Span<Input> inputs() const;
/**
* Outputs of the function.
*/
Span<Output> outputs() const;
/**
* During execution the function retrieves inputs and sets outputs in #params. For some
* functions, this method is called more than once. After execution, the function either has
* computed all required outputs or is waiting for more inputs.
*/
void execute(Params &params, const Context &context) const;
/**
* Utility to check that the guarantee by #Input::usage is followed.
*/
bool always_used_inputs_available(const Params &params) const;
private:
/**
* Needs to be implemented by subclasses. This is separate from #execute so that additional
* debugging logic can be implemented in #execute.
*/
virtual void execute_impl(Params &params, const Context &context) const = 0;
};
/* -------------------------------------------------------------------- */
/** \name #LazyFunction Inline Methods
* \{ */
inline Span<Input> LazyFunction::inputs() const
{
return inputs_;
}
inline Span<Output> LazyFunction::outputs() const
{
return outputs_;
}
inline void LazyFunction::execute(Params &params, const Context &context) const
{
BLI_assert(this->always_used_inputs_available(params));
this->execute_impl(params, context);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #Params Inline Methods
* \{ */
inline Params::Params(const LazyFunction &fn) : fn_(fn)
{
}
inline void *Params::try_get_input_data_ptr(const int index) const
{
return this->try_get_input_data_ptr_impl(index);
}
inline void *Params::try_get_input_data_ptr_or_request(const int index)
{
return this->try_get_input_data_ptr_or_request_impl(index);
}
inline void *Params::get_output_data_ptr(const int index)
{
return this->get_output_data_ptr_impl(index);
}
inline void Params::output_set(const int index)
{
this->output_set_impl(index);
}
inline bool Params::output_was_set(const int index) const
{
return this->output_was_set_impl(index);
}
inline ValueUsage Params::get_output_usage(const int index) const
{
return this->get_output_usage_impl(index);
}
inline void Params::set_input_unused(const int index)
{
this->set_input_unused_impl(index);
}
template<typename T> inline T Params::extract_input(const int index)
{
void *data = this->try_get_input_data_ptr(index);
BLI_assert(data != nullptr);
T return_value = std::move(*static_cast<T *>(data));
return return_value;
}
template<typename T> inline const T &Params::get_input(const int index)
{
const void *data = this->try_get_input_data_ptr(index);
BLI_assert(data != nullptr);
return *static_cast<const T *>(data);
}
template<typename T> inline T *Params::try_get_input_data_ptr_or_request(const int index)
{
return static_cast<T *>(this->try_get_input_data_ptr_or_request(index));
}
template<typename T> inline void Params::set_output(const int index, T &&value)
{
using DecayT = std::decay_t<T>;
void *data = this->get_output_data_ptr(index);
new (data) DecayT(std::forward<T>(value));
this->output_set(index);
}
/** \} */
} // namespace blender::fn::lazy_function

View File

@@ -0,0 +1,122 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup fn
*
* This file common utilities for actually executing a lazy-function.
*/
#include "BLI_parameter_pack_utils.hh"
#include "FN_lazy_function.hh"
namespace blender::fn::lazy_function {
/**
* Most basic implementation of #Params. It does not actually implement any logic for how to
* retrieve inputs or set outputs. Instead, code using #BasicParams has to implement that.
*/
class BasicParams : public Params {
private:
const Span<GMutablePointer> inputs_;
const Span<GMutablePointer> outputs_;
MutableSpan<std::optional<ValueUsage>> input_usages_;
Span<ValueUsage> output_usages_;
MutableSpan<bool> set_outputs_;
public:
BasicParams(const LazyFunction &fn,
const Span<GMutablePointer> inputs,
const Span<GMutablePointer> outputs,
MutableSpan<std::optional<ValueUsage>> input_usages,
Span<ValueUsage> output_usages,
MutableSpan<bool> set_outputs);
void *try_get_input_data_ptr_impl(const int index) const override;
void *try_get_input_data_ptr_or_request_impl(const int index) override;
void *get_output_data_ptr_impl(const int index) override;
void output_set_impl(const int index) override;
bool output_was_set_impl(const int index) const override;
ValueUsage get_output_usage_impl(const int index) const override;
void set_input_unused_impl(const int index) override;
};
namespace detail {
/**
* Utility to implement #execute_lazy_function_eagerly.
*/
template<typename... Inputs, typename... Outputs, size_t... InIndices, size_t... OutIndices>
inline void execute_lazy_function_eagerly_impl(
const LazyFunction &fn,
UserData *user_data,
std::tuple<Inputs...> &inputs,
std::tuple<Outputs *...> &outputs,
std::index_sequence<InIndices...> /* in_indices */,
std::index_sequence<OutIndices...> /* out_indices */)
{
constexpr size_t InputsNum = sizeof...(Inputs);
constexpr size_t OutputsNum = sizeof...(Outputs);
std::array<GMutablePointer, InputsNum> input_pointers;
std::array<GMutablePointer, OutputsNum> output_pointers;
std::array<std::optional<ValueUsage>, InputsNum> input_usages;
std::array<ValueUsage, OutputsNum> output_usages;
std::array<bool, OutputsNum> set_outputs;
(
[&]() {
constexpr size_t I = InIndices;
using T = Inputs;
const CPPType &type = CPPType::get<T>();
input_pointers[I] = {type, &std::get<I>(inputs)};
}(),
...);
(
[&]() {
constexpr size_t I = OutIndices;
using T = Outputs;
const CPPType &type = CPPType::get<T>();
output_pointers[I] = {type, std::get<I>(outputs)};
}(),
...);
output_usages.fill(ValueUsage::Used);
set_outputs.fill(false);
LinearAllocator<> allocator;
Context context;
context.user_data = user_data;
context.storage = fn.init_storage(allocator);
BasicParams params{
fn, input_pointers, output_pointers, input_usages, output_usages, set_outputs};
fn.execute(params, context);
fn.destruct_storage(context.storage);
}
} // namespace detail
/**
* In some cases (mainly for tests), the set of inputs and outputs for a lazy-function is known at
* compile time and one just wants to compute the outputs based on the inputs, without any
* lazyness.
*
* This function does exactly that. It takes all inputs in a tuple and writes the outputs to points
* provided in a second tuple. Since all inputs have to be provided, the lazy-function has to
* compute all outputs.
*/
template<typename... Inputs, typename... Outputs>
inline void execute_lazy_function_eagerly(const LazyFunction &fn,
UserData *user_data,
std::tuple<Inputs...> inputs,
std::tuple<Outputs *...> outputs)
{
BLI_assert(fn.inputs().size() == sizeof...(Inputs));
BLI_assert(fn.outputs().size() == sizeof...(Outputs));
detail::execute_lazy_function_eagerly_impl(fn,
user_data,
inputs,
outputs,
std::make_index_sequence<sizeof...(Inputs)>(),
std::make_index_sequence<sizeof...(Outputs)>());
}
} // namespace blender::fn::lazy_function

View File

@@ -0,0 +1,421 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup fn
*
* This file contains a graph data structure that allows composing multiple lazy-functions into a
* combined lazy-function.
*
* There are two types of nodes in the graph:
* - #FunctionNode: Corresponds to a #LazyFunction. The inputs and outputs of the function become
* input and output sockets of the node.
* - #DummyNode: Is used to indicate inputs and outputs of the entire graph. It can have an
* arbitrary number of sockets.
*/
#include "BLI_linear_allocator.hh"
#include "FN_lazy_function.hh"
namespace blender::fn::lazy_function {
class Socket;
class InputSocket;
class OutputSocket;
class Node;
class Graph;
/**
* A #Socket is the interface of a #Node. Every #Socket is either an #InputSocket or #OutputSocket.
* Links can be created from output sockets to input sockets.
*/
class Socket : NonCopyable, NonMovable {
protected:
/**
* The node the socket belongs to.
*/
Node *node_;
/**
* Data type of the socket. Only sockets with the same type can be linked.
*/
const CPPType *type_;
/**
* Indicates whether this is an #InputSocket or #OutputSocket.
*/
bool is_input_;
/**
* Index of the socket. E.g. 0 for the first input and the first output socket.
*/
int index_in_node_;
friend Graph;
public:
bool is_input() const;
bool is_output() const;
int index() const;
InputSocket &as_input();
OutputSocket &as_output();
const InputSocket &as_input() const;
const OutputSocket &as_output() const;
const Node &node() const;
Node &node();
const CPPType &type() const;
std::string name() const;
};
class InputSocket : public Socket {
private:
/**
* An input can have at most one link connected to it. The linked socket is the "origin" because
* it's where the data is coming from. The type of the origin must be the same as the type of
* this socket.
*/
OutputSocket *origin_;
/**
* Can be null or a non-owning pointer to a value of the type of the socket. This value will be
* used when the input is used but not linked.
*
* This is technically not needed, because one could just create a separate node that just
* outputs the value, but that would have more overhead. Especially because it's commonly the
* case that most inputs are unlinked.
*/
const void *default_value_ = nullptr;
friend Graph;
public:
OutputSocket *origin();
const OutputSocket *origin() const;
const void *default_value() const;
void set_default_value(const void *value);
};
class OutputSocket : public Socket {
private:
/**
* An output can be linked to an arbitrary number of inputs of the same type.
*/
Vector<InputSocket *> targets_;
friend Graph;
public:
Span<InputSocket *> targets();
Span<const InputSocket *> targets() const;
};
/**
* A #Node has input and output sockets. Every node is either a #FunctionNode or a #DummyNode.
*/
class Node : NonCopyable, NonMovable {
protected:
/**
* The function this node corresponds to. If this is null, the node is a #DummyNode.
* The function is not owned by this #Node nor by the #Graph.
*/
const LazyFunction *fn_ = nullptr;
/**
* Input sockets of the node.
*/
Span<InputSocket *> inputs_;
/**
* Output sockets of the node.
*/
Span<OutputSocket *> outputs_;
/**
* An index that is set when calling #Graph::update_node_indices. This can be used to create
* efficient mappings from nodes to other data using just an array instead of a hash map.
*
* This is technically not necessary but has better performance than always using hash maps.
*/
int index_in_graph_ = -1;
friend Graph;
public:
bool is_dummy() const;
bool is_function() const;
int index_in_graph() const;
Span<const InputSocket *> inputs() const;
Span<const OutputSocket *> outputs() const;
Span<InputSocket *> inputs();
Span<OutputSocket *> outputs();
const InputSocket &input(int index) const;
const OutputSocket &output(int index) const;
InputSocket &input(int index);
OutputSocket &output(int index);
std::string name() const;
};
/**
* A #Node that corresponds to a specific #LazyFunction.
*/
class FunctionNode : public Node {
public:
const LazyFunction &function() const;
};
/**
* A #Node that does *not* correspond to a #LazyFunction. Instead it can be used to indicate inputs
* and outputs of the entire graph. It can have an arbitrary number of inputs and outputs.
*/
class DummyNode : public Node {
private:
std::string name_;
friend Node;
};
/**
* A container for an arbitrary number of nodes and links between their sockets.
*/
class Graph : NonCopyable, NonMovable {
private:
/**
* Used to allocate nodes and sockets in the graph.
*/
LinearAllocator<> allocator_;
/**
* Contains all nodes in the graph so that it is efficient to iterate over them.
*/
Vector<Node *> nodes_;
public:
~Graph();
/**
* Get all nodes in the graph. The index in the span corresponds to #Node::index_in_graph.
*/
Span<const Node *> nodes() const;
/**
* Add a new function node with sockets that match the passed in #LazyFunction.
*/
FunctionNode &add_function(const LazyFunction &fn);
/**
* Add a new dummy node with the given socket types.
*/
DummyNode &add_dummy(Span<const CPPType *> input_types, Span<const CPPType *> output_types);
/**
* Add a link between the two given sockets.
* This has undefined behavior when the input is linked to something else already.
*/
void add_link(OutputSocket &from, InputSocket &to);
/**
* Make sure that #Node::index_in_graph is up to date.
*/
void update_node_indices();
/**
* Can be used to assert that #update_node_indices has been called.
*/
bool node_indices_are_valid() const;
/**
* Utility to generate a dot graph string for the graph. This can be used for debugging.
*/
std::string to_dot() const;
};
/* -------------------------------------------------------------------- */
/** \name #Socket Inline Methods
* \{ */
inline bool Socket::is_input() const
{
return is_input_;
}
inline bool Socket::is_output() const
{
return !is_input_;
}
inline int Socket::index() const
{
return index_in_node_;
}
inline InputSocket &Socket::as_input()
{
BLI_assert(this->is_input());
return *static_cast<InputSocket *>(this);
}
inline OutputSocket &Socket::as_output()
{
BLI_assert(this->is_output());
return *static_cast<OutputSocket *>(this);
}
inline const InputSocket &Socket::as_input() const
{
BLI_assert(this->is_input());
return *static_cast<const InputSocket *>(this);
}
inline const OutputSocket &Socket::as_output() const
{
BLI_assert(this->is_output());
return *static_cast<const OutputSocket *>(this);
}
inline const Node &Socket::node() const
{
return *node_;
}
inline Node &Socket::node()
{
return *node_;
}
inline const CPPType &Socket::type() const
{
return *type_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #InputSocket Inline Methods
* \{ */
inline const OutputSocket *InputSocket::origin() const
{
return origin_;
}
inline OutputSocket *InputSocket::origin()
{
return origin_;
}
inline const void *InputSocket::default_value() const
{
return default_value_;
}
inline void InputSocket::set_default_value(const void *value)
{
default_value_ = value;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #OutputSocket Inline Methods
* \{ */
inline Span<const InputSocket *> OutputSocket::targets() const
{
return targets_;
}
inline Span<InputSocket *> OutputSocket::targets()
{
return targets_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #Node Inline Methods
* \{ */
inline bool Node::is_dummy() const
{
return fn_ == nullptr;
}
inline bool Node::is_function() const
{
return fn_ != nullptr;
}
inline int Node::index_in_graph() const
{
return index_in_graph_;
}
inline Span<const InputSocket *> Node::inputs() const
{
return inputs_;
}
inline Span<const OutputSocket *> Node::outputs() const
{
return outputs_;
}
inline Span<InputSocket *> Node::inputs()
{
return inputs_;
}
inline Span<OutputSocket *> Node::outputs()
{
return outputs_;
}
inline const InputSocket &Node::input(const int index) const
{
return *inputs_[index];
}
inline const OutputSocket &Node::output(const int index) const
{
return *outputs_[index];
}
inline InputSocket &Node::input(const int index)
{
return *inputs_[index];
}
inline OutputSocket &Node::output(const int index)
{
return *outputs_[index];
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #FunctionNode Inline Methods
* \{ */
inline const LazyFunction &FunctionNode::function() const
{
BLI_assert(fn_ != nullptr);
return *fn_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #Graph Inline Methods
* \{ */
inline Span<const Node *> Graph::nodes() const
{
return nodes_;
}
/** \} */
} // namespace blender::fn::lazy_function

View File

@@ -0,0 +1,83 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup fn
*
* This file provides means to create a #LazyFunction from #Graph (which could then e.g. be used in
* another #Graph again).
*/
#include "BLI_vector.hh"
#include "BLI_vector_set.hh"
#include "FN_lazy_function_graph.hh"
namespace blender::fn::lazy_function {
/**
* Can be implemented to log values produced during graph evaluation.
*/
class GraphExecutorLogger {
public:
virtual ~GraphExecutorLogger() = default;
virtual void log_socket_value(const Context &context,
const Socket &socket,
GPointer value) const;
};
/**
* Has to be implemented when some of the nodes in the graph may have side effects. The
* #GraphExecutor has to know about that to make sure that these nodes will be executed even though
* their outputs are not needed.
*/
class GraphExecutorSideEffectProvider {
public:
virtual ~GraphExecutorSideEffectProvider() = default;
virtual Vector<const FunctionNode *> get_nodes_with_side_effects(const Context &context) const;
};
class GraphExecutor : public LazyFunction {
public:
using Logger = GraphExecutorLogger;
using SideEffectProvider = GraphExecutorSideEffectProvider;
private:
/**
* The graph that is evaluated.
*/
const Graph &graph_;
/**
* Input and output sockets of the entire graph.
*/
VectorSet<const OutputSocket *> graph_inputs_;
VectorSet<const InputSocket *> graph_outputs_;
/**
* Optional logger for events that happen during execution.
*/
const Logger *logger_;
/**
* Optional side effect provider. It knows which nodes have side effects based on the context
* during evaluation.
*/
const SideEffectProvider *side_effect_provider_;
friend class Executor;
public:
GraphExecutor(const Graph &graph,
Span<const OutputSocket *> graph_inputs,
Span<const InputSocket *> graph_outputs,
const Logger *logger,
const SideEffectProvider *side_effect_provider);
void *init_storage(LinearAllocator<> &allocator) const override;
void destruct_storage(void *storage) const override;
private:
void execute_impl(Params &params, const Context &context) const override;
};
} // namespace blender::fn::lazy_function

View File

@@ -157,6 +157,7 @@ namespace multi_function_types {
using fn::MFContext;
using fn::MFContextBuilder;
using fn::MFDataType;
using fn::MFParamCategory;
using fn::MFParams;
using fn::MFParamsBuilder;
using fn::MFParamType;

View File

@@ -16,3 +16,6 @@ MAKE_FIELD_CPP_TYPE(BoolField, bool);
MAKE_FIELD_CPP_TYPE(Int8Field, int8_t);
MAKE_FIELD_CPP_TYPE(Int32Field, int32_t);
MAKE_FIELD_CPP_TYPE(StringField, std::string);
BLI_CPP_TYPE_MAKE(StringValueOrFieldVector,
blender::Vector<blender::fn::ValueOrField<std::string>>,
CPPTypeFlags::None);

View File

@@ -0,0 +1,66 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup fn
*/
#include "BLI_array.hh"
#include "FN_lazy_function.hh"
namespace blender::fn::lazy_function {
std::string LazyFunction::name() const
{
return debug_name_;
}
std::string LazyFunction::input_name(int index) const
{
return inputs_[index].debug_name;
}
std::string LazyFunction::output_name(int index) const
{
return outputs_[index].debug_name;
}
void *LazyFunction::init_storage(LinearAllocator<> &UNUSED(allocator)) const
{
return nullptr;
}
void LazyFunction::destruct_storage(void *storage) const
{
BLI_assert(storage == nullptr);
UNUSED_VARS_NDEBUG(storage);
}
bool LazyFunction::always_used_inputs_available(const Params &params) const
{
for (const int i : inputs_.index_range()) {
const Input &fn_input = inputs_[i];
if (fn_input.usage == ValueUsage::Used) {
if (params.try_get_input_data_ptr(i) == nullptr) {
return false;
}
}
}
return true;
}
void Params::set_default_remaining_outputs()
{
for (const int i : fn_.outputs().index_range()) {
if (this->output_was_set(i)) {
continue;
}
const Output &fn_output = fn_.outputs()[i];
const CPPType &type = *fn_output.type;
void *data_ptr = this->get_output_data_ptr(i);
type.value_initialize(data_ptr);
this->output_set(i);
}
}
} // namespace blender::fn::lazy_function

View File

@@ -0,0 +1,65 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup fn
*/
#include "FN_lazy_function_execute.hh"
namespace blender::fn::lazy_function {
BasicParams::BasicParams(const LazyFunction &fn,
const Span<GMutablePointer> inputs,
const Span<GMutablePointer> outputs,
MutableSpan<std::optional<ValueUsage>> input_usages,
Span<ValueUsage> output_usages,
MutableSpan<bool> set_outputs)
: Params(fn),
inputs_(inputs),
outputs_(outputs),
input_usages_(input_usages),
output_usages_(output_usages),
set_outputs_(set_outputs)
{
}
void *BasicParams::try_get_input_data_ptr_impl(const int index) const
{
return inputs_[index].get();
}
void *BasicParams::try_get_input_data_ptr_or_request_impl(const int index)
{
void *value = inputs_[index].get();
if (value == nullptr) {
input_usages_[index] = ValueUsage::Used;
}
return value;
}
void *BasicParams::get_output_data_ptr_impl(const int index)
{
return outputs_[index].get();
}
void BasicParams::output_set_impl(const int index)
{
set_outputs_[index] = true;
}
bool BasicParams::output_was_set_impl(const int index) const
{
return set_outputs_[index];
}
ValueUsage BasicParams::get_output_usage_impl(const int index) const
{
return output_usages_[index];
}
void BasicParams::set_input_unused_impl(const int index)
{
input_usages_[index] = ValueUsage::Unused;
}
} // namespace blender::fn::lazy_function

View File

@@ -0,0 +1,181 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_dot_export.hh"
#include "FN_lazy_function_graph.hh"
namespace blender::fn::lazy_function {
Graph::~Graph()
{
for (Node *node : nodes_) {
for (InputSocket *socket : node->inputs_) {
std::destroy_at(socket);
}
for (OutputSocket *socket : node->outputs_) {
std::destroy_at(socket);
}
std::destroy_at(node);
}
}
FunctionNode &Graph::add_function(const LazyFunction &fn)
{
const Span<Input> inputs = fn.inputs();
const Span<Output> outputs = fn.outputs();
FunctionNode &node = *allocator_.construct<FunctionNode>().release();
node.fn_ = &fn;
node.inputs_ = allocator_.construct_elements_and_pointer_array<InputSocket>(inputs.size());
node.outputs_ = allocator_.construct_elements_and_pointer_array<OutputSocket>(outputs.size());
for (const int i : inputs.index_range()) {
InputSocket &socket = *node.inputs_[i];
socket.index_in_node_ = i;
socket.is_input_ = true;
socket.node_ = &node;
socket.type_ = inputs[i].type;
}
for (const int i : outputs.index_range()) {
OutputSocket &socket = *node.outputs_[i];
socket.index_in_node_ = i;
socket.is_input_ = false;
socket.node_ = &node;
socket.type_ = outputs[i].type;
}
nodes_.append(&node);
return node;
}
DummyNode &Graph::add_dummy(Span<const CPPType *> input_types, Span<const CPPType *> output_types)
{
DummyNode &node = *allocator_.construct<DummyNode>().release();
node.fn_ = nullptr;
node.inputs_ = allocator_.construct_elements_and_pointer_array<InputSocket>(input_types.size());
node.outputs_ = allocator_.construct_elements_and_pointer_array<OutputSocket>(
output_types.size());
for (const int i : input_types.index_range()) {
InputSocket &socket = *node.inputs_[i];
socket.index_in_node_ = i;
socket.is_input_ = true;
socket.node_ = &node;
socket.type_ = input_types[i];
}
for (const int i : output_types.index_range()) {
OutputSocket &socket = *node.outputs_[i];
socket.index_in_node_ = i;
socket.is_input_ = false;
socket.node_ = &node;
socket.type_ = output_types[i];
}
nodes_.append(&node);
return node;
}
void Graph::add_link(OutputSocket &from, InputSocket &to)
{
BLI_assert(to.origin_ == nullptr);
BLI_assert(from.type_ == to.type_);
to.origin_ = &from;
from.targets_.append(&to);
}
void Graph::update_node_indices()
{
for (const int i : nodes_.index_range()) {
nodes_[i]->index_in_graph_ = i;
}
}
bool Graph::node_indices_are_valid() const
{
for (const int i : nodes_.index_range()) {
if (nodes_[i]->index_in_graph_ != i) {
return false;
}
}
return true;
}
std::string Socket::name() const
{
if (node_->is_function()) {
const FunctionNode &fn_node = static_cast<const FunctionNode &>(*node_);
const LazyFunction &fn = fn_node.function();
if (is_input_) {
return fn.input_name(index_in_node_);
}
return fn.output_name(index_in_node_);
}
return "Unnamed";
}
std::string Node::name() const
{
if (fn_ == nullptr) {
return static_cast<const DummyNode *>(this)->name_;
}
return fn_->name();
}
std::string Graph::to_dot() const
{
dot::DirectedGraph digraph;
digraph.set_rankdir(dot::Attr_rankdir::LeftToRight);
Map<const Node *, dot::NodeWithSocketsRef> dot_nodes;
for (const Node *node : nodes_) {
dot::Node &dot_node = digraph.new_node("");
if (node->is_dummy()) {
dot_node.set_background_color("lightblue");
}
else {
dot_node.set_background_color("white");
}
Vector<std::string> input_names;
Vector<std::string> output_names;
for (const InputSocket *socket : node->inputs()) {
input_names.append(socket->name());
}
for (const OutputSocket *socket : node->outputs()) {
output_names.append(socket->name());
}
dot_nodes.add_new(node,
dot::NodeWithSocketsRef(dot_node, node->name(), input_names, output_names));
}
for (const Node *node : nodes_) {
for (const InputSocket *socket : node->inputs()) {
const dot::NodeWithSocketsRef &to_dot_node = dot_nodes.lookup(&socket->node());
const dot::NodePort to_dot_port = to_dot_node.input(socket->index());
if (const OutputSocket *origin = socket->origin()) {
dot::NodeWithSocketsRef &from_dot_node = dot_nodes.lookup(&origin->node());
digraph.new_edge(from_dot_node.output(origin->index()), to_dot_port);
}
else if (const void *default_value = socket->default_value()) {
const CPPType &type = socket->type();
std::string value_string;
if (type.is_printable()) {
value_string = type.to_string(default_value);
}
else {
value_string = "<" + type.name() + ">";
}
dot::Node &default_value_dot_node = digraph.new_node(value_string);
default_value_dot_node.set_shape(dot::Attr_shape::Ellipse);
digraph.new_edge(default_value_dot_node, to_dot_port);
}
}
}
return digraph.to_dot_string();
}
} // namespace blender::fn::lazy_function

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,238 @@
/* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
#include "FN_lazy_function_execute.hh"
#include "FN_lazy_function_graph.hh"
#include "FN_lazy_function_graph_executor.hh"
#include "BLI_task.h"
#include "BLI_timeit.hh"
namespace blender::fn::lazy_function::tests {
class AddLazyFunction : public LazyFunction {
public:
AddLazyFunction()
{
debug_name_ = "Add";
inputs_.append({"A", CPPType::get<int>()});
inputs_.append({"B", CPPType::get<int>()});
outputs_.append({"Result", CPPType::get<int>()});
}
void execute_impl(Params &params, const Context &UNUSED(context)) const override
{
const int a = params.get_input<int>(0);
const int b = params.get_input<int>(1);
params.set_output(0, a + b);
}
};
class StoreValueFunction : public LazyFunction {
private:
int *dst1_;
int *dst2_;
public:
StoreValueFunction(int *dst1, int *dst2) : dst1_(dst1), dst2_(dst2)
{
debug_name_ = "Store Value";
inputs_.append({"A", CPPType::get<int>()});
inputs_.append({"B", CPPType::get<int>(), ValueUsage::Maybe});
}
void execute_impl(Params &params, const Context &UNUSED(context)) const override
{
*dst1_ = params.get_input<int>(0);
if (int *value = params.try_get_input_data_ptr_or_request<int>(1)) {
*dst2_ = *value;
}
}
};
class SimpleSideEffectProvider : public GraphExecutor::SideEffectProvider {
private:
Vector<const FunctionNode *> side_effect_nodes_;
public:
SimpleSideEffectProvider(Span<const FunctionNode *> side_effect_nodes)
: side_effect_nodes_(side_effect_nodes)
{
}
Vector<const FunctionNode *> get_nodes_with_side_effects(
const Context &UNUSED(context)) const override
{
return side_effect_nodes_;
}
};
TEST(lazy_function, SideEffects)
{
BLI_task_scheduler_init();
int dst1 = 0;
int dst2 = 0;
const AddLazyFunction add_fn;
const StoreValueFunction store_fn{&dst1, &dst2};
Graph graph;
FunctionNode &add_node_1 = graph.add_function(add_fn);
FunctionNode &add_node_2 = graph.add_function(add_fn);
FunctionNode &store_node = graph.add_function(store_fn);
DummyNode &input_node = graph.add_dummy({}, {&CPPType::get<int>()});
graph.add_link(input_node.output(0), add_node_1.input(0));
graph.add_link(input_node.output(0), add_node_2.input(0));
graph.add_link(add_node_1.output(0), store_node.input(0));
graph.add_link(add_node_2.output(0), store_node.input(1));
const int value_10 = 10;
const int value_100 = 100;
add_node_1.input(1).set_default_value(&value_10);
add_node_2.input(1).set_default_value(&value_100);
graph.update_node_indices();
SimpleSideEffectProvider side_effect_provider{{&store_node}};
GraphExecutor executor_fn{graph, {&input_node.output(0)}, {}, nullptr, &side_effect_provider};
execute_lazy_function_eagerly(executor_fn, nullptr, std::make_tuple(5), std::make_tuple());
EXPECT_EQ(dst1, 15);
EXPECT_EQ(dst2, 105);
}
enum class LazyFunctionEventType {
SetInput,
RequestOutput,
SetOutputUnused,
};
struct LazyFunctionEvent {
LazyFunctionEventType type;
int index;
void *value;
};
static void execute_lazy_function_test(const LazyFunction &fn,
const Span<LazyFunctionEvent> events,
const Span<GMutablePointer> outputs)
{
const Span<Input> fn_inputs = fn.inputs();
const Span<Output> fn_outputs = fn.outputs();
BLI_assert(outputs.size() == fn_outputs.size());
LinearAllocator<> allocator;
Vector<GMutablePointer> inputs(fn_inputs.size());
Array<std::optional<ValueUsage>> input_usages(fn_inputs.size());
Array<ValueUsage> output_usages(fn_outputs.size(), ValueUsage::Unused);
Array<bool> set_outputs(fn_outputs.size(), false);
void *storage = fn.init_storage(allocator);
Context context;
context.storage = storage;
BasicParams params(fn, inputs, outputs, input_usages, output_usages, set_outputs);
if (fn.always_used_inputs_available(params)) {
fn.execute(params, context);
}
for (const LazyFunctionEvent &event : events) {
switch (event.type) {
case LazyFunctionEventType::SetInput: {
inputs[event.index] = GMutablePointer{fn_inputs[event.index].type, event.value};
break;
}
case LazyFunctionEventType::RequestOutput: {
output_usages[event.index] = ValueUsage::Used;
break;
}
case LazyFunctionEventType::SetOutputUnused: {
output_usages[event.index] = ValueUsage::Unused;
break;
}
}
if (fn.always_used_inputs_available(params)) {
fn.execute(params, context);
}
}
fn.destruct_storage(storage);
}
static Vector<Node *> build_add_node_chain(Graph &graph,
const int chain_length,
const int *default_value)
{
static AddLazyFunction fn;
Vector<Node *> nodes;
for ([[maybe_unused]] const int i : IndexRange(chain_length)) {
Node &node = graph.add_function(fn);
node.input(0).set_default_value(default_value);
node.input(1).set_default_value(default_value);
nodes.append(&node);
}
for (const int i : IndexRange(chain_length - 1)) {
Node &n1 = *nodes[i];
Node &n2 = *nodes[i + 1];
graph.add_link(n1.output(0), n2.input(0));
}
return nodes;
}
struct MultiChainResult {
Vector<Node *> first_nodes;
Node *last_node = nullptr;
};
static MultiChainResult build_multiple_chains(Graph &graph,
const int chain_length,
const int chain_num,
const int *default_value)
{
static AddLazyFunction fn;
MultiChainResult result;
for ([[maybe_unused]] const int i : IndexRange(chain_num)) {
Vector<Node *> chain = build_add_node_chain(graph, chain_length, default_value);
result.first_nodes.append(chain[0]);
if (result.last_node == nullptr) {
result.last_node = chain.last();
}
else {
Node &node = graph.add_function(fn);
node.input(0).set_default_value(default_value);
node.input(1).set_default_value(default_value);
graph.add_link(result.last_node->output(0), node.input(0));
graph.add_link(chain.last()->output(0), node.input(1));
result.last_node = &node;
}
}
return result;
}
TEST(lazy_function, Simple)
{
BLI_task_scheduler_init(); /* Without this, no parallelism. */
const int value_1 = 1;
Graph graph;
MultiChainResult node_chain = build_multiple_chains(graph, 1e4, 24, &value_1);
DummyNode &output_node = graph.add_dummy({&CPPType::get<int>()}, {});
graph.add_link(node_chain.last_node->output(0), output_node.input(0));
graph.update_node_indices();
// std::cout << graph.to_dot() << "\n";
GraphExecutor executor_fn{graph, {}, {&output_node.input(0)}, nullptr, nullptr};
// SCOPED_TIMER("run");
int result;
for ([[maybe_unused]] const int i : IndexRange(1e2)) {
execute_lazy_function_test(executor_fn,
{LazyFunctionEvent{LazyFunctionEventType::RequestOutput, 0}},
Span<GMutablePointer>{{&result}});
}
std::cout << "Result: " << result << "\n";
}
} // namespace blender::fn::lazy_function::tests

View File

@@ -65,7 +65,6 @@ set(SRC
intern/MOD_mirror.c
intern/MOD_multires.c
intern/MOD_nodes.cc
intern/MOD_nodes_evaluator.cc
intern/MOD_none.c
intern/MOD_normal_edit.c
intern/MOD_ocean.c
@@ -105,7 +104,6 @@ set(SRC
MOD_modifiertypes.h
MOD_nodes.h
intern/MOD_meshcache_util.h
intern/MOD_nodes_evaluator.hh
intern/MOD_solidify_util.h
intern/MOD_ui_common.h
intern/MOD_util.h

View File

@@ -36,6 +36,7 @@
#include "DNA_windowmanager_types.h"
#include "BKE_attribute_math.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_customdata.h"
#include "BKE_geometry_fields.hh"
#include "BKE_geometry_set_instances.hh"
@@ -73,7 +74,6 @@
#include "MOD_modifiertypes.h"
#include "MOD_nodes.h"
#include "MOD_nodes_evaluator.hh"
#include "MOD_ui_common.h"
#include "ED_object.h"
@@ -81,15 +81,18 @@
#include "ED_spreadsheet.h"
#include "ED_undo.h"
#include "NOD_derived_node_tree.hh"
#include "NOD_geometry.h"
#include "NOD_geometry_nodes_eval_log.hh"
#include "NOD_geometry_nodes_to_lazy_function_graph.hh"
#include "NOD_node_declaration.hh"
#include "FN_field.hh"
#include "FN_field_cpp_type.hh"
#include "FN_lazy_function_execute.hh"
#include "FN_lazy_function_graph_executor.hh"
#include "FN_multi_function.hh"
namespace lf = blender::fn::lazy_function;
using blender::Array;
using blender::ColorGeometry4f;
using blender::CPPType;
@@ -106,6 +109,7 @@ using blender::MultiValueMap;
using blender::MutableSpan;
using blender::Set;
using blender::Span;
using blender::Stack;
using blender::StringRef;
using blender::StringRefNull;
using blender::Vector;
@@ -117,11 +121,17 @@ using blender::fn::ValueOrFieldCPPType;
using blender::nodes::FieldInferencingInterface;
using blender::nodes::GeoNodeExecParams;
using blender::nodes::InputSocketFieldType;
using blender::nodes::geo_eval_log::GeoModifierLog;
using blender::threading::EnumerableThreadSpecific;
using namespace blender::fn::multi_function_types;
using namespace blender::nodes::derived_node_tree_types;
using geo_log::eNamedAttrUsage;
using geo_log::GeometryAttributeInfo;
using blender::nodes::geo_eval_log::GeometryAttributeInfo;
using blender::nodes::geo_eval_log::GeometryInfoLog;
using blender::nodes::geo_eval_log::GeoNodeLog;
using blender::nodes::geo_eval_log::GeoTreeLog;
using blender::nodes::geo_eval_log::NamedAttributeUsage;
using blender::nodes::geo_eval_log::NodeWarning;
using blender::nodes::geo_eval_log::NodeWarningType;
using blender::nodes::geo_eval_log::ValueLog;
static void initData(ModifierData *md)
{
@@ -756,36 +766,37 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
}
static void initialize_group_input(NodesModifierData &nmd,
const bNodeSocket &socket,
const bNodeSocket &interface_socket,
const int input_index,
void *r_value)
{
const bNodeSocketType &socket_type = *socket.typeinfo;
const bNodeSocket &bsocket = socket;
const eNodeSocketDatatype socket_data_type = static_cast<eNodeSocketDatatype>(bsocket.type);
const bNodeSocketType &socket_type = *interface_socket.typeinfo;
const eNodeSocketDatatype socket_data_type = static_cast<eNodeSocketDatatype>(
interface_socket.type);
if (nmd.settings.properties == nullptr) {
socket_type.get_geometry_nodes_cpp_value(bsocket, r_value);
socket_type.get_geometry_nodes_cpp_value(interface_socket, r_value);
return;
}
const IDProperty *property = IDP_GetPropertyFromGroup(nmd.settings.properties,
socket.identifier);
interface_socket.identifier);
if (property == nullptr) {
socket_type.get_geometry_nodes_cpp_value(bsocket, r_value);
socket_type.get_geometry_nodes_cpp_value(interface_socket, r_value);
return;
}
if (!id_property_type_matches_socket(bsocket, *property)) {
socket_type.get_geometry_nodes_cpp_value(bsocket, r_value);
if (!id_property_type_matches_socket(interface_socket, *property)) {
socket_type.get_geometry_nodes_cpp_value(interface_socket, r_value);
return;
}
if (!input_has_attribute_toggle(*nmd.node_group, socket.runtime->index_in_node)) {
if (!input_has_attribute_toggle(*nmd.node_group, input_index)) {
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
return;
}
const IDProperty *property_use_attribute = IDP_GetPropertyFromGroup(
nmd.settings.properties, (socket.identifier + use_attribute_suffix).c_str());
nmd.settings.properties, (interface_socket.identifier + use_attribute_suffix).c_str());
const IDProperty *property_attribute_name = IDP_GetPropertyFromGroup(
nmd.settings.properties, (socket.identifier + attribute_name_suffix).c_str());
nmd.settings.properties, (interface_socket.identifier + attribute_name_suffix).c_str());
if (property_use_attribute == nullptr || property_attribute_name == nullptr) {
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
return;
@@ -831,13 +842,25 @@ static Vector<SpaceSpreadsheet *> find_spreadsheet_editors(Main *bmain)
return spreadsheets;
}
static void find_sockets_to_preview_for_spreadsheet(SpaceSpreadsheet *sspreadsheet,
NodesModifierData *nmd,
const ModifierEvalContext *ctx,
const DerivedNodeTree &tree,
Set<DSocket> &r_sockets_to_preview)
static const lf::FunctionNode &find_viewer_lf_node(const bNode &viewer_bnode)
{
Vector<SpreadsheetContext *> context_path = sspreadsheet->context_path;
return *blender::nodes::ensure_geometry_nodes_lazy_function_graph(viewer_bnode.owner_tree())
->mapping.viewer_node_map.lookup(&viewer_bnode);
}
static const lf::FunctionNode &find_group_lf_node(const bNode &group_bnode)
{
return *blender::nodes::ensure_geometry_nodes_lazy_function_graph(group_bnode.owner_tree())
->mapping.group_node_map.lookup(&group_bnode);
}
static void find_side_effect_nodes_for_spreadsheet(
const SpaceSpreadsheet &sspreadsheet,
const NodesModifierData &nmd,
const ModifierEvalContext &ctx,
const bNodeTree &root_tree,
MultiValueMap<blender::ComputeContextHash, const lf::FunctionNode *> &r_side_effect_nodes)
{
Vector<SpreadsheetContext *> context_path = sspreadsheet.context_path;
if (context_path.size() < 3) {
return;
}
@@ -848,11 +871,11 @@ static void find_sockets_to_preview_for_spreadsheet(SpaceSpreadsheet *sspreadshe
return;
}
SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context_path[0];
if (object_context->object != DEG_get_original_object(ctx->object)) {
if (object_context->object != DEG_get_original_object(ctx.object)) {
return;
}
SpreadsheetContextModifier *modifier_context = (SpreadsheetContextModifier *)context_path[1];
if (StringRef(modifier_context->modifier_name) != nmd->modifier.name) {
if (StringRef(modifier_context->modifier_name) != nmd.modifier.name) {
return;
}
for (SpreadsheetContext *context : context_path.as_span().drop_front(2)) {
@@ -861,61 +884,77 @@ static void find_sockets_to_preview_for_spreadsheet(SpaceSpreadsheet *sspreadshe
}
}
Span<SpreadsheetContextNode *> nested_group_contexts =
context_path.as_span().drop_front(2).drop_back(1).cast<SpreadsheetContextNode *>();
SpreadsheetContextNode *last_context = (SpreadsheetContextNode *)context_path.last();
blender::ComputeContextBuilder compute_context_builder;
compute_context_builder.push<blender::bke::ModifierComputeContext>(nmd.modifier.name);
const DTreeContext *context = &tree.root_context();
const Span<SpreadsheetContextNode *> nested_group_contexts =
context_path.as_span().drop_front(2).drop_back(1).cast<SpreadsheetContextNode *>();
const SpreadsheetContextNode *last_context = (SpreadsheetContextNode *)context_path.last();
Stack<const bNode *> group_node_stack;
const bNodeTree *group = &root_tree;
for (SpreadsheetContextNode *node_context : nested_group_contexts) {
const bNodeTree &btree = context->btree();
const bNode *found_node = nullptr;
for (const bNode *bnode : btree.all_nodes()) {
if (STREQ(bnode->name, node_context->node_name)) {
found_node = bnode;
for (const bNode *node : group->nodes_by_type("GeometryNodeGroup")) {
if (STREQ(node->name, node_context->node_name)) {
found_node = node;
break;
}
}
if (found_node == nullptr) {
return;
}
context = context->child_context(*found_node);
if (context == nullptr) {
if (found_node->id == nullptr) {
return;
}
group_node_stack.push(found_node);
group = reinterpret_cast<const bNodeTree *>(found_node->id);
compute_context_builder.push<blender::bke::NodeGroupComputeContext>(node_context->node_name);
}
const bNodeTree &btree = context->btree();
for (const bNode *bnode : btree.nodes_by_type("GeometryNodeViewer")) {
if (STREQ(bnode->name, last_context->node_name)) {
const DNode viewer_node{context, bnode};
for (const bNodeSocket *input_socket : bnode->input_sockets()) {
if (input_socket->is_available() && input_socket->is_logically_linked()) {
r_sockets_to_preview.add(DSocket{context, input_socket});
}
}
const bNode *found_viewer_node = nullptr;
for (const bNode *viewer_node : group->nodes_by_type("GeometryNodeViewer")) {
if (STREQ(viewer_node->name, last_context->node_name)) {
found_viewer_node = viewer_node;
break;
}
}
if (found_viewer_node == nullptr) {
return;
}
/* Not only mark the viewer node as having side effects, but also all group nodes it is contained
* in. */
r_side_effect_nodes.add(compute_context_builder.hash(),
&find_viewer_lf_node(*found_viewer_node));
compute_context_builder.pop();
while (!compute_context_builder.is_empty()) {
r_side_effect_nodes.add(compute_context_builder.hash(),
&find_group_lf_node(*group_node_stack.pop()));
compute_context_builder.pop();
}
}
static void find_sockets_to_preview(NodesModifierData *nmd,
const ModifierEvalContext *ctx,
const DerivedNodeTree &tree,
Set<DSocket> &r_sockets_to_preview)
static void find_side_effect_nodes(
const NodesModifierData &nmd,
const ModifierEvalContext &ctx,
const bNodeTree &tree,
MultiValueMap<blender::ComputeContextHash, const lf::FunctionNode *> &r_side_effect_nodes)
{
Main *bmain = DEG_get_bmain(ctx->depsgraph);
Main *bmain = DEG_get_bmain(ctx.depsgraph);
/* Based on every visible spreadsheet context path, get a list of sockets that need to have their
* intermediate geometries cached for display. */
Vector<SpaceSpreadsheet *> spreadsheets = find_spreadsheet_editors(bmain);
for (SpaceSpreadsheet *sspreadsheet : spreadsheets) {
find_sockets_to_preview_for_spreadsheet(sspreadsheet, nmd, ctx, tree, r_sockets_to_preview);
find_side_effect_nodes_for_spreadsheet(*sspreadsheet, nmd, ctx, tree, r_side_effect_nodes);
}
}
static void clear_runtime_data(NodesModifierData *nmd)
{
if (nmd->runtime_eval_log != nullptr) {
delete (geo_log::ModifierLog *)nmd->runtime_eval_log;
delete static_cast<GeoModifierLog *>(nmd->runtime_eval_log);
nmd->runtime_eval_log = nullptr;
}
}
@@ -1079,92 +1118,107 @@ static void store_output_attributes(GeometrySet &geometry,
/**
* Evaluate a node group to compute the output geometry.
*/
static GeometrySet compute_geometry(const DerivedNodeTree &tree,
Span<const bNode *> group_input_nodes,
const bNode &output_node,
GeometrySet input_geometry_set,
NodesModifierData *nmd,
const ModifierEvalContext *ctx)
static GeometrySet compute_geometry(
const bNodeTree &btree,
const blender::nodes::GeometryNodesLazyFunctionGraphInfo &lf_graph_info,
const bNode &output_node,
GeometrySet input_geometry_set,
NodesModifierData *nmd,
const ModifierEvalContext *ctx)
{
blender::ResourceScope scope;
blender::LinearAllocator<> &allocator = scope.linear_allocator();
blender::nodes::NodeMultiFunctions mf_by_node{tree};
const blender::nodes::GeometryNodeLazyFunctionMapping &mapping = lf_graph_info.mapping;
Map<DOutputSocket, GMutablePointer> group_inputs;
Vector<const lf::OutputSocket *> graph_inputs;
Vector<const lf::InputSocket *> graph_outputs;
for (const lf::OutputSocket *socket : mapping.group_input_sockets) {
graph_inputs.append(socket);
}
for (const bNodeSocket *bsocket : output_node.input_sockets().drop_back(1)) {
const lf::InputSocket &socket = mapping.dummy_socket_map.lookup(bsocket)->as_input();
graph_outputs.append(&socket);
}
const DTreeContext *root_context = &tree.root_context();
for (const bNode *group_input_node : group_input_nodes) {
Span<const bNodeSocket *> group_input_sockets = group_input_node->output_sockets().drop_back(
1);
if (group_input_sockets.is_empty()) {
Array<GMutablePointer> param_inputs(graph_inputs.size());
Array<GMutablePointer> param_outputs(graph_outputs.size());
Array<std::optional<lf::ValueUsage>> param_input_usages(graph_inputs.size());
Array<lf::ValueUsage> param_output_usages(graph_outputs.size(), lf::ValueUsage::Used);
Array<bool> param_set_outputs(graph_outputs.size(), false);
blender::nodes::GeometryNodesLazyFunctionLogger lf_logger(lf_graph_info);
blender::nodes::GeometryNodesLazyFunctionSideEffectProvider lf_side_effect_provider(
lf_graph_info);
lf::GraphExecutor graph_executor{
lf_graph_info.graph, graph_inputs, graph_outputs, &lf_logger, &lf_side_effect_provider};
blender::nodes::GeoNodesModifierData geo_nodes_modifier_data;
geo_nodes_modifier_data.depsgraph = ctx->depsgraph;
geo_nodes_modifier_data.self_object = ctx->object;
auto eval_log = std::make_unique<GeoModifierLog>();
if (logging_enabled(ctx)) {
geo_nodes_modifier_data.eval_log = eval_log.get();
}
MultiValueMap<blender::ComputeContextHash, const lf::FunctionNode *> r_side_effect_nodes;
find_side_effect_nodes(*nmd, *ctx, btree, r_side_effect_nodes);
geo_nodes_modifier_data.side_effect_nodes = &r_side_effect_nodes;
blender::nodes::GeoNodesLFUserData user_data;
user_data.modifier_data = &geo_nodes_modifier_data;
blender::bke::ModifierComputeContext modifier_compute_context{nullptr, nmd->modifier.name};
user_data.compute_context = &modifier_compute_context;
blender::LinearAllocator<> allocator;
Vector<GMutablePointer> inputs_to_destruct;
int input_index;
LISTBASE_FOREACH_INDEX (bNodeSocket *, interface_socket, &btree.inputs, input_index) {
if (interface_socket->type == SOCK_GEOMETRY && input_index == 0) {
param_inputs[input_index] = &input_geometry_set;
continue;
}
Span<const bNodeSocket *> remaining_input_sockets = group_input_sockets;
/* If the group expects a geometry as first input, use the geometry that has been passed to
* modifier. */
const bNodeSocket *first_input_socket = group_input_sockets[0];
if (first_input_socket->type == SOCK_GEOMETRY) {
GeometrySet *geometry_set_in =
allocator.construct<GeometrySet>(input_geometry_set).release();
group_inputs.add_new({root_context, first_input_socket}, geometry_set_in);
remaining_input_sockets = remaining_input_sockets.drop_front(1);
}
/* Initialize remaining group inputs. */
for (const bNodeSocket *socket : remaining_input_sockets) {
const CPPType &cpp_type = *socket->typeinfo->geometry_nodes_cpp_type;
void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment());
initialize_group_input(*nmd, *socket, value_in);
group_inputs.add_new({root_context, socket}, {cpp_type, value_in});
}
const CPPType *type = interface_socket->typeinfo->geometry_nodes_cpp_type;
BLI_assert(type != nullptr);
void *value = allocator.allocate(type->size(), type->alignment());
initialize_group_input(*nmd, *interface_socket, input_index, value);
param_inputs[input_index] = {type, value};
inputs_to_destruct.append({type, value});
}
Vector<DInputSocket> group_outputs;
for (const bNodeSocket *socket_ref : output_node.input_sockets().drop_back(1)) {
group_outputs.append({root_context, socket_ref});
for (const int i : graph_outputs.index_range()) {
const lf::InputSocket &socket = *graph_outputs[i];
const CPPType &type = socket.type();
void *buffer = allocator.allocate(type.size(), type.alignment());
param_outputs[i] = {type, buffer};
}
std::optional<geo_log::GeoLogger> geo_logger;
lf::Context lf_context;
lf_context.storage = graph_executor.init_storage(allocator);
lf_context.user_data = &user_data;
lf::BasicParams lf_params{graph_executor,
param_inputs,
param_outputs,
param_input_usages,
param_output_usages,
param_set_outputs};
graph_executor.execute(lf_params, lf_context);
graph_executor.destruct_storage(lf_context.storage);
blender::modifiers::geometry_nodes::GeometryNodesEvaluationParams eval_params;
for (GMutablePointer &ptr : inputs_to_destruct) {
ptr.destruct();
}
GeometrySet output_geometry_set = std::move(*static_cast<GeometrySet *>(param_outputs[0].get()));
store_output_attributes(output_geometry_set, *nmd, output_node, param_outputs);
for (GMutablePointer &ptr : param_outputs) {
ptr.destruct();
}
if (logging_enabled(ctx)) {
Set<DSocket> preview_sockets;
find_sockets_to_preview(nmd, ctx, tree, preview_sockets);
eval_params.force_compute_sockets.extend(preview_sockets.begin(), preview_sockets.end());
geo_logger.emplace(std::move(preview_sockets));
geo_logger->log_input_geometry(input_geometry_set);
}
/* Don't keep a reference to the input geometry components to avoid copies during evaluation. */
input_geometry_set.clear();
eval_params.input_values = group_inputs;
eval_params.output_sockets = group_outputs;
eval_params.mf_by_node = &mf_by_node;
eval_params.modifier_ = nmd;
eval_params.depsgraph = ctx->depsgraph;
eval_params.self_object = ctx->object;
eval_params.geo_logger = geo_logger.has_value() ? &*geo_logger : nullptr;
blender::modifiers::geometry_nodes::evaluate_geometry_nodes(eval_params);
GeometrySet output_geometry_set = std::move(*eval_params.r_output_values[0].get<GeometrySet>());
if (geo_logger.has_value()) {
geo_logger->log_output_geometry(output_geometry_set);
NodesModifierData *nmd_orig = (NodesModifierData *)BKE_modifier_get_original(ctx->object,
&nmd->modifier);
clear_runtime_data(nmd_orig);
nmd_orig->runtime_eval_log = new geo_log::ModifierLog(*geo_logger);
}
store_output_attributes(output_geometry_set, *nmd, output_node, eval_params.r_output_values);
for (GMutablePointer value : eval_params.r_output_values) {
value.destruct();
NodesModifierData *nmd_orig = reinterpret_cast<NodesModifierData *>(
BKE_modifier_get_original(ctx->object, &nmd->modifier));
delete static_cast<GeoModifierLog *>(nmd_orig->runtime_eval_log);
nmd_orig->runtime_eval_log = eval_log.release();
}
return output_geometry_set;
@@ -1225,27 +1279,18 @@ static void modifyGeometry(ModifierData *md,
return;
}
const bNodeTree &tree = *nmd->node_group;
tree.ensure_topology_cache();
check_property_socket_sync(ctx->object, md);
const bNodeTree &root_tree_ref = *nmd->node_group;
DerivedNodeTree tree{root_tree_ref};
if (tree.has_link_cycles()) {
BKE_modifier_set_error(ctx->object, md, "Node group has cycles");
const bNode *output_node = tree.group_output_node();
if (output_node == nullptr) {
BKE_modifier_set_error(ctx->object, md, "Node group must have a group output node");
geometry_set.clear();
return;
}
Span<const bNode *> input_nodes = root_tree_ref.nodes_by_type("NodeGroupInput");
Span<const bNode *> output_nodes = root_tree_ref.nodes_by_type("NodeGroupOutput");
if (output_nodes.size() != 1) {
BKE_modifier_set_error(ctx->object, md, "Node group must have a single output node");
geometry_set.clear();
return;
}
const bNode &output_node = *output_nodes[0];
Span<const bNodeSocket *> group_outputs = output_node.input_sockets().drop_back(1);
Span<const bNodeSocket *> group_outputs = output_node->input_sockets().drop_back(1);
if (group_outputs.is_empty()) {
BKE_modifier_set_error(ctx->object, md, "Node group must have an output socket");
geometry_set.clear();
@@ -1259,6 +1304,14 @@ static void modifyGeometry(ModifierData *md,
return;
}
const blender::nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
blender::nodes::ensure_geometry_nodes_lazy_function_graph(tree);
if (lf_graph_info == nullptr) {
BKE_modifier_set_error(ctx->object, md, "Cannot evaluate node group");
geometry_set.clear();
return;
}
bool use_orig_index_verts = false;
bool use_orig_index_edges = false;
bool use_orig_index_polys = false;
@@ -1270,7 +1323,7 @@ static void modifyGeometry(ModifierData *md,
}
geometry_set = compute_geometry(
tree, input_nodes, output_node, std::move(geometry_set), nmd, ctx);
tree, *lf_graph_info, *output_node, std::move(geometry_set), nmd, ctx);
if (geometry_set.has_mesh()) {
/* Add #CD_ORIGINDEX layers if they don't exist already. This is required because the
@@ -1342,6 +1395,16 @@ static NodesModifierData *get_modifier_data(Main &bmain,
return reinterpret_cast<NodesModifierData *>(md);
}
static GeoTreeLog *get_root_tree_log(const NodesModifierData &nmd)
{
if (nmd.runtime_eval_log == nullptr) {
return nullptr;
}
GeoModifierLog &modifier_log = *static_cast<GeoModifierLog *>(nmd.runtime_eval_log);
blender::bke::ModifierComputeContext compute_context{nullptr, nmd.modifier.name};
return &modifier_log.get_tree_log(compute_context.hash());
}
static void attribute_search_update_fn(
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
{
@@ -1350,27 +1413,52 @@ static void attribute_search_update_fn(
if (nmd == nullptr) {
return;
}
const geo_log::ModifierLog *modifier_log = static_cast<const geo_log::ModifierLog *>(
nmd->runtime_eval_log);
if (modifier_log == nullptr) {
if (nmd->node_group == nullptr) {
return;
}
const geo_log::GeometryValueLog *geometry_log = data.is_output ?
modifier_log->output_geometry_log() :
modifier_log->input_geometry_log();
if (geometry_log == nullptr) {
GeoTreeLog *tree_log = get_root_tree_log(*nmd);
if (tree_log == nullptr) {
return;
}
tree_log->ensure_existing_attributes();
nmd->node_group->ensure_topology_cache();
Span<GeometryAttributeInfo> infos = geometry_log->attributes();
/* The shared attribute search code expects a span of pointers, so convert to that. */
Array<const GeometryAttributeInfo *> info_ptrs(infos.size());
for (const int i : infos.index_range()) {
info_ptrs[i] = &infos[i];
Vector<const bNodeSocket *> sockets_to_check;
if (data.is_output) {
for (const bNode *node : nmd->node_group->nodes_by_type("NodeGroupOutput")) {
for (const bNodeSocket *socket : node->input_sockets()) {
if (socket->type == SOCK_GEOMETRY) {
sockets_to_check.append(socket);
}
}
}
}
else {
for (const bNode *node : nmd->node_group->nodes_by_type("NodeGroupInput")) {
for (const bNodeSocket *socket : node->output_sockets()) {
if (socket->type == SOCK_GEOMETRY) {
sockets_to_check.append(socket);
}
}
}
}
Set<StringRef> names;
Vector<const GeometryAttributeInfo *> attributes;
for (const bNodeSocket *socket : sockets_to_check) {
const ValueLog *value_log = tree_log->find_socket_value_log(*socket);
if (value_log == nullptr) {
continue;
}
if (const GeometryInfoLog *geo_log = dynamic_cast<const GeometryInfoLog *>(value_log)) {
for (const GeometryAttributeInfo &attribute : geo_log->attributes) {
if (names.add(attribute.name)) {
attributes.append(&attribute);
}
}
}
}
blender::ui::attribute_search_add_items(
str, data.is_output, info_ptrs.as_span(), items, is_first);
str, data.is_output, attributes.as_span(), items, is_first);
}
static void attribute_search_exec_fn(bContext *C, void *data_v, void *item_v)
@@ -1401,8 +1489,7 @@ static void add_attribute_search_button(const bContext &C,
const bNodeSocket &socket,
const bool is_output)
{
const geo_log::ModifierLog *log = static_cast<geo_log::ModifierLog *>(nmd.runtime_eval_log);
if (log == nullptr) {
if (nmd.runtime_eval_log == nullptr) {
uiItemR(layout, md_ptr, rna_path_attribute_name.c_str(), 0, "", ICON_NONE);
return;
}
@@ -1627,15 +1714,14 @@ static void panel_draw(const bContext *C, Panel *panel)
}
/* Draw node warnings. */
if (nmd->runtime_eval_log != nullptr) {
const geo_log::ModifierLog &log = *static_cast<geo_log::ModifierLog *>(nmd->runtime_eval_log);
log.foreach_node_log([&](const geo_log::NodeLog &node_log) {
for (const geo_log::NodeWarning &warning : node_log.warnings()) {
if (warning.type != geo_log::NodeWarningType::Info) {
uiItemL(layout, warning.message.c_str(), ICON_ERROR);
}
GeoTreeLog *tree_log = get_root_tree_log(*nmd);
if (tree_log != nullptr) {
tree_log->ensure_node_warnings();
for (const NodeWarning &warning : tree_log->all_warnings) {
if (warning.type != NodeWarningType::Info) {
uiItemL(layout, warning.message.c_str(), ICON_ERROR);
}
});
}
}
modifier_panel_end(layout, ptr);
@@ -1672,17 +1758,14 @@ static void internal_dependencies_panel_draw(const bContext *UNUSED(C), Panel *p
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data);
if (nmd->runtime_eval_log == nullptr) {
GeoTreeLog *tree_log = get_root_tree_log(*nmd);
if (tree_log == nullptr) {
return;
}
const geo_log::ModifierLog &log = *static_cast<geo_log::ModifierLog *>(nmd->runtime_eval_log);
Map<std::string, eNamedAttrUsage> usage_by_attribute;
log.foreach_node_log([&](const geo_log::NodeLog &node_log) {
for (const geo_log::UsedNamedAttribute &used_attribute : node_log.used_named_attributes()) {
usage_by_attribute.lookup_or_add_as(used_attribute.name,
used_attribute.usage) |= used_attribute.usage;
}
});
tree_log->ensure_used_named_attributes();
const Map<std::string, NamedAttributeUsage> &usage_by_attribute =
tree_log->used_named_attributes;
if (usage_by_attribute.is_empty()) {
uiItemL(layout, IFACE_("No named attributes used"), ICON_INFO);
@@ -1691,7 +1774,7 @@ static void internal_dependencies_panel_draw(const bContext *UNUSED(C), Panel *p
struct NameWithUsage {
StringRefNull name;
eNamedAttrUsage usage;
NamedAttributeUsage usage;
};
Vector<NameWithUsage> sorted_used_attribute;
@@ -1706,20 +1789,20 @@ static void internal_dependencies_panel_draw(const bContext *UNUSED(C), Panel *p
for (const NameWithUsage &attribute : sorted_used_attribute) {
const StringRefNull attribute_name = attribute.name;
const eNamedAttrUsage usage = attribute.usage;
const NamedAttributeUsage usage = attribute.usage;
/* #uiLayoutRowWithHeading doesn't seem to work in this case. */
uiLayout *split = uiLayoutSplit(layout, 0.4f, false);
std::stringstream ss;
Vector<std::string> usages;
if ((usage & eNamedAttrUsage::Read) != eNamedAttrUsage::None) {
if ((usage & NamedAttributeUsage::Read) != NamedAttributeUsage::None) {
usages.append(TIP_("Read"));
}
if ((usage & eNamedAttrUsage::Write) != eNamedAttrUsage::None) {
if ((usage & NamedAttributeUsage::Write) != NamedAttributeUsage::None) {
usages.append(TIP_("Write"));
}
if ((usage & eNamedAttrUsage::Remove) != eNamedAttrUsage::None) {
if ((usage & NamedAttributeUsage::Remove) != NamedAttributeUsage::None) {
usages.append(TIP_("Remove"));
}
for (const int i : usages.index_range()) {

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_generic_pointer.hh"
#include "BLI_map.hh"
#include "NOD_derived_node_tree.hh"
#include "NOD_geometry_nodes_eval_log.hh"
#include "NOD_multi_function.hh"
#include "DNA_modifier_types.h"
#include "FN_multi_function.hh"
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
namespace blender::modifiers::geometry_nodes {
using namespace nodes::derived_node_tree_types;
struct GeometryNodesEvaluationParams {
blender::LinearAllocator<> allocator;
Map<DOutputSocket, GMutablePointer> input_values;
Vector<DInputSocket> output_sockets;
/* These sockets will be computed but are not part of the output. Their value can be retrieved in
* `log_socket_value_fn`. These sockets are not part of `output_sockets` because then the
* evaluator would have to keep the socket values in memory until the end, which might not be
* necessary in all cases. Sometimes `log_socket_value_fn` might just want to look at the value
* and then it can be freed. */
Vector<DSocket> force_compute_sockets;
nodes::NodeMultiFunctions *mf_by_node;
const NodesModifierData *modifier_;
Depsgraph *depsgraph;
Object *self_object;
geo_log::GeoLogger *geo_logger;
Vector<GMutablePointer> r_output_values;
};
void evaluate_geometry_nodes(GeometryNodesEvaluationParams &params);
} // namespace blender::modifiers::geometry_nodes

View File

@@ -40,7 +40,8 @@ set(INC
set(SRC
intern/derived_node_tree.cc
intern/geometry_nodes_eval_log.cc
intern/geometry_nodes_log.cc
intern/geometry_nodes_to_lazy_function_graph.cc
intern/math_functions.cc
intern/node_common.cc
intern/node_declaration.cc
@@ -58,7 +59,7 @@ set(SRC
NOD_function.h
NOD_geometry.h
NOD_geometry_exec.hh
NOD_geometry_nodes_eval_log.hh
NOD_geometry_nodes_to_lazy_function_graph.hh
NOD_math_functions.hh
NOD_multi_function.hh
NOD_node_declaration.hh

View File

@@ -3,6 +3,7 @@
#pragma once
#include "FN_field.hh"
#include "FN_lazy_function.hh"
#include "FN_multi_function_builder.hh"
#include "BKE_geometry_fields.hh"
@@ -11,9 +12,8 @@
#include "DNA_node_types.h"
#include "NOD_derived_node_tree.hh"
#include "NOD_geometry_nodes_eval_log.hh"
#include "NOD_geometry_nodes_to_lazy_function_graph.hh"
struct Depsgraph;
struct ModifierData;
namespace blender::nodes {
@@ -40,75 +40,18 @@ using fn::FieldInput;
using fn::FieldOperation;
using fn::GField;
using fn::ValueOrField;
using geometry_nodes_eval_log::eNamedAttrUsage;
using geometry_nodes_eval_log::NodeWarningType;
/**
* This class exists to separate the memory management details of the geometry nodes evaluator
* from the node execution functions and related utilities.
*/
class GeoNodeExecParamsProvider {
public:
DNode dnode;
const Object *self_object = nullptr;
const ModifierData *modifier = nullptr;
Depsgraph *depsgraph = nullptr;
geometry_nodes_eval_log::GeoLogger *logger = nullptr;
/**
* Returns true when the node is allowed to get/extract the input value. The identifier is
* expected to be valid. This may return false if the input value has been consumed already.
*/
virtual bool can_get_input(StringRef identifier) const = 0;
/**
* Returns true when the node is allowed to set the output value. The identifier is expected to
* be valid. This may return false if the output value has been set already.
*/
virtual bool can_set_output(StringRef identifier) const = 0;
/**
* Take ownership of an input value. The caller is responsible for destructing the value. It does
* not have to be freed, because the memory is managed by the geometry nodes evaluator.
*/
virtual GMutablePointer extract_input(StringRef identifier) = 0;
/**
* Similar to #extract_input, but has to be used for multi-input sockets.
*/
virtual Vector<GMutablePointer> extract_multi_input(StringRef identifier) = 0;
/**
* Get the input value for the identifier without taking ownership of it.
*/
virtual GPointer get_input(StringRef identifier) const = 0;
/**
* Prepare a memory buffer for an output value of the node. The returned memory has to be
* initialized by the caller. The identifier and type are expected to be correct.
*/
virtual GMutablePointer alloc_output_value(const CPPType &type) = 0;
/**
* The value has been allocated with #alloc_output_value.
*/
virtual void set_output(StringRef identifier, GMutablePointer value) = 0;
/* A description for these methods is provided in GeoNodeExecParams. */
virtual void set_input_unused(StringRef identifier) = 0;
virtual bool output_is_required(StringRef identifier) const = 0;
virtual bool lazy_require_input(StringRef identifier) = 0;
virtual bool lazy_output_is_required(StringRef identifier) const = 0;
virtual void set_default_remaining_outputs() = 0;
};
using geo_eval_log::NamedAttributeUsage;
using geo_eval_log::NodeWarningType;
class GeoNodeExecParams {
private:
GeoNodeExecParamsProvider *provider_;
const bNode &node_;
lf::Params &params_;
const lf::Context &lf_context_;
public:
GeoNodeExecParams(GeoNodeExecParamsProvider &provider) : provider_(&provider)
GeoNodeExecParams(const bNode &node, lf::Params &params, const lf::Context &lf_context)
: node_(node), params_(params), lf_context_(lf_context)
{
}
@@ -116,20 +59,6 @@ class GeoNodeExecParams {
static inline constexpr bool is_field_base_type_v =
is_same_any_v<T, float, int, bool, ColorGeometry4f, float3, std::string>;
/**
* Get the input value for the input socket with the given identifier.
*
* The node calling becomes responsible for destructing the value before it is done
* executing. This method can only be called once for each identifier.
*/
GMutablePointer extract_input(StringRef identifier)
{
#ifdef DEBUG
this->check_input_access(identifier);
#endif
return provider_->extract_input(identifier);
}
/**
* Get the input value for the input socket with the given identifier.
*
@@ -151,8 +80,8 @@ class GeoNodeExecParams {
#ifdef DEBUG
this->check_input_access(identifier, &CPPType::get<T>());
#endif
GMutablePointer gvalue = this->extract_input(identifier);
T value = gvalue.relocate_out<T>();
const int index = this->get_input_index(identifier);
T value = params_.extract_input<T>(index);
if constexpr (std::is_same_v<T, GeometrySet>) {
this->check_input_geometry_set(identifier, value);
}
@@ -163,27 +92,6 @@ class GeoNodeExecParams {
void check_input_geometry_set(StringRef identifier, const GeometrySet &geometry_set) const;
void check_output_geometry_set(const GeometrySet &geometry_set) const;
/**
* Get input as vector for multi input socket with the given identifier.
*
* This method can only be called once for each identifier.
*/
template<typename T> Vector<T> extract_multi_input(StringRef identifier)
{
Vector<GMutablePointer> gvalues = provider_->extract_multi_input(identifier);
Vector<T> values;
for (GMutablePointer gvalue : gvalues) {
if constexpr (is_field_base_type_v<T>) {
const ValueOrField<T> value_or_field = gvalue.relocate_out<ValueOrField<T>>();
values.append(value_or_field.as_value());
}
else {
values.append(gvalue.relocate_out<T>());
}
}
return values;
}
/**
* Get the input value for the input socket with the given identifier.
*/
@@ -202,9 +110,8 @@ class GeoNodeExecParams {
#ifdef DEBUG
this->check_input_access(identifier, &CPPType::get<T>());
#endif
GPointer gvalue = provider_->get_input(identifier);
BLI_assert(gvalue.is_type<T>());
const T &value = *(const T *)gvalue.get();
const int index = this->get_input_index(identifier);
const T &value = params_.get_input<T>(index);
if constexpr (std::is_same_v<T, GeometrySet>) {
this->check_input_geometry_set(identifier, value);
}
@@ -226,25 +133,37 @@ class GeoNodeExecParams {
this->set_output(identifier, ValueOrField<BaseType>(std::forward<T>(value)));
}
else {
const CPPType &type = CPPType::get<StoredT>();
#ifdef DEBUG
const CPPType &type = CPPType::get<StoredT>();
this->check_output_access(identifier, type);
#endif
if constexpr (std::is_same_v<StoredT, GeometrySet>) {
this->check_output_geometry_set(value);
}
GMutablePointer gvalue = provider_->alloc_output_value(type);
new (gvalue.get()) StoredT(std::forward<T>(value));
provider_->set_output(identifier, gvalue);
const int index = this->get_output_index(identifier);
params_.set_output(index, std::forward<T>(value));
}
}
geo_eval_log::GeoTreeLogger *get_local_tree_logger() const
{
GeoNodesLFUserData *user_data = this->user_data();
BLI_assert(user_data != nullptr);
const ComputeContext *compute_context = user_data->compute_context;
BLI_assert(compute_context != nullptr);
if (user_data->modifier_data->eval_log == nullptr) {
return nullptr;
}
return &user_data->modifier_data->eval_log->get_local_tree_logger(*compute_context);
}
/**
* Tell the evaluator that a specific input won't be used anymore.
*/
void set_input_unused(StringRef identifier)
{
provider_->set_input_unused(identifier);
const int index = this->get_input_index(identifier);
params_.set_input_unused(index);
}
/**
@@ -254,7 +173,8 @@ class GeoNodeExecParams {
*/
bool output_is_required(StringRef identifier) const
{
return provider_->output_is_required(identifier);
const int index = this->get_output_index(identifier);
return params_.get_output_usage(index) != lf::ValueUsage::Unused;
}
/**
@@ -265,7 +185,8 @@ class GeoNodeExecParams {
*/
bool lazy_require_input(StringRef identifier)
{
return provider_->lazy_require_input(identifier);
const int index = this->get_input_index(identifier);
return params_.try_get_input_data_ptr_or_request(index) == nullptr;
}
/**
@@ -275,7 +196,8 @@ class GeoNodeExecParams {
*/
bool lazy_output_is_required(StringRef identifier)
{
return provider_->lazy_output_is_required(identifier);
const int index = this->get_output_index(identifier);
return params_.get_output_usage(index) == lf::ValueUsage::Used;
}
/**
@@ -283,17 +205,32 @@ class GeoNodeExecParams {
*/
const bNode &node() const
{
return *provider_->dnode;
return node_;
}
const Object *self_object() const
{
return provider_->self_object;
if (const auto *data = this->user_data()) {
if (data->modifier_data) {
return data->modifier_data->self_object;
}
}
return nullptr;
}
Depsgraph *depsgraph() const
{
return provider_->depsgraph;
if (const auto *data = this->user_data()) {
if (data->modifier_data) {
return data->modifier_data->depsgraph;
}
}
return nullptr;
}
GeoNodesLFUserData *user_data() const
{
return dynamic_cast<GeoNodesLFUserData *>(lf_context_.user_data);
}
/**
@@ -306,7 +243,7 @@ class GeoNodeExecParams {
void set_default_remaining_outputs();
void used_named_attribute(std::string attribute_name, eNamedAttrUsage usage);
void used_named_attribute(std::string attribute_name, NamedAttributeUsage usage);
private:
/* Utilities for detecting common errors at when using this class. */
@@ -315,6 +252,38 @@ class GeoNodeExecParams {
/* Find the active socket with the input name (not the identifier). */
const bNodeSocket *find_available_socket(const StringRef name) const;
int get_input_index(const StringRef identifier) const
{
int counter = 0;
for (const bNodeSocket *socket : node_.input_sockets()) {
if (!socket->is_available()) {
continue;
}
if (socket->identifier == identifier) {
return counter;
}
counter++;
}
BLI_assert_unreachable();
return -1;
}
int get_output_index(const StringRef identifier) const
{
int counter = 0;
for (const bNodeSocket *socket : node_.output_sockets()) {
if (!socket->is_available()) {
continue;
}
if (socket->identifier == identifier) {
return counter;
}
counter++;
}
BLI_assert_unreachable();
return -1;
}
};
} // namespace blender::nodes

View File

@@ -1,411 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/**
* Many geometry nodes related UI features need access to data produced during evaluation. Not only
* is the final output required but also the intermediate results. Those features include
* attribute search, node warnings, socket inspection and the viewer node.
*
* This file provides the framework for logging data during evaluation and accessing the data after
* evaluation.
*
* During logging every thread gets its own local logger to avoid too much locking (logging
* generally happens for every socket). After geometry nodes evaluation is done, the thread-local
* logging information is combined and post-processed to make it easier for the UI to lookup.
* necessary information.
*/
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_function_ref.hh"
#include "BLI_generic_pointer.hh"
#include "BLI_linear_allocator.hh"
#include "BLI_map.hh"
#include "BKE_geometry_set.hh"
#include "NOD_derived_node_tree.hh"
#include "FN_field.hh"
#include <chrono>
struct SpaceNode;
struct SpaceSpreadsheet;
namespace blender::nodes::geometry_nodes_eval_log {
/** Contains information about a value that has been computed during geometry nodes evaluation. */
class ValueLog {
public:
virtual ~ValueLog() = default;
};
/** Contains an owned copy of a value of a generic type. */
class GenericValueLog : public ValueLog {
private:
GMutablePointer data_;
public:
GenericValueLog(GMutablePointer data) : data_(data)
{
}
~GenericValueLog()
{
data_.destruct();
}
GPointer value() const
{
return data_;
}
};
class GFieldValueLog : public ValueLog {
private:
fn::GField field_;
const CPPType &type_;
Vector<std::string> input_tooltips_;
public:
GFieldValueLog(fn::GField field, bool log_full_field);
const fn::GField &field() const
{
return field_;
}
Span<std::string> input_tooltips() const
{
return input_tooltips_;
}
const CPPType &type() const
{
return type_;
}
};
struct GeometryAttributeInfo {
std::string name;
/** Can be empty when #name does not actually exist on a geometry yet. */
std::optional<eAttrDomain> domain;
std::optional<eCustomDataType> data_type;
};
/** Contains information about a geometry set. In most cases this does not store the entire
* geometry set as this would require too much memory. */
class GeometryValueLog : public ValueLog {
private:
Vector<GeometryAttributeInfo> attributes_;
Vector<GeometryComponentType> component_types_;
std::unique_ptr<GeometrySet> full_geometry_;
public:
struct MeshInfo {
int verts_num, edges_num, faces_num;
};
struct CurveInfo {
int splines_num;
};
struct PointCloudInfo {
int points_num;
};
struct InstancesInfo {
int instances_num;
};
struct EditDataInfo {
bool has_deformed_positions;
bool has_deform_matrices;
};
std::optional<MeshInfo> mesh_info;
std::optional<CurveInfo> curve_info;
std::optional<PointCloudInfo> pointcloud_info;
std::optional<InstancesInfo> instances_info;
std::optional<EditDataInfo> edit_data_info;
GeometryValueLog(const GeometrySet &geometry_set, bool log_full_geometry = false);
Span<GeometryAttributeInfo> attributes() const
{
return attributes_;
}
Span<GeometryComponentType> component_types() const
{
return component_types_;
}
const GeometrySet *full_geometry() const
{
return full_geometry_.get();
}
};
enum class NodeWarningType {
Error,
Warning,
Info,
};
struct NodeWarning {
NodeWarningType type;
std::string message;
};
struct NodeWithWarning {
DNode node;
NodeWarning warning;
};
struct NodeWithExecutionTime {
DNode node;
std::chrono::microseconds exec_time;
};
struct NodeWithDebugMessage {
DNode node;
std::string message;
};
/** The same value can be referenced by multiple sockets when they are linked. */
struct ValueOfSockets {
Span<DSocket> sockets;
destruct_ptr<ValueLog> value;
};
enum class eNamedAttrUsage {
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Remove = 1 << 2,
};
ENUM_OPERATORS(eNamedAttrUsage, eNamedAttrUsage::Remove);
struct UsedNamedAttribute {
std::string name;
eNamedAttrUsage usage;
};
struct NodeWithUsedNamedAttribute {
DNode node;
UsedNamedAttribute attribute;
};
class GeoLogger;
class ModifierLog;
/** Every thread has its own local logger to avoid having to communicate between threads during
* evaluation. After evaluation the individual logs are combined. */
class LocalGeoLogger {
private:
/* Back pointer to the owner of this local logger. */
GeoLogger *main_logger_;
/* Allocator for the many small allocations during logging. This is in a `unique_ptr` so that
* ownership can be transferred later on. */
std::unique_ptr<LinearAllocator<>> allocator_;
Vector<ValueOfSockets> values_;
Vector<NodeWithWarning> node_warnings_;
Vector<NodeWithExecutionTime> node_exec_times_;
Vector<NodeWithDebugMessage> node_debug_messages_;
Vector<NodeWithUsedNamedAttribute> used_named_attributes_;
friend ModifierLog;
public:
LocalGeoLogger(GeoLogger &main_logger) : main_logger_(&main_logger)
{
this->allocator_ = std::make_unique<LinearAllocator<>>();
}
void log_value_for_sockets(Span<DSocket> sockets, GPointer value);
void log_multi_value_socket(DSocket socket, Span<GPointer> values);
void log_node_warning(DNode node, NodeWarningType type, std::string message);
void log_execution_time(DNode node, std::chrono::microseconds exec_time);
void log_used_named_attribute(DNode node, std::string attribute_name, eNamedAttrUsage usage);
/**
* Log a message that will be displayed in the node editor next to the node.
* This should only be used for debugging purposes and not to display information to users.
*/
void log_debug_message(DNode node, std::string message);
};
/** The root logger class. */
class GeoLogger {
private:
/**
* Log the entire value for these sockets, because they may be inspected afterwards.
* We don't log everything, because that would take up too much memory and cause significant
* slowdowns.
*/
Set<DSocket> log_full_sockets_;
threading::EnumerableThreadSpecific<LocalGeoLogger> threadlocals_;
/* These are only optional since they don't have a default constructor. */
std::unique_ptr<GeometryValueLog> input_geometry_log_;
std::unique_ptr<GeometryValueLog> output_geometry_log_;
friend LocalGeoLogger;
friend ModifierLog;
public:
GeoLogger(Set<DSocket> log_full_sockets)
: log_full_sockets_(std::move(log_full_sockets)),
threadlocals_([this]() { return LocalGeoLogger(*this); })
{
}
void log_input_geometry(const GeometrySet &geometry)
{
input_geometry_log_ = std::make_unique<GeometryValueLog>(geometry);
}
void log_output_geometry(const GeometrySet &geometry)
{
output_geometry_log_ = std::make_unique<GeometryValueLog>(geometry);
}
LocalGeoLogger &local()
{
return threadlocals_.local();
}
auto begin()
{
return threadlocals_.begin();
}
auto end()
{
return threadlocals_.end();
}
};
/** Contains information that has been logged for one specific socket. */
class SocketLog {
private:
ValueLog *value_ = nullptr;
friend ModifierLog;
public:
const ValueLog *value() const
{
return value_;
}
};
/** Contains information that has been logged for one specific node. */
class NodeLog {
private:
Vector<SocketLog> input_logs_;
Vector<SocketLog> output_logs_;
Vector<NodeWarning, 0> warnings_;
Vector<std::string, 0> debug_messages_;
Vector<UsedNamedAttribute, 0> used_named_attributes_;
std::chrono::microseconds exec_time_;
friend ModifierLog;
public:
const SocketLog *lookup_socket_log(eNodeSocketInOut in_out, int index) const;
const SocketLog *lookup_socket_log(const bNode &node, const bNodeSocket &socket) const;
void execution_time(std::chrono::microseconds exec_time);
Span<SocketLog> input_logs() const
{
return input_logs_;
}
Span<SocketLog> output_logs() const
{
return output_logs_;
}
Span<NodeWarning> warnings() const
{
return warnings_;
}
Span<std::string> debug_messages() const
{
return debug_messages_;
}
Span<UsedNamedAttribute> used_named_attributes() const
{
return used_named_attributes_;
}
std::chrono::microseconds execution_time() const
{
return exec_time_;
}
Vector<const GeometryAttributeInfo *> lookup_available_attributes() const;
};
/** Contains information that has been logged for one specific tree. */
class TreeLog {
private:
Map<std::string, destruct_ptr<NodeLog>> node_logs_;
Map<std::string, destruct_ptr<TreeLog>> child_logs_;
friend ModifierLog;
public:
const NodeLog *lookup_node_log(StringRef node_name) const;
const NodeLog *lookup_node_log(const bNode &node) const;
const TreeLog *lookup_child_log(StringRef node_name) const;
void foreach_node_log(FunctionRef<void(const NodeLog &)> fn) const;
};
/** Contains information about an entire geometry nodes evaluation. */
class ModifierLog {
private:
LinearAllocator<> allocator_;
/* Allocators of the individual loggers. */
Vector<std::unique_ptr<LinearAllocator<>>> logger_allocators_;
destruct_ptr<TreeLog> root_tree_logs_;
Vector<destruct_ptr<ValueLog>> logged_values_;
std::unique_ptr<GeometryValueLog> input_geometry_log_;
std::unique_ptr<GeometryValueLog> output_geometry_log_;
public:
ModifierLog(GeoLogger &logger);
const TreeLog &root_tree() const
{
return *root_tree_logs_;
}
/* Utilities to find logged information for a specific context. */
static const ModifierLog *find_root_by_node_editor_context(const SpaceNode &snode);
static const TreeLog *find_tree_by_node_editor_context(const SpaceNode &snode);
static const NodeLog *find_node_by_node_editor_context(const SpaceNode &snode,
const bNode &node);
static const NodeLog *find_node_by_node_editor_context(const SpaceNode &snode,
const StringRef node_name);
static const SocketLog *find_socket_by_node_editor_context(const SpaceNode &snode,
const bNode &node,
const bNodeSocket &socket);
static const NodeLog *find_node_by_spreadsheet_editor_context(
const SpaceSpreadsheet &sspreadsheet);
void foreach_node_log(FunctionRef<void(const NodeLog &)> fn) const;
const GeometryValueLog *input_geometry_log() const;
const GeometryValueLog *output_geometry_log() const;
private:
using LogByTreeContext = Map<const DTreeContext *, TreeLog *>;
TreeLog &lookup_or_add_tree_log(LogByTreeContext &log_by_tree_context,
const DTreeContext &tree_context);
NodeLog &lookup_or_add_node_log(LogByTreeContext &log_by_tree_context, DNode node);
SocketLog &lookup_or_add_socket_log(LogByTreeContext &log_by_tree_context, DSocket socket);
};
} // namespace blender::nodes::geometry_nodes_eval_log

View File

@@ -0,0 +1,213 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <chrono>
#include "BLI_compute_context.hh"
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_generic_pointer.hh"
#include "BLI_multi_value_map.hh"
#include "BKE_attribute.h"
#include "BKE_geometry_set.hh"
#include "FN_field.hh"
#include "DNA_node_types.h"
struct SpaceNode;
struct SpaceSpreadsheet;
struct NodesModifierData;
namespace blender::nodes::geo_eval_log {
using fn::GField;
enum class NodeWarningType {
Error,
Warning,
Info,
};
struct NodeWarning {
NodeWarningType type;
std::string message;
};
enum class NamedAttributeUsage {
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Remove = 1 << 2,
};
ENUM_OPERATORS(NamedAttributeUsage, NamedAttributeUsage::Remove);
class ValueLog {
public:
virtual ~ValueLog() = default;
};
class GenericValueLog : public ValueLog {
public:
GMutablePointer value;
GenericValueLog(const GMutablePointer value) : value(value)
{
}
~GenericValueLog()
{
this->value.destruct();
}
};
class FieldInfoLog : public ValueLog {
public:
const CPPType &type;
Vector<std::string> input_tooltips;
FieldInfoLog(const GField &field);
};
struct GeometryAttributeInfo {
std::string name;
/** Can be empty when #name does not actually exist on a geometry yet. */
std::optional<eAttrDomain> domain;
std::optional<eCustomDataType> data_type;
};
class GeometryInfoLog : public ValueLog {
public:
Vector<GeometryAttributeInfo> attributes;
Vector<GeometryComponentType> component_types;
struct MeshInfo {
int verts_num, edges_num, faces_num;
};
struct CurveInfo {
int splines_num;
};
struct PointCloudInfo {
int points_num;
};
struct InstancesInfo {
int instances_num;
};
struct EditDataInfo {
bool has_deformed_positions;
bool has_deform_matrices;
};
std::optional<MeshInfo> mesh_info;
std::optional<CurveInfo> curve_info;
std::optional<PointCloudInfo> pointcloud_info;
std::optional<InstancesInfo> instances_info;
std::optional<EditDataInfo> edit_data_info;
GeometryInfoLog(const GeometrySet &geometry_set);
};
class ViewerNodeLog {
public:
GeometrySet geometry;
GField field;
};
using Clock = std::chrono::steady_clock;
using TimePoint = Clock::time_point;
class GeoTreeLogger {
public:
std::optional<ComputeContextHash> parent_hash;
std::optional<std::string> group_node_name;
Vector<ComputeContextHash> children_hashes;
LinearAllocator<> *allocator = nullptr;
Vector<std::pair<std::string, NodeWarning>> node_warnings;
Vector<destruct_ptr<ValueLog>> socket_values_owner;
Vector<std::tuple<std::string, std::string, ValueLog *>> input_socket_values;
Vector<std::tuple<std::string, std::string, ValueLog *>> output_socket_values;
Vector<std::tuple<std::string, TimePoint, TimePoint>> node_execution_times;
Vector<std::tuple<std::string, destruct_ptr<ViewerNodeLog>>, 0> viewer_node_logs_;
Vector<std::tuple<std::string, std::string, NamedAttributeUsage>, 0> used_named_attributes_;
GeoTreeLogger();
~GeoTreeLogger();
void log_value(const bNode &node, const bNodeSocket &socket, GPointer value);
void log_viewer_node(const bNode &viewer_node, const GeometrySet &geometry, const GField &field);
};
class GeoNodeLog {
public:
Vector<NodeWarning> warnings;
std::chrono::nanoseconds run_time{0};
Map<std::string, ValueLog *> input_values_;
Map<std::string, ValueLog *> output_values_;
Map<std::string, NamedAttributeUsage> used_named_attributes;
GeoNodeLog();
~GeoNodeLog();
};
class GeoModifierLog;
class GeoTreeLog {
private:
GeoModifierLog *modifier_log_;
Vector<GeoTreeLogger *> tree_loggers_;
VectorSet<ComputeContextHash> children_hashes_;
bool reduced_node_warnings_ = false;
bool reduced_node_run_times_ = false;
bool reduced_socket_values_ = false;
bool reduced_viewer_node_logs_ = false;
bool reduced_existing_attributes_ = false;
bool reduced_used_named_attributes_ = false;
public:
Map<std::string, GeoNodeLog> nodes;
Map<std::string, ViewerNodeLog *, 0> viewer_node_logs;
Vector<NodeWarning> all_warnings;
std::chrono::nanoseconds run_time_sum{0};
Vector<const GeometryAttributeInfo *> existing_attributes;
Map<std::string, NamedAttributeUsage> used_named_attributes;
GeoTreeLog(GeoModifierLog *modifier_log, Vector<GeoTreeLogger *> tree_loggers);
~GeoTreeLog();
void ensure_node_warnings();
void ensure_node_run_time();
void ensure_socket_values();
void ensure_viewer_node_logs();
void ensure_existing_attributes();
void ensure_used_named_attributes();
ValueLog *find_socket_value_log(const bNodeSocket &query_socket);
};
class GeoModifierLog {
private:
struct LocalData {
LinearAllocator<> allocator;
Map<ComputeContextHash, destruct_ptr<GeoTreeLogger>> tree_logger_by_context;
};
threading::EnumerableThreadSpecific<LocalData> data_per_thread_;
Map<ComputeContextHash, std::unique_ptr<GeoTreeLog>> tree_logs_;
public:
GeoTreeLogger &get_local_tree_logger(const ComputeContext &compute_context);
GeoTreeLog &get_tree_log(const ComputeContextHash &compute_context_hash);
struct ObjectAndModifier {
const Object *object;
const NodesModifierData *nmd;
};
static std::optional<ObjectAndModifier> get_modifier_for_node_editor(const SpaceNode &snode);
static GeoTreeLog *get_tree_log_for_node_editor(const SpaceNode &snode);
static const ViewerNodeLog *find_viewer_node_log_for_spreadsheet(
const SpaceSpreadsheet &sspreadsheet);
};
} // namespace blender::nodes::geo_eval_log

View File

@@ -0,0 +1,91 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "FN_lazy_function_graph.hh"
#include "FN_lazy_function_graph_executor.hh"
#include "NOD_geometry_nodes_log.hh"
#include "NOD_multi_function.hh"
#include "BLI_compute_context.hh"
struct Object;
struct Depsgraph;
namespace blender::nodes {
namespace lf = fn::lazy_function;
using lf::LazyFunction;
struct GeoNodesModifierData {
const Object *self_object = nullptr;
Depsgraph *depsgraph = nullptr;
geo_eval_log::GeoModifierLog *eval_log = nullptr;
const MultiValueMap<ComputeContextHash, const lf::FunctionNode *> *side_effect_nodes;
};
struct GeoNodesLFUserData : public lf::UserData {
GeoNodesModifierData *modifier_data = nullptr;
const ComputeContext *compute_context = nullptr;
};
struct GeometryNodeLazyFunctionMapping {
Map<const bNodeSocket *, lf::Socket *> dummy_socket_map;
Vector<lf::OutputSocket *> group_input_sockets;
MultiValueMap<const lf::Socket *, const bNodeSocket *> bsockets_by_lf_socket_map;
Map<const bNode *, const lf::FunctionNode *> group_node_map;
Map<const bNode *, const lf::FunctionNode *> viewer_node_map;
};
struct GeometryNodesLazyFunctionGraphInfo {
LinearAllocator<> allocator;
std::unique_ptr<NodeMultiFunctions> node_multi_functions;
Vector<std::unique_ptr<LazyFunction>> functions;
Vector<GMutablePointer> values_to_destruct;
GeometryNodeLazyFunctionMapping mapping;
lf::Graph graph;
~GeometryNodesLazyFunctionGraphInfo()
{
for (GMutablePointer &p : this->values_to_destruct) {
p.destruct();
}
}
};
class GeometryNodesLazyFunctionLogger : public fn::lazy_function::GraphExecutor::Logger {
private:
const GeometryNodesLazyFunctionGraphInfo &lf_graph_info_;
public:
GeometryNodesLazyFunctionLogger(const GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
: lf_graph_info_(lf_graph_info)
{
}
void log_socket_value(const fn::lazy_function::Context &context,
const fn::lazy_function::Socket &lf_socket,
GPointer value) const override;
};
class GeometryNodesLazyFunctionSideEffectProvider
: public fn::lazy_function::GraphExecutor::SideEffectProvider {
private:
const GeometryNodesLazyFunctionGraphInfo &lf_graph_info_;
public:
GeometryNodesLazyFunctionSideEffectProvider(
const GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
: lf_graph_info_(lf_graph_info)
{
}
Vector<const lf::FunctionNode *> get_nodes_with_side_effects(
const lf::Context &context) const override;
};
const GeometryNodesLazyFunctionGraphInfo *ensure_geometry_nodes_lazy_function_graph(
const bNodeTree &btree);
} // namespace blender::nodes

View File

@@ -6,8 +6,6 @@
#include "DNA_node_types.h"
#include "NOD_derived_node_tree.hh"
namespace blender::nodes {
using namespace fn::multi_function_types;
@@ -60,9 +58,9 @@ class NodeMultiFunctions {
Map<const bNode *, Item> map_;
public:
NodeMultiFunctions(const DerivedNodeTree &tree);
NodeMultiFunctions(const bNodeTree &tree);
const Item &try_get(const DNode &node) const;
const Item &try_get(const bNode &node) const;
};
/* -------------------------------------------------------------------- */
@@ -107,10 +105,10 @@ inline void NodeMultiFunctionBuilder::construct_and_set_matching_fn(Args &&...ar
/** \name #NodeMultiFunctions Inline Methods
* \{ */
inline const NodeMultiFunctions::Item &NodeMultiFunctions::try_get(const DNode &node) const
inline const NodeMultiFunctions::Item &NodeMultiFunctions::try_get(const bNode &node) const
{
static Item empty_item;
const Item *item = map_.lookup_ptr(node.bnode());
const Item *item = map_.lookup_ptr(&node);
if (item == nullptr) {
return empty_item;
}

View File

@@ -4,3 +4,4 @@
#include "NOD_geometry_exec.hh"
BLI_CPP_TYPE_MAKE(GeometrySet, GeometrySet, CPPTypeFlags::Printable);
BLI_CPP_TYPE_MAKE(GeometrySetVector, blender::Vector<GeometrySet>, CPPTypeFlags::None);

View File

@@ -93,7 +93,7 @@ static void node_geo_exec(GeoNodeExecParams params)
/* The instance transform matrices are owned by the instance group, so we have to
* keep all of them around for use during the boolean operation. */
Vector<bke::GeometryInstanceGroup> set_groups;
Vector<GeometrySet> geometry_sets = params.extract_multi_input<GeometrySet>("Mesh 2");
Vector<GeometrySet> geometry_sets = params.extract_input<Vector<GeometrySet>>("Mesh 2");
for (const GeometrySet &geometry_set : geometry_sets) {
bke::geometry_set_gather_instances(geometry_set, set_groups);
}

View File

@@ -12,7 +12,7 @@ static void node_declare(NodeDeclarationBuilder &b)
static void node_geo_exec(GeoNodeExecParams params)
{
Vector<GeometrySet> geometries = params.extract_multi_input<GeometrySet>("Geometry");
Vector<GeometrySet> geometries = params.extract_input<Vector<GeometrySet>>("Geometry");
GeometrySet instances_geometry;
InstancesComponent &instances_component =
instances_geometry.get_component_for_write<InstancesComponent>();

View File

@@ -88,7 +88,7 @@ static void node_geo_exec(GeoNodeExecParams params)
return;
}
params.used_named_attribute(name, eNamedAttrUsage::Read);
params.used_named_attribute(name, NamedAttributeUsage::Read);
switch (data_type) {
case CD_PROP_FLOAT:

View File

@@ -177,7 +177,7 @@ static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet
static void node_geo_exec(GeoNodeExecParams params)
{
Vector<GeometrySet> geometry_sets = params.extract_multi_input<GeometrySet>("Geometry");
Vector<GeometrySet> geometry_sets = params.extract_input<Vector<GeometrySet>>("Geometry");
GeometrySet geometry_set_result;
join_component_type<MeshComponent>(geometry_sets, geometry_set_result);

View File

@@ -55,7 +55,7 @@ static void node_geo_exec(GeoNodeExecParams params)
});
if (attribute_exists && !cannot_delete) {
params.used_named_attribute(name, eNamedAttrUsage::Remove);
params.used_named_attribute(name, NamedAttributeUsage::Remove);
}
if (!attribute_exists) {

View File

@@ -149,7 +149,7 @@ static void node_geo_exec(GeoNodeExecParams params)
return;
}
params.used_named_attribute(name, eNamedAttrUsage::Write);
params.used_named_attribute(name, NamedAttributeUsage::Write);
const NodeGeometryStoreNamedAttribute &storage = node_storage(params.node());
const eCustomDataType data_type = static_cast<eCustomDataType>(storage.data_type);

View File

@@ -13,12 +13,13 @@ static void node_declare(NodeDeclarationBuilder &b)
static void node_geo_exec(GeoNodeExecParams params)
{
Vector<std::string> strings = params.extract_multi_input<std::string>("Strings");
Vector<fn::ValueOrField<std::string>> strings =
params.extract_input<Vector<fn::ValueOrField<std::string>>>("Strings");
const std::string delim = params.extract_input<std::string>("Delimiter");
std::string output;
for (const int i : strings.index_range()) {
output += strings[i];
output += strings[i].as_value();
if (i < (strings.size() - 1)) {
output += delim;
}

View File

@@ -1,520 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_geometry_nodes_eval_log.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set_instances.hh"
#include "DNA_modifier_types.h"
#include "DNA_space_types.h"
#include "FN_field_cpp_type.hh"
#include "BLT_translation.h"
#include <chrono>
namespace blender::nodes::geometry_nodes_eval_log {
using fn::FieldCPPType;
using fn::FieldInput;
using fn::GField;
using fn::ValueOrFieldCPPType;
ModifierLog::ModifierLog(GeoLogger &logger)
: input_geometry_log_(std::move(logger.input_geometry_log_)),
output_geometry_log_(std::move(logger.output_geometry_log_))
{
root_tree_logs_ = allocator_.construct<TreeLog>();
LogByTreeContext log_by_tree_context;
/* Combine all the local loggers that have been used by separate threads. */
for (LocalGeoLogger &local_logger : logger) {
/* Take ownership of the allocator. */
logger_allocators_.append(std::move(local_logger.allocator_));
for (ValueOfSockets &value_of_sockets : local_logger.values_) {
ValueLog *value_log = value_of_sockets.value.get();
/* Take centralized ownership of the logged value. It might be referenced by multiple
* sockets. */
logged_values_.append(std::move(value_of_sockets.value));
for (const DSocket &socket : value_of_sockets.sockets) {
SocketLog &socket_log = this->lookup_or_add_socket_log(log_by_tree_context, socket);
socket_log.value_ = value_log;
}
}
for (NodeWithWarning &node_with_warning : local_logger.node_warnings_) {
NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context,
node_with_warning.node);
node_log.warnings_.append(node_with_warning.warning);
}
for (NodeWithExecutionTime &node_with_exec_time : local_logger.node_exec_times_) {
NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context,
node_with_exec_time.node);
node_log.exec_time_ = node_with_exec_time.exec_time;
}
for (NodeWithDebugMessage &debug_message : local_logger.node_debug_messages_) {
NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context, debug_message.node);
node_log.debug_messages_.append(debug_message.message);
}
for (NodeWithUsedNamedAttribute &node_with_attribute_name :
local_logger.used_named_attributes_) {
NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context,
node_with_attribute_name.node);
node_log.used_named_attributes_.append(std::move(node_with_attribute_name.attribute));
}
}
}
TreeLog &ModifierLog::lookup_or_add_tree_log(LogByTreeContext &log_by_tree_context,
const DTreeContext &tree_context)
{
TreeLog *tree_log = log_by_tree_context.lookup_default(&tree_context, nullptr);
if (tree_log != nullptr) {
return *tree_log;
}
const DTreeContext *parent_context = tree_context.parent_context();
if (parent_context == nullptr) {
return *root_tree_logs_.get();
}
TreeLog &parent_log = this->lookup_or_add_tree_log(log_by_tree_context, *parent_context);
destruct_ptr<TreeLog> owned_tree_log = allocator_.construct<TreeLog>();
tree_log = owned_tree_log.get();
log_by_tree_context.add_new(&tree_context, tree_log);
parent_log.child_logs_.add_new(tree_context.parent_node()->name, std::move(owned_tree_log));
return *tree_log;
}
NodeLog &ModifierLog::lookup_or_add_node_log(LogByTreeContext &log_by_tree_context, DNode node)
{
TreeLog &tree_log = this->lookup_or_add_tree_log(log_by_tree_context, *node.context());
NodeLog &node_log = *tree_log.node_logs_.lookup_or_add_cb(node->name, [&]() {
destruct_ptr<NodeLog> node_log = allocator_.construct<NodeLog>();
node_log->input_logs_.resize(node->input_sockets().size());
node_log->output_logs_.resize(node->output_sockets().size());
return node_log;
});
return node_log;
}
SocketLog &ModifierLog::lookup_or_add_socket_log(LogByTreeContext &log_by_tree_context,
DSocket socket)
{
NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context, socket.node());
MutableSpan<SocketLog> socket_logs = socket->is_input() ? node_log.input_logs_ :
node_log.output_logs_;
SocketLog &socket_log = socket_logs[socket->index()];
return socket_log;
}
void ModifierLog::foreach_node_log(FunctionRef<void(const NodeLog &)> fn) const
{
if (root_tree_logs_) {
root_tree_logs_->foreach_node_log(fn);
}
}
const GeometryValueLog *ModifierLog::input_geometry_log() const
{
return input_geometry_log_.get();
}
const GeometryValueLog *ModifierLog::output_geometry_log() const
{
return output_geometry_log_.get();
}
const NodeLog *TreeLog::lookup_node_log(StringRef node_name) const
{
const destruct_ptr<NodeLog> *node_log = node_logs_.lookup_ptr_as(node_name);
if (node_log == nullptr) {
return nullptr;
}
return node_log->get();
}
const NodeLog *TreeLog::lookup_node_log(const bNode &node) const
{
return this->lookup_node_log(node.name);
}
const TreeLog *TreeLog::lookup_child_log(StringRef node_name) const
{
const destruct_ptr<TreeLog> *tree_log = child_logs_.lookup_ptr_as(node_name);
if (tree_log == nullptr) {
return nullptr;
}
return tree_log->get();
}
void TreeLog::foreach_node_log(FunctionRef<void(const NodeLog &)> fn) const
{
for (auto node_log : node_logs_.items()) {
fn(*node_log.value);
}
for (auto child : child_logs_.items()) {
child.value->foreach_node_log(fn);
}
}
const SocketLog *NodeLog::lookup_socket_log(eNodeSocketInOut in_out, int index) const
{
BLI_assert(index >= 0);
Span<SocketLog> socket_logs = (in_out == SOCK_IN) ? input_logs_ : output_logs_;
if (index >= socket_logs.size()) {
return nullptr;
}
return &socket_logs[index];
}
const SocketLog *NodeLog::lookup_socket_log(const bNode &node, const bNodeSocket &socket) const
{
ListBase sockets = socket.in_out == SOCK_IN ? node.inputs : node.outputs;
int index = BLI_findindex(&sockets, &socket);
return this->lookup_socket_log((eNodeSocketInOut)socket.in_out, index);
}
GFieldValueLog::GFieldValueLog(fn::GField field, bool log_full_field) : type_(field.cpp_type())
{
const std::shared_ptr<const fn::FieldInputs> &field_input_nodes = field.node().field_inputs();
/* Put the deduplicated field inputs into a vector so that they can be sorted below. */
Vector<std::reference_wrapper<const FieldInput>> field_inputs;
if (field_input_nodes) {
field_inputs.extend(field_input_nodes->deduplicated_nodes.begin(),
field_input_nodes->deduplicated_nodes.end());
}
std::sort(
field_inputs.begin(), field_inputs.end(), [](const FieldInput &a, const FieldInput &b) {
const int index_a = (int)a.category();
const int index_b = (int)b.category();
if (index_a == index_b) {
return a.socket_inspection_name().size() < b.socket_inspection_name().size();
}
return index_a < index_b;
});
for (const FieldInput &field_input : field_inputs) {
input_tooltips_.append(field_input.socket_inspection_name());
}
if (log_full_field) {
field_ = std::move(field);
}
}
GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_full_geometry)
{
static std::array all_component_types = {GEO_COMPONENT_TYPE_CURVE,
GEO_COMPONENT_TYPE_INSTANCES,
GEO_COMPONENT_TYPE_MESH,
GEO_COMPONENT_TYPE_POINT_CLOUD,
GEO_COMPONENT_TYPE_VOLUME};
/* Keep track handled attribute names to make sure that we do not return the same name twice.
* Currently #GeometrySet::attribute_foreach does not do that. Note that this will merge
* attributes with the same name but different domains or data types on separate components. */
Set<StringRef> names;
geometry_set.attribute_foreach(
all_component_types,
true,
[&](const bke::AttributeIDRef &attribute_id,
const bke::AttributeMetaData &meta_data,
const GeometryComponent &UNUSED(component)) {
if (attribute_id.is_named() && names.add(attribute_id.name())) {
this->attributes_.append({attribute_id.name(), meta_data.domain, meta_data.data_type});
}
});
for (const GeometryComponent *component : geometry_set.get_components_for_read()) {
component_types_.append(component->type());
switch (component->type()) {
case GEO_COMPONENT_TYPE_MESH: {
const MeshComponent &mesh_component = *(const MeshComponent *)component;
MeshInfo &info = this->mesh_info.emplace();
info.verts_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_POINT);
info.edges_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_EDGE);
info.faces_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_FACE);
break;
}
case GEO_COMPONENT_TYPE_CURVE: {
const CurveComponent &curve_component = *(const CurveComponent *)component;
CurveInfo &info = this->curve_info.emplace();
info.splines_num = curve_component.attribute_domain_size(ATTR_DOMAIN_CURVE);
break;
}
case GEO_COMPONENT_TYPE_POINT_CLOUD: {
const PointCloudComponent &pointcloud_component = *(const PointCloudComponent *)component;
PointCloudInfo &info = this->pointcloud_info.emplace();
info.points_num = pointcloud_component.attribute_domain_size(ATTR_DOMAIN_POINT);
break;
}
case GEO_COMPONENT_TYPE_INSTANCES: {
const InstancesComponent &instances_component = *(const InstancesComponent *)component;
InstancesInfo &info = this->instances_info.emplace();
info.instances_num = instances_component.instances_num();
break;
}
case GEO_COMPONENT_TYPE_EDIT: {
const GeometryComponentEditData &edit_component = *(
const GeometryComponentEditData *)component;
if (const bke::CurvesEditHints *curve_edit_hints =
edit_component.curves_edit_hints_.get()) {
EditDataInfo &info = this->edit_data_info.emplace();
info.has_deform_matrices = curve_edit_hints->deform_mats.has_value();
info.has_deformed_positions = curve_edit_hints->positions.has_value();
}
break;
}
case GEO_COMPONENT_TYPE_VOLUME: {
break;
}
}
}
if (log_full_geometry) {
full_geometry_ = std::make_unique<GeometrySet>(geometry_set);
full_geometry_->ensure_owns_direct_data();
}
}
Vector<const GeometryAttributeInfo *> NodeLog::lookup_available_attributes() const
{
Vector<const GeometryAttributeInfo *> attributes;
Set<StringRef> names;
for (const SocketLog &socket_log : input_logs_) {
const ValueLog *value_log = socket_log.value();
if (const GeometryValueLog *geo_value_log = dynamic_cast<const GeometryValueLog *>(
value_log)) {
for (const GeometryAttributeInfo &attribute : geo_value_log->attributes()) {
if (names.add(attribute.name)) {
attributes.append(&attribute);
}
}
}
}
return attributes;
}
const ModifierLog *ModifierLog::find_root_by_node_editor_context(const SpaceNode &snode)
{
if (snode.id == nullptr) {
return nullptr;
}
if (GS(snode.id->name) != ID_OB) {
return nullptr;
}
Object *object = (Object *)snode.id;
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Nodes) {
NodesModifierData *nmd = (NodesModifierData *)md;
if (nmd->node_group == snode.nodetree) {
return (ModifierLog *)nmd->runtime_eval_log;
}
}
}
return nullptr;
}
const TreeLog *ModifierLog::find_tree_by_node_editor_context(const SpaceNode &snode)
{
const ModifierLog *eval_log = ModifierLog::find_root_by_node_editor_context(snode);
if (eval_log == nullptr) {
return nullptr;
}
Vector<bNodeTreePath *> tree_path_vec = snode.treepath;
if (tree_path_vec.is_empty()) {
return nullptr;
}
TreeLog *current = eval_log->root_tree_logs_.get();
for (bNodeTreePath *path : tree_path_vec.as_span().drop_front(1)) {
destruct_ptr<TreeLog> *tree_log = current->child_logs_.lookup_ptr_as(path->node_name);
if (tree_log == nullptr) {
return nullptr;
}
current = tree_log->get();
}
return current;
}
const NodeLog *ModifierLog::find_node_by_node_editor_context(const SpaceNode &snode,
const bNode &node)
{
const TreeLog *tree_log = ModifierLog::find_tree_by_node_editor_context(snode);
if (tree_log == nullptr) {
return nullptr;
}
return tree_log->lookup_node_log(node);
}
const NodeLog *ModifierLog::find_node_by_node_editor_context(const SpaceNode &snode,
const StringRef node_name)
{
const TreeLog *tree_log = ModifierLog::find_tree_by_node_editor_context(snode);
if (tree_log == nullptr) {
return nullptr;
}
return tree_log->lookup_node_log(node_name);
}
const SocketLog *ModifierLog::find_socket_by_node_editor_context(const SpaceNode &snode,
const bNode &node,
const bNodeSocket &socket)
{
const NodeLog *node_log = ModifierLog::find_node_by_node_editor_context(snode, node);
if (node_log == nullptr) {
return nullptr;
}
return node_log->lookup_socket_log(node, socket);
}
const NodeLog *ModifierLog::find_node_by_spreadsheet_editor_context(
const SpaceSpreadsheet &sspreadsheet)
{
Vector<SpreadsheetContext *> context_path = sspreadsheet.context_path;
if (context_path.size() <= 2) {
return nullptr;
}
if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) {
return nullptr;
}
if (context_path[1]->type != SPREADSHEET_CONTEXT_MODIFIER) {
return nullptr;
}
for (SpreadsheetContext *context : context_path.as_span().drop_front(2)) {
if (context->type != SPREADSHEET_CONTEXT_NODE) {
return nullptr;
}
}
Span<SpreadsheetContextNode *> node_contexts =
context_path.as_span().drop_front(2).cast<SpreadsheetContextNode *>();
Object *object = ((SpreadsheetContextObject *)context_path[0])->object;
StringRefNull modifier_name = ((SpreadsheetContextModifier *)context_path[1])->modifier_name;
if (object == nullptr) {
return nullptr;
}
const ModifierLog *eval_log = nullptr;
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Nodes) {
if (md->name == modifier_name) {
NodesModifierData *nmd = (NodesModifierData *)md;
eval_log = (const ModifierLog *)nmd->runtime_eval_log;
break;
}
}
}
if (eval_log == nullptr) {
return nullptr;
}
const TreeLog *tree_log = &eval_log->root_tree();
for (SpreadsheetContextNode *context : node_contexts.drop_back(1)) {
tree_log = tree_log->lookup_child_log(context->node_name);
if (tree_log == nullptr) {
return nullptr;
}
}
const NodeLog *node_log = tree_log->lookup_node_log(node_contexts.last()->node_name);
return node_log;
}
void LocalGeoLogger::log_value_for_sockets(Span<DSocket> sockets, GPointer value)
{
const CPPType &type = *value.type();
Span<DSocket> copied_sockets = allocator_->construct_array_copy(sockets);
if (type.is<GeometrySet>()) {
bool log_full_geometry = false;
for (const DSocket &socket : sockets) {
if (main_logger_->log_full_sockets_.contains(socket)) {
log_full_geometry = true;
break;
}
}
const GeometrySet &geometry_set = *value.get<GeometrySet>();
destruct_ptr<GeometryValueLog> value_log = allocator_->construct<GeometryValueLog>(
geometry_set, log_full_geometry);
values_.append({copied_sockets, std::move(value_log)});
}
else if (const ValueOrFieldCPPType *value_or_field_type =
dynamic_cast<const ValueOrFieldCPPType *>(&type)) {
const void *value_or_field = value.get();
if (value_or_field_type->is_field(value_or_field)) {
GField field = *value_or_field_type->get_field_ptr(value_or_field);
bool log_full_field = false;
if (!field.node().depends_on_input()) {
/* Always log constant fields so that their value can be shown in socket inspection.
* In the future we can also evaluate the field here and only store the value. */
log_full_field = true;
}
if (!log_full_field) {
for (const DSocket &socket : sockets) {
if (main_logger_->log_full_sockets_.contains(socket)) {
log_full_field = true;
break;
}
}
}
destruct_ptr<GFieldValueLog> value_log = allocator_->construct<GFieldValueLog>(
std::move(field), log_full_field);
values_.append({copied_sockets, std::move(value_log)});
}
else {
const CPPType &base_type = value_or_field_type->base_type();
const void *value = value_or_field_type->get_value_ptr(value_or_field);
void *buffer = allocator_->allocate(base_type.size(), base_type.alignment());
base_type.copy_construct(value, buffer);
destruct_ptr<GenericValueLog> value_log = allocator_->construct<GenericValueLog>(
GMutablePointer{base_type, buffer});
values_.append({copied_sockets, std::move(value_log)});
}
}
else {
void *buffer = allocator_->allocate(type.size(), type.alignment());
type.copy_construct(value.get(), buffer);
destruct_ptr<GenericValueLog> value_log = allocator_->construct<GenericValueLog>(
GMutablePointer{type, buffer});
values_.append({copied_sockets, std::move(value_log)});
}
}
void LocalGeoLogger::log_multi_value_socket(DSocket socket, Span<GPointer> values)
{
/* Doesn't have to be logged currently. */
UNUSED_VARS(socket, values);
}
void LocalGeoLogger::log_node_warning(DNode node, NodeWarningType type, std::string message)
{
node_warnings_.append({node, {type, std::move(message)}});
}
void LocalGeoLogger::log_execution_time(DNode node, std::chrono::microseconds exec_time)
{
node_exec_times_.append({node, exec_time});
}
void LocalGeoLogger::log_used_named_attribute(DNode node,
std::string attribute_name,
eNamedAttrUsage usage)
{
used_named_attributes_.append({node, {std::move(attribute_name), usage}});
}
void LocalGeoLogger::log_debug_message(DNode node, std::string message)
{
node_debug_messages_.append({node, std::move(message)});
}
} // namespace blender::nodes::geometry_nodes_eval_log

View File

@@ -0,0 +1,588 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_geometry_nodes_log.hh"
#include "NOD_geometry_nodes_to_lazy_function_graph.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_curves.hh"
#include "BKE_node_runtime.hh"
#include "FN_field_cpp_type.hh"
#include "DNA_modifier_types.h"
#include "DNA_space_types.h"
namespace blender::nodes::geo_eval_log {
using fn::FieldInput;
using fn::FieldInputs;
FieldInfoLog::FieldInfoLog(const GField &field) : type(field.cpp_type())
{
const std::shared_ptr<const fn::FieldInputs> &field_input_nodes = field.node().field_inputs();
/* Put the deduplicated field inputs into a vector so that they can be sorted below. */
Vector<std::reference_wrapper<const FieldInput>> field_inputs;
if (field_input_nodes) {
field_inputs.extend(field_input_nodes->deduplicated_nodes.begin(),
field_input_nodes->deduplicated_nodes.end());
}
std::sort(
field_inputs.begin(), field_inputs.end(), [](const FieldInput &a, const FieldInput &b) {
const int index_a = (int)a.category();
const int index_b = (int)b.category();
if (index_a == index_b) {
return a.socket_inspection_name().size() < b.socket_inspection_name().size();
}
return index_a < index_b;
});
for (const FieldInput &field_input : field_inputs) {
this->input_tooltips.append(field_input.socket_inspection_name());
}
}
GeometryInfoLog::GeometryInfoLog(const GeometrySet &geometry_set)
{
static std::array all_component_types = {GEO_COMPONENT_TYPE_CURVE,
GEO_COMPONENT_TYPE_INSTANCES,
GEO_COMPONENT_TYPE_MESH,
GEO_COMPONENT_TYPE_POINT_CLOUD,
GEO_COMPONENT_TYPE_VOLUME};
/* Keep track handled attribute names to make sure that we do not return the same name twice.
* Currently #GeometrySet::attribute_foreach does not do that. Note that this will merge
* attributes with the same name but different domains or data types on separate components. */
Set<StringRef> names;
geometry_set.attribute_foreach(
all_component_types,
true,
[&](const bke::AttributeIDRef &attribute_id,
const bke::AttributeMetaData &meta_data,
const GeometryComponent &UNUSED(component)) {
if (attribute_id.is_named() && names.add(attribute_id.name())) {
this->attributes.append({attribute_id.name(), meta_data.domain, meta_data.data_type});
}
});
for (const GeometryComponent *component : geometry_set.get_components_for_read()) {
this->component_types.append(component->type());
switch (component->type()) {
case GEO_COMPONENT_TYPE_MESH: {
const MeshComponent &mesh_component = *(const MeshComponent *)component;
MeshInfo &info = this->mesh_info.emplace();
info.verts_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_POINT);
info.edges_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_EDGE);
info.faces_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_FACE);
break;
}
case GEO_COMPONENT_TYPE_CURVE: {
const CurveComponent &curve_component = *(const CurveComponent *)component;
CurveInfo &info = this->curve_info.emplace();
info.splines_num = curve_component.attribute_domain_size(ATTR_DOMAIN_CURVE);
break;
}
case GEO_COMPONENT_TYPE_POINT_CLOUD: {
const PointCloudComponent &pointcloud_component = *(const PointCloudComponent *)component;
PointCloudInfo &info = this->pointcloud_info.emplace();
info.points_num = pointcloud_component.attribute_domain_size(ATTR_DOMAIN_POINT);
break;
}
case GEO_COMPONENT_TYPE_INSTANCES: {
const InstancesComponent &instances_component = *(const InstancesComponent *)component;
InstancesInfo &info = this->instances_info.emplace();
info.instances_num = instances_component.instances_num();
break;
}
case GEO_COMPONENT_TYPE_EDIT: {
const GeometryComponentEditData &edit_component = *(
const GeometryComponentEditData *)component;
if (const bke::CurvesEditHints *curve_edit_hints =
edit_component.curves_edit_hints_.get()) {
EditDataInfo &info = this->edit_data_info.emplace();
info.has_deform_matrices = curve_edit_hints->deform_mats.has_value();
info.has_deformed_positions = curve_edit_hints->positions.has_value();
}
break;
}
case GEO_COMPONENT_TYPE_VOLUME: {
break;
}
}
}
}
/* Avoid generating these in every translation unit. */
GeoTreeLogger::GeoTreeLogger() = default;
GeoTreeLogger::~GeoTreeLogger() = default;
GeoNodeLog::GeoNodeLog() = default;
GeoNodeLog::~GeoNodeLog() = default;
GeoTreeLog::GeoTreeLog(GeoModifierLog *modifier_log, Vector<GeoTreeLogger *> tree_loggers)
: modifier_log_(modifier_log), tree_loggers_(std::move(tree_loggers))
{
for (GeoTreeLogger *tree_logger : tree_loggers_) {
for (const ComputeContextHash &hash : tree_logger->children_hashes) {
children_hashes_.add(hash);
}
}
}
GeoTreeLog::~GeoTreeLog() = default;
void GeoTreeLogger::log_value(const bNode &node, const bNodeSocket &socket, const GPointer value)
{
const CPPType &type = *value.type();
auto store_logged_value = [&](destruct_ptr<ValueLog> value_log) {
auto &socket_values = socket.in_out == SOCK_IN ? this->input_socket_values :
this->output_socket_values;
socket_values.append({node.name, socket.identifier, value_log.get()});
this->socket_values_owner.append(std::move(value_log));
};
auto log_generic_value = [&](const CPPType &type, const void *value) {
void *buffer = this->allocator->allocate(type.size(), type.alignment());
type.copy_construct(value, buffer);
store_logged_value(this->allocator->construct<GenericValueLog>(GMutablePointer{type, buffer}));
};
if (type.is<GeometrySet>()) {
const GeometrySet &geometry = *value.get<GeometrySet>();
store_logged_value(this->allocator->construct<GeometryInfoLog>(geometry));
}
else if (const auto *value_or_field_type = dynamic_cast<const fn::ValueOrFieldCPPType *>(
&type)) {
const void *value_or_field = value.get();
const CPPType &base_type = value_or_field_type->base_type();
if (value_or_field_type->is_field(value_or_field)) {
const GField *field = value_or_field_type->get_field_ptr(value_or_field);
if (field->node().depends_on_input()) {
store_logged_value(this->allocator->construct<FieldInfoLog>(*field));
}
else {
BUFFER_FOR_CPP_TYPE_VALUE(base_type, value);
fn::evaluate_constant_field(*field, value);
log_generic_value(base_type, value);
}
}
else {
const void *value = value_or_field_type->get_value_ptr(value_or_field);
log_generic_value(base_type, value);
}
}
else {
log_generic_value(type, value.get());
}
}
void GeoTreeLogger::log_viewer_node(const bNode &viewer_node,
const GeometrySet &geometry,
const GField &field)
{
destruct_ptr<ViewerNodeLog> log = this->allocator->construct<ViewerNodeLog>();
log->geometry = geometry;
log->field = field;
log->geometry.ensure_owns_direct_data();
this->viewer_node_logs_.append({viewer_node.name, std::move(log)});
}
void GeoTreeLog::ensure_node_warnings()
{
if (reduced_node_warnings_) {
return;
}
for (GeoTreeLogger *tree_logger : tree_loggers_) {
for (const std::pair<std::string, NodeWarning> &warnings : tree_logger->node_warnings) {
this->nodes.lookup_or_add_default(warnings.first).warnings.append(warnings.second);
this->all_warnings.append(warnings.second);
}
}
for (const ComputeContextHash &child_hash : children_hashes_) {
GeoTreeLog &child_log = modifier_log_->get_tree_log(child_hash);
child_log.ensure_node_warnings();
const std::optional<std::string> &group_node_name =
child_log.tree_loggers_[0]->group_node_name;
if (group_node_name.has_value()) {
this->nodes.lookup_or_add_default(*group_node_name).warnings.extend(child_log.all_warnings);
}
this->all_warnings.extend(child_log.all_warnings);
}
reduced_node_warnings_ = true;
}
void GeoTreeLog::ensure_node_run_time()
{
if (reduced_node_run_times_) {
return;
}
for (GeoTreeLogger *tree_logger : tree_loggers_) {
for (const std::tuple<std::string, TimePoint, TimePoint> &timings :
tree_logger->node_execution_times) {
const StringRefNull node_name = std::get<0>(timings);
const std::chrono::nanoseconds duration = std::get<2>(timings) - std::get<1>(timings);
this->nodes.lookup_or_add_default_as(node_name).run_time += duration;
this->run_time_sum += duration;
}
}
for (const ComputeContextHash &child_hash : children_hashes_) {
GeoTreeLog &child_log = modifier_log_->get_tree_log(child_hash);
child_log.ensure_node_run_time();
const std::optional<std::string> &group_node_name =
child_log.tree_loggers_[0]->group_node_name;
if (group_node_name.has_value()) {
this->nodes.lookup_or_add_default(*group_node_name).run_time += child_log.run_time_sum;
}
this->run_time_sum += child_log.run_time_sum;
}
reduced_node_run_times_ = true;
}
void GeoTreeLog::ensure_socket_values()
{
if (reduced_socket_values_) {
return;
}
for (GeoTreeLogger *tree_logger : tree_loggers_) {
for (const std::tuple<std::string, std::string, ValueLog *> &value_log_data :
tree_logger->input_socket_values) {
this->nodes.lookup_or_add_as(std::get<0>(value_log_data))
.input_values_.add(std::get<1>(value_log_data), std::get<2>(value_log_data));
}
for (const std::tuple<std::string, std::string, ValueLog *> &value_log_data :
tree_logger->output_socket_values) {
this->nodes.lookup_or_add_as(std::get<0>(value_log_data))
.output_values_.add(std::get<1>(value_log_data), std::get<2>(value_log_data));
}
}
reduced_socket_values_ = true;
}
void GeoTreeLog::ensure_viewer_node_logs()
{
if (reduced_viewer_node_logs_) {
return;
}
for (GeoTreeLogger *tree_logger : tree_loggers_) {
for (const std::tuple<std::string, destruct_ptr<ViewerNodeLog>> &viewer_log :
tree_logger->viewer_node_logs_) {
this->viewer_node_logs.add(std::get<0>(viewer_log), std::get<1>(viewer_log).get());
}
}
reduced_viewer_node_logs_ = true;
}
void GeoTreeLog::ensure_existing_attributes()
{
if (reduced_existing_attributes_) {
return;
}
this->ensure_socket_values();
Set<StringRef> names;
auto handle_value_log = [&](const ValueLog &value_log) {
const GeometryInfoLog *geo_log = dynamic_cast<const GeometryInfoLog *>(&value_log);
if (geo_log == nullptr) {
return;
}
for (const GeometryAttributeInfo &attribute : geo_log->attributes) {
if (names.add(attribute.name)) {
this->existing_attributes.append(&attribute);
}
}
};
for (const GeoNodeLog &node_log : this->nodes.values()) {
for (const ValueLog *value_log : node_log.input_values_.values()) {
handle_value_log(*value_log);
}
for (const ValueLog *value_log : node_log.output_values_.values()) {
handle_value_log(*value_log);
}
}
reduced_existing_attributes_ = true;
}
void GeoTreeLog::ensure_used_named_attributes()
{
if (reduced_used_named_attributes_) {
return;
}
auto add_attribute = [&](const StringRef node_name,
const StringRef attribute_name,
const NamedAttributeUsage &usage) {
this->nodes.lookup_or_add_as(node_name).used_named_attributes.lookup_or_add_as(attribute_name,
usage) |= usage;
this->used_named_attributes.lookup_or_add_as(attribute_name, usage) |= usage;
};
for (GeoTreeLogger *tree_logger : tree_loggers_) {
for (const std::tuple<std::string, std::string, NamedAttributeUsage> &item :
tree_logger->used_named_attributes_) {
add_attribute(std::get<0>(item), std::get<1>(item), std::get<2>(item));
}
}
for (const ComputeContextHash &child_hash : children_hashes_) {
GeoTreeLog &child_log = modifier_log_->get_tree_log(child_hash);
child_log.ensure_used_named_attributes();
if (const std::optional<std::string> &group_node_name =
child_log.tree_loggers_[0]->group_node_name) {
for (const auto &item : child_log.used_named_attributes.items()) {
add_attribute(*group_node_name, item.key, item.value);
}
}
}
reduced_used_named_attributes_ = true;
}
ValueLog *GeoTreeLog::find_socket_value_log(const bNodeSocket &query_socket)
{
/**
* Geometry nodes does not log values for every socket. That would produce a lot of redundant
* data,because often many linked sockets have the same value. To find the logged value for a
* socket one might have to look at linked sockets as well.
*/
BLI_assert(reduced_socket_values_);
if (query_socket.is_multi_input()) {
/* Not supported currently. */
return nullptr;
}
Set<const bNodeSocket *> added_sockets;
Stack<const bNodeSocket *> sockets_to_check;
sockets_to_check.push(&query_socket);
added_sockets.add(&query_socket);
while (!sockets_to_check.is_empty()) {
const bNodeSocket &socket = *sockets_to_check.pop();
const bNode &node = socket.owner_node();
if (GeoNodeLog *node_log = this->nodes.lookup_ptr(node.name)) {
ValueLog *value_log = socket.is_input() ?
node_log->input_values_.lookup_default(socket.identifier,
nullptr) :
node_log->output_values_.lookup_default(socket.identifier,
nullptr);
if (value_log != nullptr) {
return value_log;
}
}
if (socket.is_input()) {
const Span<const bNodeLink *> links = socket.directly_linked_links();
for (const bNodeLink *link : links) {
const bNodeSocket &from_socket = *link->fromsock;
if (added_sockets.add(&from_socket)) {
sockets_to_check.push(&from_socket);
}
}
}
else {
if (node.is_reroute()) {
const bNodeSocket &input_socket = node.input_socket(0);
if (added_sockets.add(&input_socket)) {
sockets_to_check.push(&input_socket);
}
const Span<const bNodeLink *> links = input_socket.directly_linked_links();
for (const bNodeLink *link : links) {
const bNodeSocket &from_socket = *link->fromsock;
if (added_sockets.add(&from_socket)) {
sockets_to_check.push(&from_socket);
}
}
}
else if (node.is_muted()) {
if (const bNodeSocket *input_socket = socket.internal_link_input()) {
if (added_sockets.add(input_socket)) {
sockets_to_check.push(input_socket);
}
const Span<const bNodeLink *> links = input_socket->directly_linked_links();
for (const bNodeLink *link : links) {
const bNodeSocket &from_socket = *link->fromsock;
if (added_sockets.add(&from_socket)) {
sockets_to_check.push(&from_socket);
}
}
}
}
}
}
return nullptr;
}
GeoTreeLogger &GeoModifierLog::get_local_tree_logger(const ComputeContext &compute_context)
{
LocalData &local_data = data_per_thread_.local();
Map<ComputeContextHash, destruct_ptr<GeoTreeLogger>> &local_tree_loggers =
local_data.tree_logger_by_context;
destruct_ptr<GeoTreeLogger> &tree_logger_ptr = local_tree_loggers.lookup_or_add_default(
compute_context.hash());
if (tree_logger_ptr) {
return *tree_logger_ptr;
}
tree_logger_ptr = local_data.allocator.construct<GeoTreeLogger>();
GeoTreeLogger &tree_logger = *tree_logger_ptr;
tree_logger.allocator = &local_data.allocator;
const ComputeContext *parent_compute_context = compute_context.parent();
if (parent_compute_context != nullptr) {
tree_logger.parent_hash = parent_compute_context->hash();
GeoTreeLogger &parent_logger = this->get_local_tree_logger(*parent_compute_context);
parent_logger.children_hashes.append(compute_context.hash());
}
if (const bke::NodeGroupComputeContext *node_group_compute_context =
dynamic_cast<const bke::NodeGroupComputeContext *>(&compute_context)) {
tree_logger.group_node_name.emplace(node_group_compute_context->node_name());
}
return tree_logger;
}
GeoTreeLog &GeoModifierLog::get_tree_log(const ComputeContextHash &compute_context_hash)
{
GeoTreeLog &reduced_tree_log = *tree_logs_.lookup_or_add_cb(compute_context_hash, [&]() {
Vector<GeoTreeLogger *> tree_logs;
for (LocalData &local_data : data_per_thread_) {
destruct_ptr<GeoTreeLogger> *tree_log = local_data.tree_logger_by_context.lookup_ptr(
compute_context_hash);
if (tree_log != nullptr) {
tree_logs.append(tree_log->get());
}
}
return std::make_unique<GeoTreeLog>(this, std::move(tree_logs));
});
return reduced_tree_log;
}
std::optional<GeoModifierLog::ObjectAndModifier> GeoModifierLog::get_modifier_for_node_editor(
const SpaceNode &snode)
{
if (snode.id == nullptr) {
return std::nullopt;
}
if (GS(snode.id->name) != ID_OB) {
return std::nullopt;
}
const Object *object = reinterpret_cast<Object *>(snode.id);
const NodesModifierData *used_modifier = nullptr;
if (snode.flag & SNODE_PIN) {
LISTBASE_FOREACH (const ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Nodes) {
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
/* Would be good to store the name of the pinned modifier in the node editor. */
if (nmd->node_group == snode.nodetree) {
used_modifier = nmd;
break;
}
}
}
}
else {
LISTBASE_FOREACH (const ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Nodes) {
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
if (nmd->node_group == snode.nodetree) {
if (md->flag & eModifierFlag_Active) {
used_modifier = nmd;
break;
}
}
}
}
}
if (used_modifier == nullptr) {
return std::nullopt;
}
return ObjectAndModifier{object, used_modifier};
}
GeoTreeLog *GeoModifierLog::get_tree_log_for_node_editor(const SpaceNode &snode)
{
std::optional<ObjectAndModifier> object_and_modifier = get_modifier_for_node_editor(snode);
if (!object_and_modifier) {
return nullptr;
}
GeoModifierLog *modifier_log = static_cast<GeoModifierLog *>(
object_and_modifier->nmd->runtime_eval_log);
if (modifier_log == nullptr) {
return nullptr;
}
Vector<const bNodeTreePath *> tree_path = snode.treepath;
if (tree_path.is_empty()) {
return nullptr;
}
ComputeContextBuilder compute_context_builder;
compute_context_builder.push<bke::ModifierComputeContext>(
object_and_modifier->nmd->modifier.name);
for (const bNodeTreePath *path_item : tree_path.as_span().drop_front(1)) {
compute_context_builder.push<bke::NodeGroupComputeContext>(path_item->node_name);
}
return &modifier_log->get_tree_log(compute_context_builder.hash());
}
const ViewerNodeLog *GeoModifierLog::find_viewer_node_log_for_spreadsheet(
const SpaceSpreadsheet &sspreadsheet)
{
Vector<const SpreadsheetContext *> context_path = sspreadsheet.context_path;
if (context_path.size() < 3) {
return nullptr;
}
if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) {
return nullptr;
}
if (context_path[1]->type != SPREADSHEET_CONTEXT_MODIFIER) {
return nullptr;
}
const SpreadsheetContextObject *object_context =
reinterpret_cast<const SpreadsheetContextObject *>(context_path[0]);
const SpreadsheetContextModifier *modifier_context =
reinterpret_cast<const SpreadsheetContextModifier *>(context_path[1]);
if (object_context->object == nullptr) {
return nullptr;
}
NodesModifierData *nmd = nullptr;
LISTBASE_FOREACH (ModifierData *, md, &object_context->object->modifiers) {
if (STREQ(md->name, modifier_context->modifier_name)) {
if (md->type == eModifierType_Nodes) {
nmd = reinterpret_cast<NodesModifierData *>(md);
}
}
}
if (nmd == nullptr) {
return nullptr;
}
if (nmd->runtime_eval_log == nullptr) {
return nullptr;
}
nodes::geo_eval_log::GeoModifierLog *modifier_log =
static_cast<nodes::geo_eval_log::GeoModifierLog *>(nmd->runtime_eval_log);
ComputeContextBuilder compute_context_builder;
compute_context_builder.push<bke::ModifierComputeContext>(modifier_context->modifier_name);
for (const SpreadsheetContext *context : context_path.as_span().drop_front(2).drop_back(1)) {
if (context->type != SPREADSHEET_CONTEXT_NODE) {
return nullptr;
}
const SpreadsheetContextNode &node_context = *reinterpret_cast<const SpreadsheetContextNode *>(
context);
compute_context_builder.push<bke::NodeGroupComputeContext>(node_context.node_name);
}
const ComputeContextHash context_hash = compute_context_builder.hash();
nodes::geo_eval_log::GeoTreeLog &tree_log = modifier_log->get_tree_log(context_hash);
tree_log.ensure_viewer_node_logs();
const SpreadsheetContext *last_context = context_path.last();
if (last_context->type != SPREADSHEET_CONTEXT_NODE) {
return nullptr;
}
const SpreadsheetContextNode &last_node_context =
*reinterpret_cast<const SpreadsheetContextNode *>(last_context);
const ViewerNodeLog *viewer_log = tree_log.viewer_node_logs.lookup(last_node_context.node_name);
return viewer_log;
}
} // namespace blender::nodes::geo_eval_log

File diff suppressed because it is too large Load Diff

View File

@@ -11,34 +11,27 @@
#include "node_geometry_util.hh"
using blender::nodes::geometry_nodes_eval_log::LocalGeoLogger;
namespace blender::nodes {
void GeoNodeExecParams::error_message_add(const NodeWarningType type, std::string message) const
{
if (provider_->logger == nullptr) {
return;
if (geo_eval_log::GeoTreeLogger *tree_logger = this->get_local_tree_logger()) {
tree_logger->node_warnings.append({node_.name, {type, std::move(message)}});
}
LocalGeoLogger &local_logger = provider_->logger->local();
local_logger.log_node_warning(provider_->dnode, type, std::move(message));
}
void GeoNodeExecParams::used_named_attribute(std::string attribute_name,
const eNamedAttrUsage usage)
const NamedAttributeUsage usage)
{
if (provider_->logger == nullptr) {
return;
if (geo_eval_log::GeoTreeLogger *tree_logger = this->get_local_tree_logger()) {
tree_logger->used_named_attributes_.append({node_.name, std::move(attribute_name), usage});
}
LocalGeoLogger &local_logger = provider_->logger->local();
local_logger.log_used_named_attribute(provider_->dnode, std::move(attribute_name), usage);
}
void GeoNodeExecParams::check_input_geometry_set(StringRef identifier,
const GeometrySet &geometry_set) const
{
const SocketDeclaration &decl =
*provider_->dnode->input_by_identifier(identifier).runtime->declaration;
const SocketDeclaration &decl = *node_.input_by_identifier(identifier).runtime->declaration;
const decl::Geometry *geo_decl = dynamic_cast<const decl::Geometry *>(&decl);
if (geo_decl == nullptr) {
return;
@@ -118,7 +111,7 @@ void GeoNodeExecParams::check_output_geometry_set(const GeometrySet &geometry_se
const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name) const
{
for (const bNodeSocket *socket : provider_->dnode->runtime->inputs) {
for (const bNodeSocket *socket : node_.input_sockets()) {
if (socket->is_available() && socket->name == name) {
return socket;
}
@@ -129,19 +122,19 @@ const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name
std::string GeoNodeExecParams::attribute_producer_name() const
{
return provider_->dnode->label_or_name() + TIP_(" node");
return node_.label_or_name() + TIP_(" node");
}
void GeoNodeExecParams::set_default_remaining_outputs()
{
provider_->set_default_remaining_outputs();
params_.set_default_remaining_outputs();
}
void GeoNodeExecParams::check_input_access(StringRef identifier,
const CPPType *requested_type) const
{
const bNodeSocket *found_socket = nullptr;
for (const bNodeSocket *socket : provider_->dnode->input_sockets()) {
for (const bNodeSocket *socket : node_.input_sockets()) {
if (socket->identifier == identifier) {
found_socket = socket;
break;
@@ -151,7 +144,7 @@ void GeoNodeExecParams::check_input_access(StringRef identifier,
if (found_socket == nullptr) {
std::cout << "Did not find an input socket with the identifier '" << identifier << "'.\n";
std::cout << "Possible identifiers are: ";
for (const bNodeSocket *socket : provider_->dnode->input_sockets()) {
for (const bNodeSocket *socket : node_.input_sockets()) {
if (socket->is_available()) {
std::cout << "'" << socket->identifier << "', ";
}
@@ -164,13 +157,7 @@ void GeoNodeExecParams::check_input_access(StringRef identifier,
<< "' is disabled.\n";
BLI_assert_unreachable();
}
else if (!provider_->can_get_input(identifier)) {
std::cout << "The identifier '" << identifier
<< "' is valid, but there is no value for it anymore.\n";
std::cout << "Most likely it has been extracted before.\n";
BLI_assert_unreachable();
}
else if (requested_type != nullptr) {
else if (requested_type != nullptr && (found_socket->flag & SOCK_MULTI_INPUT) == 0) {
const CPPType &expected_type = *found_socket->typeinfo->geometry_nodes_cpp_type;
if (*requested_type != expected_type) {
std::cout << "The requested type '" << requested_type->name() << "' is incorrect. Expected '"
@@ -183,7 +170,7 @@ void GeoNodeExecParams::check_input_access(StringRef identifier,
void GeoNodeExecParams::check_output_access(StringRef identifier, const CPPType &value_type) const
{
const bNodeSocket *found_socket = nullptr;
for (const bNodeSocket *socket : provider_->dnode->output_sockets()) {
for (const bNodeSocket *socket : node_.output_sockets()) {
if (socket->identifier == identifier) {
found_socket = socket;
break;
@@ -193,8 +180,8 @@ void GeoNodeExecParams::check_output_access(StringRef identifier, const CPPType
if (found_socket == nullptr) {
std::cout << "Did not find an output socket with the identifier '" << identifier << "'.\n";
std::cout << "Possible identifiers are: ";
for (const bNodeSocket *socket : provider_->dnode->output_sockets()) {
if (!(socket->flag & SOCK_UNAVAIL)) {
for (const bNodeSocket *socket : node_.output_sockets()) {
if (socket->is_available()) {
std::cout << "'" << socket->identifier << "', ";
}
}
@@ -206,7 +193,7 @@ void GeoNodeExecParams::check_output_access(StringRef identifier, const CPPType
<< "' is disabled.\n";
BLI_assert_unreachable();
}
else if (!provider_->can_set_output(identifier)) {
else if (params_.output_was_set(this->get_output_index(identifier))) {
std::cout << "The identifier '" << identifier << "' has been set already.\n";
BLI_assert_unreachable();
}

View File

@@ -3,21 +3,21 @@
#include "NOD_multi_function.hh"
#include "BKE_node.h"
#include "BKE_node_runtime.hh"
namespace blender::nodes {
NodeMultiFunctions::NodeMultiFunctions(const DerivedNodeTree &tree)
NodeMultiFunctions::NodeMultiFunctions(const bNodeTree &tree)
{
for (const bNodeTree *btree : tree.used_btrees()) {
for (const bNode *bnode : btree->all_nodes()) {
if (bnode->typeinfo->build_multi_function == nullptr) {
continue;
}
NodeMultiFunctionBuilder builder{*bnode, *btree};
bnode->typeinfo->build_multi_function(builder);
if (builder.built_fn_ != nullptr) {
map_.add_new(bnode, {builder.built_fn_, std::move(builder.owned_built_fn_)});
}
tree.ensure_topology_cache();
for (const bNode *bnode : tree.all_nodes()) {
if (bnode->typeinfo->build_multi_function == nullptr) {
continue;
}
NodeMultiFunctionBuilder builder{*bnode, tree};
bnode->typeinfo->build_multi_function(builder);
if (builder.built_fn_ != nullptr) {
map_.add_new(bnode, {builder.built_fn_, std::move(builder.owned_built_fn_)});
}
}
}