mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-14 09:15:06 +00:00
Compare commits
684
Commits
glparser-util
...
2.2.33
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49db8e111d | ||
|
|
bd9a3c3c5c | ||
|
|
01ff612d1d | ||
|
|
0cae2be1f6 | ||
|
|
85d0641a73 | ||
|
|
46f8157087 | ||
|
|
d3dee841e8 | ||
|
|
3d3d0f9e18 | ||
|
|
23aa04361d | ||
|
|
69428c12f6 | ||
|
|
3978be161d | ||
|
|
2ed838acf1 | ||
|
|
d29a8278cd | ||
|
|
2f1f380e8e | ||
|
|
990dadc03f | ||
|
|
1946c2ad65 | ||
|
|
bfb66d84ee | ||
|
|
cae4fea2bb | ||
|
|
78abc05711 | ||
|
|
159f7ff8cc | ||
|
|
0d959c2b90 | ||
|
|
ce2462cad3 | ||
|
|
5457df5369 | ||
|
|
acedba4eac | ||
|
|
21a054a4db | ||
|
|
d056bf6fa7 | ||
|
|
5ed1c92590 | ||
|
|
3389b952d0 | ||
|
|
b49d37d827 | ||
|
|
12857006c6 | ||
|
|
38af64f10c | ||
|
|
10922edca3 | ||
|
|
2a8c8756d3 | ||
|
|
23e36f2b76 | ||
|
|
fcf8ad064a | ||
|
|
6dfb0312b1 | ||
|
|
0a4d48964e | ||
|
|
231a2ab216 | ||
|
|
61bb779dea | ||
|
|
7cd97d1216 | ||
|
|
fe82cd320a | ||
|
|
c7a0f3a208 | ||
|
|
338d94197c | ||
|
|
75a0278138 | ||
|
|
c97fb7004f | ||
|
|
bdcb4d1882 | ||
|
|
37afddab5d | ||
|
|
d876a203da | ||
|
|
85892c0f7f | ||
|
|
1587a12cd7 | ||
|
|
38b1d9a0c3 | ||
|
|
12fa08ab46 | ||
|
|
80e5b544c0 | ||
|
|
db7d328481 | ||
|
|
9f19d94c5f | ||
|
|
bc08e4baed | ||
|
|
8387b4e9cd | ||
|
|
6c888a634a | ||
|
|
fe67a22d5a | ||
|
|
66d2a548fc | ||
|
|
d16c1e7cf6 | ||
|
|
a8e95f70c1 | ||
|
|
9cb35c67cd | ||
|
|
cbd16b1a76 | ||
|
|
cd0a9608d3 | ||
|
|
fa71eda3db | ||
|
|
9705722469 | ||
|
|
977db3d604 | ||
|
|
c10d30b2da | ||
|
|
2f4ac2a9b7 | ||
|
|
9081f4c3e8 | ||
|
|
1f274a64ea | ||
|
|
9814a96b10 | ||
|
|
af599f292e | ||
|
|
7bb5bfd45c | ||
|
|
74ea5c4709 | ||
|
|
233c74e0d7 | ||
|
|
e17b45c842 | ||
|
|
c9d46e70e0 | ||
|
|
a8c29fa3f9 | ||
|
|
7c28a69bfe | ||
|
|
7f3fb35ebb | ||
|
|
44c930f4ab | ||
|
|
b76284048a | ||
|
|
762c5661eb | ||
|
|
1babfd968c | ||
|
|
888a4de0c9 | ||
|
|
a23cefba92 | ||
|
|
f2c07dea28 | ||
|
|
53776684c8 | ||
|
|
9dd60f6a75 | ||
|
|
87c83f0645 | ||
|
|
c578970a66 | ||
|
|
ef68af5850 | ||
|
|
b6a4497fb3 | ||
|
|
939e31461a | ||
|
|
02f0b0fca1 | ||
|
|
b7d439e6b1 | ||
|
|
c36647af34 | ||
|
|
f787801a0e | ||
|
|
f192793395 | ||
|
|
3876f7e693 | ||
|
|
94ee37bfed | ||
|
|
26c161f0e8 | ||
|
|
75639d3b73 | ||
|
|
a07cecb879 | ||
|
|
81b82748ce | ||
|
|
8d0c90c423 | ||
|
|
8ce6a2f8c6 | ||
|
|
2aa4ac7509 | ||
|
|
8f98151049 | ||
|
|
2b70318327 | ||
|
|
3b4ebcf611 | ||
|
|
2845c9e078 | ||
|
|
6f054a09a9 | ||
|
|
37a10eee3f | ||
|
|
744c1f971f | ||
|
|
32d1c27b9c | ||
|
|
f180894da7 | ||
|
|
bb67f82701 | ||
|
|
73f5556595 | ||
|
|
19bc7cbaed | ||
|
|
d189a644e2 | ||
|
|
94ba40ae4d | ||
|
|
961b23fd2d | ||
|
|
61b5301f1a | ||
|
|
0e539ae034 | ||
|
|
e581326641 | ||
|
|
c707cc51f1 | ||
|
|
7d890bf3e7 | ||
|
|
ffc324bab6 | ||
|
|
0dfffca88e | ||
|
|
a66db1517a | ||
|
|
269b7245d3 | ||
|
|
67f2484976 | ||
|
|
1c8687e6fd | ||
|
|
e6bbd155da | ||
|
|
2f64fef6a6 | ||
|
|
b0fd1ea46e | ||
|
|
6d8cbf9c2f | ||
|
|
096dd01446 | ||
|
|
22c9479946 | ||
|
|
b2f597a73a | ||
|
|
553dec5b6d | ||
|
|
bc8116006c | ||
|
|
bf9492dffb | ||
|
|
69aed42b89 | ||
|
|
ffe54f61b9 | ||
|
|
c86e1de9f6 | ||
|
|
6af401c834 | ||
|
|
b17bb42c49 | ||
|
|
3659b59db9 | ||
|
|
8c31eec525 | ||
|
|
166116d692 | ||
|
|
4b21bc77b6 | ||
|
|
8da561d3b6 | ||
|
|
413df76ad4 | ||
|
|
18fe05eda8 | ||
|
|
4fa8cfe3e4 | ||
|
|
b5dd1c04ce | ||
|
|
6c5eedb7c1 | ||
|
|
9d5eb01091 | ||
|
|
0d54a65026 | ||
|
|
8148d8f927 | ||
|
|
5c441e4418 | ||
|
|
78562f12d9 | ||
|
|
031456f224 | ||
|
|
ff35c684ca | ||
|
|
5c4ef022d5 | ||
|
|
4eb837a2c0 | ||
|
|
6570b6a0a5 | ||
|
|
493b641d56 | ||
|
|
3f4995466c | ||
|
|
56d7568fd3 | ||
|
|
4910cfd6d3 | ||
|
|
d1c6677caf | ||
|
|
8cf0296284 | ||
|
|
4fd98e5026 | ||
|
|
8e293f13fb | ||
|
|
2db20ad866 | ||
|
|
10a5b550f2 | ||
|
|
c1403dcf13 | ||
|
|
5d1fabfca9 | ||
|
|
555b2b7dd2 | ||
|
|
7017786a7c | ||
|
|
1c49649685 | ||
|
|
2bae79c4ee | ||
|
|
c22935660a | ||
|
|
79c488102b | ||
|
|
cc1353b73a | ||
|
|
d1e6dc8c94 | ||
|
|
ecbf8fea2f | ||
|
|
610209605f | ||
|
|
112aae3735 | ||
|
|
5844b1b5c1 | ||
|
|
05f01c17e6 | ||
|
|
f07d96316a | ||
|
|
5b704d1bc2 | ||
|
|
26135124f4 | ||
|
|
3abc89b7b2 | ||
|
|
03d908d658 | ||
|
|
76bf19c8f6 | ||
|
|
b27834cbd9 | ||
|
|
eb0ecb0483 | ||
|
|
9acb0e67a7 | ||
|
|
f4f9b56b17 | ||
|
|
2196439e6c | ||
|
|
dd68c64239 | ||
|
|
0decc515e4 | ||
|
|
b3e1e89c88 | ||
|
|
32bff85526 | ||
|
|
02fc95f239 | ||
|
|
07fd6d78a0 | ||
|
|
87aa853f81 | ||
|
|
ab69eea654 | ||
|
|
edccf63b66 | ||
|
|
80413fb3de | ||
|
|
f3ed9aa82b | ||
|
|
307c563b6d | ||
|
|
f92ce1d24e | ||
|
|
a7fac16d97 | ||
|
|
20c3caaed4 | ||
|
|
bf08cd50d9 | ||
|
|
805a266059 | ||
|
|
d3c9963280 | ||
|
|
4630bc4fd8 | ||
|
|
ce32a3d3df | ||
|
|
1991dc58ec | ||
|
|
ae19236e1a | ||
|
|
7ad368c937 | ||
|
|
5c86c87bcf | ||
|
|
99ba7fa775 | ||
|
|
5ae4ab8c5c | ||
|
|
ddd68e023f | ||
|
|
f78cfdd5fb | ||
|
|
183bcfbf41 | ||
|
|
502f30c0f9 | ||
|
|
ceff04adba | ||
|
|
b4dfdb8cc5 | ||
|
|
3064818477 | ||
|
|
27524e6a89 | ||
|
|
5d5e910fc0 | ||
|
|
f325fd7fc3 | ||
|
|
ec278d3d7a | ||
|
|
eef3d2f50a | ||
|
|
3da597ab18 | ||
|
|
0e79380618 | ||
|
|
b639d5c646 | ||
|
|
0d209d426d | ||
|
|
bf24084033 | ||
|
|
9267ecc735 | ||
|
|
dcc8d8deed | ||
|
|
5dc4f1fab8 | ||
|
|
d3cea1bfe4 | ||
|
|
5db2b248d7 | ||
|
|
21ab526284 | ||
|
|
4c8ea679ef | ||
|
|
2317379df2 | ||
|
|
24dae49c7f | ||
|
|
865de3cbab | ||
|
|
9c45549e35 | ||
|
|
2a20198292 | ||
|
|
f0b68f93af | ||
|
|
dd1d3aa518 | ||
|
|
c9633fd745 | ||
|
|
e2e94b2958 | ||
|
|
3046b043a0 | ||
|
|
8a6c8035db | ||
|
|
2b15370c00 | ||
|
|
3ca9e32191 | ||
|
|
efbca29381 | ||
|
|
9c1b6c75ff | ||
|
|
fa91b70528 | ||
|
|
61de8385ca | ||
|
|
962e7527a4 | ||
|
|
2ab2713fe9 | ||
|
|
7cf652cd4e | ||
|
|
48eba2fd96 | ||
|
|
356ba376fe | ||
|
|
ef6b0742a9 | ||
|
|
787929d0bd | ||
|
|
0e1fd9d0e9 | ||
|
|
58938e82a7 | ||
|
|
be6c8c85b5 | ||
|
|
39bca37d0d | ||
|
|
1c61be04a7 | ||
|
|
7a97cd9386 | ||
|
|
5b09bde7a0 | ||
|
|
edc0081b7c | ||
|
|
913256b032 | ||
|
|
e1f5083d3e | ||
|
|
b55e693ad2 | ||
|
|
21c01a1aa4 | ||
|
|
83a26b2291 | ||
|
|
7e44c1efcb | ||
|
|
155f209db5 | ||
|
|
c4a6c165a0 | ||
|
|
624fe6b453 | ||
|
|
fd45fda0ab | ||
|
|
00fc97a611 | ||
|
|
445dc487b9 | ||
|
|
f43fbd4d9f | ||
|
|
dbabc4b3dc | ||
|
|
e01097264b | ||
|
|
0a922f3129 | ||
|
|
4a2dcd0b22 | ||
|
|
0db691db0b | ||
|
|
e5cd1372e7 | ||
|
|
fbd07cc575 | ||
|
|
91f2cf9dfb | ||
|
|
34a6967c71 | ||
|
|
9c295a1e22 | ||
|
|
9fd02900ef | ||
|
|
27e9a4d4fd | ||
|
|
dfc08053d5 | ||
|
|
1af03fa369 | ||
|
|
3ae3b2527d | ||
|
|
fd2a14a000 | ||
|
|
9f0f6a6232 | ||
|
|
2942c7eb84 | ||
|
|
47762f921e | ||
|
|
bdc4431760 | ||
|
|
7e1beadfb5 | ||
|
|
59a4b84023 | ||
|
|
8d53e25e0a | ||
|
|
bbf83db379 | ||
|
|
ec82280b9b | ||
|
|
d7588e4c14 | ||
|
|
54c99386d4 | ||
|
|
465abf889a | ||
|
|
4acb83be6a | ||
|
|
80f9724ee6 | ||
|
|
8cf55bb216 | ||
|
|
3ebc8868c7 | ||
|
|
0938e6d975 | ||
|
|
d3022a40d2 | ||
|
|
2645c803f9 | ||
|
|
d91c356a99 | ||
|
|
189ff4a80f | ||
|
|
ffb4ca9472 | ||
|
|
7d8c8d306d | ||
|
|
117ff0cacf | ||
|
|
83fdd57534 | ||
|
|
951be04e9a | ||
|
|
0ad69f3433 | ||
|
|
0dabecc784 | ||
|
|
9b3fad4c6d | ||
|
|
e1e8256861 | ||
|
|
e65c01c2a3 | ||
|
|
b9de4df18e | ||
|
|
a5d31cd1b6 | ||
|
|
29f8d718cc | ||
|
|
52b0db2566 | ||
|
|
e9eb45f4f2 | ||
|
|
92e28a4cd5 | ||
|
|
f46a86889c | ||
|
|
c827d446d5 | ||
|
|
00ef355ce8 | ||
|
|
1282056fff | ||
|
|
6c422d47a2 | ||
|
|
057ea8035c | ||
|
|
3ea47c8385 | ||
|
|
306d732d81 | ||
|
|
a483669579 | ||
|
|
53996500d7 | ||
|
|
04dd8ce6c7 | ||
|
|
8c9edfa4c3 | ||
|
|
3f44195756 | ||
|
|
c98f056d74 | ||
|
|
7e25f5d03e | ||
|
|
b5553654d0 | ||
|
|
a3fffb47be | ||
|
|
09cc557be0 | ||
|
|
aff5ff7e7b | ||
|
|
e7d08a53b6 | ||
|
|
3b2a0af5c4 | ||
|
|
8c44e79990 | ||
|
|
d2e491ed47 | ||
|
|
bccc5b00ca | ||
|
|
5ed26f3af7 | ||
|
|
9e7d19b883 | ||
|
|
5f61e387fb | ||
|
|
70cfa2ae90 | ||
|
|
37717bd4db | ||
|
|
b179340a0f | ||
|
|
465e36148c | ||
|
|
a3f0efd629 | ||
|
|
2386d630c5 | ||
|
|
6a784d8d3b | ||
|
|
fe9028d300 | ||
|
|
4c5e5df290 | ||
|
|
39ee364418 | ||
|
|
4c9cae5e0b | ||
|
|
11b53c9f8f | ||
|
|
c769a08a4e | ||
|
|
90dac9f39d | ||
|
|
c5c4b290b1 | ||
|
|
4b46de4566 | ||
|
|
9f2a71be4e | ||
|
|
79fe08417c | ||
|
|
5de64abdef | ||
|
|
2e07465166 | ||
|
|
2b610b9aea | ||
|
|
ab192ddd2a | ||
|
|
cb5e2ed7d4 | ||
|
|
e914ef7563 | ||
|
|
748dbd55a1 | ||
|
|
140c0c2285 | ||
|
|
af26442b98 | ||
|
|
ef07ee9f77 | ||
|
|
1b8087e8a3 | ||
|
|
9107ea9ae5 | ||
|
|
938a6bda90 | ||
|
|
e9f6f72741 | ||
|
|
379330f997 | ||
|
|
2fecb3a05b | ||
|
|
c085af35bd | ||
|
|
a36aec36a2 | ||
|
|
bdd6259c3f | ||
|
|
d7d2228a6c | ||
|
|
cf9993fce4 | ||
|
|
3ae57ca594 | ||
|
|
9816cb2148 | ||
|
|
b1e496f56d | ||
|
|
c8e50227fb | ||
|
|
066381a7c0 | ||
|
|
9a0ea1ad65 | ||
|
|
8e26a0179b | ||
|
|
1d96af8e3a | ||
|
|
f7eb86cdb6 | ||
|
|
b38fafa831 | ||
|
|
7dc2625f8c | ||
|
|
3d97977050 | ||
|
|
5f41c60bc2 | ||
|
|
0325a96bc2 | ||
|
|
ad123d0bd5 | ||
|
|
4b89997985 | ||
|
|
f5c9fa3301 | ||
|
|
d6a6d3b525 | ||
|
|
f079bed977 | ||
|
|
c9ec40e94b | ||
|
|
8380f3853d | ||
|
|
534ae6b05c | ||
|
|
68d523d3df | ||
|
|
2f22a7b3ad | ||
|
|
7b79b90dd6 | ||
|
|
417f23f826 | ||
|
|
ca3c46c873 | ||
|
|
82fa9ade2b | ||
|
|
aaab8fd0bc | ||
|
|
bd56ccc4b8 | ||
|
|
e17f60308d | ||
|
|
1dd98978ad | ||
|
|
dae986eb0b | ||
|
|
5fdf84edd0 | ||
|
|
b19d023c11 | ||
|
|
41eafbd151 | ||
|
|
dd970b4c6a | ||
|
|
f79ad7f22a | ||
|
|
6947ffa3e2 | ||
|
|
969d2c3710 | ||
|
|
c5988d29c3 | ||
|
|
dc68c2c265 | ||
|
|
51533a81ec | ||
|
|
90eabb5d40 | ||
|
|
74843cb1f9 | ||
|
|
5e0344a529 | ||
|
|
3cdd934987 | ||
|
|
9d90ff137c | ||
|
|
bee9541797 | ||
|
|
b75a5d8f7d | ||
|
|
8d3ab41c53 | ||
|
|
48d032e654 | ||
|
|
1968914582 | ||
|
|
a70bd76ff3 | ||
|
|
db919b5f37 | ||
|
|
cde47ea423 | ||
|
|
5ef6cb11c0 | ||
|
|
b7015bf10e | ||
|
|
46602ab921 | ||
|
|
63e9fa0df1 | ||
|
|
dcea970a77 | ||
|
|
b29240d6da | ||
|
|
9d5a02ed21 | ||
|
|
b454483aae | ||
|
|
72b13543c6 | ||
|
|
36d98fcb22 | ||
|
|
44c5b9617a | ||
|
|
f3f1677db8 | ||
|
|
d396b611ad | ||
|
|
08f4c0f9db | ||
|
|
50b0d22dc2 | ||
|
|
7fed54264e | ||
|
|
be87401be6 | ||
|
|
94254006df | ||
|
|
f79774df65 | ||
|
|
2f2fc22df3 | ||
|
|
26300bbb1b | ||
|
|
88e4d2fd6c | ||
|
|
bb52476d27 | ||
|
|
6d60477b93 | ||
|
|
362724610c | ||
|
|
ea4a3cdf0b | ||
|
|
65959268d0 | ||
|
|
58d820cd7e | ||
|
|
685d68bd9c | ||
|
|
ad63d2d62c | ||
|
|
d2730ccca8 | ||
|
|
e9257b8739 | ||
|
|
3984225faa | ||
|
|
52db6b56d6 | ||
|
|
34bfe513f1 | ||
|
|
42d7ed58aa | ||
|
|
9a4f0f9b99 | ||
|
|
be90a01725 | ||
|
|
e5e8250d44 | ||
|
|
062a03ffc2 | ||
|
|
9f5afaaec3 | ||
|
|
b05f17a477 | ||
|
|
57caf08212 | ||
|
|
085ec79c21 | ||
|
|
38641268ae | ||
|
|
fe8e723638 | ||
|
|
16debfd518 | ||
|
|
8cfc3e1f4a | ||
|
|
2c808fae43 | ||
|
|
1649c3c0c6 | ||
|
|
b84286ba3b | ||
|
|
ee6c79802a | ||
|
|
d3366ad31a | ||
|
|
92b6dcc010 | ||
|
|
89132f3ef9 | ||
|
|
f0d45b0d62 | ||
|
|
173d986a5b | ||
|
|
473a690054 | ||
|
|
2515b3520f | ||
|
|
a32f9ca244 | ||
|
|
941fc88484 | ||
|
|
4106dd3865 | ||
|
|
61f6aa19a8 | ||
|
|
77875f7f91 | ||
|
|
241c32def1 | ||
|
|
a8107f97e8 | ||
|
|
8bec433699 | ||
|
|
2d39fb2397 | ||
|
|
cfd8eb1411 | ||
|
|
59c270a5b9 | ||
|
|
87ac03281c | ||
|
|
36b82fa2ce | ||
|
|
2fc2e2e38d | ||
|
|
33de1de9c4 | ||
|
|
55811b9d7b | ||
|
|
a10d92f52e | ||
|
|
7d2fe12ce9 | ||
|
|
006ded801e | ||
|
|
03afd6939e | ||
|
|
adc5421e00 | ||
|
|
dfe60e4b3e | ||
|
|
72c6cdfa60 | ||
|
|
c5daea86f8 | ||
|
|
f8958ee990 | ||
|
|
073260060b | ||
|
|
771fee124d | ||
|
|
680285bf54 | ||
|
|
df8784536e | ||
|
|
c8991d810c | ||
|
|
e3b02642ac | ||
|
|
2759dd6689 | ||
|
|
996dbaef2c | ||
|
|
72ca675c93 | ||
|
|
e6e230e270 | ||
|
|
43d7282ba5 | ||
|
|
4b2d57a32d | ||
|
|
06bf71126f | ||
|
|
4c0f2f2685 | ||
|
|
78334717e7 | ||
|
|
a77765a2c9 | ||
|
|
1a9fe45ee9 | ||
|
|
21ef5905c8 | ||
|
|
5b67eaa474 | ||
|
|
c9d59c3b90 | ||
|
|
fa7ff74fec | ||
|
|
a34a3ebd39 | ||
|
|
2e7476d287 | ||
|
|
3244071012 | ||
|
|
7035dbde02 | ||
|
|
97ae04c2fa | ||
|
|
e7fa7f62d0 | ||
|
|
ea39712749 | ||
|
|
66dd3eaa94 | ||
|
|
270697cd10 | ||
|
|
2b53a851ef | ||
|
|
34b6efde7b | ||
|
|
86edb721da | ||
|
|
39c5f4bf7b | ||
|
|
5dc54bb1da | ||
|
|
7924769ab1 | ||
|
|
8afe2e2ccf | ||
|
|
e8dd60e088 | ||
|
|
9a621405ba | ||
|
|
45a677030d | ||
|
|
4d531022d2 | ||
|
|
48ba2160ad | ||
|
|
334ec81f9b | ||
|
|
86c726e50b | ||
|
|
e6d778dad3 | ||
|
|
dce5013ffb | ||
|
|
6eef63619c | ||
|
|
a8153fa0f7 | ||
|
|
49c04cf776 | ||
|
|
85b0277a66 | ||
|
|
0525b31262 | ||
|
|
fdb2053c50 | ||
|
|
31abbcce32 | ||
|
|
063b2242e8 | ||
|
|
577f905dc5 | ||
|
|
071d2d4a39 | ||
|
|
891715b113 | ||
|
|
30a35ef25e | ||
|
|
33453b3b8c | ||
|
|
62e699f6d0 | ||
|
|
f107d173b7 | ||
|
|
d0948b36f3 | ||
|
|
ae1211f280 | ||
|
|
3d76cb3073 | ||
|
|
45fb120223 | ||
|
|
d7a196eb5c | ||
|
|
4d53e2af9b | ||
|
|
cb6336a365 | ||
|
|
5095021e0e | ||
|
|
f88ecbf68d | ||
|
|
7da9b9eb1c | ||
|
|
02c394108f | ||
|
|
720aaca1b7 | ||
|
|
1e71b1c4d8 | ||
|
|
df4c423e93 | ||
|
|
78e0074e5a | ||
|
|
0350ea2cb6 | ||
|
|
bed724ba59 | ||
|
|
6fa973dbc8 | ||
|
|
c4ad460f34 | ||
|
|
2a8404c6c2 | ||
|
|
108db9031b | ||
|
|
d6d8a5dad6 | ||
|
|
ad2b73edf9 | ||
|
|
bb5526598e | ||
|
|
611bbf02c8 | ||
|
|
ca16036604 | ||
|
|
f118211257 | ||
|
|
81d03ea552 | ||
|
|
67c8628aeb | ||
|
|
f9b9873637 | ||
|
|
c66bf8658e | ||
|
|
052824017d | ||
|
|
6a5d914a43 | ||
|
|
208373deaa | ||
|
|
f22a3f612b | ||
|
|
9ff1015d19 | ||
|
|
0023fe3da2 | ||
|
|
7e2399714c | ||
|
|
9029182353 | ||
|
|
e6039691b5 | ||
|
|
5db1edba33 | ||
|
|
d450461726 | ||
|
|
95f8afc0f3 | ||
|
|
19635e20ec | ||
|
|
ead4e11376 | ||
|
|
b0ceb5306a | ||
|
|
99a81369f0 | ||
|
|
e2ee822948 | ||
|
|
9fe7e800f6 | ||
|
|
debfcdfda0 | ||
|
|
df662188f4 | ||
|
|
842916405f | ||
|
|
09afe81290 | ||
|
|
292809d8e8 | ||
|
|
47503ce254 | ||
|
|
f738adbdef | ||
|
|
70d15f2383 | ||
|
|
d66b5577f4 | ||
|
|
e293f12fb8 | ||
|
|
b69eca6749 | ||
|
|
03342f08c6 | ||
|
|
b85614f983 |
No files matched your search
@@ -20,3 +20,11 @@ indent_size = 2
|
||||
|
||||
[*.lang]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.java]
|
||||
max_line_length = 240
|
||||
ij_java_wrap_long_lines = false
|
||||
ij_java_use_single_class_imports = true
|
||||
ij_java_class_count_to_use_import_on_demand = 999
|
||||
ij_java_names_count_to_use_import_on_demand = 999
|
||||
ij_java_packages_to_use_import_on_demand = empty
|
||||
@@ -39,6 +39,7 @@ body:
|
||||
- Java 19
|
||||
- Java 20
|
||||
- Java 21
|
||||
- Java 25
|
||||
- Other (Please Specify)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Description of your *PR* and other info
|
||||
<!-- Please include a summary of the change -->
|
||||
<!-- Any details that you think are important to review this PR? -->
|
||||
<!-- Are there any Issues / PRs related to this one? -->
|
||||
|
||||
<!-- Add a screenshot or a video demonstration for new features -->
|
||||
|
||||
<details open><summary>Template</summary> <!-- OPEN is intentional -->
|
||||
|
||||
### Before submitting a *pull request* make sure you have:
|
||||
- [ ] Read the [AI Contribution Policy](https://github.com/GTNewHorizons/Angelica/blob/master/AI_POLICY.md)
|
||||
- [ ] At least [skimmed](https://github.com/GTNewHorizons/Angelica/search?q=is%3Apr&type=Issues) the tracker for similar pull requests
|
||||
|
||||
</details>
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
: "${REPO:?REPO required}"
|
||||
: "${TAG:?TAG required}"
|
||||
|
||||
if [ -z "${DISCORD_WEBHOOK_URL:-}" ]; then
|
||||
echo "DISCORD_WEBHOOK_URL not set, skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! release_json=$(gh release view "$TAG" --repo "$REPO" --json body,url,publishedAt 2>&1); then
|
||||
echo "Could not read release $TAG in $REPO; skipping:"
|
||||
printf '%s\n' "$release_json"
|
||||
exit 0
|
||||
fi
|
||||
body=$(jq -r '.body // ""' <<<"$release_json")
|
||||
url=$(jq -r '.url' <<<"$release_json")
|
||||
ts=$(jq -r '.publishedAt // ""' <<<"$release_json")
|
||||
[ -n "$ts" ] || ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
payload=$(jq -n \
|
||||
--arg title "$TAG" \
|
||||
--arg url "$url" \
|
||||
--arg desc "$body" \
|
||||
--arg ts "$ts" \
|
||||
'{
|
||||
embeds: [{
|
||||
title: ("Angelica " + $title + " released"),
|
||||
url: $url,
|
||||
description: (if ($desc | length) > 3900
|
||||
then $desc[0:3900] + "\n\n...[full changelog](" + $url + ")"
|
||||
else $desc end),
|
||||
color: 3066993,
|
||||
footer: { text: "GitHub Releases" },
|
||||
timestamp: $ts
|
||||
}]
|
||||
}')
|
||||
|
||||
curl -fsS -X POST -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL"
|
||||
Executable
+125
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds the libTracyClient and AngelicaTracyCapture shared libraries for the tracy-client module.
|
||||
set -euo pipefail
|
||||
|
||||
TRACY_VERSION="${TRACY_VERSION:-v0.14.1}"
|
||||
TRACY_VERSION_BARE="${TRACY_VERSION#v}"
|
||||
OUT_DIR="${OUT_DIR:-dist/tracy-natives}"
|
||||
TRACY_REPO="https://github.com/wolfpld/tracy"
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
case "$(uname -s)" in
|
||||
Darwin) PLAT_OS="macos" ;;
|
||||
Linux) PLAT_OS="linux" ;;
|
||||
*) PLAT_OS="windows" ;;
|
||||
esac
|
||||
case "$(uname -m)" in
|
||||
arm64|aarch64) PLAT_ARCH="arm64" ;;
|
||||
*) PLAT_ARCH="x64" ;;
|
||||
esac
|
||||
RES_DIR="${RES_DIR:-$REPO_ROOT/tracy-client/src/main/resources/natives/tracy/$TRACY_VERSION_BARE/$PLAT_OS-$PLAT_ARCH}"
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
|
||||
if [[ -n "${TRACY_SRC:-}" ]]; then
|
||||
SRC_DIR="$WORK_DIR/tracy"
|
||||
git clone --shared --no-checkout "$TRACY_SRC" "$SRC_DIR"
|
||||
git -C "$SRC_DIR" checkout --detach "$TRACY_VERSION"
|
||||
else
|
||||
SRC_DIR="$WORK_DIR/tracy"
|
||||
git clone --depth 1 --branch "$TRACY_VERSION" "$TRACY_REPO" "$SRC_DIR"
|
||||
fi
|
||||
|
||||
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
|
||||
|
||||
CLIENT_BUILD_DIR="$WORK_DIR/build-client"
|
||||
cmake -S "$REPO_ROOT/tracy-client/src/main/native" -B "$CLIENT_BUILD_DIR" \
|
||||
-DTRACY_SRC_DIR="$SRC_DIR" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DTRACY_STATIC=OFF \
|
||||
-DTRACY_ENABLE=ON \
|
||||
-DTRACY_ON_DEMAND=ON \
|
||||
-DTRACY_MANUAL_LIFETIME=ON \
|
||||
-DTRACY_NO_SAMPLING=ON \
|
||||
-DTRACY_NO_SYSTEM_TRACING=ON \
|
||||
-DTRACY_NO_CRASH_HANDLER=ON \
|
||||
-DTRACY_LTO=OFF
|
||||
cmake --build "$CLIENT_BUILD_DIR" --config Release --parallel
|
||||
|
||||
CPM_CACHE_DIR="$WORK_DIR/cpm"
|
||||
CAPTURE_BUILD_DIR="$WORK_DIR/build-capture"
|
||||
cmake -S "$REPO_ROOT/tracy-client/src/main/native-capture" -B "$CAPTURE_BUILD_DIR" \
|
||||
-DTRACY_SRC_DIR="$SRC_DIR" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCPM_SOURCE_CACHE="$CPM_CACHE_DIR"
|
||||
cmake --build "$CAPTURE_BUILD_DIR" --config Release --parallel
|
||||
|
||||
find_one() {
|
||||
find "$1" -name "$2" 2>/dev/null | head -1
|
||||
}
|
||||
|
||||
CLIENT_LIB_NAME=""
|
||||
CLIENT_LIB=""
|
||||
for NAME in libTracyClient.so libTracyClient.dylib TracyClient.dll; do
|
||||
LIB="$(find_one "$CLIENT_BUILD_DIR" "$NAME")"
|
||||
if [[ -n "$LIB" ]]; then
|
||||
CLIENT_LIB="$LIB"
|
||||
CLIENT_LIB_NAME="$NAME"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "$CLIENT_LIB" ]]; then
|
||||
echo "error: libTracyClient not found under $CLIENT_BUILD_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CAPTURE_LIB_NAME=""
|
||||
CAPTURE_LIB=""
|
||||
for NAME in libAngelicaTracyCapture.so libAngelicaTracyCapture.dylib AngelicaTracyCapture.dll; do
|
||||
LIB="$(find_one "$CAPTURE_BUILD_DIR" "$NAME")"
|
||||
if [[ -n "$LIB" ]]; then
|
||||
CAPTURE_LIB="$LIB"
|
||||
CAPTURE_LIB_NAME="$NAME"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "$CAPTURE_LIB" ]]; then
|
||||
echo "error: AngelicaTracyCapture not found under $CAPTURE_BUILD_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STRIPPED_CAPTURE_LIB="$WORK_DIR/$CAPTURE_LIB_NAME"
|
||||
cp -L "$CAPTURE_LIB" "$STRIPPED_CAPTURE_LIB"
|
||||
case "$PLAT_OS" in
|
||||
macos) strip -x "$STRIPPED_CAPTURE_LIB" ;;
|
||||
linux) strip --strip-unneeded "$STRIPPED_CAPTURE_LIB" ;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
LICENSE_ZSTD="$(find_one "$CPM_CACHE_DIR/zstd" LICENSE)"
|
||||
LICENSE_CAPSTONE="$(find_one "$CPM_CACHE_DIR/capstone" LICENSE.TXT)"
|
||||
LICENSE_PPQSORT="$(find_one "$CPM_CACHE_DIR/ppqsort" LICENSE)"
|
||||
|
||||
LICENSE_NAMES=(LICENSE LICENSE-zstd LICENSE-capstone LICENSE-ppqsort)
|
||||
LICENSE_SRCS=("$SRC_DIR/LICENSE" "$LICENSE_ZSTD" "$LICENSE_CAPSTONE" "$LICENSE_PPQSORT")
|
||||
|
||||
for i in "${!LICENSE_NAMES[@]}"; do
|
||||
if [[ -z "${LICENSE_SRCS[$i]}" || ! -f "${LICENSE_SRCS[$i]}" ]]; then
|
||||
echo "error: ${LICENSE_NAMES[$i]} source not found under $CPM_CACHE_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p "$OUT_DIR" "$RES_DIR"
|
||||
for DEST in "$OUT_DIR" "$RES_DIR"; do
|
||||
cp -L "$CLIENT_LIB" "$DEST/$CLIENT_LIB_NAME"
|
||||
cp -L "$STRIPPED_CAPTURE_LIB" "$DEST/$CAPTURE_LIB_NAME"
|
||||
for i in "${!LICENSE_NAMES[@]}"; do
|
||||
cp "${LICENSE_SRCS[$i]}" "$DEST/${LICENSE_NAMES[$i]}"
|
||||
done
|
||||
done
|
||||
|
||||
echo "built: $OUT_DIR/$CLIENT_LIB_NAME, $OUT_DIR/$CAPTURE_LIB_NAME ($TRACY_VERSION)"
|
||||
echo "staged: $RES_DIR/$CLIENT_LIB_NAME, $RES_DIR/$CAPTURE_LIB_NAME"
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# usage: companion-version.sh <name> <catalog-key>; TAG is <name>-v<version>[-N] or unset
|
||||
set -euo pipefail
|
||||
|
||||
name="${1:?name required}"
|
||||
key="${2:?catalog key required}"
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
|
||||
base="$(sed -nE "s/^$key = \"(.*)\"\$/\\1/p" gradle/libs.versions.toml)"
|
||||
[[ -n "$base" ]] || { echo "$key not found" >&2; exit 1; }
|
||||
|
||||
tag="${TAG:-}"
|
||||
if [[ -z "$tag" ]]; then
|
||||
echo "$base"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
version="${tag#"$name"-v}"
|
||||
if [[ "$version" == "$tag" || ( "$version" != "$base" && ! "$version" =~ ^"$base"-[1-9][0-9]*$ ) ]]; then
|
||||
echo "bad tag '$tag': want $name-v$base[-N]" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$version"
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# usage: release-companion-jar.sh <jar> <notes>; TAG is <name>-v<version>
|
||||
set -euo pipefail
|
||||
|
||||
: "${TAG:?TAG required}"
|
||||
jar="${1:?jar required}"
|
||||
notes="${2:?notes required}"
|
||||
|
||||
gh release create "$TAG" "$jar" --latest=false --title "angelica-${TAG%%-v*} ${TAG#*-v}" --notes "$notes"
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$REPO_ROOT/umbra"
|
||||
|
||||
gradle_args=(:compileJava :compileMixinJava :test --no-daemon --console=plain --stacktrace)
|
||||
if [[ -n "${UMBRA_GRADLE_ARGS:-}" ]]; then
|
||||
read -ra extra_args <<< "$UMBRA_GRADLE_ARGS"
|
||||
gradle_args+=("${extra_args[@]}")
|
||||
fi
|
||||
|
||||
sh ./gradlew "${gradle_args[@]}"
|
||||
@@ -9,5 +9,42 @@ on:
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master
|
||||
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@05e7cd7ce0ea015acda8c10c1c39fb3a20a49e60
|
||||
with:
|
||||
disable-spotless-pr: true
|
||||
java-distribution: temurin
|
||||
cache-rfg: true
|
||||
secrets: inherit
|
||||
|
||||
sdl-gpu-tests:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
concurrency:
|
||||
group: sdl-gpu-tests-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Install a software Vulkan driver
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y xvfb mesa-vulkan-drivers
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: |
|
||||
21
|
||||
25
|
||||
- uses: gradle/actions/setup-gradle@v5
|
||||
- name: Run SDL-GPU tests
|
||||
run: xvfb-run -a --server-args="-screen 0 1366x768x24" ./gradlew --build-cache --stacktrace --continue :sdl-gpu:glsmSdlTest :sdl-gpu:sdlGpuDeviceTest
|
||||
- name: Check LWJGL did not extract natives to the shared temp dir
|
||||
run: test ! -e "/tmp/lwjgl_$USER"
|
||||
- name: Attach SDL-GPU test results
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: sdl-gpu-test-results
|
||||
path: |
|
||||
sdl-gpu/build/test-results/
|
||||
sdl-gpu/build/reports/tests/
|
||||
sdl-gpu/hs_err_pid*.log
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Build angelica-moltenvk
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "moltenvk-v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: Publish angelica-moltenvk.jar to the GTNH maven
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
- uses: gradle/actions/setup-gradle@v4
|
||||
- name: Compute version
|
||||
shell: bash
|
||||
env:
|
||||
TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
|
||||
run: |
|
||||
version="$(bash .github/scripts/companion-version.sh moltenvk lwjgl3Release)"
|
||||
echo "MOLTENVK_VERSION=$version" >> "$GITHUB_ENV"
|
||||
- name: Build angelica-moltenvk.jar
|
||||
run: ./gradlew -PmoltenvkVersion="$MOLTENVK_VERSION" :moltenvk:distJar
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: angelica-moltenvk
|
||||
path: moltenvk/build/libs/angelica-moltenvk-*.jar
|
||||
if-no-files-found: error
|
||||
- name: Publish to GTNH maven
|
||||
if: github.ref_type == 'tag' || inputs.publish
|
||||
env:
|
||||
MAVEN_USER: ${{ secrets.MAVEN_USER }}
|
||||
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||
run: ./gradlew -PpublishMoltenVK=true -PmoltenvkVersion="$MOLTENVK_VERSION" :moltenvk:publish
|
||||
- name: Create GitHub release
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: bash .github/scripts/release-companion-jar.sh moltenvk/build/libs/angelica-moltenvk-*.jar "MoltenVK for the SDL GPU Vulkan driver on macOS."
|
||||
@@ -0,0 +1,97 @@
|
||||
name: Build Tracy natives
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "tracy-v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: Publish angelica-tracy.jar to the GTNH maven
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [linux-x64, windows-x64, macos-x64, macos-arm64]
|
||||
include:
|
||||
- platform: linux-x64
|
||||
os: ubuntu-22.04
|
||||
- platform: windows-x64
|
||||
os: windows-2025
|
||||
- platform: macos-x64
|
||||
os: macos-15-intel
|
||||
- platform: macos-arm64
|
||||
os: macos-15
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Build Tracy natives
|
||||
shell: bash
|
||||
env:
|
||||
OUT_DIR: dist/tracy-natives
|
||||
run: |
|
||||
export TRACY_VERSION="v$(sed -nE 's/^tracy = "(.*)"$/\1/p' gradle/libs.versions.toml)"
|
||||
bash .github/scripts/build-tracy-natives.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tracy-natives-${{ matrix.platform }}
|
||||
path: dist/tracy-natives/*
|
||||
if-no-files-found: error
|
||||
|
||||
assemble:
|
||||
needs: build
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
- uses: gradle/actions/setup-gradle@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: tracy-natives-*
|
||||
path: staging
|
||||
- name: Stage natives into tracy-client resources
|
||||
shell: bash
|
||||
run: |
|
||||
version="$(sed -nE 's/^tracy = "(.*)"$/\1/p' gradle/libs.versions.toml)"
|
||||
for d in staging/tracy-natives-*; do
|
||||
dest="tracy-client/src/main/resources/natives/tracy/$version/${d#staging/tracy-natives-}"
|
||||
mkdir -p "$dest"
|
||||
cp "$d"/* "$dest/"
|
||||
done
|
||||
find tracy-client/src/main/resources/natives -type f
|
||||
- name: Compute version
|
||||
shell: bash
|
||||
env:
|
||||
TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
|
||||
run: |
|
||||
version="$(bash .github/scripts/companion-version.sh tracy tracy)"
|
||||
echo "TRACY_JAR_VERSION=$version" >> "$GITHUB_ENV"
|
||||
- name: Build angelica-tracy.jar
|
||||
run: ./gradlew -PtracyJarVersion="$TRACY_JAR_VERSION" :tracy-client:distJar
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: angelica-tracy
|
||||
path: tracy-client/build/libs/angelica-tracy-*.jar
|
||||
if-no-files-found: error
|
||||
- name: Publish to GTNH maven
|
||||
if: github.ref_type == 'tag' || inputs.publish
|
||||
env:
|
||||
MAVEN_USER: ${{ secrets.MAVEN_USER }}
|
||||
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||
run: ./gradlew -PpublishTracy=true -PtracyJarVersion="$TRACY_JAR_VERSION" :tracy-client:publish
|
||||
- name: Create GitHub release
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: bash .github/scripts/release-companion-jar.sh tracy-client/build/libs/angelica-tracy-*.jar "Tracy profiler backend for Angelica."
|
||||
@@ -3,7 +3,7 @@ name: Release tagged build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: [ '*' ]
|
||||
tags: [ '*', '!tracy-v*', '!moltenvk-v*' ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -12,3 +12,22 @@ jobs:
|
||||
release-tags:
|
||||
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/release-tags.yml@master
|
||||
secrets: inherit
|
||||
|
||||
notify-discord:
|
||||
needs: [release-tags]
|
||||
runs-on: ubuntu-24.04
|
||||
if: >-
|
||||
${{ !endsWith(github.ref_name, '-snapshot')
|
||||
&& !contains(github.event.head_commit.message, '[snapshot]')
|
||||
&& !endsWith(github.ref_name, '-pre') }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
- name: Announce release to Discord
|
||||
shell: bash
|
||||
env:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_RELEASE_WEBHOOK_URL }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: bash .github/scripts/announce-release.sh
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Umbra build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master, main ]
|
||||
push:
|
||||
branches: [ master, main ]
|
||||
|
||||
jobs:
|
||||
umbra-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: |
|
||||
21
|
||||
25
|
||||
- uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
validate-wrappers: true
|
||||
- run: .github/scripts/umbra-build.sh
|
||||
+14
-2
@@ -1,10 +1,12 @@
|
||||
.gradle
|
||||
.kotlin/
|
||||
.lwjgl/
|
||||
.settings
|
||||
/.idea/
|
||||
/.vs/
|
||||
/.vscode/
|
||||
/run/
|
||||
/build/
|
||||
run/
|
||||
build/
|
||||
/eclipse/
|
||||
.classpath
|
||||
.project
|
||||
@@ -12,6 +14,7 @@
|
||||
/config/
|
||||
/crash-reports/
|
||||
/logs/
|
||||
tmp/
|
||||
options.txt
|
||||
/saves/
|
||||
usernamecache.json
|
||||
@@ -36,3 +39,12 @@ addon.local.gradle
|
||||
addon.local.gradle.kts
|
||||
addon.late.local.gradle
|
||||
addon.late.local.gradle.kts
|
||||
local/
|
||||
tracy-client/src/main/resources/natives/
|
||||
tracy-client/angelica/
|
||||
dist/tracy-natives/
|
||||
*.tracy
|
||||
*.jfr
|
||||
|
||||
tracy-client/angelica/
|
||||
shaderc_failures/
|
||||
@@ -0,0 +1,57 @@
|
||||
# AI Usage Policy
|
||||
|
||||
🌐 **Languages:** [English](AI_POLICY.md) | [Українська](AI_POLICY_uk.md)
|
||||
|
||||
This project has strict rules for AI usage:
|
||||
|
||||
* **The human-in-the-loop must fully understand all code.** If you
|
||||
can't explain what your changes do and how they interact with the
|
||||
greater system without the aid of AI tools, do not contribute
|
||||
those changes.
|
||||
|
||||
* **Issues and discussions can use AI assistance but must have a full
|
||||
human-in-the-loop.** This means that any content generated with AI
|
||||
must have been reviewed *and edited* by a human before submission.
|
||||
The author must do their research and trim this down.
|
||||
|
||||
* **No AI-generated media is allowed (textures, videos, audio, etc.).**
|
||||
Text and code are the only acceptable AI-generated content, per the
|
||||
other rules in this policy.
|
||||
|
||||
* **Commit messages, PR descriptions must read in your own voice.**
|
||||
No AI-authored or AI co-authored commits, including Co-authored-by AI tags.
|
||||
|
||||
* **Only contribute fixes for bugs you have personally hit in-game.**
|
||||
If you haven’t experienced or reproduced a bug firsthand, and don’t
|
||||
already know the relevant code well, don’t throw AI at it and forward it to us.
|
||||
Patches written for problems the author never actually experienced themselves
|
||||
tend to fix the wrong thing and/or produce another.
|
||||
|
||||
* **The voice and intent of written content must be yours.**
|
||||
For documentation and especially quests, the tone and the point you're trying to make
|
||||
has to come from you, not an AI. Use AI to help with phrasing or cleanup if you like,
|
||||
but if the substance and voice aren't yours, don't contribute it.
|
||||
|
||||
* **Bad AI use may be blocked.** People who produce bad contributions
|
||||
that are clearly AI (slop) may be blocked from making future contributions.
|
||||
We love to help junior developers learn and grow, but if you're interested
|
||||
in that, don't use AI and we'll help you. We're sorry that bad AI
|
||||
users have ruined this for you.
|
||||
|
||||
|
||||
## There are Humans Here
|
||||
|
||||
Please remember that this project is maintained by humans.
|
||||
|
||||
Every discussion, issue, and pull request is read and reviewed by
|
||||
humans (and sometimes machines, too). It is a boundary point at which
|
||||
people interact with each other and work is done. It is rude and
|
||||
disrespectful to approach this boundary with low-effort, unqualified
|
||||
work, since it puts the burden of validation on the maintainer.
|
||||
|
||||
In a perfect world, AI would produce high-quality, accurate work
|
||||
every time. But today, that reality depends on the user of the AI.
|
||||
And today, many users of AI do not use it well. So, until that
|
||||
changes, we need to have strict rules to protect maintainers.
|
||||
|
||||
Based on https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md
|
||||
@@ -0,0 +1,56 @@
|
||||
# Політика використання ШІ
|
||||
|
||||
🌐 **Мови:** [English](AI_POLICY.md) | [Українська](AI_POLICY_uk.md)
|
||||
|
||||
Цей проєкт має суворі правила щодо використання штучного інтелекту (ШІ):
|
||||
|
||||
* **Людина, яка контролює процес, повинна повністю розуміти весь код.**
|
||||
Якщо ви не можете пояснити, що роблять ваші зміни та як вони взаємодіють із
|
||||
більшою системою без допомоги інструментів ШІ, не пропонуйте ці зміни.
|
||||
|
||||
* **Обговорення та задачі можуть створюватися за допомогою ШІ, але повинні перебувати
|
||||
під повним контролем людини.** Це означає, що будь-який контент, згенерований за допомогою ШІ,
|
||||
перед надсиланням має бути перевірений *та відредагований* людиною.
|
||||
Автор повинен сам розібратися в питанні та відсіяти все зайве.
|
||||
|
||||
* **Медіаконтент, згенерований ШІ, заборонений (текстури, відео, аудіо тощо).**
|
||||
Текст і код є єдиним контентом, згенерованим ШІ, який дозволено використовувати
|
||||
згідно з іншими правилами цієї політики.
|
||||
|
||||
* **Повідомлення до комітів та описи PR повинні бути написані у вашому власному стилі.**
|
||||
Коміти, повністю або частково створені ШІ, заборонені, включно з тегами `Co-authored-by` з вказівкою ШІ.
|
||||
|
||||
* **Подавайте виправлення лише для тих багів, із якими ви особисто зіткнулися в грі.**
|
||||
Якщо ви особисто не стикалися з багом, не відтворили його власноруч і ще не знаєте
|
||||
добре відповідний код, не натравлюйте на нього ШІ, щоб потім перенаправити результат нам.
|
||||
Патчі, написані для проблем, із якими автор особисто ніколи не стикався, зазвичай
|
||||
виправляють не те, що потрібно, та/або створюють нові баги.
|
||||
|
||||
* **Стиль та суть письмового контенту повинні належати вам.**
|
||||
Для документації та особливо для квестів тон і думка, яку ви намагаєтеся донести, мають
|
||||
виходити від вас, а не від ШІ. Ви можете використовувати ШІ для покращення формулювань
|
||||
або вичитки, якщо бажаєте, але якщо суть і стиль не ваші, не пропонуйте такий контент.
|
||||
|
||||
* **За зловживання ШІ можна отримати блокування.** Користувачі, які створюють
|
||||
низькоякісний контент, очевидно згенерований ШІ (слоп), можуть бути заблоковані для подальших внесків.
|
||||
Ми залюбки допомагаємо розробникам-початківцям навчатися та рости, але якщо ви зацікавлені
|
||||
в цьому, не використовуйте ШІ, і ми вам допоможемо. Нам прикро, що недобросовісні
|
||||
користувачі ШІ зіпсували це для вас.
|
||||
|
||||
|
||||
## Тут працюють люди
|
||||
|
||||
Будь ласка, пам’ятайте, що цей проєкт підтримують люди.
|
||||
|
||||
Кожне обговорення, задача та запит на злиття читаються й перевіряються
|
||||
людьми (а іноді й машинами). Це точка перетину, де люди взаємодіють
|
||||
один з одним та виконують роботу. Неґречно та неповажно приходити сюди
|
||||
з неякісною, некваліфікованою та зробленою абияк роботою, оскільки це
|
||||
перекладає тягар перевірки на мейнтейнерів.
|
||||
|
||||
В ідеальному світі ШІ щоразу створював би високоякісну та точну роботу.
|
||||
Але сьогодні ця реальність залежить від користувача ШІ. І сьогодні чимало
|
||||
користувачів використовують ШІ неналежним чином. Тому, поки це не зміниться,
|
||||
ми змушені мати суворі правила, щоб захистити мейнтейнерів.
|
||||
|
||||
Створено на основі https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md
|
||||
@@ -0,0 +1,17 @@
|
||||
# Angelica dev commands
|
||||
|
||||
# Build with headless display
|
||||
build:
|
||||
xvfb-run --server-args="-screen 0 1366x768x24" ./gradlew build
|
||||
|
||||
# Run tests with headless display
|
||||
test:
|
||||
xvfb-run --server-args="-screen 0 1366x768x24" ./gradlew test
|
||||
|
||||
# Build jar only (no tests)
|
||||
jar:
|
||||
./gradlew jar
|
||||
|
||||
# Clean build directory
|
||||
clean:
|
||||
./gradlew clean
|
||||
@@ -7,40 +7,80 @@
|
||||
* It's a cute angle -mitchej123
|
||||
* It's the angel that pulls you out of OptiFine's hell -Roadhog360
|
||||
|
||||
## **Beta Quality - Here there be Dragons**
|
||||
* Shaders sneak preview - Please don't report issues yet, but please do submit fixes
|
||||
* Complementary & BSL mostly function, but have some bugs
|
||||
* Sildurs, Seus, and any other shader probably doesn't work
|
||||
## What's in it
|
||||
* Shaders - Iris backport & enhancements
|
||||
* Terrain - Celeritas 2.5.12 with threaded chunk building and translucency sorting
|
||||
* GL 3.3 core profile with Java based fixed function pipeline emulation
|
||||
* Renderer selection - OpenGL, or SDL GPU for Vulkan/Metal/Direct3D 12
|
||||
* Batching and instancing for entities, tile entities, items, particles and weather
|
||||
* Dynamic lights
|
||||
* HUD caching
|
||||
* Custom fonts and text effects
|
||||
* MCPatcher - connected textures, custom item textures, custom colors, random mobs, better skies
|
||||
* Bundled NotFine, zoom, FPS reducer
|
||||
|
||||
## Status
|
||||
* OpenGL + Celeritas terrain - Default
|
||||
* Shaders - Generally Available
|
||||
* Complementary + Euphoria Patches is the primary compatibility target
|
||||
* Sildurs, Seus, and any other shaders might work
|
||||
* SDL GPU renderer - Experimental
|
||||
* MCPatcher features - Alpha
|
||||
* HUD caching - Semi-stable
|
||||
|
||||
## SDL GPU renderer
|
||||
Optional, OpenGL is the default
|
||||
* Vulkan, Metal (macOS) and Direct3D 12 (Windows) through SDL3
|
||||
* Needs [LWJGL3ify](https://github.com/GTNewHorizons/lwjgl3ify/releases/) 3.0.25+
|
||||
* Falls back to OpenGL when no device can be created
|
||||
* Vulkan on macOS via MoltenVK from the optional `angelica-moltenvk` jar
|
||||
|
||||
## Core Profile
|
||||
Targets GL 3.3+ core profile
|
||||
* FFP emulation via generated GLSL shaders
|
||||
* Display list emulation
|
||||
* Software matrix stacks and state management through GLSM
|
||||
* GL_QUADS -> GL_TRIANGLES conversion
|
||||
* Automatic transformation of mod shaders that use compat-only GLSL builtins (`gl_ModelViewMatrix`, `texture2D`, etc.)
|
||||
|
||||
|
||||
# Required Dependencies
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.14
|
||||
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.4.2
|
||||
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
|
||||
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.11.19
|
||||
* NOTE: Some mods are not required, but a specific version is required if present - see: `Minimum versions` below.
|
||||
|
||||
# Minimum versions (if present)
|
||||
* [LWJGL3ify](https://github.com/GTNewHorizons/lwjgl3ify/releases/) 1.5.3+ (3.0.25+ for the SDL GPU renderer)
|
||||
* [Hodgepodge](https://github.com/GTNewHorizons/Hodgepodge/releases) 2.4.4+
|
||||
* [ArchaicFix](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) 0.7.0+
|
||||
* [CodeChickenCore](https://github.com/GTNewHorizons/CodeChickenCore/releases) 1.2.0+ - Threading issues below that
|
||||
* [Amazing Trophies](https://github.com/GTNewHorizons/Amazing-Trophies/releases) 1.2.2+
|
||||
|
||||
# Known (temporary) Incompatibilities
|
||||
* Forge Relocation - Currently non Thread Safe; safe if not in use
|
||||
* ProjectRed Frames - Currently non Thread Safe; safe if not in use'
|
||||
* ProjectRed Frames - Currently non Thread Safe; safe if not in use
|
||||
|
||||
# Potential Incompatibilities
|
||||
* EndlessIDs - Compat commits have been made, might work, might not - not fully tested
|
||||
|
||||
# Known Incompatibilities
|
||||
* FalseTweaks - Not currently planned (Looking for volunteers) [Might work if you effectively disable most of the mod]
|
||||
* SmoothFont - Not currently planned (Looking for volunteers)
|
||||
* RPLE - Not currently planned (Looking for volunteers)
|
||||
* LiteLoader - Install [LiteloaderLoader](https://github.com/Midnight145/LiteloaderLoader)
|
||||
|
||||
# Permanent Incompatibilities
|
||||
* OptiFine [Disabled, won't fix]
|
||||
* FastCraft [Disabled, won't fix]
|
||||
* BetterFPS [Disabled, won't fix]
|
||||
* OptimizationsAndTweaks [Disabled, won't fix]
|
||||
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - use 1.2.2+
|
||||
* [LWJGL3ify 1.5.4 and below](https://github.com/GTNewHorizons/lwjgl3ify/releases/) - Use the latest version available, 1.5.7+
|
||||
* [ArchaicFix 0.6.2 and below](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) - Use 0.7.0 or above
|
||||
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 0.4.5 or above
|
||||
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
|
||||
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
|
||||
* SmoothFont - Equivalent functionality added
|
||||
* ClientFixer - Use a resource pack like [GTNH-FTI-Standard-Font](https://github.com/Eldrinn-Elantey/GTNH-FTI-Standard-Font)
|
||||
|
||||
# Building
|
||||
* `./gradlew build`, `./gradlew test`
|
||||
* Subprojects: `glsm` (state manager, FFP emulation, backends), `lwjgl3-backend`, `sdl-gpu`, `tracy-client`, `moltenvk`
|
||||
* Read the [AI policy](AI_POLICY.md) before opening a PR
|
||||
|
||||
# Credits
|
||||
* ShadersMod
|
||||
@@ -55,16 +95,16 @@
|
||||
* The [Iris](https://github.com/IrisShaders/Iris) Team: coderbot, IMS212, Justsnoopy30, FoundationGames
|
||||
* Asek3 for the Iris Forge port [Oculus](https://github.com/Asek3/Oculus/tree/839ce8eca8cf0c4f6b7a1322b906e5c99125ca69)
|
||||
* Backported code under the LGPL
|
||||
* Sodium
|
||||
* Sodium / Celeritas
|
||||
* JellySquid & the CaffeineMC team, for making Sodium in the first place
|
||||
* Embeddedt for [Embeddium](https://github.com/embeddedt/embeddium/tree/dc59ca357c25beefd6288f0d1d40b4cd8e670ab8) under the [LGPL 3.0](https://github.com/embeddedt/embeddium/blob/16.x/forge/LICENSE)
|
||||
* Embeddedt for Celeritas used under the LGPL 3.0
|
||||
* Reese's Sodium Options
|
||||
* FlashyReese for [Reese's Sodium Options](https://github.com/FlashyReese/reeses-sodium-options/tree/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b) under the [MIT license](https://github.com/FlashyReese/reeses-sodium-options/blob/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b/LICENSE.txt)
|
||||
* Neodymium
|
||||
* Makamys for [Neodymium](https://github.com/makamys/Neodymium) - Various Tessellator -> Quad functionality taken under the LGPL 3
|
||||
* MCPatcherForge
|
||||
* Original [MCPatcher](https://bitbucket.org/prupe/mcpatcher/src/master/): Paul Rupe
|
||||
* [Forge version](https://bitbucket.org/prupe/mcpatcher/src/master/): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
|
||||
* [Forge version](https://github.com/mist475/MCPatcherForge): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
|
||||
* HUDCaching
|
||||
* Created by [Moulberry](https://github.com/Moulberry/MCHUDCaching), backported from [Patcher](https://github.com/Sk1erLLC/Patcher) mod by Alexdoru under the CC-BY-SA-NC 4.0 license
|
||||
* Angelica
|
||||
@@ -82,3 +122,12 @@
|
||||
* Code from both glsl-transformation-lib and Monocle taken under their respective LGPL 3.0 licenses
|
||||
* FalseTweaks
|
||||
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
|
||||
* Beddium
|
||||
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
|
||||
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) - Code ported and/or inspired from Mesa - under the MIT license
|
||||
* Display list ID allocator ported from [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
|
||||
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
|
||||
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
|
||||
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
|
||||
* RGSS Terrain Texture Filtering
|
||||
* Indiesoft for [defold-sharp-sprite](https://github.com/indiesoftby/defold-sharp-sprite), used as the reference implementation under the MIT license
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
# Angelica<img src="img/logo-readme.png" align="right">
|
||||
|
||||
<sup>**[CurseForge](https://www.curseforge.com/minecraft/mc-mods/angelica) | [Modrinth](https://modrinth.com/mod/angelica)**</sup>
|
||||
|
||||
🌐 **Мови:** [English](README.MD) | [Українська](README_uk.MD)
|
||||
|
||||
* Маленький Кут [sic], що підтримує шейдери, водночас рятуючи ваші FPS від вірної смерті — boubou
|
||||
* Він тупий чи гострий? — Caedis
|
||||
* Це милий кут — mitchej123
|
||||
* Це янгол, який витягує вас із пекла OptiFine — Roadhog360
|
||||
|
||||
## **Бета-якість — Тут водяться дракони**
|
||||
* Шейдери — Бета
|
||||
* Complementary + Euphoria Patches є головною ціллю для сумісності
|
||||
* Sildurs, Seus та будь-які інші шейдери можуть працювати
|
||||
|
||||
## Базовий профіль
|
||||
Цільовим є базовий профіль GL 3.3+
|
||||
* Емуляція FFP через згенеровані GLSL-шейдери
|
||||
* Емуляція списків відображення
|
||||
* Програмні стеки матриць та керування станом через GLSM
|
||||
* Конвертація GL_QUADS → GL_TRIANGLES
|
||||
* Автоматична трансформація шейдерів з модів, які використовують вбудовані функції GLSL виключно для сумісності (`gl_ModelViewMatrix`, `texture2D` тощо)
|
||||
|
||||
|
||||
# Обов’язкові залежності
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
|
||||
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.11.19
|
||||
* ПРИМІТКА: Деякі моди не є обов’язковими, але якщо вони встановлені, вимагається їхня конкретна версія — див. розділ `Постійна несумісність` нижче.
|
||||
|
||||
# Відома (тимчасова) несумісність
|
||||
* Forge Relocation — Наразі не є потокобезпечним; безпечно, якщо не використовується
|
||||
* ProjectRed Frames — Наразі не є потокобезпечним; безпечно, якщо не використовується
|
||||
|
||||
# Можлива несумісність
|
||||
* EndlessIDs — Коміти для сумісності були зроблені, може працювати, а може й ні — не повністю протестовано
|
||||
|
||||
# Відома несумісність
|
||||
* FalseTweaks — Наразі не планується (Шукаємо волонтерів) [Може працювати, якщо фактично вимкнути більшу частину мода]
|
||||
* RPLE — Наразі не планується (Шукаємо волонтерів)
|
||||
* LiteLoader — Встановіть [LiteloaderLoader](https://github.com/Midnight145/LiteloaderLoader)
|
||||
|
||||
# Постійна несумісність
|
||||
* OptiFine [Вимкнено, виправлятись не буде]
|
||||
* FastCraft [Вимкнено, виправлятись не буде]
|
||||
* BetterFPS [Вимкнено, виправлятись не буде]
|
||||
* OptimizationsAndTweaks [Вимкнено, виправлятись не буде]
|
||||
* [Amazing Trophies 1.2.1 і нижче](https://github.com/GTNewHorizons/Amazing-Trophies/releases) — Використовуйте 1.2.2+
|
||||
* [LWJGL3ify 1.5.4 і нижче](https://github.com/GTNewHorizons/lwjgl3ify/releases/) — Використовуйте найновішу доступну версію, 1.5.7+
|
||||
* [ArchaicFix 0.6.2 і нижче](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) — Використовуйте 0.7.0 або вище
|
||||
* [Hodgepodge 2.4.3 і нижче](https://github.com/GTNewHorizons/Hodgepodge/releases) — Використовуйте 2.4.5 або вище
|
||||
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 — Проблеми з багатопотоковістю, використовуйте 1.3.0+
|
||||
* Neodymium — Не перевірено, але навряд чи буде сумісним. (Побудова сітки чанків точно несумісна, інші функції можуть працювати залежно від конфігурації)
|
||||
* SmoothFont — Додано еквівалентний функціонал
|
||||
* ClientFixer — Використовуйте пакет ресурсів, як-от [GTNH-FTI-Standard-Font](https://github.com/Eldrinn-Elantey/GTNH-FTI-Standard-Font)
|
||||
|
||||
# Подяки
|
||||
* ShadersMod
|
||||
* Оригінальні ShadersMods: karyonix, sonic ether, id_miner, daxnitro
|
||||
* eigenraven, Caedis, glowredman та mitchej123 за конвертацію ASM у Mixin
|
||||
* eigenraven та vlaetansky за те, що змусили оригінальний ShadersMod знову компілюватися
|
||||
* ArchaicFix
|
||||
* Включає графічні покращення з [ArchaicFix](https://github.com/embeddedt/ArchaicFix/tree/4afb943751883b27a1f0aa7c5ba71698bc897e95) від embeddedt за ліцензією [LGPL](https://github.com/embeddedt/ArchaicFix/blob/main/LICENSE.md)
|
||||
* NotFine
|
||||
* Включає модифіковану версію [NotFine](https://github.com/jss2a98aj/NotFine/tree/a2652e24b5654f437fb7138f6d1f5e947a7d7125) від jss2a98aj за ліцензією LGPL
|
||||
* Iris Shaders
|
||||
* Команда [Iris](https://github.com/IrisShaders/Iris): coderbot, IMS212, Justsnoopy30, FoundationGames
|
||||
* Asek3 за порт Iris для Forge [Oculus](https://github.com/Asek3/Oculus/tree/839ce8eca8cf0c4f6b7a1322b906e5c99125ca69)
|
||||
* Бекпортований код за ліцензією LGPL
|
||||
* Sodium / Celeritas
|
||||
* JellySquid та команді CaffeineMC за створення Sodium
|
||||
* Embeddedt за Celeritas, що використовується за ліцензією LGPL 3.0
|
||||
* Reese's Sodium Options
|
||||
* FlashyReese за [Reese's Sodium Options](https://github.com/FlashyReese/reeses-sodium-options/tree/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b) за ліцензією [MIT](https://github.com/FlashyReese/reeses-sodium-options/blob/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b/LICENSE.txt)
|
||||
* Neodymium
|
||||
* Makamys за [Neodymium](https://github.com/makamys/Neodymium) — Різноманітний функціонал Tessellator -> Quad взято за ліцензією LGPL 3
|
||||
* MCPatcherForge
|
||||
* Оригінальний [MCPatcher](https://bitbucket.org/prupe/mcpatcher/src/master/): Paul Rupe
|
||||
* [Версія для Forge](https://github.com/mist475/MCPatcherForge): Mist475 за ліцензією LGPL 3.0 з типовими ctm-текстурами для скла та книжкових полиць від DarkBum за ліцензією CC-BY-SA
|
||||
* HUDCaching
|
||||
* Створено [Moulberry](https://github.com/Moulberry/MCHUDCaching), бекпортовано з мода [Patcher](https://github.com/Sk1erLLC/Patcher) від Alexdoru за ліцензією CC-BY-SA-NC 4.0
|
||||
* Angelica
|
||||
* mitchej123, Omni, Makamys, Embeddedt, NanoLive, Caedis, Cardinalstar, Alexdoru, Eigenraven, mist475, Clepto, kurrycat за допомогу в запуску та налаштуванні Angelica!
|
||||
* IMS, jss2a98aj, || dbp ||, roadhog360 за поради та моральну підтримку
|
||||
* Forge 1.12+
|
||||
* Деякі файли були взяті з Minecraft Forge за ліцензією LGPL 2.1 і позначені відповідно
|
||||
* FRAPI та Indigo
|
||||
* API для побудови моделей значною мірою натхненний/скопійований з Fabric; автори оригінального коду (upstream) можуть використовувати його за оригінальною ліцензією Apache 2.0,
|
||||
див. LICENSE у `com.gtnewhorizons.angelica.models`
|
||||
* Dynamic Lights
|
||||
* LambdAurora за [LambDynamicLights](https://github.com/LambdAurora/LambDynamicLights) за ліцензією MIT, та anthxnymc за порт для Forge [DynamicLightsReforged](https://github.com/anthxnymc/DynamicLightsReforged), також за ліцензією MIT
|
||||
* Embeddedt та Ferri_Arnus за [glsl-transformation-lib](https://github.com/TauMC/glsl-transformation-lib)
|
||||
* Також за роботу над [Monocle](https://github.com/ferriarnus/Monocle), який використовувався як еталон для реалізації glsl-transformation-lib
|
||||
* Код з обох glsl-transformation-lib та Monocle взято за їхніми відповідними ліцензіями LGPL 3.0
|
||||
* FalseTweaks
|
||||
* FalsePattern за [FalseTweaks](https://github.com/falsepattern/falsetweaks) — Оптимізації рендерингу предметів взято з FalseTweaks за ліцензією LGPL 3.0
|
||||
* Beddium
|
||||
* Venn та Falsepattern за [Beddium](https://github.com/vfx-dev/Beddium) — MinimapRenderer, SafeChunkTracker та деякі команди для налагодження адаптовано за ліцензією LGPL 3.0
|
||||
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Код портований з Mesa та/або натхненний нею — за ліцензією MIT
|
||||
* Розподілювач ID списків відображення портовано з [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
|
||||
* Генерація вершинних шейдерів FFP натхненна ffvertex_prog.c (Copyright 2007 VMware, Inc.)
|
||||
* Генерація фрагментних шейдерів FFP натхненна ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
|
||||
* Оптимізація параметрів туману натхненна st_nir_lower_fog.c (Copyright 2023 Google LLC) та prog_statevars.c (Copyright 1999-2007 Brian Paul)
|
||||
+225
-15
@@ -1,27 +1,81 @@
|
||||
import com.modrinth.minotaur.TaskModrinthUpload
|
||||
import net.darkhax.curseforgegradle.TaskPublishCurseForge
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
id("com.gtnewhorizons.gtnhconvention")
|
||||
}
|
||||
|
||||
minecraft {
|
||||
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
|
||||
apply(from = "gradle/mod-version.gradle.kts")
|
||||
|
||||
val lwjglDebug = false
|
||||
val gpuHud = false
|
||||
val renderdoc = false
|
||||
val rgp = false
|
||||
extra["lwjglDebug"] = lwjglDebug
|
||||
|
||||
minecraft {
|
||||
extraRunJvmArguments.add("-Dangelica.debug.testBlocks=true")
|
||||
extraRunJvmArguments.add("-Dangelica.tracy=true")
|
||||
extraRunJvmArguments.add("-Dangelica.sdlgpu.enable=true")
|
||||
extraRunJvmArguments.add("-Dangelica.unmappedGL=fail")
|
||||
extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=$lwjglDebug")
|
||||
|
||||
extraRunJvmArguments.add("-Dsun.net.client.defaultConnectTimeout=5000")
|
||||
extraRunJvmArguments.add("-Dsun.net.client.defaultReadTimeout=5000")
|
||||
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
|
||||
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
|
||||
//extraRunJvmArguments.add("-Dangelica.debug.redirectorLogspam=true")
|
||||
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events("passed", "skipped", "failed")
|
||||
apply(from = "gradle/angelica-run-common.gradle.kts")
|
||||
|
||||
if (gpuHud) {
|
||||
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
|
||||
if (org.gradle.internal.os.OperatingSystem.current().isMacOsX) {
|
||||
environment("MTL_HUD_ENABLED", "1")
|
||||
} else {
|
||||
environment("MANGOHUD", "1")
|
||||
environment("LD_PRELOAD", "/usr/\$LIB/mangohud/libMangoHud_shim.so")
|
||||
}
|
||||
}
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(8)
|
||||
}
|
||||
dependsOn(tasks.extractNatives2)
|
||||
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
inputs.property("version", project.version.toString())
|
||||
filesMatching("META-INF/rfb-plugin/*") {
|
||||
expand("version" to project.version.toString())
|
||||
|
||||
configurations.all {
|
||||
exclude(group = "com.github.GTNewHorizons", module = "Angelica")
|
||||
}
|
||||
|
||||
val osName = System.getProperty("os.name").lowercase()
|
||||
val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
|
||||
val isMacOsArm64 = isMacOs && System.getProperty("os.arch") == "aarch64"
|
||||
|
||||
// Linux runClient: RenderDoc frame capture (F12) OR RADV RGP/SQTT capture. Mutually exclusive; RGP wins
|
||||
tasks.withType<JavaExec>().configureEach {
|
||||
if (name.startsWith("runClient") && !isMacOs && !osName.contains("windows")) {
|
||||
environment("WAYLAND_DISPLAY", "")
|
||||
environment("XDG_SESSION_TYPE", "x11")
|
||||
val rgpFrame = project.findProperty("rgpFrame") as String?
|
||||
val rgpCapture = rgp || project.hasProperty("rgp") || rgpFrame != null
|
||||
if (rgpCapture) {
|
||||
environment("MESA_VK_TRACE", "rgp")
|
||||
if (rgpFrame != null) {
|
||||
environment("MESA_VK_TRACE_FRAME", rgpFrame)
|
||||
doFirst { logger.lifecycle("RGP: capturing frame $rgpFrame -> /tmp/java_*.rgp") }
|
||||
} else {
|
||||
environment("MESA_VK_TRACE_TRIGGER", "/tmp/angelica_rgp.trigger")
|
||||
doFirst { logger.lifecycle("RGP: touch /tmp/angelica_rgp.trigger (or press F1) to capture -> /tmp/java_*.rgp") }
|
||||
}
|
||||
} else if (renderdoc || project.hasProperty("renderdoc")) {
|
||||
val renderdocLib = file(project.findProperty("renderdocLib") as String? ?: "${System.getProperty("user.home")}/Downloads/renderdoc_1.44/lib/librenderdoc.so")
|
||||
if (renderdocLib.isFile) {
|
||||
environment("LD_PRELOAD", renderdocLib.absolutePath)
|
||||
doFirst { logger.lifecycle("RENDERDOC: LD_PRELOAD set, press F12 to capture") }
|
||||
} else {
|
||||
doFirst { logger.lifecycle("RENDERDOC: enabled but ${renderdocLib.absolutePath} not found; set -PrenderdocLib=<path>") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,3 +85,159 @@ tasks.register<Copy>("copyDependencies") {
|
||||
from(configurations.default)
|
||||
into("testDependencies")
|
||||
}
|
||||
|
||||
val embedOnly: Configuration by configurations
|
||||
val shadowImplementation: Configuration by configurations
|
||||
|
||||
val dependencyClasspath: FileCollection = sourceSets.main.get().runtimeClasspath - sourceSets.main.get().output
|
||||
|
||||
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = embedOnly
|
||||
classpath = dependencyClasspath
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
|
||||
dependsOn(shadowImplementation)
|
||||
from(shadowImplementation.map(::zipTree))
|
||||
exclude("module-info.class")
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
archiveFileName = "shadow-stripped.jar"
|
||||
}
|
||||
|
||||
val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = files(stripModuleInfoFromShadow.map { it.archiveFile })
|
||||
classpath = dependencyClasspath
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
val glsmTestFixtures: Configuration by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
attributes {
|
||||
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
|
||||
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
|
||||
}
|
||||
}
|
||||
|
||||
val downgradeGlsmTestFixturesForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = glsmTestFixtures
|
||||
classpath = dependencyClasspath
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
fun downgraded(task: TaskProvider<DowngradeFiles>): FileCollection = files(task.map { it.outputMap.values })
|
||||
|
||||
fun Test.configureAngelicaJava8() {
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(8)
|
||||
if (isMacOsArm64) {
|
||||
vendor = JvmVendorSpec.AZUL
|
||||
}
|
||||
}
|
||||
dependsOn(tasks.extractNatives2)
|
||||
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
|
||||
if (isMacOs) {
|
||||
jvmArgs("-Dapple.awt.UIElement=true")
|
||||
}
|
||||
testLogging { events("passed", "skipped", "failed") }
|
||||
|
||||
val swaps = mapOf(
|
||||
embedOnly to downgradeEmbedOnlyForTest,
|
||||
shadowImplementation to downgradeShadowImplForTest,
|
||||
glsmTestFixtures to downgradeGlsmTestFixturesForTest,
|
||||
)
|
||||
dependsOn(swaps.values)
|
||||
for ((original, downgrade) in swaps) {
|
||||
classpath = classpath.minus(original).plus(downgraded(downgrade))
|
||||
}
|
||||
}
|
||||
|
||||
if (isMacOs && providers.gradleProperty("clientJvmVendor").isPresent) {
|
||||
for (javaVersion in listOf(17, 21, 25)) {
|
||||
tasks.named<JavaExec>("runClient$javaVersion") {
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||
vendor = JvmVendorSpec.matching(providers.gradleProperty("clientJvmVendor").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forge's asm-debug-all 5.0.3 cannot read the Java 25 class files ArchScan walks; this ASM has to resolve first.
|
||||
val archScanAsm: Configuration by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
archScanAsm(libs.asm.tree.test)
|
||||
archScanAsm(libs.asm.commons.test)
|
||||
}
|
||||
|
||||
sourceSets.test {
|
||||
compileClasspath = archScanAsm + compileClasspath
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform { excludeTags = setOf("gl-core") }
|
||||
configureAngelicaJava8()
|
||||
classpath = archScanAsm + classpath
|
||||
jvmArgs("-Dceleritas.lwjglService=org.taumc.celeritas.lwjgl.HeadlessTestLWJGLService")
|
||||
dependsOn(":glsm:classes", ":lwjgl3-backend:classes", ":sdl-gpu:classes")
|
||||
}
|
||||
|
||||
val downgradeRootMainClasses = tasks.named<DowngradeFiles>("downgradeMainClasses")
|
||||
val downgradeRootTestClasses = tasks.named<DowngradeFiles>("downgradeTestClasses")
|
||||
|
||||
// JVMDowngrader only declares outputs for inputs that exist at configuration, so a clean tree has none.
|
||||
for (name in listOf("main", "mixin", "test")) {
|
||||
tasks.named<DowngradeFiles>("downgrade${name.replaceFirstChar { it.uppercase() }}Classes") {
|
||||
sourceSets[name].output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
|
||||
}
|
||||
}
|
||||
|
||||
val glCoreTest by tasks.registering(Test::class) {
|
||||
description = "Runs the gl-core tests in their own JVM with a real GL context."
|
||||
group = "verification"
|
||||
useJUnitPlatform { includeTags = setOf("gl-core") }
|
||||
|
||||
dependsOn(downgradeRootMainClasses, downgradeRootTestClasses)
|
||||
val rawMain = sourceSets["main"].output.classesDirs
|
||||
val rawTest = sourceSets["test"].output.classesDirs
|
||||
val downgradedTest = downgraded(downgradeRootTestClasses)
|
||||
|
||||
setTestClassesDirs(downgradedTest)
|
||||
classpath = downgradedTest
|
||||
.plus(downgraded(downgradeRootMainClasses))
|
||||
.plus(sourceSets["test"].runtimeClasspath.minus(rawMain).minus(rawTest))
|
||||
|
||||
configureAngelicaJava8()
|
||||
}
|
||||
|
||||
val verifyTestsRan by tasks.registering
|
||||
listOf(tasks.test, glCoreTest).forEach { testTask ->
|
||||
val name = testTask.name
|
||||
val verify = tasks.register("verify${name.replaceFirstChar { it.uppercase() }}Ran") {
|
||||
val resultsDir = layout.buildDirectory.dir("test-results/$name")
|
||||
dependsOn(testTask)
|
||||
doLast {
|
||||
val dir = resultsDir.get().asFile
|
||||
val xmls = dir.listFiles { f -> f.name.startsWith("TEST-") && f.name.endsWith(".xml") } ?: emptyArray()
|
||||
check(xmls.isNotEmpty()) { ":$name produced no TEST-*.xml in $dir - test task likely went NO-SOURCE" }
|
||||
}
|
||||
}
|
||||
verifyTestsRan.configure { dependsOn(verify) }
|
||||
}
|
||||
tasks.check { dependsOn(verifyTestsRan) }
|
||||
|
||||
apply(from = "gradle/angelica-shadow-common.gradle.kts")
|
||||
apply(from = "experimental-surround/surround.root.gradle.kts")
|
||||
|
||||
tasks.withType<TaskPublishCurseForge>().configureEach {
|
||||
uploadArtifacts.forEach { it.addGameVersion("Client") }
|
||||
}
|
||||
|
||||
tasks.withType<TaskModrinthUpload>().configureEach {
|
||||
setDependsOn(dependsOn.filterNot { it == "build" } + "assemble")
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
// Re-export Gradle's generated typed catalog accessors so applied scripts (dependencies.gradle.kts) can use them.
|
||||
dependencies {
|
||||
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
|
||||
implementation(libs.asm.build)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
create("libs") { from(files("../gradle/libs.versions.toml")) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import org.objectweb.asm.AnnotationVisitor
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import org.objectweb.asm.Opcodes
|
||||
import java.io.File
|
||||
|
||||
private const val LWJGL3_AWARE_DESC = "Lme/eigenraven/lwjgl3ify/api/Lwjgl3Aware;"
|
||||
|
||||
/**
|
||||
* Stamps @Lwjgl3Aware onto every class under [classesDir] - classes that already carry the annotation are left untouched.
|
||||
*/
|
||||
fun injectLwjgl3Aware(classesDir: File) {
|
||||
if (!classesDir.isDirectory) return
|
||||
|
||||
classesDir.walkTopDown()
|
||||
.filter { it.isFile && it.extension == "class" && it.name != "module-info.class" }
|
||||
.forEach { classFile ->
|
||||
val reader = ClassReader(classFile.readBytes())
|
||||
if (hasLwjgl3Aware(reader)) return@forEach
|
||||
|
||||
val writer = ClassWriter(reader, 0)
|
||||
reader.accept(object : ClassVisitor(Opcodes.ASM9, writer) {
|
||||
override fun visit(
|
||||
version: Int,
|
||||
access: Int,
|
||||
name: String?,
|
||||
signature: String?,
|
||||
superName: String?,
|
||||
interfaces: Array<out String>?
|
||||
) {
|
||||
super.visit(version, access, name, signature, superName, interfaces)
|
||||
super.visitAnnotation(LWJGL3_AWARE_DESC, true)?.visitEnd()
|
||||
}
|
||||
}, 0)
|
||||
classFile.writeBytes(writer.toByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasLwjgl3Aware(reader: ClassReader): Boolean {
|
||||
var found = false
|
||||
reader.accept(object : ClassVisitor(Opcodes.ASM9) {
|
||||
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
|
||||
if (descriptor == LWJGL3_AWARE_DESC) found = true
|
||||
return null
|
||||
}
|
||||
}, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
|
||||
return found
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.ArchiveOperations
|
||||
import org.gradle.api.tasks.SourceSetContainer
|
||||
import org.gradle.api.tasks.Sync
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
import org.gradle.kotlin.dsl.get
|
||||
import org.gradle.kotlin.dsl.register
|
||||
import org.gradle.kotlin.dsl.the
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class UnpackLwjgl3Natives : Sync() {
|
||||
@get:Inject
|
||||
abstract val archives: ArchiveOperations
|
||||
}
|
||||
|
||||
// LWJGL extracts jar-packed natives to a shared temp dir with no cross-process safety; plain files it loads in place.
|
||||
fun Project.registerLwjgl3NativesUnpack(): TaskProvider<UnpackLwjgl3Natives> {
|
||||
val jars = the<SourceSetContainer>()["test"].runtimeClasspath.filter {
|
||||
it.name.startsWith("lwjgl") && it.name.contains("-natives-") && !it.name.startsWith("lwjgl-platform-")
|
||||
}
|
||||
return tasks.register<UnpackLwjgl3Natives>("unpackLwjgl3Natives") {
|
||||
val archives = archives
|
||||
from(jars.elements.map { files -> files.map { archives.zipTree(it) } })
|
||||
exclude("META-INF/**")
|
||||
into(layout.buildDirectory.dir("lwjgl3-natives"))
|
||||
}
|
||||
}
|
||||
|
||||
fun Test.loadLwjgl3NativesFrom(unpack: TaskProvider<UnpackLwjgl3Natives>) {
|
||||
classpath = project.files(unpack) + classpath
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Add your dependencies here. Supported configurations:
|
||||
* - api("group:name:version:classifier"): if you use the types from this dependency in the public API of this mod
|
||||
* Available at runtime and compiletime for mods depending on this mod
|
||||
* - implementation("g:n:v:c"): if you need this for internal implementation details of the mod, but none of it is visible via the public API
|
||||
* Available at runtime but not compiletime for mods depending on this mod
|
||||
* - compileOnly("g:n:v:c"): if the mod you're building doesn't need this dependency during runtime at all, e.g. for optional mods
|
||||
* Not available at all for mods depending on this mod, only visible at compiletime for this mod
|
||||
* - compileOnlyApi("g:n:v:c"): like compileOnly, but also visible at compiletime for mods depending on this mod
|
||||
* Available at compiletime but not runtime for mods depending on this mod
|
||||
* - runtimeOnlyNonPublishable("g:n:v:c"): if you want to include a mod in this mod's runClient/runServer runs, but not publish it as a dependency
|
||||
* Not available at all for mods depending on this mod, only visible at runtime for this mod
|
||||
* - devOnlyNonPublishable("g:n:v:c"): a combination of runtimeOnlyNonPublishable and compileOnly for dependencies present at both compiletime and runtime,
|
||||
* but not published as Maven dependencies - useful for RFG-deobfuscated dependencies or local testing
|
||||
* - runtimeOnly("g:n:v:c"): if you don't need this at compile time, but want it to be present at runtime
|
||||
* Available at runtime for mods depending on this mod
|
||||
* - annotationProcessor("g:n:v:c"): mostly for java compiler plugins, if you know you need this, use it, otherwise don't worry
|
||||
* - testCONFIG("g:n:v:c") - replace CONFIG by one of the above (except api), same as above but for the test sources instead of main
|
||||
*
|
||||
* - shadowImplementation("g:n:v:c"): effectively the same as API, but the dependency is included in your jar under a renamed package name
|
||||
* Requires you to enable usesShadowedDependencies in gradle.properties
|
||||
*
|
||||
* - compile("g:n:v:c"): deprecated, replace with "api" (works like the old "compile") or "implementation" (can be more efficient)
|
||||
*
|
||||
* You can exclude transitive dependencies (dependencies of the chosen dependency) by appending { transitive = false } if needed,
|
||||
* but use this sparingly as it can break using your mod as another mod's dependency if you're not careful.
|
||||
*
|
||||
* To depend on obfuscated jars you can use `devOnlyNonPublishable(rfg.deobf("dep:spec:1.2.3"))` to fetch an obfuscated jar from maven,
|
||||
* or `devOnlyNonPublishable(rfg.deobf(project.files("libs/my-mod-jar.jar")))` to use a file.
|
||||
*
|
||||
* Gradle names for some of the configuration can be misleading, compileOnlyApi and runtimeOnly both get published as dependencies in Maven, but compileOnly does not.
|
||||
* The buildscript adds runtimeOnlyNonPublishable to also have a runtime dependency that's not published.
|
||||
*
|
||||
* For more details, see https://docs.gradle.org/8.0.1/userguide/java_library_plugin.html#sec:java_library_configurations_graph
|
||||
*/
|
||||
configurations {
|
||||
transformedMod // Mods that can be transformed - used for compiling angelica, but not necessary at runtime
|
||||
transformedMod.canBeConsumed = false
|
||||
transformedModCompileOnly
|
||||
transformedModCompileOnly.canBeConsumed = false
|
||||
|
||||
// Add the transformed mod dependencies to the compilation classpaths, but don't publish them in the Maven metadata
|
||||
compileClasspath.extendsFrom(transformedMod, transformedModCompileOnly)
|
||||
testCompileClasspath.extendsFrom(transformedMod, transformedModCompileOnly)
|
||||
|
||||
// Can be enabled for testing if desired
|
||||
// runtimeClasspath.extendsFrom(transformedMod)
|
||||
// testRuntimeClasspath.extendsFrom(transformedMod)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.5.78")
|
||||
|
||||
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:0.4.0") { transitive = false }
|
||||
|
||||
compileOnly("org.projectlombok:lombok:1.18.22") { transitive = false }
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.22")
|
||||
|
||||
// Use modern models+textures to inject QuadProviders
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:TX-Loader:1.7.2:dev')
|
||||
|
||||
// Iris Shaders
|
||||
compileOnly('org.jetbrains:annotations:24.0.1')
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.5.20:dev")
|
||||
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
|
||||
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-4.g6b42bca") {
|
||||
exclude module: "antlr4" // we only want to shadow the runtime module
|
||||
}
|
||||
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
|
||||
compileOnly "org.apache.ant:ant:1.8.2"
|
||||
|
||||
// Because who doesn't want NEI
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.6.45-GTNH:dev')
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.3.10:dev')
|
||||
|
||||
// Notfine Deps
|
||||
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles:1.0.4:dev")
|
||||
compileOnly("com.github.GTNewHorizons:twilightforest:2.6.35:dev") { transitive = false }
|
||||
compileOnly(rfg.deobf('curse.maven:witchery-69673:2234410'))
|
||||
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.12.14-GTNH:dev") { transitive = false }
|
||||
compileOnly("com.github.GTNewHorizons:Natura:2.7.5:dev")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.6.4:dev")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.1:dev") { transitive = false }
|
||||
compileOnly(rfg.deobf('maven.modrinth:backhand:1.4.1')) { transitive = false }
|
||||
|
||||
compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.5.1:api") { transitive = false }
|
||||
compileOnly("com.falsepattern:endlessids-mc1.7.10:1.5-beta0003:dev") { transitive = false }
|
||||
|
||||
compileOnly(rfg.deobf("curse.maven:extrautils-225561:2264383"))
|
||||
compileOnly(rfg.deobf("curse.maven:dynamiclights-227874:2337326"))
|
||||
|
||||
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.6:dev") // Mixin Version
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds-Legacy:1.4.7:dev") // ASM Version
|
||||
|
||||
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
|
||||
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:HoloInventory:2.4.13-GTNH:dev")
|
||||
|
||||
runtimeOnlyNonPublishable(rfg.deobf("CoreTweaks:CoreTweaks:0.3.3.2"))
|
||||
|
||||
// Hodgepodge
|
||||
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
|
||||
|
||||
// Better Crashes
|
||||
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.0-GTNH:dev")
|
||||
|
||||
// Tests
|
||||
testImplementation(platform('org.junit:junit-bom:5.9.2'))
|
||||
testImplementation('org.junit.jupiter:junit-jupiter')
|
||||
testImplementation("org.mockito:mockito-core:3.+")
|
||||
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Angelica's dependency declarations. Supported configurations:
|
||||
* - api("group:name:version:classifier"): if you use the types from this dependency in the public API of this mod
|
||||
* Available at runtime and compiletime for mods depending on this mod
|
||||
* - implementation("g:n:v:c"): if you need this for internal implementation details of the mod, but none of it is visible via the public API
|
||||
* Available at runtime but not compiletime for mods depending on this mod
|
||||
* - compileOnly("g:n:v:c"): if the mod you're building doesn't need this dependency during runtime at all, e.g. for optional mods
|
||||
* Not available at all for mods depending on this mod, only visible at compiletime for this mod
|
||||
* - runtimeOnlyNonPublishable("g:n:v:c"): if you want to include a mod in this mod's runClient/runServer runs, but not publish it as a dependency
|
||||
* - devOnlyNonPublishable("g:n:v:c"): a combination of runtimeOnlyNonPublishable and compileOnly, not published as a Maven dependency
|
||||
* - shadowImplementation("g:n:v:c"): like api, but included in your jar under a renamed package name
|
||||
* - embedOnly("g:n:v:c"): embedded in the jar without relocation
|
||||
*
|
||||
* Versions come from gradle/libs.versions.toml. Mod coordinates stay literal: nearly all carry a
|
||||
* :dev classifier, which a version catalog cannot express, and curse/modrinth "versions" are file ids.
|
||||
*
|
||||
* To depend on obfuscated jars use `rfg.deobf("dep:spec:1.2.3")`.
|
||||
*/
|
||||
import com.gtnewhorizons.retrofuturagradle.modutils.ModUtils
|
||||
import org.gradle.accessors.dm.LibrariesForLibs
|
||||
import org.gradle.kotlin.dsl.DependencyHandlerScope
|
||||
|
||||
val libs = the<LibrariesForLibs>()
|
||||
val rfg = (dependencies as ExtensionAware).extensions.getByType<ModUtils.RfgDependencyExtension>()
|
||||
|
||||
val api = configurations["api"]
|
||||
val compileOnly = configurations["compileOnly"]
|
||||
val annotationProcessor = configurations["annotationProcessor"]
|
||||
val shadowImplementation = configurations["shadowImplementation"]
|
||||
val runtimeOnlyNonPublishable = configurations["runtimeOnlyNonPublishable"]
|
||||
val devOnlyNonPublishable = configurations["devOnlyNonPublishable"]
|
||||
|
||||
// Mods that can be transformed - used for compiling angelica, but not necessary at runtime
|
||||
val transformedMod by configurations.creating { isCanBeConsumed = false }
|
||||
val transformedModCompileOnly by configurations.creating { isCanBeConsumed = false }
|
||||
|
||||
// Embed in JAR without relocation (compile only, shadowed into JAR)
|
||||
val embedOnly by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
// Add the transformed mod dependencies to the compilation classpaths, but don't publish them in the Maven metadata
|
||||
configurations["compileClasspath"].extendsFrom(transformedMod, transformedModCompileOnly, embedOnly)
|
||||
configurations["testCompileClasspath"].extendsFrom(transformedMod, transformedModCompileOnly, embedOnly)
|
||||
configurations["testRuntimeClasspath"].extendsFrom(embedOnly)
|
||||
configurations["testRuntimeClasspath"].exclude(group = "com.github.GTNewHorizons", module = "lwjgl3ify")
|
||||
|
||||
// Prevents transitive Angelica deps from resolving against this project
|
||||
configurations.configureEach {
|
||||
exclude(group = "com.github.GTNewHorizons", module = "Angelica")
|
||||
}
|
||||
|
||||
DependencyHandlerScope.of(dependencies).apply {
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.197:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.197:dev")
|
||||
|
||||
runtimeOnlyNonPublishable(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
|
||||
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-994-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ae2stuff:0.10.19-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BetterLoadingScreen:1.7.9-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BetterQuesting:3.8.72-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BuildCraft:7.1.63:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Botania:1.13.32-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:LogisticsPipes:1.5.32-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.55-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.18-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:IguanaTweaksTConstruct:2.7.10:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenBlocks:1.12.18-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Schematica:1.12.6-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:TinkersConstruct:1.14.93-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ServerUtilities:2.4.1:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ironchest:6.1.13:dev")
|
||||
runtimeOnlyNonPublishable("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:waila:1.19.30:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Draconic-Evolution:1.5.30-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:HoloInventory:2.5.15-GTNH:dev")
|
||||
|
||||
runtimeOnlyNonPublishable("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
|
||||
|
||||
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
|
||||
// runtimeOnlyNonPublishable(libs.lwjgl3ify)
|
||||
|
||||
compileOnly(libs.lombok) { isTransitive = false }
|
||||
annotationProcessor(libs.lombok)
|
||||
|
||||
// Iris Shaders
|
||||
compileOnly(libs.annotations)
|
||||
api(libs.gtnhlib) { artifact { classifier = "dev" } }
|
||||
|
||||
shadowImplementation(project(":glsm")) { isTransitive = false }
|
||||
shadowImplementation(project(":lwjgl3-backend")) { isTransitive = false }
|
||||
shadowImplementation(project(":sdl-gpu")) { isTransitive = false }
|
||||
runtimeOnlyNonPublishable(project(":tracy-client")) { isTransitive = false }
|
||||
if (System.getProperty("os.name").lowercase().contains("mac")) {
|
||||
runtimeOnlyNonPublishable(project(":moltenvk")) { isTransitive = false }
|
||||
}
|
||||
devOnlyNonPublishable(libs.async.profiler)
|
||||
shadowImplementation(libs.eventbus)
|
||||
|
||||
shadowImplementation(libs.jcpp) // Apache 2.0
|
||||
shadowImplementation(libs.glsl.transformation.lib) {
|
||||
exclude(module = "antlr4") // we only want to shadow the runtime module
|
||||
}
|
||||
shadowImplementation(libs.antlr4.runtime)
|
||||
compileOnly(libs.ant)
|
||||
|
||||
// Celeritas (isTransitive=false: deps provided by GTNHLib/runtime)
|
||||
embedOnly(libs.celeritas.common) { isTransitive = false }
|
||||
|
||||
// Because who doesn't want NEI
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.8.109-GTNH:dev")
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:CodeChickenCore:1.4.16:dev")
|
||||
|
||||
// BQ Testing
|
||||
// devOnlyNonPublishable("com.github.GTNewHorizons:BetterQuesting:3.8.74-GTNH:dev")
|
||||
|
||||
// Display List Testing
|
||||
/*
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-994-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ae2stuff:0.10.19-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BuildCraft:7.1.61:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:IguanaTweaksTConstruct:2.7.10:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenBlocks:1.12.18-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.47-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:TinkersConstruct:1.14.93-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ServerUtilities:2.4.1:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ironchest:6.1.13:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:waila:1.19.30:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Botania:1.13.25-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.18-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Draconic-Evolution:1.5.27-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:HoloInventory:2.5.15-GTNH:dev")
|
||||
*/
|
||||
|
||||
//devOnlyNonPublishable("com.github.GTNewHorizons:worldedit-gtnh:v0.0.9")
|
||||
|
||||
// Notfine Deps
|
||||
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles-Expanded:2.2.21-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:twilightforest:2.7.36:dev") { isTransitive = false }
|
||||
compileOnly(rfg.deobf("curse.maven:witchery-69673:2234410"))
|
||||
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.14.93-GTNH:dev") { isTransitive = false }
|
||||
compileOnly("com.github.GTNewHorizons:Natura:2.8.19:dev")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.8.20:dev")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.3:dev") { isTransitive = false }
|
||||
|
||||
compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.5.1:api") { isTransitive = false }
|
||||
compileOnly("com.falsepattern:endlessids-mc1.7.10:1.5-beta0003:dev") { isTransitive = false }
|
||||
|
||||
compileOnly(rfg.deobf("curse.maven:extrautils-225561:2264383"))
|
||||
compileOnly(rfg.deobf("curse.maven:dynamiclights-227874:2337326"))
|
||||
|
||||
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.11:dev")
|
||||
|
||||
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5663_H261"))
|
||||
|
||||
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
|
||||
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
|
||||
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
|
||||
compileOnly("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:OpenBlocks:1.12.19-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Draconic-Evolution:1.5.30-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Chisel:2.17.31-GTNH:dev")
|
||||
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
|
||||
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:CosmeticArmorReworked:1.0.6-GTNH:dev")
|
||||
|
||||
// For testing alternative splash screens - https://github.com/MalTeeez/ModernSplash/releases
|
||||
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
|
||||
|
||||
// Hodgepodge
|
||||
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
|
||||
|
||||
// Better Crashes
|
||||
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.5-GTNH:dev")
|
||||
|
||||
// Distant Horizons
|
||||
compileOnly(libs.distant.horizons.api)
|
||||
|
||||
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
|
||||
compileOnly("com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.5-alpha:dev") { isTransitive = false }
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
val glsmTestFixtures = configurations["glsmTestFixtures"]
|
||||
val testImplementation = configurations["testImplementation"]
|
||||
val testCompileOnly = configurations["testCompileOnly"]
|
||||
val testRuntimeOnly = configurations["testRuntimeOnly"]
|
||||
|
||||
DependencyHandlerScope.of(dependencies).apply {
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(libs.mockito.core)
|
||||
testImplementation(libs.lwjgl2)
|
||||
|
||||
testImplementation(libs.junit.platform.engine)
|
||||
testImplementation(libs.junit.platform.reporting)
|
||||
testImplementation(libs.junit.platform.launcher)
|
||||
|
||||
testImplementation(libs.retrofuturabootstrap) { isTransitive = false }
|
||||
|
||||
glsmTestFixtures(testFixtures(project(":glsm")) as ModuleDependency) { isTransitive = false }
|
||||
testCompileOnly(testFixtures(project(":glsm")))
|
||||
testImplementation("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
|
||||
testRuntimeOnly(libs.fastutil)
|
||||
testRuntimeOnly(libs.joml)
|
||||
testImplementation(libs.distant.horizons.api)
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -1,4 +1,7 @@
|
||||
# A specification for models
|
||||
|
||||
🌐 **Languages:** [English](model-spec.md) | [Українська](model-spec_uk.md)
|
||||
|
||||
This is borrowed from [the unofficial Minecraft wiki](https://minecraft.wiki/w/Tutorials/Models) under the CC BY-NC-SA 3.0.
|
||||
|
||||
The folder `assets/<namespace>/models/block` holds the model files for all the specified variants. The names of the files can be changed, but must always correspond with the names used in the variant files.
|
||||
@@ -8,7 +11,7 @@ The folder `assets/<namespace>/models/block` holds the model files for all the s
|
||||
* Can be set to `"builtin/generated"` to use a model that is created out of the specified icon. Note that only the first layer is supported, and rotation can be achieved only by using block states files.
|
||||
* `[TAG_byte]` **ambientocclusion**: Whether to use [ambient occlusion](http://en.wikipedia.org/wiki/Ambient_occlusion) (`true` - default), or not (`false`). Note:only works on Parent file
|
||||
* `[TAG_object]` **display**: Holds the different places where item models are displayed.
|
||||
* `[TAG_compound]` ***Position***: Named `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground`, or `fixed`. Place where an item model is displayed. Holds its rotation, translation and scale for the specified situation. '''fixed''' refers to item frames, while the rest are as their name states. Note that translations are applied to the model before rotations.
|
||||
* `[TAG_compound]` ***Position***: Named `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground`, or `fixed`. Place where an item model is displayed. Holds its rotation, translation and scale for the specified situation. **'''fixed'''** refers to item frames, while the rest are as their name states. Note that translations are applied to the model before rotations.
|
||||
* `[TAG_list]` **rotation**: Specifies the rotation of the model according to the scheme `[x, y, z]`.
|
||||
* `[TAG_list]` **translation**: Specifies the position of the model according to the scheme `[x, y, z]`. The values are clamped between -80 and 80.
|
||||
* `[TAG_list]` **scale**: Specifies the scale of the model according to the scheme `[x, y, z]`. If the value is greater than 4, it is displayed as 4.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Специфікація моделей
|
||||
|
||||
🌐 **Мови:** [English](model-spec.md) | [Українська](model-spec_uk.md)
|
||||
|
||||
Запозичено з [неофіційної Minecraft wiki](https://minecraft.wiki/w/Tutorials/Models) на умовах ліцензії CC BY-NC-SA 3.0.
|
||||
|
||||
Папка `assets/<namespace>/models/block` містить файли моделей для усіх вказаних варіантів. Назви файлів можна змінювати, але вони завжди повинні відповідати назвам, що використовуються у файлах варіантів.
|
||||
|
||||
* `[TAG_compound]` Кореневий тег
|
||||
* `[TAG_string]` **parent**: Завантажує іншу модель із вказаного шляху у формі [локації ресурсу](https://minecraft.wiki/w/Tutorials/Models#File_path). Якщо задано і `"parent"`, і `"elements"`, тег `"elements"` перевизначає тег `"elements"` із попередньої моделі.
|
||||
* Може мати значення `"builtin/generated"`, щоб використовувати модель, створену із вказаної піктограми. Зверніть увагу: підтримується лише перший шар, а обертання можна досягти лише за допомогою файлів станів блоку.
|
||||
* `[TAG_byte]` **ambientocclusion**: Визначає, чи використовувати [розсіяне затінення](http://en.wikipedia.org/wiki/Ambient_occlusion) (`true` — за замовчуванням), чи ні (`false`). Примітка: працює лише у батьківському файлі.
|
||||
* `[TAG_object]` **display**: Містить різні місця відображення моделей предметів.
|
||||
* `[TAG_compound]` ***Position***: Має назву `thirdperson_righthand`, `thirdperson_lefthand`, `firstperson_righthand`, `firstperson_lefthand`, `gui`, `head`, `ground` або `fixed`. Місце, де відображається модель предмета. Містить її обертання, зміщення та масштаб для вказаної ситуації. **'''fixed'''** стосується рамок для предметів, а решта відповідають своїм назвам. Зверніть увагу: зміщення застосовується до моделі перед обертанням.
|
||||
* `[TAG_list]` **rotation**: Задає обертання моделі за схемою `[x, y, z]`.
|
||||
* `[TAG_list]` **translation**: Задає зміщення моделі за схемою `[x, y, z]`. Значення обмежені в діапазоні від -80 до 80.
|
||||
* `[TAG_list]` **scale**: Задає масштаб моделі за схемою `[x, y, z]`. Якщо значення більше за 4, воно відображається як 4.
|
||||
* `[TAG_compound]` **textures**: Містить текстури моделі у формі [локації ресурсу](https://minecraft.wiki/w/Tutorials/Models#File_path) або у вигляді іншої текстурної змінної.
|
||||
* `[TAG_string]` **particle**: Текстура, з якої завантажуються частинки. Ця текстура також використовується як оверлей, якщо ви перебуваєте в [порталі Незеру](https://minecraft.wiki/w/Nether_portal), а також для нерухомих текстур [води](https://minecraft.wiki/w/water) та [лави](https://minecraft.wiki/w/Lava).<sup>[[1]](https://bugs.mojang.com/browse/MC-240042)</sup> Ця текстура також вважається текстурною змінною, на яку можна посилатися як `"#particle"`. Примітка: Усі частинки руйнування для блоків без моделей вшиті в код, наприклад, для [бар’єрів](https://minecraft.wiki/w/Barrier).
|
||||
* `[TAG_string]` ***Texture variable***: Визначає текстурну змінну з ім’ям `name` та призначає її текстурі або іншій змінній.
|
||||
* `[TAG_list]` **elements**: Містить усі елементи моделі. Вони можуть мати лише кубічну форму. Якщо задано і `"parent"`, і `"elements"`, тег `"elements"` перевизначає тег `"elements"` із попередньої моделі.
|
||||
* `[TAG_compound]` Елемент.
|
||||
* `[TAG_list]` **from**: Початкова точка кубоїда за схемою `[x, y, z]`. Значення мають бути від -16 до 32.
|
||||
* `[TAG_list]` **to**: Кінцева точка кубоїда за схемою `[x, y, z]`. Значення мають бути від -16 до 32.
|
||||
* `[TAG_compound]` **rotation**: Визначає обертання елемента.
|
||||
* `[TAG_list]` **origin**: Встановлює центр обертання за схемою `[x, y, z]`.
|
||||
* `[TAG_string]` **axis**: Задає вісь обертання: може бути `"x"`, `"y"` або `"z"`.
|
||||
* `[TAG_float]` **angle**: Задає кут обертання. Може бути від 45 до -45 градусів із кроком у 22,5 градуса.
|
||||
* `[TAG_byte]` **rescale**: Визначає, чи масштабувати грані по всьому блоку. Може бути `true` або `false`. За замовчуванням — `false`.
|
||||
* `[TAG_byte]` **shade**: Визначає, чи рендерити тіні (`true` — за замовчуванням), чи ні (`false`).
|
||||
* `[TAG_compound]` **faces**: Містить усі грані кубоїда. Якщо грань пропущено, вона не рендериться.
|
||||
* `[TAG_compound]` ***Face***: Має назву `down`, `up`, `north`, `south`, `west` або `east`. Містить властивості вказаної грані.
|
||||
* `[TAG_list]` **uv**: Визначає ділянку текстури для використання за схемою `[x1, y1, x2, y2]`. Поведінка текстури стає непередбачуваною, якщо значення UV виходять за межі 0 або 16. Якщо значення `x1` та `x2` поміняти місцями (наприклад, з `0, 0, 16, 16` на `16, 0, 0, 16`), текстура віддзеркалюється. Параметр UV є необов’язковим; якщо його не вказано, він автоматично генерується на основі позиції елемента.
|
||||
* `[TAG_string]` **texture**: Вказує текстуру у формі текстурної змінної зі знаком `#` на початку.
|
||||
* `[TAG_string]` **cullface**: Визначає, чи не потрібно рендерити грань, якщо до неї в указаній позиції торкається інший блок. Позиція може бути: `down`, `up`, `north`, `south`, `west` або `east`. Цей параметр також визначає сторону блоку, з якої береться рівень освітлення для цієї грані; якщо параметр не задано, за замовчуванням використовується відповідна сторона.
|
||||
* `[TAG_int]` **rotation**: Обертає текстуру за годинниковою стрілкою на вказаний кут у градусах. Може бути 0, 90, 180 або 270. За замовчуванням — 0. Обертання не впливає на те, яка частина текстури використовується. Натомість воно зводиться до перестановки вершин вибраної текстури (вибраних неявно або явно через `uv`).
|
||||
* `[TAG_int]` **tintindex**: Визначає, чи потрібно тонувати текстуру за допомогою вшитого індексу відтінку. Значення за замовчуванням, -1, вказує на те, що відтінок не використовується. Будь-яке інше число передається в `BlockColors` для отримання значення відтінку, відповідного цьому індексу. Однак більшість блоків не мають визначеного значення відтінку (у такому разі використовується білий колір). Крім того, жоден ванільний блок наразі не використовує кілька значень відтінку, тому значення індексу відтінку ігнорується (якщо воно відмінне від -1); втім, його можна використовувати для модифікованих блоків, яким потрібні кілька різних значень відтінку в одному блоці.
|
||||
@@ -0,0 +1,58 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
apply(from = rootProject.file("experimental-surround/surround.project.gradle.kts"))
|
||||
|
||||
val mixinProviderSpec = rootProject.extra["mixinProviderSpec"] as String
|
||||
|
||||
val refmapProbe = sourceSets.create("refmapProbe")
|
||||
|
||||
dependencies {
|
||||
compileOnly(mixinProviderSpec)
|
||||
testImplementation(mixinProviderSpec)
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(project(":experimental-surround-runtime"))
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
"refmapProbeCompileOnly"(mixinProviderSpec)
|
||||
"refmapProbeCompileOnly"(project(":experimental-surround-runtime"))
|
||||
"refmapProbeAnnotationProcessor"(mixinProviderSpec)
|
||||
"refmapProbeAnnotationProcessor"(sourceSets.main.get().output)
|
||||
}
|
||||
|
||||
val probeRefmaps = layout.buildDirectory.dir("refmap-probe")
|
||||
val probeSrg = file("src/refmapProbe/mappings.srg")
|
||||
val surroundAp = "com.gtnewhorizons.angelica.experimental.surround.ap.SurroundProcessor"
|
||||
val mixinAps = "org.spongepowered.tools.obfuscation.MixinObfuscationProcessorInjection,org.spongepowered.tools.obfuscation.MixinObfuscationProcessorTargets"
|
||||
|
||||
fun JavaCompile.probe(name: String, processors: String?) {
|
||||
val refmap = probeRefmaps.map { it.file("refmap-probe/$name.json") }
|
||||
val srg = probeSrg
|
||||
javaCompiler = javaToolchains.compilerFor { languageVersion = JavaLanguageVersion.of(25) }
|
||||
outputs.file(refmap)
|
||||
inputs.file(srg)
|
||||
options.compilerArgumentProviders.add(CommandLineArgumentProvider {
|
||||
listOf("-proc:only", "-AoutRefMapFile=" + refmap.get().asFile.absolutePath,
|
||||
"-AreobfSrgFile=" + srg.absolutePath, "-AdefaultObfuscationEnv=searge") +
|
||||
(if (processors == null) emptyList() else listOf("-processor", processors))
|
||||
})
|
||||
}
|
||||
|
||||
fun JavaCompile.probeOrdered(name: String, classesDir: String, processors: String) {
|
||||
source = refmapProbe.java
|
||||
classpath = refmapProbe.compileClasspath
|
||||
options.annotationProcessorPath = refmapProbe.annotationProcessorPath
|
||||
destinationDirectory = layout.buildDirectory.dir("classes/$classesDir")
|
||||
probe(name, processors)
|
||||
}
|
||||
|
||||
tasks.named<JavaCompile>(refmapProbe.compileJavaTaskName) { probe("discovered", null) }
|
||||
val probeSurroundFirst by tasks.registering(JavaCompile::class) { probeOrdered("surround-first", "refmapProbeFirst", "$surroundAp,$mixinAps") }
|
||||
val probeSurroundLast by tasks.registering(JavaCompile::class) { probeOrdered("surround-last", "refmapProbeLast", "$mixinAps,$surroundAp") }
|
||||
|
||||
sourceSets.test { resources.srcDir(files(probeRefmaps).builtBy(refmapProbe.compileJavaTaskName, probeSurroundFirst, probeSurroundLast)) }
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.ap;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.tools.obfuscation.mirror.AnnotationHandle;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@SupportedAnnotationTypes("com.gtnewhorizons.angelica.experimental.surround.Surround")
|
||||
public class SurroundProcessor extends AbstractProcessor {
|
||||
|
||||
record Registration(TypeElement mixin, ExecutableElement method, AnnotationMirror surround) {
|
||||
}
|
||||
|
||||
private final List<Registration> registrations = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
return SourceVersion.latestSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
if (roundEnv.processingOver()) {
|
||||
if (!this.registrations.isEmpty()) {
|
||||
register();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (TypeElement annotation : annotations) {
|
||||
for (Element annotated : roundEnv.getElementsAnnotatedWith(annotation)) {
|
||||
if (annotated.getEnclosingElement() instanceof TypeElement mixin && AnnotationHandle.of(mixin, Mixin.class).exists()) {
|
||||
for (AnnotationMirror mirror : annotated.getAnnotationMirrors()) {
|
||||
if (mirror.getAnnotationType().asElement().equals(annotation)) {
|
||||
this.registrations.add(new Registration(mixin, (ExecutableElement) annotated, mirror));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void register() {
|
||||
final ProcessingEnvironment env = this.processingEnv;
|
||||
try {
|
||||
// pinned by UniMixins 0.3.1; recheck on bump
|
||||
final Class<?> annotatedMixins = Class.forName("org.spongepowered.tools.obfuscation.AnnotatedMixins", false, AnnotationHandle.class.getClassLoader());
|
||||
final Method forEnvironment = annotatedMixins.getDeclaredMethod("getMixinsForEnvironment", ProcessingEnvironment.class);
|
||||
final Method registerInjector = annotatedMixins.getDeclaredMethod("registerInjector", TypeElement.class, ExecutableElement.class, AnnotationHandle.class);
|
||||
final Method writeReferences = annotatedMixins.getDeclaredMethod("writeReferences");
|
||||
forEnvironment.setAccessible(true);
|
||||
registerInjector.setAccessible(true);
|
||||
writeReferences.setAccessible(true);
|
||||
final Object mixins = forEnvironment.invoke(null, env);
|
||||
for (Registration registration : this.registrations) {
|
||||
registerInjector.invoke(mixins, registration.mixin(), registration.method(), AnnotationHandle.of(registration.surround()));
|
||||
}
|
||||
writeReferences.invoke(mixins);
|
||||
} catch (ReflectiveOperationException | LinkageError | RuntimeException e) {
|
||||
env.getMessager().printMessage(Diagnostic.Kind.ERROR, "@Surround: refmap entries were not written: " + e + (e.getCause() == null ? "" : " caused by " + e.getCause()));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
com.gtnewhorizons.angelica.experimental.surround.ap.SurroundProcessor
|
||||
@@ -0,0 +1,20 @@
|
||||
package probe;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(RefmapTarget.class)
|
||||
class MixinRefmapTarget {
|
||||
@Surround(method = "run()V")
|
||||
private void wholeMethod() {}
|
||||
|
||||
@Surround.Finally
|
||||
private void always() {}
|
||||
|
||||
@Surround(method = "run(Ljava/lang/String;)V", id = "call", at = @At(value = "INVOKE", target = "Lprobe/RefmapTarget;helper()V"))
|
||||
private void callForm() {}
|
||||
|
||||
@Surround.Finally("call")
|
||||
private void alwaysCall() {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package probe;
|
||||
|
||||
public class RefmapTarget {
|
||||
void run() {}
|
||||
void run(String s) { helper(); }
|
||||
void helper() {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
MD: probe/RefmapTarget/run ()V probe/RefmapTarget/func_1_a ()V
|
||||
MD: probe/RefmapTarget/run (Ljava/lang/String;)V probe/RefmapTarget/func_2_b (Ljava/lang/String;)V
|
||||
MD: probe/RefmapTarget/helper ()V probe/RefmapTarget/func_3_c ()V
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.ap;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class RefmapRegistrationTest {
|
||||
|
||||
private static final Pattern ENTRY = Pattern.compile("\"([^\"]+)\": \"([^\"]+)\"");
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"discovered", "surround-first", "surround-last"})
|
||||
void surroundTargetsAreRemappedInTheRefmap(String variant) throws IOException {
|
||||
final Map<String, String> expected = new HashMap<>();
|
||||
expected.put("run()V", "Lprobe/RefmapTarget;func_1_a()V");
|
||||
expected.put("run(Ljava/lang/String;)V", "Lprobe/RefmapTarget;func_2_b(Ljava/lang/String;)V");
|
||||
expected.put("Lprobe/RefmapTarget;helper()V", "Lprobe/RefmapTarget;func_3_c()V");
|
||||
assertEquals(expected, mappings(variant, "probe/MixinRefmapTarget"));
|
||||
}
|
||||
|
||||
private static Map<String, String> mappings(String variant, String mixin) throws IOException {
|
||||
final String json = new String(RefmapRegistrationTest.class.getResourceAsStream("/refmap-probe/" + variant + ".json").readAllBytes(), UTF_8);
|
||||
final int start = json.indexOf('{', json.indexOf("\"" + mixin + "\"", json.indexOf("\"mappings\"")));
|
||||
final Matcher m = ENTRY.matcher(json.substring(start, json.indexOf('}', start)));
|
||||
final Map<String, String> entries = new HashMap<>();
|
||||
while (m.find()) entries.put(m.group(1), m.group(2));
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
apply(plugin = "xyz.wagyourtail.jvmdowngrader")
|
||||
|
||||
apply(from = rootProject.file("experimental-surround/surround.project.gradle.kts"))
|
||||
|
||||
// Required by RFG consumers (runClient17 java17Dependencies) to pick a variant.
|
||||
val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation", String::class.java)
|
||||
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
|
||||
|
||||
configurations {
|
||||
listOf("apiElements", "runtimeElements").forEach { name ->
|
||||
named(name) {
|
||||
attributes {
|
||||
attribute(rfgObfAttr, "mcp")
|
||||
attribute(rfgTransformedAttr, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mixinProviderSpec = rootProject.extra["mixinProviderSpec"] as String
|
||||
|
||||
dependencies {
|
||||
compileOnly(mixinProviderSpec)
|
||||
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(mixinProviderSpec)
|
||||
testImplementation(libs.launchwrapper)
|
||||
testImplementation(libs.commons.io)
|
||||
testImplementation(libs.guava)
|
||||
testImplementation(libs.log4j.core)
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testRuntimeOnly(libs.jvmdowngrader.java.api) { artifact { classifier = "downgraded-8" } }
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "WagYourTail"
|
||||
url = uri("https://maven.wagyourtail.xyz/releases")
|
||||
content { includeGroup("xyz.wagyourtail.jvmdowngrader") }
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeFiles>().configureEach {
|
||||
outputs.cacheIf { true }
|
||||
downgradeTo.set(JavaVersion.VERSION_1_8)
|
||||
multiReleaseOriginal.set(false)
|
||||
multiReleaseVersions.set(emptySet())
|
||||
logLevel.set("FATAL")
|
||||
}
|
||||
|
||||
fun SourceSet.registerDowngrade(taskName: String) = tasks.register<DowngradeFiles>(taskName) {
|
||||
inputCollection = output.classesDirs
|
||||
classpath = compileClasspath
|
||||
output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
|
||||
dependsOn(tasks.named(classesTaskName))
|
||||
}
|
||||
|
||||
val downgradeMainClasses = sourceSets["main"].registerDowngrade("downgradeMainClasses")
|
||||
val downgradeTestClasses = sourceSets["test"].registerDowngrade("downgradeTestClasses")
|
||||
|
||||
val goldenDir = layout.projectDirectory.dir("src/test/resources/golden").asFile.absolutePath
|
||||
|
||||
// Runs on Java 8 against the JVMDowngrader output, the bytecode production ships.
|
||||
fun Test.configureSurroundTest(runDirName: String) {
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(8)
|
||||
if (System.getProperty("os.name").lowercase().contains("mac") && System.getProperty("os.arch") == "aarch64") {
|
||||
vendor = JvmVendorSpec.AZUL
|
||||
}
|
||||
}
|
||||
dependsOn(downgradeMainClasses, downgradeTestClasses)
|
||||
val downgradedTest = files(downgradeTestClasses.map { it.outputCollection })
|
||||
testClassesDirs = downgradedTest
|
||||
classpath = downgradedTest
|
||||
.plus(files(downgradeMainClasses.map { it.outputCollection }))
|
||||
.plus(sourceSets["test"].runtimeClasspath.minus(sourceSets["main"].output.classesDirs).minus(sourceSets["test"].output.classesDirs))
|
||||
val dir = layout.buildDirectory.dir(runDirName).get().asFile
|
||||
workingDir = dir
|
||||
systemProperty("surround.golden.dir", goldenDir)
|
||||
systemProperty("mixin.debug.verify", "true")
|
||||
val javac = javaToolchains.compilerFor { languageVersion = JavaLanguageVersion.of(25) }
|
||||
jvmArgumentProviders.add(CommandLineArgumentProvider { listOf("-Dsurround.javac=" + javac.get().executablePath.asFile.absolutePath) })
|
||||
doFirst { dir.mkdirs() }
|
||||
}
|
||||
|
||||
fun Test.configureUntaggedSurroundTest(runDirName: String, vararg systemProps: Pair<String, String>) {
|
||||
useJUnitPlatform { excludeTags("mixinextras") }
|
||||
for ((key, value) in systemProps) {
|
||||
systemProperty(key, value)
|
||||
}
|
||||
configureSurroundTest(runDirName)
|
||||
}
|
||||
|
||||
fun Test.configureTaggedSurroundTest(runDirName: String, tag: String, vararg systemProps: Pair<String, String>) {
|
||||
useJUnitPlatform { includeTags(tag) }
|
||||
for ((key, value) in systemProps) {
|
||||
systemProperty(key, value)
|
||||
}
|
||||
configureSurroundTest(runDirName)
|
||||
}
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
configureUntaggedSurroundTest("test-run")
|
||||
}
|
||||
|
||||
val mixinExtrasTest by tasks.registering(Test::class) {
|
||||
configureTaggedSurroundTest(
|
||||
"test-run-mixinextras",
|
||||
"mixinextras",
|
||||
"surround.test.mixinextras" to "true"
|
||||
)
|
||||
}
|
||||
|
||||
val mixinExtrasAdditionsFirstTest by tasks.registering(Test::class) {
|
||||
configureTaggedSurroundTest(
|
||||
"test-run-mixinextras-additions-first",
|
||||
"mixinextras",
|
||||
"surround.test.mixinextras" to "true",
|
||||
"surround.test.additionsFirst" to "true"
|
||||
)
|
||||
}
|
||||
|
||||
val obfTest by tasks.registering(Test::class) {
|
||||
configureUntaggedSurroundTest("test-run-obf", "surround.test.obf" to "true")
|
||||
filter { excludeTestsMatching("*.RejectionTest") }
|
||||
}
|
||||
|
||||
tasks.named("check") {
|
||||
dependsOn(mixinExtrasTest, mixinExtrasAdditionsFirstTest, obfTest)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Exits;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode;
|
||||
|
||||
record CallSiteSpec(MethodNode target, InjectionNode node, MethodNode entry, String entryDesc, Type[] operands, Type result, Exits exits, String description, int[] entryLocals, int[][] locals) {}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Slice;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Experimental and internal to Angelica; the API may change without notice.
|
||||
* <p>
|
||||
* Surrounds one method call ({@link #at()} set) or a whole method ({@link #at()} empty) with try/catch/finally.
|
||||
* The call form replaces MixinExtras' {@code @WrapOperation} and the whole-method form replaces {@code @WrapMethod},
|
||||
* without their per-call {@code Operation} and {@code Object[]} allocation. Use it when the original runs at most once
|
||||
* with its original arguments.
|
||||
* <p>
|
||||
* A surround can:
|
||||
* <ul>
|
||||
* <li>run code before the original: the entry handler, the annotated method itself;</li>
|
||||
* <li>hand state from that code to everything after it: {@link Carry @Surround.Carry} locals;</li>
|
||||
* <li>read the call's operands, the target's parameters, and locals at the call: operands,
|
||||
* {@link Local @Surround.Local};</li>
|
||||
* <li>skip the original and supply its result instead: {@link Skip @Surround.Skip} + {@link Skipped @Surround.Skipped};</li>
|
||||
* <li>replace the result after a normal completion: {@link Return @Surround.Return};</li>
|
||||
* <li>react to an exception, rethrowing or (calls only) swallowing it and supplying the result:
|
||||
* {@link Catch @Surround.Catch};</li>
|
||||
* <li>run code on every way out: {@link Finally @Surround.Finally}.</li>
|
||||
* </ul>
|
||||
* It cannot call the original twice or with different arguments.
|
||||
*
|
||||
* <h2>Example</h2>
|
||||
* <pre>
|
||||
* @Surround(method = "doRender(Lnet/minecraft/entity/item/EntityItem;DDDFF)V")
|
||||
* private void enter(EntityItem entity, double x, double y, double z, float yaw, float partialTicks) {
|
||||
* @Surround.Carry int prevItemId = ItemIdManager.getItemId();
|
||||
* ItemIdManager.setItemId(entity.getEntityItem());
|
||||
* }
|
||||
*
|
||||
* @Surround.Finally
|
||||
* private void restore(@Surround.Carry int prevItemId) {
|
||||
* ItemIdManager.setItemIdRaw(prevItemId);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <h2>Handlers</h2>
|
||||
* The annotated method is the entry handler. Exit handlers are methods in the same mixin whose {@code value} is this
|
||||
* surround's {@link #id()}:
|
||||
* <pre>
|
||||
* @Surround void enter(operands, @Local params) before the original
|
||||
* @Surround.Skipped T skipped(operands, @Local params, @Carry params) instead of the original
|
||||
* @Surround.Return T ret(T result, operands, @Local params, @Carry params) after it completes normally
|
||||
* @Surround.Catch void caught(E error, operands, @Local params, @Carry params) when it throws E
|
||||
* @Surround.Finally void always(operands, @Local params, @Carry params) on every way out
|
||||
* </pre>
|
||||
* {@code T} is the return type of the call or method. When it is {@code void}, {@code ret} takes no result and
|
||||
* {@code Skipped} returns {@code void}.
|
||||
* <ul>
|
||||
* <li>The entry handler returns {@code void}. Exit handlers are static exactly when the entry handler is.</li>
|
||||
* <li>Per surround: at most one {@code Skipped}, one {@code Return} and one {@code Finally}, and one {@code Catch} per
|
||||
* exception type. A method has one role.</li>
|
||||
* <li>{@link #id()} is required when the mixin declares more than one {@code @Surround}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Parameters</h2>
|
||||
* Declare them in this order, each group optional:
|
||||
* <ol>
|
||||
* <li>Operands, as a leading prefix. For a call: the receiver (unless static), then the arguments. A reference
|
||||
* operand may be declared as a supertype. For a whole method: the target's parameters.</li>
|
||||
* <li>{@link Local @Surround.Local} parameters (call form only). Exit handlers see the value as of entry.</li>
|
||||
* <li>{@link Carry @Surround.Carry} parameters (exit handlers only).</li>
|
||||
* </ol>
|
||||
*
|
||||
* <h2>Carrying state</h2>
|
||||
* Exit handlers see entry-handler locals only through {@link Carry @Surround.Carry}; see there for the rules. Two
|
||||
* carries of one kind are told apart by name:
|
||||
* <pre>
|
||||
* @Surround(method = ..., at = @At(value = "INVOKE", target = ...))
|
||||
* private void enter() {
|
||||
* @Surround.Carry("depth") int depth = GLStateManager.pushState(StateSet.BLEND);
|
||||
* @Surround.Carry("prev") int prevProgram = GLStateManager.getActiveProgram();
|
||||
* @Surround.Carry Class<?> prevRenderable = TesrAttribution.currentRenderable;
|
||||
* }
|
||||
*
|
||||
* @Surround.Finally
|
||||
* private void exit(@Surround.Carry("depth") int depth, @Surround.Carry("prev") int prevProgram,
|
||||
* @Surround.Carry Class<?> prevRenderable) { ... }
|
||||
* </pre>
|
||||
*
|
||||
* <h2>Entry-handler body</h2>
|
||||
* <ul>
|
||||
* <li>Every path must fall through to the end: no early {@code return}. Branch with {@code if} instead.</li>
|
||||
* <li>Do not assign its parameters. To change arguments, use {@code @ModifyVariable} or {@code @WrapMethod}.</li>
|
||||
* <li>In the call form it is inlined at every matched call, as are the exit-handler calls. Keep it short: move bulky
|
||||
* logic into a helper and carry the helper's result. Huge targets stop being JIT-compiled past 8000 bytes.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Skipping</h2>
|
||||
* A {@link Skip @Surround.Skip} local in the entry handler decides whether the original runs; see there for the rules.
|
||||
*
|
||||
* <h2>Order and exceptions</h2>
|
||||
* <ol>
|
||||
* <li>The entry handler. If it throws, nothing else runs.</li>
|
||||
* <li>The original, or {@link Skipped} when skipped.</li>
|
||||
* <li>{@link Return}, only if the original completed normally (not after {@code Skipped}). Its return value replaces
|
||||
* the result.</li>
|
||||
* <li>{@link Finally}, on every way out: normal, after a catch, or rethrowing.</li>
|
||||
* </ol>
|
||||
* {@link Catch} covers step 2, not {@code Return}, and only the most specific matching one runs. It rethrows when
|
||||
* it returns, unless {@link Catch#handle()} is set (call form only): the exception then ends there, and the
|
||||
* handler's return value becomes the call's result.
|
||||
*
|
||||
* <h2>Whole-method form</h2>
|
||||
* <ul>
|
||||
* <li>The original body becomes a private {@code name$surround} method, so stack traces gain that frame.</li>
|
||||
* <li>A {@code synchronized} target holds its lock across the handlers too.</li>
|
||||
* <li>An entry handler that calls the target goes through the surround again.</li>
|
||||
* <li>{@code @Local}, {@link #slice()} and {@code handle} are call-form only.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>With other injectors</h2>
|
||||
* <ul>
|
||||
* <li>A call surround encloses a {@code @Redirect} or {@code @WrapOperation} on the same call. A whole-method surround
|
||||
* encloses every other injection in the method, including call surrounds.</li>
|
||||
* <li>Several surrounds on one call or method nest, lowest priority innermost. An outer skip skips the inner ones
|
||||
* entirely, and {@code Return} replacements chain from inner to outer.</li>
|
||||
* <li>A matched call that another injector removed is skipped with a warning.</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Surround {
|
||||
|
||||
String[] method() default {};
|
||||
|
||||
/**
|
||||
* Calls to surround, as INVOKE-style {@code @At} without shift or by; each match is surrounded separately. Empty
|
||||
* surrounds the whole method.
|
||||
*/
|
||||
At[] at() default {};
|
||||
|
||||
/**
|
||||
* As for {@code @Inject}; calls only.
|
||||
*/
|
||||
Slice[] slice() default {};
|
||||
|
||||
/**
|
||||
* Names this surround for its exit handlers. Required when the mixin declares more than one {@code @Surround};
|
||||
* unique per mixin.
|
||||
*/
|
||||
String id() default "";
|
||||
|
||||
boolean remap() default true;
|
||||
|
||||
/**
|
||||
* As for {@code @Inject}, as are {@link #expect()} and {@link #allow()}; each surrounded call or whole target counts once.
|
||||
*/
|
||||
int require() default -1;
|
||||
|
||||
int expect() default 1;
|
||||
|
||||
int allow() default -1;
|
||||
|
||||
/**
|
||||
* Carries a value from the entry handler to the exit handlers.
|
||||
* <p>
|
||||
* Annotate a local in the entry handler, then declare a parameter with the same annotation in any exit handler that
|
||||
* needs the value:
|
||||
* <pre>
|
||||
* @Surround(method = ...)
|
||||
* private void enter() {
|
||||
* @Surround.Carry int prevItemId = ItemIdManager.getItemId();
|
||||
* ItemIdManager.setItemId(...);
|
||||
* }
|
||||
*
|
||||
* @Surround.Finally
|
||||
* private void restore(@Surround.Carry int prevItemId) {
|
||||
* ItemIdManager.setItemIdRaw(prevItemId);
|
||||
* }
|
||||
* </pre>
|
||||
* Declaring the local:
|
||||
* <ul>
|
||||
* <li>Annotate the declaration itself: {@code @Surround.Carry int x = ...}. Not a parameter of the entry handler
|
||||
* (use operands for those), and not part of the type (e.g. an array component).</li>
|
||||
* <li>Declare it at the top level of the entry handler and initialize it there. It must still be in scope and
|
||||
* definitely assigned when the entry handler ends.</li>
|
||||
* </ul>
|
||||
* Receiving it:
|
||||
* <ul>
|
||||
* <li>Carry parameters come last, after operands and {@code @Local} parameters. An exit handler may take any
|
||||
* subset.</li>
|
||||
* <li>Parameters bind to locals by kind: reference, {@code int}-like ({@code boolean}, {@code byte},
|
||||
* {@code char}, {@code short}, {@code int}), {@code long}, {@code float}, {@code double}. If the entry handler
|
||||
* carries two locals of one kind, name every one of that kind with the same {@link #value()} on the local and on
|
||||
* each parameter that reads it: {@code @Surround.Carry("depth") int depth}. Names are unique within a handler.</li>
|
||||
* <li>A reference is cast to the parameter's type on read. Every exit handler must read a given carry as the same
|
||||
* type.</li>
|
||||
* </ul>
|
||||
* Each carry lives in a local slot of the target method, with no allocation, so nested and recursive invocations
|
||||
* keep separate values. The entry handler returns {@code void}; carry its results instead.
|
||||
*/
|
||||
@Target({ElementType.TYPE_USE, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Carry {
|
||||
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether the original runs.
|
||||
* <p>
|
||||
* Annotate one {@code boolean} or {@code int}-like local in the entry handler, declared like a {@link Carry}: at
|
||||
* the top level, initialized at its declaration, in scope at the end:
|
||||
* <pre>
|
||||
* @Surround(method = ..., at = @At(value = "INVOKE", target = ...))
|
||||
* private void enter(RenderBlocks renderer, Block block) {
|
||||
* @Surround.Skip boolean skip = FallingBlockRendering.isActive();
|
||||
* }
|
||||
*
|
||||
* @Surround.Skipped
|
||||
* private boolean replacement(RenderBlocks renderer, Block block) { ... }
|
||||
* </pre>
|
||||
* <ul>
|
||||
* <li>If the local is nonzero when the entry handler ends, the original does not run and {@link Skipped} runs in
|
||||
* its place; {@link Return} does not run after it, {@link Catch} and {@link Finally} still do.</li>
|
||||
* <li>{@link Skipped} is required unless the result is {@code void}, and rejected without a {@code Skip}
|
||||
* local.</li>
|
||||
* <li>At most one per entry handler.</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Target(ElementType.TYPE_USE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Skip {
|
||||
}
|
||||
|
||||
/**
|
||||
* Supplies the result in place of a skipped original. Required unless that result is {@code void}.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Skipped {
|
||||
|
||||
/**
|
||||
* The {@link Surround#id()} of the surround this handler belongs to; empty for the default.
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs after the original completes normally. Takes the result first (none for {@code void}) and returns its
|
||||
* replacement.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Return {
|
||||
|
||||
/**
|
||||
* The {@link Surround#id()} of the surround this handler belongs to; empty for the default.
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the original or {@link Skipped} throws the first parameter's type, unless a more specific
|
||||
* {@code Catch} matches. Rethrows when it returns.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Catch {
|
||||
|
||||
/**
|
||||
* The {@link Surround#id()} of the surround this handler belongs to; empty for the default.
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Call form only: the exception ends here and the returned value becomes the call's result.
|
||||
*/
|
||||
boolean handle() default false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on every way out.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Finally {
|
||||
|
||||
/**
|
||||
* The {@link Surround#id()} of the surround this handler belongs to; empty for the default.
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* As MixinExtras' {@code @Local}, read at the call; exit handlers see its value at entry. Calls only.
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Local {
|
||||
|
||||
int ordinal() default -1;
|
||||
|
||||
int index() default -1;
|
||||
|
||||
String[] name() default {};
|
||||
|
||||
boolean argsOnly() default false;
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.llamalad7.mixinextras.utils.MixinInternals;
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.transformer.ext.IExtension;
|
||||
import org.spongepowered.asm.mixin.transformer.ext.ITargetClassContext;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class SurroundApplicatorExtension implements IExtension {
|
||||
|
||||
private static final String MIXINEXTRAS_PACKAGE = "com.llamalad7.mixinextras.";
|
||||
private static final Pattern HANDLER_NAME = Pattern.compile("surround\\$[a-z]{3,}[0-9a-f]{3,}\\$.*");
|
||||
|
||||
private final Map<ITargetClassContext, Pending> pending = new WeakHashMap<>();
|
||||
|
||||
private record Pending(List<CallSiteSpec> calls, List<CallSiteSpec> wholes, Set<String> declarers) {}
|
||||
|
||||
private static final class Holder {
|
||||
|
||||
static final SurroundApplicatorExtension INSTANCE = create();
|
||||
|
||||
private static SurroundApplicatorExtension create() {
|
||||
final SurroundApplicatorExtension extension = new SurroundApplicatorExtension();
|
||||
// MixinExtras 0.5.0 internal (pinned by UniMixins 0.3.1); recheck on bump
|
||||
MixinInternals.registerExtension(extension, false);
|
||||
return extension;
|
||||
}
|
||||
}
|
||||
|
||||
static SurroundApplicatorExtension instance() {
|
||||
return Holder.INSTANCE;
|
||||
}
|
||||
|
||||
void declare(ITargetClassContext context, String mixin) {
|
||||
this.pending(context).declarers.add(mixin);
|
||||
}
|
||||
|
||||
void offer(ITargetClassContext context, CallSiteSpec spec) {
|
||||
final Pending pending = this.pending(context);
|
||||
(spec.node() == null ? pending.wholes : pending.calls).add(spec);
|
||||
}
|
||||
|
||||
private Pending pending(ITargetClassContext context) {
|
||||
return this.pending.computeIfAbsent(context, key -> new Pending(new ArrayList<>(), new ArrayList<>(), new HashSet<>()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkActive(MixinEnvironment environment) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preApply(ITargetClassContext context) {
|
||||
// MixinExtras 0.5.0 internal (pinned by UniMixins 0.3.1); recheck on bump
|
||||
final List<IExtension> active = MixinInternals.getExtensions().getActiveExtensions();
|
||||
boolean seen = false;
|
||||
for (int i = 0, n = active.size(); i < n; i++) {
|
||||
final IExtension extension = active.get(i);
|
||||
if (extension == this) {
|
||||
seen = true;
|
||||
} else if (seen && extension.getClass().getName().startsWith(MIXINEXTRAS_PACKAGE)) {
|
||||
MixinInternals.unregisterExtension(this);
|
||||
MixinInternals.registerExtension(this, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postApply(ITargetClassContext context) {
|
||||
final ClassNode targetClass = context.getClassNode();
|
||||
final Pending pending = this.pending.remove(context);
|
||||
checkHandlersRenamed(targetClass, pending == null ? Set.of() : pending.declarers);
|
||||
if (pending == null) {
|
||||
return;
|
||||
}
|
||||
final List<CallSiteSpec> calls = pending.calls;
|
||||
for (int i = 0, n = calls.size(); i < n; i++) {
|
||||
SurroundCallSite.apply(targetClass, calls.get(i));
|
||||
}
|
||||
final List<CallSiteSpec> wholes = pending.wholes;
|
||||
for (int i = 0, n = wholes.size(); i < n; i++) {
|
||||
SurroundCallSite.apply(targetClass, wholes.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void export(MixinEnvironment env, String name, boolean force, ClassNode classNode) {
|
||||
}
|
||||
|
||||
static void checkHandlersRenamed(ClassNode classNode, Set<String> declarers) {
|
||||
for (MethodNode method : classNode.methods) {
|
||||
if (method.visibleAnnotations == null) {
|
||||
continue;
|
||||
}
|
||||
for (AnnotationNode annotation : method.visibleAnnotations) {
|
||||
final String desc = annotation.desc;
|
||||
if (SurroundSpec.CATCH.equals(desc) || SurroundSpec.FINALLY.equals(desc) || SurroundSpec.SKIPPED.equals(desc) || SurroundSpec.RETURN.equals(desc)) {
|
||||
final String mixin = SurroundSpec.mergedBy(method);
|
||||
if (mixin != null && !declarers.contains(mixin)) {
|
||||
throw new IllegalStateException("@Surround " + SurroundBinding.exitRef(method) + " " + SurroundInjector.noSurroundForId(Annotations.<String>getValue(annotation, "value", ""), Set.of()));
|
||||
}
|
||||
} else if (SurroundSpec.SURROUND.equals(desc) && !HANDLER_NAME.matcher(method.name).matches()) {
|
||||
throw new IllegalStateException(method.name + method.desc + " in " + classNode.name + " (" + SurroundSpec.describeOwner(method) + ") carries @Surround but was never parsed as an injector: the InjectionInfo registry lacked @Surround when mixins were applied to this target; SurroundBootstrap.init() must run before any class a @Surround targets is transformed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+543
@@ -0,0 +1,543 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.TypePath;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.FrameNode;
|
||||
import org.spongepowered.asm.lib.tree.IincInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.LabelNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableAnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.lib.tree.VarInsnNode;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
import org.spongepowered.asm.util.Bytecode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
final class SurroundBinding {
|
||||
|
||||
private static final int TOP = -1;
|
||||
private static final String[] SUGARS = {"Local", "Share", "Cancellable"};
|
||||
static final String SUGAR_HINT = ", which a @Surround handler cannot resolve; use @Surround.Carry, or @Surround.Local on a call";
|
||||
|
||||
enum Role {
|
||||
ENTRY(SurroundSpec.SURROUND), CATCH(SurroundSpec.CATCH), FINALLY(SurroundSpec.FINALLY), SKIPPED(SurroundSpec.SKIPPED), RETURN(SurroundSpec.RETURN);
|
||||
|
||||
static final Role[] EXITS = {CATCH, FINALLY, SKIPPED, RETURN};
|
||||
|
||||
final String annotation;
|
||||
|
||||
Role(String annotation) {
|
||||
this.annotation = annotation;
|
||||
}
|
||||
}
|
||||
|
||||
record Load(int slot, Type type) {}
|
||||
|
||||
static final class Handler {
|
||||
|
||||
final MethodNode method;
|
||||
final Role role;
|
||||
final Type[] params;
|
||||
final boolean isStatic;
|
||||
final boolean handles;
|
||||
final int leading;
|
||||
final int localStart;
|
||||
final int carryStart;
|
||||
private final String[] carryIds;
|
||||
int index;
|
||||
Load[] loads;
|
||||
|
||||
Handler(MethodNode method, Role role, int leading) {
|
||||
this.method = method;
|
||||
this.role = role;
|
||||
this.params = Type.getArgumentTypes(method.desc);
|
||||
this.isStatic = Bytecode.isStatic(method);
|
||||
final AnnotationNode caught = role == Role.CATCH ? Annotations.get(method.visibleAnnotations, SurroundSpec.CATCH) : null;
|
||||
this.handles = caught != null && Annotations.<Boolean>getValue(caught, "handle", Boolean.FALSE).booleanValue();
|
||||
this.leading = leading;
|
||||
final int count = this.params.length;
|
||||
int firstLocal = count;
|
||||
int firstCarry = count;
|
||||
int section = 0;
|
||||
String[] ids = null;
|
||||
for (int i = 0; i < count; i++) {
|
||||
final AnnotationNode carry = carry(method, i);
|
||||
final boolean local = local(method, i) != null;
|
||||
if (carry != null && role == Role.ENTRY) {
|
||||
throw this.reject("has @Surround.Carry on " + paramRef(method, i) + "; only the entry handler's own locals can be carried");
|
||||
}
|
||||
if (local && carry != null) {
|
||||
throw this.reject(paramRef(method, i) + " cannot be both @Surround.Local and @Surround.Carry");
|
||||
}
|
||||
if ((local || carry != null) && i < leading) {
|
||||
throw this.reject("cannot mark its " + (role == Role.CATCH ? "caught Throwable" : "result") + " parameter @Surround." + (local ? "Local" : "Carry"));
|
||||
}
|
||||
final int next = carry != null ? 2 : local ? 1 : 0;
|
||||
if (next < section) {
|
||||
throw this.reject(paramRef(method, i) + " is out of order; declare operands first, then @Surround.Local, then @Surround.Carry parameters");
|
||||
}
|
||||
section = next;
|
||||
if (local && firstLocal == count) {
|
||||
firstLocal = i;
|
||||
}
|
||||
if (carry != null) {
|
||||
if (ids == null) {
|
||||
firstCarry = i;
|
||||
ids = new String[count - i];
|
||||
}
|
||||
ids[i - firstCarry] = Annotations.<String>getValue(carry, "value", "");
|
||||
}
|
||||
}
|
||||
this.localStart = Math.min(firstLocal, firstCarry);
|
||||
this.carryStart = firstCarry;
|
||||
this.carryIds = ids;
|
||||
}
|
||||
|
||||
Type caught() {
|
||||
return this.params[0];
|
||||
}
|
||||
|
||||
String ref() {
|
||||
return this.role == Role.ENTRY ? "entry handler" : exitRef(this.method);
|
||||
}
|
||||
|
||||
SurroundRejection reject(String message) {
|
||||
return new SurroundRejection(this.ref() + " " + message);
|
||||
}
|
||||
}
|
||||
|
||||
static final class Exits {
|
||||
|
||||
final Handler[] handlers;
|
||||
final int catches;
|
||||
final Handler finallyHandler;
|
||||
final Handler skipped;
|
||||
final Handler returnHandler;
|
||||
final boolean handles;
|
||||
final int skipSlot;
|
||||
final Type[] carried;
|
||||
final int argsSize;
|
||||
|
||||
Exits(Handler[] handlers, int skipSlot, Type[] carried) {
|
||||
this.handlers = handlers;
|
||||
int catches = 0;
|
||||
Handler finallyHandler = null;
|
||||
Handler skipped = null;
|
||||
Handler returnHandler = null;
|
||||
boolean handles = false;
|
||||
int argsSize = 0;
|
||||
for (int i = 0; i < handlers.length; i++) {
|
||||
final Handler handler = handlers[i];
|
||||
handler.index = i;
|
||||
handles |= handler.handles;
|
||||
argsSize = Math.max(argsSize, (handler.isStatic ? 0 : 1) + Bytecode.getArgsSize(handler.params));
|
||||
switch (handler.role) {
|
||||
case CATCH -> catches++;
|
||||
case FINALLY -> finallyHandler = handler;
|
||||
case SKIPPED -> skipped = handler;
|
||||
case RETURN -> returnHandler = handler;
|
||||
}
|
||||
}
|
||||
this.catches = catches;
|
||||
this.finallyHandler = finallyHandler;
|
||||
this.skipped = skipped;
|
||||
this.returnHandler = returnHandler;
|
||||
this.handles = handles;
|
||||
this.skipSlot = skipSlot;
|
||||
this.carried = carried;
|
||||
this.argsSize = argsSize;
|
||||
}
|
||||
}
|
||||
|
||||
private SurroundBinding() {
|
||||
}
|
||||
|
||||
static Type[] bind(MethodNode entry, int[] kinds, Handler[] handlers) {
|
||||
checkNoParameterStores(entry);
|
||||
final List<Carried> carried = carriedLocals(entry, kinds);
|
||||
final Type[] slots = new Type[entry.maxLocals];
|
||||
for (Handler exit : handlers) {
|
||||
final Load[] loads = new Load[exit.params.length - exit.carryStart];
|
||||
for (int i = exit.carryStart; i < exit.params.length; i++) {
|
||||
loads[i - exit.carryStart] = resolve(carried, slots, exit, i);
|
||||
}
|
||||
exit.loads = loads;
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
static int skipSlot(MethodNode entry, int[] kinds) {
|
||||
final List<LocalVariableAnnotationNode> annotations = entry.visibleLocalVariableAnnotations;
|
||||
int slot = -1;
|
||||
for (int a = 0, n = annotations == null ? 0 : annotations.size(); a < n; a++) {
|
||||
final LocalVariableAnnotationNode annotation = annotations.get(a);
|
||||
if (!SurroundSpec.SKIP.equals(annotation.desc)) {
|
||||
continue;
|
||||
}
|
||||
if (slot != -1) {
|
||||
throw new SurroundRejection("declares two @Surround.Skip locals; there can only be one");
|
||||
}
|
||||
slot = annotatedSlot(entry, annotation, 0);
|
||||
checkLocalAnnotation(entry, kinds, "@Surround.Skip", annotation, slot, "");
|
||||
if (kinds[slot] != Type.INT) {
|
||||
throw new SurroundRejection("has a @Surround.Skip local that is " + withArticle(kinds[slot]) + "; it must be boolean or int-like");
|
||||
}
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
private static AnnotationNode carry(MethodNode method, int index) {
|
||||
final List<AnnotationNode> invisible = parameter(method.invisibleParameterAnnotations, index);
|
||||
for (int i = 0, n = invisible == null ? 0 : invisible.size(); i < n; i++) {
|
||||
final String name = Type.getType(invisible.get(i).desc).getClassName();
|
||||
for (String sugar : SUGARS) {
|
||||
if (name.endsWith("mixinextras.sugar." + sugar)) {
|
||||
throw new SurroundRejection(paramRef(method, index) + " of '" + method.name + "' is annotated @" + sugar + SUGAR_HINT);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Annotations.get(parameter(method.visibleParameterAnnotations, index), SurroundSpec.CARRY);
|
||||
}
|
||||
|
||||
static AnnotationNode local(MethodNode method, int index) {
|
||||
return Annotations.get(parameter(method.visibleParameterAnnotations, index), SurroundSpec.LOCAL);
|
||||
}
|
||||
|
||||
private static List<AnnotationNode> parameter(List<AnnotationNode>[] table, int index) {
|
||||
return table == null || index >= table.length ? null : table[index];
|
||||
}
|
||||
|
||||
static String paramRef(MethodNode method, int index) {
|
||||
final String name = paramName(method, index);
|
||||
return name == null || name.isEmpty() ? "parameter " + index : "parameter " + index + " ('" + name + "')";
|
||||
}
|
||||
|
||||
static String paramName(MethodNode method, int index) {
|
||||
if (method.localVariables == null) {
|
||||
return null;
|
||||
}
|
||||
final int slot = (Bytecode.isStatic(method) ? 0 : 1) + Bytecode.getArgsSize(Type.getArgumentTypes(method.desc), 0, index);
|
||||
LocalVariableNode earliest = null;
|
||||
int earliestPos = Integer.MAX_VALUE;
|
||||
for (LocalVariableNode local : method.localVariables) {
|
||||
if (local.index != slot) {
|
||||
continue;
|
||||
}
|
||||
final int pos = method.instructions.indexOf(local.start);
|
||||
if (pos < earliestPos) {
|
||||
earliestPos = pos;
|
||||
earliest = local;
|
||||
}
|
||||
}
|
||||
return earliest == null ? null : earliest.name;
|
||||
}
|
||||
|
||||
static AbstractInsnNode soleReturn(MethodNode entry) {
|
||||
AbstractInsnNode sole = null;
|
||||
AbstractInsnNode last = null;
|
||||
int returns = 0;
|
||||
for (AbstractInsnNode insn = entry.instructions.getFirst(); insn != null; insn = insn.getNext()) {
|
||||
if (insn.getOpcode() < 0) {
|
||||
continue;
|
||||
}
|
||||
last = insn;
|
||||
if (insn.getOpcode() == Opcodes.RETURN) {
|
||||
sole = insn;
|
||||
returns++;
|
||||
}
|
||||
}
|
||||
if (returns != 1 || sole != last) {
|
||||
throw new SurroundRejection("must reach its end to hand over to the body; restructure it so every path falls through to its end, with no early return, throw or endless loop");
|
||||
}
|
||||
return sole;
|
||||
}
|
||||
|
||||
private static Load resolve(List<Carried> carried, Type[] slots, Handler exit, int index) {
|
||||
final Type type = exit.params[index];
|
||||
final String id = exit.carryIds[index - exit.carryStart];
|
||||
final int kind = kindOf(type);
|
||||
final boolean named = !id.isEmpty();
|
||||
boolean idDeclared = false;
|
||||
int matches = 0;
|
||||
Carried match = null;
|
||||
for (int i = 0, n = carried.size(); i < n; i++) {
|
||||
final Carried candidate = carried.get(i);
|
||||
if (named && !id.equals(candidate.id())) {
|
||||
continue;
|
||||
}
|
||||
idDeclared = true;
|
||||
if (candidate.kind() == kind) {
|
||||
matches++;
|
||||
match = candidate;
|
||||
}
|
||||
}
|
||||
final String param = paramRef(exit.method, index);
|
||||
if (named && !idDeclared) {
|
||||
throw exit.reject(param + " asks for the carried local '" + id + "', which the entry handler does not declare" + declaredList(carried));
|
||||
}
|
||||
if (matches == 0) {
|
||||
throw exit.reject(param + " is " + type.getClassName() + " (" + withArticle(kind) + "), which matches no carried local of that kind" + declaredList(carried));
|
||||
}
|
||||
if (matches > 1) {
|
||||
throw exit.reject(param + " is " + type.getClassName() + " (" + withArticle(kind) + "), which matches " + matches + " carried locals of that kind; name them with @Surround.Carry(\"...\") on both sides");
|
||||
}
|
||||
final Type existing = slots[match.slot()];
|
||||
if (existing != null && !existing.equals(type)) {
|
||||
throw exit.reject(param + " reads carried local " + match + " as " + type.getClassName() + ", but another exit handler reads it as " + existing.getClassName());
|
||||
}
|
||||
slots[match.slot()] = type;
|
||||
return new Load(match.slot(), type);
|
||||
}
|
||||
|
||||
private static List<Carried> carriedLocals(MethodNode entry, int[] kinds) {
|
||||
final List<Carried> carried = new ArrayList<>();
|
||||
final List<LocalVariableAnnotationNode> annotations = entry.visibleLocalVariableAnnotations;
|
||||
for (int a = 0, n = annotations == null ? 0 : annotations.size(); a < n; a++) {
|
||||
final LocalVariableAnnotationNode annotation = annotations.get(a);
|
||||
if (!SurroundSpec.CARRY.equals(annotation.desc)) {
|
||||
continue;
|
||||
}
|
||||
final int slot = annotatedSlot(entry, annotation, 0);
|
||||
final String id = Annotations.<String>getValue(annotation, "value", "");
|
||||
checkLocalAnnotation(entry, kinds, "@Surround.Carry", annotation, slot, id);
|
||||
if (!id.isEmpty()) {
|
||||
for (int i = 0, count = carried.size(); i < count; i++) {
|
||||
if (id.equals(carried.get(i).id())) {
|
||||
throw new SurroundRejection("declares two @Surround.Carry locals named '" + id + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
carried.add(new Carried(slot, kinds[slot], id));
|
||||
}
|
||||
return carried;
|
||||
}
|
||||
|
||||
// FML's deobf remapper (ASM LocalVariablesSorter) corrupts annotation indices of long/double locals; the LVT stays right.
|
||||
private static int annotatedSlot(MethodNode entry, LocalVariableAnnotationNode annotation, int range) {
|
||||
final int raw = annotation.index.get(range).intValue();
|
||||
final LabelNode start = annotation.start.get(range);
|
||||
final LabelNode end = annotation.end.get(range);
|
||||
boolean any = false;
|
||||
boolean rawWide = false;
|
||||
boolean rawFound = false;
|
||||
int wide = -1;
|
||||
int wideCount = 0;
|
||||
for (int i = 0, n = entry.localVariables == null ? 0 : entry.localVariables.size(); i < n; i++) {
|
||||
final LocalVariableNode local = entry.localVariables.get(i);
|
||||
if (local.start != start || local.end != end) {
|
||||
continue;
|
||||
}
|
||||
any = true;
|
||||
final boolean isWide = Type.getType(local.desc).getSize() == 2;
|
||||
if (isWide) {
|
||||
wide = local.index;
|
||||
wideCount++;
|
||||
}
|
||||
if (local.index == raw) {
|
||||
rawFound = true;
|
||||
rawWide = isWide;
|
||||
}
|
||||
}
|
||||
final int slot = !any || rawFound && !(rawWide && wideCount > 1) ? raw : wideCount == 1 && !rawFound ? wide : -1;
|
||||
if (slot < 0) {
|
||||
throw new SurroundRejection("has a long or double annotated local that shares its scope with another; initialize it at its declaration");
|
||||
}
|
||||
if (slot >= entry.maxLocals) {
|
||||
throw new SurroundRejection("has an annotated local outside its method's locals (slot " + slot + " of " + entry.maxLocals + "); compile with local variable tables");
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
private static void checkLocalAnnotation(MethodNode entry, int[] kinds, String what, LocalVariableAnnotationNode annotation, int slot, String id) {
|
||||
final String local = id.isEmpty() ? "in slot " + slot : "named '" + id + "'";
|
||||
final TypePath path = annotation.typePath;
|
||||
for (int i = 0, length = path == null ? 0 : path.getLength(); i < length; i++) {
|
||||
if (path.getStep(i) != TypePath.ARRAY_ELEMENT) {
|
||||
throw new SurroundRejection("has a " + what + " " + local + " inside the local's type (" + path + "); " + what + " must annotate the local itself, not part of its type");
|
||||
}
|
||||
}
|
||||
for (int i = 1, count = annotation.index.size(); i < count; i++) {
|
||||
if (annotatedSlot(entry, annotation, i) != slot) {
|
||||
throw new SurroundRejection("has a " + what + " local " + local + " recorded in several slots " + annotation.index + "; initialize it at its declaration");
|
||||
}
|
||||
}
|
||||
LabelNode end = annotation.end.get(0);
|
||||
for (int i = 1, n = annotation.end.size(); i < n; i++) {
|
||||
if (entry.instructions.indexOf(annotation.end.get(i)) > entry.instructions.indexOf(end)) {
|
||||
end = annotation.end.get(i);
|
||||
}
|
||||
}
|
||||
for (AbstractInsnNode insn = end.getNext(); insn != null; insn = insn.getNext()) {
|
||||
if (insn.getOpcode() >= 0 && insn.getOpcode() != Opcodes.RETURN) {
|
||||
throw new SurroundRejection("has a " + what + " local " + local + " that goes out of scope before the entry handler ends; declare it in the entry handler's outermost block");
|
||||
}
|
||||
}
|
||||
if (kinds[slot] == TOP) {
|
||||
throw new SurroundRejection("has a " + what + " local " + local + " that is not definitely assigned when the entry handler ends; initialize it at its declaration");
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkNoParameterStores(MethodNode entry) {
|
||||
final int limit = Bytecode.getFirstNonArgLocalIndex(entry);
|
||||
for (AbstractInsnNode insn = entry.instructions.getFirst(); insn != null; insn = insn.getNext()) {
|
||||
final int var;
|
||||
if (insn instanceof VarInsnNode varInsn && storeKind(insn.getOpcode()) != -1) {
|
||||
var = varInsn.var;
|
||||
} else if (insn instanceof IincInsnNode iinc) {
|
||||
var = iinc.var;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (var < limit) {
|
||||
throw new SurroundRejection("must not assign its parameters; use @ModifyVariable or @WrapMethod to change arguments");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int[] kindsAtEnd(MethodNode entry, AbstractInsnNode soleReturn) {
|
||||
final int maxLocals = entry.maxLocals;
|
||||
int[] vars = initialVars(entry);
|
||||
int count = vars.length;
|
||||
int[] state = toSlots(vars, count, maxLocals);
|
||||
for (AbstractInsnNode insn = entry.instructions.getFirst(); insn != soleReturn; insn = insn.getNext()) {
|
||||
if (insn instanceof FrameNode frame) {
|
||||
final int locals = frame.local == null ? 0 : frame.local.size();
|
||||
switch (frame.type) {
|
||||
case Opcodes.F_NEW, Opcodes.F_FULL, Opcodes.F_APPEND -> {
|
||||
if (frame.type != Opcodes.F_APPEND) {
|
||||
count = 0;
|
||||
}
|
||||
if (count + locals > vars.length) {
|
||||
vars = Arrays.copyOf(vars, count + locals);
|
||||
}
|
||||
for (int i = 0; i < locals; i++) {
|
||||
vars[count++] = frameKind(frame.local.get(i));
|
||||
}
|
||||
}
|
||||
case Opcodes.F_CHOP -> count -= locals;
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
state = toSlots(vars, count, maxLocals);
|
||||
} else if (insn instanceof VarInsnNode var && storeKind(insn.getOpcode()) != -1) {
|
||||
store(state, var.var, storeKind(insn.getOpcode()));
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
private static void store(int[] state, int var, int kind) {
|
||||
if (var > 0 && wide(state[var - 1])) {
|
||||
state[var - 1] = TOP;
|
||||
}
|
||||
state[var] = kind;
|
||||
if (wide(kind) && var + 1 < state.length) {
|
||||
state[var + 1] = TOP;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean wide(int kind) {
|
||||
return kind == Type.LONG || kind == Type.DOUBLE;
|
||||
}
|
||||
|
||||
private static int[] initialVars(MethodNode entry) {
|
||||
final Type[] args = Type.getArgumentTypes(entry.desc);
|
||||
final int receiver = Bytecode.isStatic(entry) ? 0 : 1;
|
||||
final int[] vars = new int[receiver + args.length];
|
||||
if (receiver != 0) {
|
||||
vars[0] = Type.OBJECT;
|
||||
}
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
vars[receiver + i] = kindOf(args[i]);
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
private static int frameKind(Object entry) {
|
||||
if (entry instanceof String || Opcodes.NULL.equals(entry) || Opcodes.UNINITIALIZED_THIS.equals(entry)) {
|
||||
return Type.OBJECT;
|
||||
} else if (Opcodes.INTEGER.equals(entry)) {
|
||||
return Type.INT;
|
||||
} else if (Opcodes.FLOAT.equals(entry)) {
|
||||
return Type.FLOAT;
|
||||
} else if (Opcodes.LONG.equals(entry)) {
|
||||
return Type.LONG;
|
||||
} else if (Opcodes.DOUBLE.equals(entry)) {
|
||||
return Type.DOUBLE;
|
||||
}
|
||||
return TOP;
|
||||
}
|
||||
|
||||
private static int[] toSlots(int[] vars, int count, int maxLocals) {
|
||||
final int[] slots = new int[maxLocals];
|
||||
Arrays.fill(slots, TOP);
|
||||
int slot = 0;
|
||||
for (int i = 0; i < count && slot < maxLocals; i++) {
|
||||
slots[slot] = vars[i];
|
||||
slot += wide(vars[i]) ? 2 : 1;
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
private static int storeKind(int opcode) {
|
||||
return switch (opcode) {
|
||||
case Opcodes.ISTORE -> Type.INT;
|
||||
case Opcodes.LSTORE -> Type.LONG;
|
||||
case Opcodes.FSTORE -> Type.FLOAT;
|
||||
case Opcodes.DSTORE -> Type.DOUBLE;
|
||||
case Opcodes.ASTORE -> Type.OBJECT;
|
||||
default -> -1;
|
||||
};
|
||||
}
|
||||
|
||||
static int kindOf(Type type) {
|
||||
return switch (type.getSort()) {
|
||||
case Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT -> Type.INT;
|
||||
case Type.LONG -> Type.LONG;
|
||||
case Type.FLOAT -> Type.FLOAT;
|
||||
case Type.DOUBLE -> Type.DOUBLE;
|
||||
default -> Type.OBJECT;
|
||||
};
|
||||
}
|
||||
|
||||
private static String kindDescription(int kind) {
|
||||
return switch (kind) {
|
||||
case Type.INT -> "int-like value";
|
||||
case Type.LONG -> "long";
|
||||
case Type.FLOAT -> "float";
|
||||
case Type.DOUBLE -> "double";
|
||||
default -> "reference";
|
||||
};
|
||||
}
|
||||
|
||||
private static String withArticle(int kind) {
|
||||
return (kind == Type.INT ? "an " : "a ") + kindDescription(kind);
|
||||
}
|
||||
|
||||
private static String declaredList(List<Carried> carried) {
|
||||
final StringJoiner all = new StringJoiner(", ", "; the entry handler carries ", "").setEmptyValue("; the entry handler carries nothing");
|
||||
for (Carried local : carried) {
|
||||
all.add(local.toString());
|
||||
}
|
||||
return all.toString();
|
||||
}
|
||||
|
||||
static String exitRef(MethodNode exit) {
|
||||
return "exit handler '" + exit.name + "'";
|
||||
}
|
||||
|
||||
private record Carried(int slot, int kind, String id) {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (this.id.isEmpty() ? "an unnamed " + kindDescription(this.kind) : "'" + this.id + "', " + withArticle(this.kind)) + " in slot " + this.slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.launch.GlobalProperties;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionInfo;
|
||||
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
|
||||
|
||||
public final class SurroundBootstrap {
|
||||
|
||||
private SurroundBootstrap() {
|
||||
}
|
||||
|
||||
private static final class Registration {
|
||||
|
||||
static {
|
||||
InjectionInfo.register(SurroundInjectionInfo.class);
|
||||
}
|
||||
|
||||
static void ensure() {
|
||||
}
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
Registration.ensure();
|
||||
if (GlobalProperties.get(GlobalProperties.Keys.INIT) != null && MixinEnvironment.getDefaultEnvironment().getActiveTransformer() instanceof IMixinTransformer) {
|
||||
SurroundApplicatorExtension.instance();
|
||||
}
|
||||
}
|
||||
}
|
||||
+605
@@ -0,0 +1,605 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Exits;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Handler;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Load;
|
||||
import com.llamalad7.mixinextras.utils.ASMUtils;
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.FrameNode;
|
||||
import org.spongepowered.asm.lib.tree.IincInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.InsnList;
|
||||
import org.spongepowered.asm.lib.tree.InsnNode;
|
||||
import org.spongepowered.asm.lib.tree.JumpInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.LabelNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.lib.tree.TryCatchBlockNode;
|
||||
import org.spongepowered.asm.lib.tree.TypeInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.VarInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.analysis.Analyzer;
|
||||
import org.spongepowered.asm.lib.tree.analysis.AnalyzerException;
|
||||
import org.spongepowered.asm.lib.tree.analysis.BasicInterpreter;
|
||||
import org.spongepowered.asm.lib.tree.analysis.BasicValue;
|
||||
import org.spongepowered.asm.lib.tree.analysis.Frame;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode;
|
||||
import org.spongepowered.asm.service.MixinService;
|
||||
import org.spongepowered.asm.util.Bytecode;
|
||||
import org.spongepowered.asm.util.Bytecode.Visibility;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
final class SurroundCallSite {
|
||||
|
||||
private static final String REGION = "surround:region";
|
||||
private static final Type[] NOTHING = new Type[0];
|
||||
|
||||
record Region(LabelNode start, LabelNode end) {}
|
||||
|
||||
private SurroundCallSite() {
|
||||
}
|
||||
|
||||
static Type[] operands(MethodInsnNode call, String targetClass) {
|
||||
final Type[] args = Type.getArgumentTypes(call.desc);
|
||||
if (call.getOpcode() == Opcodes.INVOKESTATIC) {
|
||||
return args;
|
||||
}
|
||||
final Type[] all = new Type[args.length + 1];
|
||||
all[0] = Type.getObjectType(call.getOpcode() == Opcodes.INVOKESPECIAL ? targetClass : call.owner);
|
||||
System.arraycopy(args, 0, all, 1, args.length);
|
||||
return all;
|
||||
}
|
||||
|
||||
static void apply(ClassNode classNode, CallSiteSpec spec) {
|
||||
final InjectionNode node = spec.node();
|
||||
if (node == null) {
|
||||
final MethodNode target = spec.target();
|
||||
final MethodNode body = relocate(classNode, target);
|
||||
final MethodInsnNode call = (MethodInsnNode) ASMUtils.getInvokeInstruction(classNode, body);
|
||||
target.instructions.add(call);
|
||||
target.instructions.add(new InsnNode(spec.result().getOpcode(Opcodes.IRETURN)));
|
||||
final Type[] args = operands(call, classNode.name);
|
||||
final int[] argSlots = new int[args.length];
|
||||
for (int i = 1; i < args.length; i++) {
|
||||
argSlots[i] = argSlots[i - 1] + args[i - 1].getSize();
|
||||
}
|
||||
emit(classNode, spec, target, call, null, Bytecode.isStatic(target) ? 0 : 1, argSlots);
|
||||
return;
|
||||
}
|
||||
final AbstractInsnNode champion = node.getCurrentTarget();
|
||||
final Region inner = node.<Region>getDecoration(REGION);
|
||||
final MethodNode owner = champion == null ? null : owner(classNode, inner == null ? champion : inner.start());
|
||||
if (owner == null) {
|
||||
MixinService.getService().getLogger("mixin").warn(spec.description() + " skipped: the surrounded call was removed by another injector");
|
||||
return;
|
||||
}
|
||||
if (!spec.entryDesc().equals(spec.entry().desc)) {
|
||||
throw cannotFollow(spec, "the entry handler's descriptor changed from " + spec.entryDesc() + " to " + spec.entry().desc + " after injection", null);
|
||||
}
|
||||
if (!(champion instanceof MethodInsnNode call)) {
|
||||
throw cannotFollow(spec, "the call was replaced by " + Bytecode.describeNode(champion, false) + ", which is not a method call", null);
|
||||
}
|
||||
final int offset = node.isReplaced() && call.getOpcode() != Opcodes.INVOKESTATIC ? 1 : 0;
|
||||
checkOperands(spec, call, operands(call, classNode.name), offset);
|
||||
final Exits exits = spec.exits();
|
||||
final Type result = spec.result();
|
||||
final Type actual = Type.getReturnType(call.desc);
|
||||
if ((exits.handles || exits.skipSlot >= 0 || exits.returnHandler != null) && (actual.getSize() != result.getSize() || actual.getSize() > 0 && SurroundBinding.kindOf(actual) != SurroundBinding.kindOf(result))) {
|
||||
throw cannotFollow(spec, "the call was replaced by " + Bytecode.describeNode(call, false) + ", which returns " + actual.getClassName() + " where the surround supplies or replaces a " + result.getClassName(), null);
|
||||
}
|
||||
emit(classNode, spec, owner, call, inner, offset, null);
|
||||
}
|
||||
|
||||
private static void emit(ClassNode classNode, CallSiteSpec spec, MethodNode owner, MethodInsnNode call, Region inner, int offset, int[] argSlots) {
|
||||
final boolean slots = argSlots != null;
|
||||
final Type[] args = operands(call, classNode.name);
|
||||
final Exits exits = spec.exits();
|
||||
final boolean skips = exits.skipSlot >= 0;
|
||||
final Handler returnHandler = exits.returnHandler;
|
||||
final Type result = spec.result();
|
||||
final Type actual = Type.getReturnType(call.desc);
|
||||
|
||||
final MethodNode entry = spec.entry();
|
||||
final Type[] operands = spec.operands();
|
||||
final int argBase = Bytecode.isStatic(spec.target()) ? 0 : 1;
|
||||
final Type[] entryParams = Type.getArgumentTypes(entry.desc);
|
||||
final int entryOperands = entryParams.length - spec.entryLocals().length;
|
||||
final int firstNonArg = argBase + Bytecode.getArgsSize(entryParams);
|
||||
final MethodNode inlined = inlineable(entry);
|
||||
|
||||
final boolean restoresBelow = exits.handles || !inlined.tryCatchBlocks.isEmpty();
|
||||
final boolean relocated = owner != spec.target();
|
||||
owner.maxLocals = Math.max(Math.max(owner.maxLocals, spec.target().maxLocals), usedLocals(owner));
|
||||
int next = owner.maxLocals;
|
||||
final AbstractInsnNode anchor = inner == null ? call : inner.start();
|
||||
final Frame<BasicValue> frame = !slots && (restoresBelow || relocated && hasLocals(spec)) ? frameAt(owner, anchor, spec) : null;
|
||||
final int shift = relocated ? Bytecode.getFirstNonArgLocalIndex(owner) - Bytecode.getFirstNonArgLocalIndex(spec.target()) : 0;
|
||||
|
||||
final Type[] below = restoresBelow ? below(spec, frame, args.length) : NOTHING;
|
||||
final int spillFrom = slots || restoresBelow || skips ? 0 : offset;
|
||||
final boolean spill = slots || restoresBelow || skips || args.length > offset && (entryOperands > 0 || prefixed(spec));
|
||||
final int[] spillSlots = slots ? argSlots : new int[args.length];
|
||||
if (spill && !slots) {
|
||||
for (int i = spillFrom; i < args.length; i++) {
|
||||
spillSlots[i] = next;
|
||||
next += args[i].getSize();
|
||||
}
|
||||
}
|
||||
final int[] belowSlots = new int[below.length];
|
||||
for (int i = 0; i < below.length; i++) {
|
||||
belowSlots[i] = next;
|
||||
next += below[i].getSize();
|
||||
}
|
||||
final int entryBase = next;
|
||||
final int delta = entryBase - firstNonArg;
|
||||
final int excSlot = entryBase + Math.max(0, inlined.maxLocals - firstNonArg);
|
||||
final int resSlot = excSlot + 1;
|
||||
final boolean storesResult = returnHandler != null && result.getSize() > 0;
|
||||
|
||||
final int[] entryLocals = localSlots(spec, owner, anchor, frame, shift, spec.entryLocals(), entryParams, entryOperands);
|
||||
remapLocals(inlined, argBase, firstNonArg, entryParams, entryOperands, spillSlots, offset, entryLocals, delta);
|
||||
final Handler[] handlers = exits.handlers;
|
||||
final int[][] locals = new int[handlers.length][];
|
||||
for (Handler exit : handlers) {
|
||||
locals[exit.index] = localSlots(spec, owner, anchor, frame, shift, spec.locals()[exit.index], exit.params, exit.localStart);
|
||||
}
|
||||
final Emitter emitter = new Emitter(classNode, exits, operands, Arrays.copyOfRange(spillSlots, offset, offset + operands.length), locals, delta);
|
||||
|
||||
final LabelNode sStart = new LabelNode();
|
||||
final LabelNode tStart = inner == null || skips ? new LabelNode() : inner.start();
|
||||
final LabelNode tEnd = inner == null ? new LabelNode() : inner.end();
|
||||
final LabelNode sEnd = new LabelNode();
|
||||
final LabelNode join = skips ? new LabelNode() : null;
|
||||
|
||||
final InsnList head = new InsnList();
|
||||
head.add(sStart);
|
||||
if (spill && !slots) {
|
||||
for (int i = args.length - 1; i >= spillFrom; i--) {
|
||||
final int j = i - offset;
|
||||
if (j >= 0 && j < operands.length && operands[j].getSort() >= Type.ARRAY && !args[i].equals(operands[j])) {
|
||||
head.add(new TypeInsnNode(Opcodes.CHECKCAST, operands[j].getInternalName()));
|
||||
}
|
||||
head.add(new VarInsnNode(args[i].getOpcode(Opcodes.ISTORE), spillSlots[i]));
|
||||
}
|
||||
for (int i = below.length - 1; i >= 0; i--) {
|
||||
head.add(new VarInsnNode(below[i].getOpcode(Opcodes.ISTORE), belowSlots[i]));
|
||||
}
|
||||
}
|
||||
head.add(inlined.instructions);
|
||||
if (skips) {
|
||||
final LabelNode callPath = new LabelNode();
|
||||
head.add(new VarInsnNode(Opcodes.ILOAD, exits.skipSlot + delta));
|
||||
head.add(new JumpInsnNode(Opcodes.IFEQ, callPath));
|
||||
head.add(tStart);
|
||||
loadBelow(head, below, belowSlots);
|
||||
emitter.callSkipped(head);
|
||||
head.add(new JumpInsnNode(Opcodes.GOTO, join));
|
||||
head.add(callPath);
|
||||
}
|
||||
if (spill) {
|
||||
loadBelow(head, below, belowSlots);
|
||||
for (int i = spillFrom; i < args.length; i++) {
|
||||
head.add(new VarInsnNode(args[i].getOpcode(Opcodes.ILOAD), spillSlots[i]));
|
||||
}
|
||||
}
|
||||
if (inner == null && !skips) {
|
||||
head.add(tStart);
|
||||
}
|
||||
owner.instructions.insertBefore(anchor, head);
|
||||
|
||||
final InsnList tail = new InsnList();
|
||||
if (inner == null) {
|
||||
tail.add(tEnd);
|
||||
}
|
||||
final LabelNode retStart = returnHandler == null ? null : new LabelNode();
|
||||
final LabelNode retEnd = returnHandler == null ? null : new LabelNode();
|
||||
if (returnHandler != null) {
|
||||
tail.add(retStart);
|
||||
if (result.getSize() > 0) {
|
||||
if (result.getSort() >= Type.ARRAY && !actual.equals(result)) {
|
||||
tail.add(new TypeInsnNode(Opcodes.CHECKCAST, result.getInternalName()));
|
||||
}
|
||||
tail.add(new VarInsnNode(result.getOpcode(Opcodes.ISTORE), resSlot));
|
||||
}
|
||||
emitter.callReturn(tail, result, resSlot);
|
||||
tail.add(retEnd);
|
||||
}
|
||||
if (skips) {
|
||||
tail.add(join);
|
||||
}
|
||||
emitter.callFinally(tail);
|
||||
tail.add(new JumpInsnNode(Opcodes.GOTO, sEnd));
|
||||
final AbstractInsnNode beforeHandlers = tail.getLast();
|
||||
|
||||
final List<TryCatchBlockNode> blocks = new ArrayList<>(inlined.tryCatchBlocks);
|
||||
emitter.emitHandlers(tail, blocks, tStart, tEnd, retStart, retEnd, excSlot, below, belowSlots, sEnd);
|
||||
final AbstractInsnNode firstHandler = beforeHandlers.getNext();
|
||||
tail.add(sEnd);
|
||||
owner.instructions.insert(inner == null ? call : inner.end(), tail);
|
||||
|
||||
owner.tryCatchBlocks.addAll(insertionIndex(owner, sStart, sEnd), blocks);
|
||||
owner.maxLocals = Math.max(owner.maxLocals, storesResult ? resSlot + result.getSize() : excSlot + 1);
|
||||
owner.maxStack = Math.max(owner.maxStack, spec.target().maxStack) + Math.max(inlined.maxStack, exits.argsSize + 2);
|
||||
if (slots) {
|
||||
final LabelNode end = new LabelNode();
|
||||
owner.instructions.add(end);
|
||||
final List<LocalVariableNode> names = new ArrayList<>();
|
||||
nameParameters(names, classNode, spec, argBase, entryOperands, sStart, end);
|
||||
nameEntryLocals(names, inlined, owner.instructions, exits.carried, firstNonArg, delta, end);
|
||||
if (firstHandler != null) {
|
||||
names.add(new LocalVariableNode("surround$error", SurroundSpec.THROWABLE_DESC, null, (LabelNode) firstHandler, end, excSlot));
|
||||
}
|
||||
if (storesResult) {
|
||||
names.add(new LocalVariableNode("surround$result", result.getDescriptor(), null, retStart, retEnd, resSlot));
|
||||
}
|
||||
owner.localVariables = names;
|
||||
} else {
|
||||
spec.node().decorate(REGION, new Region(sStart, sEnd));
|
||||
}
|
||||
}
|
||||
|
||||
private static void nameParameters(List<LocalVariableNode> names, ClassNode classNode, CallSiteSpec spec, int argBase, int entryOperands, LabelNode start, LabelNode end) {
|
||||
final Type[] params = spec.operands();
|
||||
final MethodNode entry = spec.entry();
|
||||
if (argBase > 0) {
|
||||
names.add(new LocalVariableNode("this", Type.getObjectType(classNode.name).getDescriptor(), null, start, end, 0));
|
||||
}
|
||||
for (int i = 0, slot = argBase; i < params.length; slot += params[i++].getSize()) {
|
||||
final String name = i < entryOperands ? SurroundBinding.paramName(entry, i) : null;
|
||||
names.add(new LocalVariableNode(name != null ? name : "arg" + i, params[i].getDescriptor(), null, start, end, slot));
|
||||
}
|
||||
}
|
||||
|
||||
private static void nameEntryLocals(List<LocalVariableNode> names, MethodNode inlined, InsnList insns, Type[] carried, int firstNonArg, int delta, LabelNode end) {
|
||||
final LocalVariableNode[] furthest = new LocalVariableNode[carried.length];
|
||||
for (LocalVariableNode local : inlined.localVariables) {
|
||||
if (!isCarried(carried, local.index)) {
|
||||
continue;
|
||||
}
|
||||
final LocalVariableNode current = furthest[local.index];
|
||||
if (current == null || insns.indexOf(local.end) > insns.indexOf(current.end)) {
|
||||
furthest[local.index] = local;
|
||||
}
|
||||
}
|
||||
for (LocalVariableNode local : inlined.localVariables) {
|
||||
if (local.index < firstNonArg) {
|
||||
continue;
|
||||
}
|
||||
if (isCarried(carried, local.index) && local == furthest[local.index]) {
|
||||
final Type type = carried[local.index];
|
||||
names.add(new LocalVariableNode(local.name, type.getSort() >= Type.ARRAY ? local.desc : type.getDescriptor(), null, local.start, end, local.index + delta));
|
||||
} else {
|
||||
local.index += delta;
|
||||
names.add(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCarried(Type[] carried, int slot) {
|
||||
return slot < carried.length && carried[slot] != null;
|
||||
}
|
||||
|
||||
private static MethodNode inlineable(MethodNode entry) {
|
||||
final MethodNode copy = new MethodNode(entry.access, entry.name, entry.desc, null, null);
|
||||
entry.accept(copy);
|
||||
for (AbstractInsnNode insn = copy.instructions.getFirst(); insn != null; ) {
|
||||
final AbstractInsnNode next = insn.getNext();
|
||||
if (insn instanceof FrameNode) {
|
||||
copy.instructions.remove(insn);
|
||||
}
|
||||
insn = next;
|
||||
}
|
||||
AbstractInsnNode last = copy.instructions.getLast();
|
||||
while (last.getOpcode() < 0) {
|
||||
last = last.getPrevious();
|
||||
}
|
||||
copy.instructions.remove(last);
|
||||
return copy;
|
||||
}
|
||||
|
||||
private static MethodNode relocate(ClassNode classNode, MethodNode target) {
|
||||
final MethodNode body = new MethodNode(target.access & ~Opcodes.ACC_SYNCHRONIZED, unique(classNode, target.name + "$surround"), target.desc, null, null);
|
||||
Bytecode.setVisibility(body, Visibility.PRIVATE);
|
||||
body.instructions = target.instructions;
|
||||
body.instructions.resetLabels();
|
||||
body.tryCatchBlocks = target.tryCatchBlocks;
|
||||
body.localVariables = target.localVariables;
|
||||
body.visibleLocalVariableAnnotations = target.visibleLocalVariableAnnotations;
|
||||
body.invisibleLocalVariableAnnotations = target.invisibleLocalVariableAnnotations;
|
||||
body.maxStack = target.maxStack;
|
||||
body.maxLocals = target.maxLocals;
|
||||
|
||||
target.instructions = new InsnList();
|
||||
target.tryCatchBlocks = new ArrayList<>();
|
||||
target.visibleLocalVariableAnnotations = null;
|
||||
target.invisibleLocalVariableAnnotations = null;
|
||||
target.maxLocals = target.maxStack = Bytecode.getFirstNonArgLocalIndex(target);
|
||||
classNode.methods.add(body);
|
||||
return body;
|
||||
}
|
||||
|
||||
private static String unique(ClassNode classNode, String base) {
|
||||
String name = base;
|
||||
for (int i = 1; ; name = base + "$" + i++) {
|
||||
final String candidate = name;
|
||||
if (classNode.methods.stream().noneMatch(method -> method.name.equals(candidate))) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasLocals(CallSiteSpec spec) {
|
||||
if (spec.entryLocals().length > 0) {
|
||||
return true;
|
||||
}
|
||||
for (int[] locals : spec.locals()) {
|
||||
if (locals.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Frame<BasicValue> frameAt(MethodNode owner, AbstractInsnNode anchor, CallSiteSpec spec) {
|
||||
final int index = owner.instructions.indexOf(anchor);
|
||||
for (owner.maxStack = Math.max(owner.maxStack, spec.target().maxStack) + 16; ; owner.maxStack <<= 1) {
|
||||
try {
|
||||
return new Analyzer<>(new BasicInterpreter()).analyze(owner.name, owner)[index];
|
||||
} catch (AnalyzerException e) {
|
||||
if (!(e.getCause() instanceof IndexOutOfBoundsException) || owner.maxStack > 0xFFFF) {
|
||||
throw cannotFollow(spec, "cannot analyze " + owner.name + owner.desc, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Type[] below(CallSiteSpec spec, Frame<BasicValue> frame, int args) {
|
||||
final int count = frame == null ? 0 : frame.getStackSize() - args;
|
||||
if (count <= 0) {
|
||||
return NOTHING;
|
||||
}
|
||||
final Type[] below = new Type[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
final BasicValue value = frame.getStack(i);
|
||||
final Type type = value.getType();
|
||||
if (type == null || type.getSort() == Type.VOID) {
|
||||
throw cannotFollow(spec, "stack value " + i + " below the call is " + value + ", which cannot be restored", null);
|
||||
}
|
||||
below[i] = value.isReference() ? Type.getObjectType(SurroundSpec.OBJECT) : type;
|
||||
}
|
||||
return below;
|
||||
}
|
||||
|
||||
private static int[] localSlots(CallSiteSpec spec, MethodNode owner, AbstractInsnNode anchor, Frame<BasicValue> frame, int shift, int[] resolved, Type[] params, int first) {
|
||||
if (resolved.length == 0 || owner == spec.target()) {
|
||||
return resolved;
|
||||
}
|
||||
final int firstNonArg = Bytecode.getFirstNonArgLocalIndex(spec.target());
|
||||
final int[] slots = new int[resolved.length];
|
||||
for (int i = 0; i < resolved.length; i++) {
|
||||
final int slot = resolved[i] < firstNonArg ? resolved[i] : resolved[i] + shift;
|
||||
checkLocal(spec, owner, anchor, frame, slot, params[first + i], resolved[i]);
|
||||
slots[i] = slot;
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
private static void checkLocal(CallSiteSpec spec, MethodNode owner, AbstractInsnNode anchor, Frame<BasicValue> frame, int slot, Type type, int resolved) {
|
||||
String found = null;
|
||||
if (owner.localVariables != null) {
|
||||
final int at = owner.instructions.indexOf(anchor);
|
||||
for (LocalVariableNode local : owner.localVariables) {
|
||||
if (local.index == slot && owner.instructions.indexOf(local.start) <= at && at < owner.instructions.indexOf(local.end) && !type.getDescriptor().equals(local.desc)) {
|
||||
found = Type.getType(local.desc).getClassName() + " '" + local.name + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found == null && frame != null) {
|
||||
final BasicValue value = slot < frame.getLocals() ? frame.getLocal(slot) : null;
|
||||
final Type actual = value == null ? null : value.getType();
|
||||
if (actual == null || actual.getSort() == Type.VOID || actual.getSize() != type.getSize() || SurroundBinding.kindOf(actual) != SurroundBinding.kindOf(type)) {
|
||||
found = value == null ? "nothing" : String.valueOf(value);
|
||||
}
|
||||
}
|
||||
if (found != null) {
|
||||
throw cannotFollow(spec, "@Surround.Local " + type.getClassName() + " resolved to local " + resolved + " at injection, but after the body moved to " + owner.name + owner.desc + " local " + slot + " holds " + found, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodNode owner(ClassNode classNode, AbstractInsnNode insn) {
|
||||
AbstractInsnNode first = insn;
|
||||
while (first.getPrevious() != null) {
|
||||
first = first.getPrevious();
|
||||
}
|
||||
final List<MethodNode> methods = classNode.methods;
|
||||
for (int i = 0, n = methods.size(); i < n; i++) {
|
||||
if (methods.get(i).instructions.getFirst() == first) {
|
||||
return methods.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void checkOperands(CallSiteSpec spec, MethodInsnNode call, Type[] args, int offset) {
|
||||
final Type[] operands = spec.operands();
|
||||
if (args.length - offset < operands.length) {
|
||||
throw cannotFollow(spec, "the call was replaced by " + Bytecode.describeNode(call, false) + ", which takes fewer arguments than the surrounded call", null);
|
||||
}
|
||||
for (int i = 0; i < operands.length; i++) {
|
||||
if (SurroundBinding.kindOf(args[offset + i]) != SurroundBinding.kindOf(operands[i])) {
|
||||
throw cannotFollow(spec, "the call was replaced by " + Bytecode.describeNode(call, false) + ", whose argument " + (offset + i) + " is " + args[offset + i].getClassName() + " where the surrounded call passed " + operands[i].getClassName(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IllegalStateException cannotFollow(CallSiteSpec spec, String detail, Throwable cause) {
|
||||
return new IllegalStateException(spec.description() + " cannot be applied, another transformation left the site in a shape it cannot follow: " + detail, cause);
|
||||
}
|
||||
|
||||
private static boolean prefixed(CallSiteSpec spec) {
|
||||
for (Handler exit : spec.exits().handlers) {
|
||||
if (exit.localStart > exit.leading) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int usedLocals(MethodNode owner) {
|
||||
int max = Bytecode.getFirstNonArgLocalIndex(owner);
|
||||
for (AbstractInsnNode insn = owner.instructions.getFirst(); insn != null; insn = insn.getNext()) {
|
||||
if (insn instanceof VarInsnNode varInsn) {
|
||||
final int opcode = insn.getOpcode();
|
||||
final boolean wide = opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE;
|
||||
max = Math.max(max, varInsn.var + (wide ? 2 : 1));
|
||||
} else if (insn instanceof IincInsnNode iinc) {
|
||||
max = Math.max(max, iinc.var + 1);
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
private static void remapLocals(MethodNode inlined, int argBase, int firstNonArg, Type[] entryParams, int entryOperands, int[] spillSlots, int offset, int[] entryLocals, int delta) {
|
||||
final int[] paramSlots = new int[firstNonArg];
|
||||
for (int i = 0, slot = argBase; i < entryParams.length; i++) {
|
||||
paramSlots[slot] = i < entryOperands ? spillSlots[offset + i] : entryLocals[i - entryOperands];
|
||||
slot += entryParams[i].getSize();
|
||||
}
|
||||
for (AbstractInsnNode insn = inlined.instructions.getFirst(); insn != null; insn = insn.getNext()) {
|
||||
if (insn instanceof VarInsnNode var) {
|
||||
var.var = remap(var.var, argBase, firstNonArg, paramSlots, delta);
|
||||
} else if (insn instanceof IincInsnNode iinc) {
|
||||
iinc.var = remap(iinc.var, argBase, firstNonArg, paramSlots, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int remap(int var, int argBase, int firstNonArg, int[] paramSlots, int delta) {
|
||||
if (var < argBase) {
|
||||
return var;
|
||||
}
|
||||
if (var < firstNonArg) {
|
||||
return paramSlots[var];
|
||||
}
|
||||
return var + delta;
|
||||
}
|
||||
|
||||
private static int insertionIndex(MethodNode owner, LabelNode start, LabelNode end) {
|
||||
final List<TryCatchBlockNode> blocks = owner.tryCatchBlocks;
|
||||
final InsnList insns = owner.instructions;
|
||||
final int from = insns.indexOf(start);
|
||||
final int to = insns.indexOf(end);
|
||||
for (int i = 0, n = blocks.size(); i < n; i++) {
|
||||
final TryCatchBlockNode block = blocks.get(i);
|
||||
final int s = insns.indexOf(block.start);
|
||||
final int e = insns.indexOf(block.end);
|
||||
if (s < to && e > from && (s < from || e > to)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return blocks.size();
|
||||
}
|
||||
|
||||
private static void loadBelow(InsnList insns, Type[] below, int[] belowSlots) {
|
||||
for (int i = 0; i < below.length; i++) {
|
||||
insns.add(new VarInsnNode(below[i].getOpcode(Opcodes.ILOAD), belowSlots[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private record Emitter(ClassNode classNode, Exits exits, Type[] operands, int[] operandSlots, int[][] locals, int delta) {
|
||||
|
||||
private void invoke(InsnList insns, Handler exit, Type leading, int leadingSlot) {
|
||||
final int[] locals = this.locals[exit.index];
|
||||
if (!exit.isStatic) {
|
||||
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
||||
}
|
||||
if (leading != null) {
|
||||
insns.add(new VarInsnNode(leading.getOpcode(Opcodes.ILOAD), leadingSlot));
|
||||
}
|
||||
for (int i = 0, n = exit.localStart - exit.leading; i < n; i++) {
|
||||
insns.add(new VarInsnNode(this.operands[i].getOpcode(Opcodes.ILOAD), this.operandSlots[i]));
|
||||
}
|
||||
for (int i = exit.localStart; i < exit.carryStart; i++) {
|
||||
insns.add(new VarInsnNode(exit.params[i].getOpcode(Opcodes.ILOAD), locals[i - exit.localStart]));
|
||||
}
|
||||
for (Load load : exit.loads) {
|
||||
final Type type = load.type();
|
||||
insns.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), load.slot() + this.delta));
|
||||
if (type.getSort() >= Type.ARRAY && !SurroundSpec.OBJECT.equals(type.getInternalName())) {
|
||||
insns.add(new TypeInsnNode(Opcodes.CHECKCAST, type.getInternalName()));
|
||||
}
|
||||
}
|
||||
// MixinExtras 0.5.0 internal (pinned by UniMixins 0.3.1); recheck on bump
|
||||
insns.add(ASMUtils.getInvokeInstruction(this.classNode, exit.method));
|
||||
}
|
||||
|
||||
void callFinally(InsnList insns) {
|
||||
if (this.exits.finallyHandler != null) {
|
||||
this.invoke(insns, this.exits.finallyHandler, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void callSkipped(InsnList insns) {
|
||||
if (this.exits.skipped != null) {
|
||||
this.invoke(insns, this.exits.skipped, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void callReturn(InsnList insns, Type result, int resSlot) {
|
||||
this.invoke(insns, this.exits.returnHandler, result.getSize() > 0 ? result : null, resSlot);
|
||||
}
|
||||
|
||||
void emitHandlers(InsnList tail, List<TryCatchBlockNode> blocks, LabelNode start, LabelNode end, LabelNode retStart, LabelNode retEnd, int excSlot, Type[] below, int[] belowSlots, LabelNode handledExit) {
|
||||
final Handler[] handlers = this.exits.handlers;
|
||||
final boolean hasFinally = this.exits.finallyHandler != null;
|
||||
final LabelNode catchAll = hasFinally ? new LabelNode() : null;
|
||||
final List<TryCatchBlockNode> catchAlls = hasFinally ? new ArrayList<>() : null;
|
||||
for (int k = 0; k < this.exits.catches; k++) {
|
||||
final Handler exit = handlers[k];
|
||||
final LabelNode handler = new LabelNode();
|
||||
final LabelNode exitStart = new LabelNode();
|
||||
final LabelNode exitEnd = new LabelNode();
|
||||
|
||||
tail.add(handler);
|
||||
tail.add(new VarInsnNode(Opcodes.ASTORE, excSlot));
|
||||
if (exit.handles) {
|
||||
loadBelow(tail, below, belowSlots);
|
||||
}
|
||||
tail.add(exitStart);
|
||||
this.invoke(tail, exit, exit.caught(), excSlot);
|
||||
tail.add(exitEnd);
|
||||
|
||||
this.callFinally(tail);
|
||||
if (exit.handles) {
|
||||
tail.add(new JumpInsnNode(Opcodes.GOTO, handledExit));
|
||||
} else {
|
||||
tail.add(new VarInsnNode(Opcodes.ALOAD, excSlot));
|
||||
tail.add(new InsnNode(Opcodes.ATHROW));
|
||||
}
|
||||
|
||||
blocks.add(new TryCatchBlockNode(start, end, handler, exit.caught().getInternalName()));
|
||||
if (hasFinally) {
|
||||
catchAlls.add(new TryCatchBlockNode(exitStart, exitEnd, catchAll, null));
|
||||
}
|
||||
}
|
||||
if (hasFinally) {
|
||||
tail.add(catchAll);
|
||||
tail.add(new VarInsnNode(Opcodes.ASTORE, excSlot));
|
||||
this.callFinally(tail);
|
||||
tail.add(new VarInsnNode(Opcodes.ALOAD, excSlot));
|
||||
tail.add(new InsnNode(Opcodes.ATHROW));
|
||||
|
||||
if (retStart != null) {
|
||||
catchAlls.add(new TryCatchBlockNode(retStart, retEnd, catchAll, null));
|
||||
}
|
||||
catchAlls.add(new TryCatchBlockNode(start, end, catchAll, null));
|
||||
blocks.addAll(catchAlls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.code.Injector;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionInfo;
|
||||
import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException;
|
||||
import org.spongepowered.asm.mixin.transformer.MixinTargetContext;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@InjectionInfo.AnnotationType(Surround.class)
|
||||
@InjectionInfo.HandlerPrefix("surround")
|
||||
public final class SurroundInjectionInfo extends InjectionInfo {
|
||||
|
||||
boolean callSite;
|
||||
|
||||
public SurroundInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) {
|
||||
super(mixin, method, annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readInjectionPoints() {
|
||||
final List<AnnotationNode> ats = Annotations.<AnnotationNode>getValue(this.annotation, this.atKey, false);
|
||||
this.callSite = ats != null && !ats.isEmpty();
|
||||
if (this.callSite) {
|
||||
super.readInjectionPoints();
|
||||
return;
|
||||
}
|
||||
final List<AnnotationNode> slices = Annotations.<AnnotationNode>getValue(this.annotation, "slice", false);
|
||||
if (slices != null && !slices.isEmpty()) {
|
||||
throw this.reject("declares @Surround.slice, which only applies when @Surround.at selects a call");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseInjectionPoints(List<AnnotationNode> ats) {
|
||||
if (!this.callSite) {
|
||||
this.injectionPoints.add(new SurroundInjectionPoint());
|
||||
return;
|
||||
}
|
||||
for (int i = 0, n = ats.size(); i < n; i++) {
|
||||
final AnnotationNode at = ats.get(i);
|
||||
final String code = Annotations.<String>getValue(at, "value");
|
||||
if (!selectsCall(code)) {
|
||||
throw this.reject("has @At(\"" + code + "\"), which does not select a method call");
|
||||
}
|
||||
if (Annotations.<At.Shift>getValue(at, "shift", At.Shift.class, At.Shift.NONE) != At.Shift.NONE || Annotations.<Integer>getValue(at, "by", Integer.valueOf(0)).intValue() != 0) {
|
||||
throw this.reject("has @At(\"" + code + "\") with a non-default shift or by, which moves the match off the call");
|
||||
}
|
||||
}
|
||||
super.parseInjectionPoints(ats);
|
||||
}
|
||||
|
||||
private static boolean selectsCall(String atCode) {
|
||||
return switch (atCode) {
|
||||
case "HEAD", "RETURN", "TAIL", "CTOR_HEAD", "FIELD", "NEW", "CONSTANT", "JUMP", "INVOKE_ASSIGN", "LOAD", "STORE" -> false;
|
||||
default -> true;
|
||||
};
|
||||
}
|
||||
|
||||
private InvalidInjectionException reject(String message) {
|
||||
return new InvalidInjectionException(this, "@Surround " + this.getMethodName() + " " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Injector parseInjector(AnnotationNode injectAnnotation) {
|
||||
return new SurroundInjector(this);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.InsnList;
|
||||
import org.spongepowered.asm.mixin.injection.InjectionPoint;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
final class SurroundInjectionPoint extends InjectionPoint {
|
||||
|
||||
@Override
|
||||
public boolean checkPriority(int targetPriority, int ownerPriority) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean find(String desc, InsnList insns, Collection<AbstractInsnNode> nodes) {
|
||||
return nodes.add(insns.getFirst());
|
||||
}
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Exits;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Handler;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.Role;
|
||||
import com.llamalad7.mixinextras.utils.InjectorUtils;
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableAnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.injection.InjectionPoint;
|
||||
import org.spongepowered.asm.mixin.injection.code.Injector;
|
||||
import org.spongepowered.asm.mixin.injection.modify.InvalidImplicitDiscriminatorException;
|
||||
import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator;
|
||||
import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator.Context;
|
||||
import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode;
|
||||
import org.spongepowered.asm.mixin.injection.struct.Target;
|
||||
import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException;
|
||||
import org.spongepowered.asm.mixin.transformer.ClassInfo;
|
||||
import org.spongepowered.asm.mixin.transformer.ClassInfo.Traversal;
|
||||
import org.spongepowered.asm.mixin.transformer.MixinTargetContext;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
import org.spongepowered.asm.util.Bytecode;
|
||||
import org.spongepowered.asm.util.asm.MethodNodeEx;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.exitRef;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.SurroundBinding.paramRef;
|
||||
|
||||
final class SurroundInjector extends Injector {
|
||||
|
||||
private final String id;
|
||||
private final boolean callSite;
|
||||
private final MixinTargetContext mixin;
|
||||
private Handler entry;
|
||||
private Exits exits;
|
||||
|
||||
SurroundInjector(SurroundInjectionInfo info) {
|
||||
super(info, "@Surround");
|
||||
this.id = info.getAnnotation().getValue("id", "");
|
||||
this.callSite = info.callSite;
|
||||
this.mixin = (MixinTargetContext) info.getMixin();
|
||||
SurroundApplicatorExtension.instance().declare(this.mixin.getTarget(), this.mixin.getClassName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(Target target, InjectionNode node) {
|
||||
this.checkTargetModifiers(target, true);
|
||||
try {
|
||||
if (this.callSite) {
|
||||
this.surroundCall(target, node);
|
||||
} else {
|
||||
final Type result = Type.getReturnType(target.method.desc);
|
||||
this.prepare(target.arguments, result, target.toString());
|
||||
this.offer(target, null, target.arguments, result, target.toString());
|
||||
}
|
||||
} catch (SurroundRejection e) {
|
||||
throw error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void surroundCall(Target target, InjectionNode node) {
|
||||
final AbstractInsnNode insn = node.getOriginalTarget();
|
||||
if (!(insn instanceof MethodInsnNode call)) {
|
||||
throw error("at must select a method call, not " + Bytecode.describeNode(insn, false) + " in " + target);
|
||||
}
|
||||
final String site = "call " + call.owner + "." + call.name + call.desc + " in " + target;
|
||||
if ("<init>".equals(call.name)) {
|
||||
throw error("at must select a method call, not the constructor " + site);
|
||||
}
|
||||
final Type[] operands = SurroundCallSite.operands(call, this.classNode.name);
|
||||
final Type result = Type.getReturnType(call.desc);
|
||||
this.prepare(operands, result, site);
|
||||
final boolean insideNew = isInsideNew(call);
|
||||
if (insideNew && this.methodNode.tryCatchBlocks != null && !this.methodNode.tryCatchBlocks.isEmpty()) {
|
||||
throw error("must not catch exceptions when it surrounds a call inside a constructor call's arguments (between NEW and <init>); move the try into a method it calls");
|
||||
}
|
||||
if (insideNew && this.exits.handles) {
|
||||
throw error("has a @Surround.Catch" + this.forId() + " that declares handle = true, but the call is inside a constructor call's arguments (between NEW and <init>), where the values below it cannot be restored");
|
||||
}
|
||||
this.offer(target, node, operands, result, site);
|
||||
}
|
||||
|
||||
private void offer(Target target, InjectionNode node, Type[] operands, Type result, String site) {
|
||||
final Handler[] handlers = this.exits.handlers;
|
||||
final int[][] locals = new int[handlers.length][];
|
||||
for (Handler exit : handlers) {
|
||||
locals[exit.index] = this.resolveLocals(target, node, exit);
|
||||
}
|
||||
final int[] entryLocals = this.resolveLocals(target, node, this.entry);
|
||||
this.info.addCallbackInvocation(this.methodNode);
|
||||
final String description = "@Surround " + this.mixin.getClassName() + "::" + this.info.getMethodName() + " surrounding " + site;
|
||||
SurroundApplicatorExtension.instance().offer(this.mixin.getTarget(), new CallSiteSpec(target.method, node, this.methodNode, this.methodNode.desc, operands, result, this.exits, description, entryLocals, locals));
|
||||
}
|
||||
|
||||
private static boolean isInsideNew(MethodInsnNode call) {
|
||||
int constructed = 0;
|
||||
for (AbstractInsnNode insn = call.getPrevious(); insn != null; insn = insn.getPrevious()) {
|
||||
if (insn.getOpcode() == Opcodes.INVOKESPECIAL && "<init>".equals(((MethodInsnNode) insn).name)) {
|
||||
constructed++;
|
||||
} else if (insn.getOpcode() == Opcodes.NEW) {
|
||||
if (constructed == 0) {
|
||||
return true;
|
||||
}
|
||||
constructed--;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int[] resolveLocals(Target target, InjectionNode node, Handler handler) {
|
||||
final int from = handler.localStart;
|
||||
final int[] slots = new int[handler.carryStart - from];
|
||||
for (int i = from; i < handler.carryStart; i++) {
|
||||
final Type type = handler.params[i];
|
||||
final LocalVariableDiscriminator discriminator = LocalVariableDiscriminator.parse(SurroundBinding.local(handler.method, i));
|
||||
// MixinExtras 0.5.0 internal (pinned by UniMixins 0.3.1); recheck on bump
|
||||
final Context context = InjectorUtils.getOrCreateLocalContext(target, node, this.info, type, discriminator.isArgsOnly());
|
||||
String detail;
|
||||
try {
|
||||
slots[i - from] = discriminator.findLocal(context);
|
||||
detail = slots[i - from] < 0 ? "no " + discriminator.toString(context) : null;
|
||||
} catch (InvalidImplicitDiscriminatorException e) {
|
||||
detail = e.getMessage();
|
||||
}
|
||||
if (detail != null) {
|
||||
throw error(handler.ref() + " " + paramRef(handler.method, i) + " is @Surround.Local " + type.getClassName() + ", which matches no single local at the call: " + detail);
|
||||
}
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
private void prepare(Type[] expected, Type result, String against) {
|
||||
if (this.exits == null) {
|
||||
this.analyze(result);
|
||||
}
|
||||
this.checkArgPrefix(this.entry, expected, against);
|
||||
for (Handler exit : this.exits.handlers) {
|
||||
if (exit.role == Role.SKIPPED || exit.role == Role.RETURN || exit.handles) {
|
||||
final Type declared = Type.getReturnType(exit.method.desc);
|
||||
if (!declared.equals(result)) {
|
||||
throw error(exit.ref() + " must return " + result.getClassName() + " to match what it surrounds, not " + declared.getClassName());
|
||||
}
|
||||
}
|
||||
if (exit.role == Role.RETURN && exit.leading == 1 && (exit.params.length == 0 || !exit.params[0].equals(result))) {
|
||||
throw error(exit.ref() + " must declare the " + result.getClassName() + " result as its first parameter");
|
||||
}
|
||||
this.checkArgPrefix(exit, expected, against);
|
||||
}
|
||||
if (this.exits.skipped == null && this.exits.skipSlot >= 0 && result.getSize() > 0) {
|
||||
throw error("can skip a " + result.getClassName() + " result but has no @Surround.Skipped" + this.forId() + " to supply it");
|
||||
}
|
||||
}
|
||||
|
||||
private void analyze(Type result) {
|
||||
if (!this.returnType.equals(Type.VOID_TYPE)) {
|
||||
throw error("must return void; carry state out with @Surround.Carry locals instead");
|
||||
}
|
||||
if (Bytecode.hasFlag(this.methodNode, Opcodes.ACC_ABSTRACT)) {
|
||||
throw error("must not be abstract");
|
||||
}
|
||||
final Handler entry = new Handler(this.methodNode, Role.ENTRY, 0);
|
||||
if (this.hasStrippedSugar()) {
|
||||
throw error("one of its parameters is annotated @Local, @Share or @Cancellable" + SurroundBinding.SUGAR_HINT);
|
||||
}
|
||||
this.checkNoLocal(entry);
|
||||
final AbstractInsnNode soleReturn = SurroundBinding.soleReturn(this.methodNode);
|
||||
|
||||
final Set<String> ids = new LinkedHashSet<>();
|
||||
for (MethodNode method : this.mixin.getMixin().getClassNode(0).methods) {
|
||||
final AnnotationNode surround = Annotations.get(method.visibleAnnotations, SurroundSpec.SURROUND);
|
||||
if (surround == null) {
|
||||
continue;
|
||||
}
|
||||
final String declared = Annotations.<String>getValue(surround, "id", "");
|
||||
if (!ids.add(declared) && declared.equals(this.id)) {
|
||||
throw error("duplicate @Surround " + describeId(this.id) + "; ids must be unique per mixin");
|
||||
}
|
||||
}
|
||||
final List<Handler> handlers = new ArrayList<>();
|
||||
for (MethodNode method : this.mixin.getClassNode().methods) {
|
||||
Role role = null;
|
||||
AnnotationNode annotation = null;
|
||||
for (Role candidate : Role.EXITS) {
|
||||
final AnnotationNode found = Annotations.get(method.visibleAnnotations, candidate.annotation);
|
||||
if (found == null) {
|
||||
continue;
|
||||
}
|
||||
if (role != null) {
|
||||
throw error(exitRef(method) + " can be only one of @Surround.Catch, @Surround.Finally, @Surround.Skipped and @Surround.Return");
|
||||
}
|
||||
role = candidate;
|
||||
annotation = found;
|
||||
}
|
||||
if (role == null) {
|
||||
continue;
|
||||
}
|
||||
final String exitId = Annotations.<String>getValue(annotation, "value", "");
|
||||
if (!ids.contains(exitId)) {
|
||||
throw error(exitRef(method) + " " + noSurroundForId(exitId, ids));
|
||||
}
|
||||
if (this.id.equals(exitId)) {
|
||||
handlers.add(this.exitHandler(this.own(method), role, role == Role.CATCH || role == Role.RETURN && result.getSize() > 0 ? 1 : 0));
|
||||
}
|
||||
}
|
||||
handlers.sort(Comparator.comparing(handler -> handler.role));
|
||||
boolean skipped = false;
|
||||
for (int i = 0, n = handlers.size(); i < n; i++) {
|
||||
final Role role = handlers.get(i).role;
|
||||
skipped |= role == Role.SKIPPED;
|
||||
if (i > 0 && role != Role.CATCH && role == handlers.get(i - 1).role) {
|
||||
throw error("declares more than one @Surround." + role.name().charAt(0) + role.name().substring(1).toLowerCase(Locale.ROOT) + this.forId() + "; there can only be one");
|
||||
}
|
||||
}
|
||||
final Handler[] sorted = handlers.toArray(new Handler[0]);
|
||||
this.sortMostSpecificFirst(sorted);
|
||||
final List<LocalVariableAnnotationNode> annotations = this.methodNode.visibleLocalVariableAnnotations;
|
||||
final int[] kinds = annotations == null || annotations.isEmpty() ? null : SurroundBinding.kindsAtEnd(this.methodNode, soleReturn);
|
||||
final int skipSlot = SurroundBinding.skipSlot(this.methodNode, kinds);
|
||||
if (skipped && skipSlot < 0) {
|
||||
throw error("has a @Surround.Skipped" + this.forId() + " but declares no @Surround.Skip local, so nothing is ever skipped");
|
||||
}
|
||||
final Type[] carried = SurroundBinding.bind(this.methodNode, kinds, sorted);
|
||||
this.entry = entry;
|
||||
this.exits = new Exits(sorted, skipSlot, carried);
|
||||
}
|
||||
|
||||
// MixinExtras strips sugar params before parse; the annotation table can then be null.
|
||||
private boolean hasStrippedSugar() {
|
||||
final List<AnnotationNode>[] invisible = this.methodNode.invisibleParameterAnnotations;
|
||||
if (invisible != null) {
|
||||
return invisible.length != this.methodArgs.length;
|
||||
}
|
||||
final ClassInfo mixin = this.mixin.getClassInfo();
|
||||
final String desc = this.methodNode.desc;
|
||||
return mixin.findMethod(MethodNodeEx.getName(this.methodNode), desc, ClassInfo.INCLUDE_ALL) == null && mixin.findMethod(this.methodNode.name, desc, ClassInfo.INCLUDE_ALL) == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sanityCheck(Target target, List<InjectionPoint> injectionPoints) {
|
||||
super.sanityCheck(target, injectionPoints);
|
||||
if (this.isInterface) {
|
||||
throw error("cannot target an interface method " + target);
|
||||
}
|
||||
if (target.method.name.endsWith("init>")) {
|
||||
throw error("cannot target the initializer " + target);
|
||||
}
|
||||
if (Bytecode.hasFlag(target.method, Opcodes.ACC_ABSTRACT) || Bytecode.hasFlag(target.method, Opcodes.ACC_NATIVE)) {
|
||||
throw error("cannot target the abstract or native method " + target);
|
||||
}
|
||||
}
|
||||
|
||||
private Handler exitHandler(MethodNode method, Role role, int leading) {
|
||||
final Handler exit = new Handler(method, role, leading);
|
||||
if (exit.handles && !this.callSite) {
|
||||
throw error(exit.ref() + " declares handle = true, which only applies when @Surround.at selects a call");
|
||||
}
|
||||
if (Bytecode.hasFlag(method, Opcodes.ACC_ABSTRACT)) {
|
||||
throw error(exit.ref() + " must not be abstract");
|
||||
}
|
||||
if ((role == Role.FINALLY || role == Role.CATCH && !exit.handles) && !Type.getReturnType(method.desc).equals(Type.VOID_TYPE)) {
|
||||
throw error(exit.ref() + " must return void; an exit handler cannot suppress or replace anything");
|
||||
}
|
||||
if (Bytecode.isStatic(method) != this.isStatic) {
|
||||
throw error(exit.ref() + " must be " + (this.isStatic ? "static" : "non-static") + " to match the entry handler" + this.forId());
|
||||
}
|
||||
if (role == Role.CATCH) {
|
||||
if (exit.params.length == 0) {
|
||||
throw error(exit.ref() + " must declare the Throwable it catches as its first parameter");
|
||||
}
|
||||
final Type caught = exit.caught();
|
||||
if (caught.getSort() != Type.OBJECT || !SurroundSpec.THROWABLE.equals(caught.getInternalName()) && !extendsClass(caught, SurroundSpec.THROWABLE)) {
|
||||
throw error(exit.ref() + " " + paramRef(method, 0) + " catches " + caught.getClassName() + ", which is not a java.lang.Throwable subtype");
|
||||
}
|
||||
}
|
||||
this.checkNoLocal(exit);
|
||||
return exit;
|
||||
}
|
||||
|
||||
private void checkNoLocal(Handler handler) {
|
||||
if (!this.callSite && handler.localStart < handler.carryStart) {
|
||||
throw error(handler.ref() + " " + paramRef(handler.method, handler.localStart) + " is @Surround.Local, which only applies when @Surround.at selects a call");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkArgPrefix(Handler handler, Type[] expected, String against) {
|
||||
final Type[] params = handler.params;
|
||||
if (handler.localStart - handler.leading > expected.length) {
|
||||
throw error(handler.ref() + " has more parameters than " + against);
|
||||
}
|
||||
for (int i = handler.leading, arg = 0; i < handler.localStart; i++, arg++) {
|
||||
if (params[i].equals(expected[arg]) || this.callSite && isAssignable(params[i], expected[arg])) {
|
||||
continue;
|
||||
}
|
||||
throw error(handler.ref() + " " + paramRef(handler.method, i) + " is " + params[i].getClassName() + ", which does not match the " + expected[arg].getClassName() + " operand of " + against);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAssignable(Type param, Type operand) {
|
||||
if (param.getSort() != Type.OBJECT || operand.getSort() != Type.OBJECT && operand.getSort() != Type.ARRAY) {
|
||||
return false;
|
||||
}
|
||||
if (SurroundSpec.OBJECT.equals(param.getInternalName())) {
|
||||
return true;
|
||||
}
|
||||
if (operand.getSort() != Type.OBJECT) {
|
||||
return false;
|
||||
}
|
||||
final ClassInfo info = ClassInfo.forName(operand.getInternalName());
|
||||
return info != null && info.findSuperClass(param.getInternalName(), Traversal.ALL, true) != null;
|
||||
}
|
||||
|
||||
private void sortMostSpecificFirst(Handler[] exits) {
|
||||
for (int k = 1; k < exits.length && exits[k].role == Role.CATCH; k++) {
|
||||
final Handler exit = exits[k];
|
||||
final Type caught = exit.caught();
|
||||
int at = k;
|
||||
for (int i = 0; i < k; i++) {
|
||||
final Type other = exits[i].caught();
|
||||
if (caught.equals(other)) {
|
||||
throw error(exit.ref() + " is a second @Surround.Catch for " + caught.getClassName() + this.forId() + "; each type may be caught once");
|
||||
}
|
||||
if (at == k && extendsClass(caught, other.getInternalName())) {
|
||||
at = i;
|
||||
}
|
||||
}
|
||||
System.arraycopy(exits, at, exits, at + 1, k - at);
|
||||
exits[at] = exit;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean extendsClass(Type type, String superName) {
|
||||
final ClassInfo info = ClassInfo.forName(type.getInternalName());
|
||||
return info != null && info.hasSuperClass(superName);
|
||||
}
|
||||
|
||||
private MethodNode own(MethodNode exit) {
|
||||
final MethodNode method = Bytecode.findMethod(this.classNode, exit.name, exit.desc);
|
||||
if (method == null) {
|
||||
throw new IllegalStateException(exit.name + exit.desc + " is missing from " + this.classNode.name);
|
||||
}
|
||||
if (!this.mixin.getClassName().equals(SurroundSpec.mergedBy(method))) {
|
||||
throw error(exitRef(exit) + " collides with " + exit.name + exit.desc + " in " + this.classNode.name + ", which is " + SurroundSpec.describeOwner(method));
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
private String forId() {
|
||||
return this.id.isEmpty() ? "" : " for id '" + this.id + "'";
|
||||
}
|
||||
|
||||
private static String describeId(String id) {
|
||||
return id.isEmpty() ? "the default id" : "id '" + id + "'";
|
||||
}
|
||||
|
||||
static String noSurroundForId(String id, Collection<String> declared) {
|
||||
final StringJoiner ids = new StringJoiner(", ", "; declared ids are ", "").setEmptyValue("; this mixin has no @Surround at all");
|
||||
for (String other : declared) {
|
||||
ids.add(describeId(other));
|
||||
}
|
||||
return "refers to " + describeId(id) + " but no @Surround in this mixin declares it" + ids;
|
||||
}
|
||||
|
||||
private InvalidInjectionException error(String message) {
|
||||
return new InvalidInjectionException(this.info, "@Surround " + this.info.getMethodName() + " " + message);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
final class SurroundRejection extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
SurroundRejection(String message) {
|
||||
super(message, null, false, false);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.AnnotationNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.transformer.meta.MixinMerged;
|
||||
import org.spongepowered.asm.util.Annotations;
|
||||
|
||||
interface SurroundSpec {
|
||||
|
||||
String SURROUND = Type.getDescriptor(Surround.class);
|
||||
String CATCH = Type.getDescriptor(Surround.Catch.class);
|
||||
String FINALLY = Type.getDescriptor(Surround.Finally.class);
|
||||
String CARRY = Type.getDescriptor(Surround.Carry.class);
|
||||
String SKIP = Type.getDescriptor(Surround.Skip.class);
|
||||
String SKIPPED = Type.getDescriptor(Surround.Skipped.class);
|
||||
String RETURN = Type.getDescriptor(Surround.Return.class);
|
||||
String LOCAL = Type.getDescriptor(Surround.Local.class);
|
||||
String MIXIN_MERGED = Type.getDescriptor(MixinMerged.class);
|
||||
String OBJECT = Type.getInternalName(Object.class);
|
||||
String THROWABLE = Type.getInternalName(Throwable.class);
|
||||
String THROWABLE_DESC = Type.getDescriptor(Throwable.class);
|
||||
|
||||
static String mergedBy(MethodNode method) {
|
||||
final AnnotationNode merged = Annotations.get(method.visibleAnnotations, MIXIN_MERGED);
|
||||
return merged == null ? null : Annotations.<String>getValue(merged, "mixin");
|
||||
}
|
||||
|
||||
static String describeOwner(MethodNode method) {
|
||||
final String owner = mergedBy(method);
|
||||
return owner == null ? "not merged from a mixin" : "merged from " + owner;
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Entity;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.ReportedException;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.lib.tree.TryCatchBlockNode;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.CALL;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.call;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.method;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.transformedNode;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CallSiteTest {
|
||||
|
||||
@BeforeEach
|
||||
void resetTrace() {
|
||||
Trace.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aRedirectedCallInsideJavacTryCatchesRethrowsInsideTheEnclosingTry() throws Exception {
|
||||
assertEquals(true, call(CALL, "render", new Entity("pig"), 1.0d, 2.0d, 3.0d, 0.5f, 0.25f, true));
|
||||
final Entity creeper = new Entity("creeper");
|
||||
creeper.explode = true;
|
||||
final InvocationTargetException thrown = assertThrows(InvocationTargetException.class, () -> call(CALL, "render", creeper, 1.0d, 2.0d, 3.0d, 0.5f, 0.25f, false));
|
||||
|
||||
final ReportedException outer = (ReportedException) thrown.getCause();
|
||||
assertEquals("Rendering entity (outer)", outer.getMessage());
|
||||
assertEquals("Rendering entity in world", outer.getCause().getMessage());
|
||||
assertSame(RuntimeException.class, outer.getCause().getCause().getClass());
|
||||
assertEquals("gpu", outer.getCause().getCause().getMessage());
|
||||
Trace.assertEvents("enterDoRender pig 1", "redirect pig", "doRender pig 1.0 0.25", "exitDoRender pig 1", "shadow pig", "boundingBox pig", "enterDoRender creeper 2", "redirect creeper", "doRender creeper 1.0 0.25", "caughtDoRender creeper gpu 2", "exitDoRender creeper 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theSurroundedCallMatchesTheGoldens() {
|
||||
MixinTestBootstrap.assertGolden(CALL, "valueSkip", "call-skip");
|
||||
MixinTestBootstrap.assertGolden(CALL, "valueBelow", "call-handle");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedSurroundsShareOneCallAndOrderTheirTryRangesInnerFirst() {
|
||||
final MethodNode target = method(transformedNode(CALL), "innerSkipOuterReturn");
|
||||
int calls = 0;
|
||||
for (AbstractInsnNode insn : target.instructions.toArray()) {
|
||||
calls += insn instanceof MethodInsnNode m && m.name.equals("twice") ? 1 : 0;
|
||||
}
|
||||
assertEquals(1, calls);
|
||||
final List<TryCatchBlockNode> blocks = target.tryCatchBlocks;
|
||||
boolean nests = false;
|
||||
for (int i = 0; i < blocks.size(); i++) {
|
||||
for (int j = 0; j < blocks.size(); j++) {
|
||||
final boolean encloses = encloses(target, blocks.get(i), blocks.get(j));
|
||||
nests |= encloses;
|
||||
assertTrue(!encloses || i > j, "block " + i + " encloses block " + j);
|
||||
}
|
||||
}
|
||||
assertTrue(nests, "no try block encloses another");
|
||||
}
|
||||
|
||||
private static boolean encloses(MethodNode target, TryCatchBlockNode outer, TryCatchBlockNode inner) {
|
||||
final int outerStart = target.instructions.indexOf(outer.start);
|
||||
final int outerEnd = target.instructions.indexOf(outer.end);
|
||||
final int innerStart = target.instructions.indexOf(inner.start);
|
||||
final int innerEnd = target.instructions.indexOf(inner.end);
|
||||
return outerStart <= innerStart && innerEnd <= outerEnd && (outerStart < innerStart || innerEnd < outerEnd);
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import com.llamalad7.mixinextras.utils.MixinInternals;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.lib.Type;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.InsnNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.mixin.transformer.ext.Extensions;
|
||||
import org.spongepowered.asm.mixin.transformer.ext.IExtension;
|
||||
import org.spongepowered.asm.mixin.transformer.meta.MixinMerged;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.WRAP;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.of;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.scenario;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CompositionTest {
|
||||
|
||||
private static final Set<String> RUN_BEFORE = Set.of("LateInjectionApplicatorExtension", "SugarPostProcessingExtension", "WrapMethodApplicatorExtension");
|
||||
private static final String CHECK_CLASS = "ExtensionCheckClass";
|
||||
|
||||
@BeforeEach
|
||||
void resetTrace() {
|
||||
Trace.reset();
|
||||
}
|
||||
|
||||
private static Object wrap(String name, Object... args) throws Exception {
|
||||
return MixinTestBootstrap.call(WRAP, name, args);
|
||||
}
|
||||
|
||||
static Stream<Arguments> mixinExtras() {
|
||||
return Stream.of(
|
||||
scenario(WRAP, "the surround is outside @WrapMethod", "wrapped", of(3), of(6), "entry 3", "wrap-before 3", "body 3", "wrap-after 6", "finally"),
|
||||
scenario(WRAP, "the entry sees the operand before @WrapOperation shifts it", "originalOperands", of(5), of(210), "enter 5", "wrap 5", "twice 105", "exit 5"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("mixinExtras")
|
||||
@Tag("mixinextras")
|
||||
void mixinExtras(String method, String target, Object[] inputs, Object[] outputs, String[] events) throws Exception {
|
||||
MixinTestBootstrap.assertScenario(target, method, inputs, outputs, events);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("mixinextras")
|
||||
void aLocalFollowsTheBodyWhenAShareShiftsItsSlot() throws Exception {
|
||||
assertEquals(10, wrap("shifted", 3));
|
||||
Trace.assertEvents("wrap 3", "enterShifted 4 4 L4", "twice 4", "exitShifted 4 L4");
|
||||
MethodNode body = null;
|
||||
for (MethodNode method : MixinTestBootstrap.transformedNode(WRAP).methods) {
|
||||
body = method.name.startsWith("shifted$") ? method : body;
|
||||
}
|
||||
assertTrue(body != null && body.desc.startsWith("(IL"), "the body did not move and gain the share");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("mixinextras")
|
||||
void ourExtensionRunsAfterMixinExtrasAndBeforeCheckClass() throws Exception {
|
||||
MixinTestBootstrap.transformer();
|
||||
final SurroundApplicatorExtension ours = SurroundApplicatorExtension.instance();
|
||||
MixinTestBootstrap.transform(WRAP);
|
||||
final Extensions extensions = MixinInternals.getExtensions();
|
||||
for (List<IExtension> list : List.of(extensions.getExtensions(), extensions.getActiveExtensions())) {
|
||||
final int at = assertSlot(list, ours);
|
||||
for (String name : RUN_BEFORE) {
|
||||
final int index = names(list).indexOf(name);
|
||||
assertTrue(index >= 0 && index < at, names(list).toString());
|
||||
assertTrue(list.get(index).getClass().getName().startsWith("com.llamalad7.mixinextras."));
|
||||
}
|
||||
}
|
||||
assertCheckClassActiveUnderVerify(extensions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUnrenamedSurroundHandlerIsFatal() {
|
||||
final String mixin = "com.example.mixin.MixinLate";
|
||||
final MethodNode method = new MethodNode(Opcodes.ACC_PRIVATE, "surround$enter", "()V", null, null);
|
||||
method.instructions.add(new InsnNode(Opcodes.RETURN));
|
||||
method.visitAnnotation(Type.getDescriptor(Surround.class), true).visitEnd();
|
||||
method.visitAnnotation(Type.getDescriptor(MixinMerged.class), true).visit("mixin", mixin);
|
||||
final ClassNode target = new ClassNode();
|
||||
target.version = Opcodes.V1_8;
|
||||
target.access = Opcodes.ACC_PUBLIC;
|
||||
target.name = "com/example/Target";
|
||||
target.superName = "java/lang/Object";
|
||||
target.methods.add(method);
|
||||
|
||||
final String message = assertThrows(IllegalStateException.class, () -> SurroundApplicatorExtension.checkHandlersRenamed(target, Set.of())).getMessage();
|
||||
assertTrue(message.startsWith("surround$enter()V in com/example/Target (merged from " + mixin + ") carries @Surround but was never parsed as an injector"), message);
|
||||
assertTrue(message.contains("SurroundBootstrap.init() must run before"), message);
|
||||
}
|
||||
|
||||
private static void assertCheckClassActiveUnderVerify(Extensions extensions) {
|
||||
final List<String> active = names(extensions.getActiveExtensions());
|
||||
assertEquals(Boolean.getBoolean("mixin.debug.verify"), active.contains(CHECK_CLASS), active.toString());
|
||||
}
|
||||
|
||||
private static List<String> names(List<IExtension> list) {
|
||||
final List<String> names = new ArrayList<>(list.size());
|
||||
for (IExtension extension : list) {
|
||||
names.add(extension.getClass().getSimpleName());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private static int assertSlot(List<IExtension> list, IExtension ours) {
|
||||
final List<String> names = names(list);
|
||||
assertEquals(list.indexOf(ours), list.lastIndexOf(ours), "registered twice: " + names);
|
||||
final int at = list.indexOf(ours);
|
||||
assertTrue(at >= 0, "missing: " + names);
|
||||
for (String after : names.subList(at + 1, names.size())) {
|
||||
assertFalse(RUN_BEFORE.contains(after), after + " runs after us: " + names);
|
||||
}
|
||||
final int check = names.indexOf(CHECK_CLASS);
|
||||
assertTrue(check < 0 || check > at, CHECK_CLASS + " runs before us: " + names);
|
||||
return at;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import makamys.mixingasm.api.MixinSafeTransformer;
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.commons.LocalVariablesSorter;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@MixinSafeTransformer
|
||||
public final class LocalSortingTransformer implements IClassTransformer {
|
||||
|
||||
public static final Set<String> SEEN = ConcurrentHashMap.newKeySet();
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] bytes) {
|
||||
SEEN.add(transformedName);
|
||||
if (bytes == null || ((bytes[6] & 0xFF) << 8 | bytes[7] & 0xFF) > Opcodes.V1_8) {
|
||||
return bytes;
|
||||
}
|
||||
final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
new ClassReader(bytes).accept(new ClassVisitor(Opcodes.ASM5, writer) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
return new LocalVariablesSorter(Opcodes.ASM5, access, desc, super.visitMethod(access, name, desc, signature, exceptions)) {
|
||||
};
|
||||
}
|
||||
}, ClassReader.EXPAND_FRAMES);
|
||||
return writer.toByteArray();
|
||||
}
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import com.llamalad7.mixinextras.MixinExtrasBootstrap;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import net.minecraft.launchwrapper.LaunchClassLoader;
|
||||
import org.junit.jupiter.api.Named;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.spongepowered.asm.launch.MixinBootstrap;
|
||||
import org.spongepowered.asm.lib.ClassReader;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
import org.spongepowered.asm.lib.util.Textifier;
|
||||
import org.spongepowered.asm.lib.util.TraceClassVisitor;
|
||||
import org.spongepowered.asm.mixin.EnvironmentStateTweaker;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.Mixins;
|
||||
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public final class MixinTestBootstrap {
|
||||
|
||||
private MixinTestBootstrap() {
|
||||
}
|
||||
|
||||
private static final class Holder {
|
||||
|
||||
static final IMixinTransformer TRANSFORMER = boot();
|
||||
}
|
||||
|
||||
public static IMixinTransformer transformer() {
|
||||
return Holder.TRANSFORMER;
|
||||
}
|
||||
|
||||
private static final Map<String, byte[]> TRANSFORMED = new HashMap<>();
|
||||
private static final Map<String, Class<?>> DEFINED = new HashMap<>();
|
||||
|
||||
public static byte[] transformedBytes(String binaryName) {
|
||||
return TRANSFORMED.computeIfAbsent(binaryName, name -> transformer().transformClassBytes(name, name, read(name)));
|
||||
}
|
||||
|
||||
public static Class<?> transform(String binaryName) {
|
||||
return DEFINED.computeIfAbsent(binaryName, name -> define(name, transformedBytes(name)));
|
||||
}
|
||||
|
||||
public static Object call(String target, String name, Object... args) throws Exception {
|
||||
final Class<?> owner = transform(target);
|
||||
for (Method method : owner.getMethods()) {
|
||||
if (method.getName().equals(name) && method.getParameterCount() == args.length) {
|
||||
final boolean isStatic = Modifier.isStatic(method.getModifiers());
|
||||
return method.invoke(isStatic ? null : owner.getDeclaredConstructor().newInstance(), args);
|
||||
}
|
||||
}
|
||||
throw new AssertionError("no " + name + " in " + target);
|
||||
}
|
||||
|
||||
static final String WHOLE = "com.gtnewhorizons.angelica.experimental.surround.integration.WholeTarget";
|
||||
static final String CALL = "com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget";
|
||||
static final String WRAP = "com.gtnewhorizons.angelica.experimental.surround.integration.WrapTarget";
|
||||
|
||||
static ClassNode transformedNode(String target) {
|
||||
return parse(transformedBytes(target));
|
||||
}
|
||||
|
||||
static ClassNode originalNode(String binaryName) {
|
||||
return parse(read(binaryName));
|
||||
}
|
||||
|
||||
private static ClassNode parse(byte[] bytes) {
|
||||
final ClassNode node = new ClassNode();
|
||||
new ClassReader(bytes).accept(node, 0);
|
||||
return node;
|
||||
}
|
||||
|
||||
static MethodNode method(ClassNode node, String name) {
|
||||
for (MethodNode method : node.methods) {
|
||||
if (method.name.equals(name)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("no method " + name + " in " + node.name);
|
||||
}
|
||||
|
||||
static void assertGolden(String target, String method, String golden) {
|
||||
final ClassNode full = transformedNode(target);
|
||||
final ClassNode slim = new ClassNode();
|
||||
slim.visit(full.version, full.access, full.name, null, full.superName, null);
|
||||
slim.methods.add(method(full, method));
|
||||
for (MethodNode candidate : full.methods) {
|
||||
if (candidate.name.startsWith(method + "$surround")) {
|
||||
slim.methods.add(candidate);
|
||||
}
|
||||
}
|
||||
final StringWriter out = new StringWriter();
|
||||
slim.accept(new TraceClassVisitor(null, new Textifier(), new PrintWriter(out)));
|
||||
final String stripped = out.toString().replaceAll("(?m)^ *LINENUMBER \\d+ L\\d+\\r?\\n", "");
|
||||
final String actual = normalize(stripped.replaceAll("\\b([a-z]+)\\$[a-z]{3,}[0-9a-f]{3,}\\$", "$1\\$\\$").replaceAll("\\bmd[0-9a-f]+\\$jvmdowngrader\\$", "md\\$jvmdowngrader\\$"));
|
||||
final Path file = Paths.get(System.getProperty("surround.golden.dir")).resolve(golden + ".asm");
|
||||
try {
|
||||
if (!Files.exists(file)) {
|
||||
Files.write(file, actual.getBytes(StandardCharsets.UTF_8));
|
||||
fail("Created golden " + file + ". Inspect it, then re-run.");
|
||||
}
|
||||
assertEquals(normalize(new String(Files.readAllBytes(file), StandardCharsets.UTF_8)), actual, "output changed; inspect the diff before updating " + file);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalize(String text) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
for (String line : text.replace("\r\n", "\n").split("\n", -1)) {
|
||||
result.append(line.replaceAll("\\s+$", "").replaceAll("(\\$jvmdowngrader\\$concat\\$[\\w$]*?\\$\\d+)\\$[0-9a-f]+\\b", "$1")).append('\n');
|
||||
}
|
||||
return result.toString().trim() + "\n";
|
||||
}
|
||||
|
||||
static final Object VOID = null;
|
||||
|
||||
static Arguments scenario(String target, String name, String method, Object[] inputs, Object[] outputs, String... events) {
|
||||
return Arguments.of(Named.of(name, method), target, inputs, outputs, events);
|
||||
}
|
||||
|
||||
static Object[] of(Object... values) {
|
||||
return values;
|
||||
}
|
||||
|
||||
static List<Object> args(Object... values) {
|
||||
return Arrays.asList(values);
|
||||
}
|
||||
|
||||
static void assertScenario(String target, String method, Object[] inputs, Object[] outputs, String[] events) throws Exception {
|
||||
Trace.reset();
|
||||
for (int i = 0; i < inputs.length; i++) {
|
||||
final Object[] args = inputs[i] instanceof List<?> list ? list.toArray() : new Object[]{inputs[i]};
|
||||
if (outputs[i] instanceof Class<?> type && Throwable.class.isAssignableFrom(type)) {
|
||||
final InvocationTargetException thrown = assertThrows(InvocationTargetException.class, () -> call(target, method, args));
|
||||
assertEquals(outputs[i], thrown.getCause().getClass());
|
||||
} else {
|
||||
assertEquals(outputs[i], call(target, method, args));
|
||||
}
|
||||
}
|
||||
Trace.assertEvents(events);
|
||||
}
|
||||
|
||||
private static final boolean MIXINEXTRAS = Boolean.getBoolean("surround.test.mixinextras");
|
||||
private static final boolean ADDITIONS_FIRST = Boolean.getBoolean("surround.test.additionsFirst");
|
||||
private static final boolean OBF = Boolean.getBoolean("surround.test.obf");
|
||||
|
||||
private static IMixinTransformer boot() {
|
||||
Launch.blackboard = new HashMap<>();
|
||||
Launch.classLoader = new LaunchClassLoader(classpath());
|
||||
if (OBF) {
|
||||
Launch.classLoader.addTransformerExclusion(LocalSortingTransformer.class.getName());
|
||||
Launch.classLoader.registerTransformer(LocalSortingTransformer.class.getName());
|
||||
if (Launch.classLoader.getTransformers().stream().noneMatch(t -> t.getClass().getName().equals(LocalSortingTransformer.class.getName()))) {
|
||||
throw new IllegalStateException("LocalSortingTransformer was not registered");
|
||||
}
|
||||
}
|
||||
|
||||
if (!MIXINEXTRAS) {
|
||||
SurroundBootstrap.init();
|
||||
}
|
||||
MixinBootstrap.init();
|
||||
if (MIXINEXTRAS) {
|
||||
if (ADDITIONS_FIRST) {
|
||||
SurroundBootstrap.init();
|
||||
}
|
||||
MixinExtrasBootstrap.init();
|
||||
if (!ADDITIONS_FIRST) {
|
||||
SurroundBootstrap.init();
|
||||
}
|
||||
}
|
||||
Mixins.addConfiguration("mixins.surround.test.json");
|
||||
if (!OBF) {
|
||||
Mixins.addConfiguration(RejectionCases.compileConfig(MIXINEXTRAS));
|
||||
}
|
||||
if (MIXINEXTRAS) {
|
||||
Mixins.addConfiguration("mixins.surround.mixinextras.test.json");
|
||||
}
|
||||
|
||||
final EnvironmentStateTweaker tweaker = new EnvironmentStateTweaker();
|
||||
tweaker.injectIntoClassLoader(Launch.classLoader);
|
||||
tweaker.getLaunchArguments();
|
||||
|
||||
return active();
|
||||
}
|
||||
|
||||
private static IMixinTransformer active() {
|
||||
final Object transformer = MixinEnvironment.getCurrentEnvironment().getActiveTransformer();
|
||||
if (!(transformer instanceof IMixinTransformer mixinTransformer)) {
|
||||
throw new IllegalStateException("no active mixin transformer, got " + transformer);
|
||||
}
|
||||
return mixinTransformer;
|
||||
}
|
||||
|
||||
private static URL[] classpath() {
|
||||
final List<URL> urls = new ArrayList<>();
|
||||
for (String entry : System.getProperty("java.class.path").split(File.pathSeparator)) {
|
||||
try {
|
||||
urls.add(new File(entry).toURI().toURL());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalStateException(entry, e);
|
||||
}
|
||||
}
|
||||
return urls.toArray(new URL[urls.size()]);
|
||||
}
|
||||
|
||||
static byte[] read(String binaryName) {
|
||||
try {
|
||||
final byte[] bytes = Launch.classLoader.getClassBytes(binaryName);
|
||||
if (bytes == null) {
|
||||
throw new IllegalStateException("missing " + binaryName);
|
||||
}
|
||||
return bytes;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(binaryName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> define(String name, byte[] bytes) {
|
||||
try {
|
||||
return new DefiningClassLoader(name, bytes).loadClass(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DefiningClassLoader extends ClassLoader {
|
||||
|
||||
private final String name;
|
||||
private final byte[] bytes;
|
||||
|
||||
DefiningClassLoader(String name, byte[] bytes) {
|
||||
super(MixinTestBootstrap.class.getClassLoader());
|
||||
this.name = name;
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
if (!this.name.equals(name)) {
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
Class<?> loaded = findLoadedClass(name);
|
||||
if (loaded == null) {
|
||||
loaded = defineClass(name, this.bytes, 0, this.bytes.length);
|
||||
}
|
||||
if (resolve) {
|
||||
resolveClass(loaded);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
final class RejectionCases {
|
||||
|
||||
static final String PACKAGE = "rejection";
|
||||
static final String CONFIG = "mixins.surround.rejection.test.json";
|
||||
static final String ABS = "at = @At(value = \"INVOKE\", target = \"Ljava/lang/Math;abs(I)I\")";
|
||||
static final String WHOLE_ENTRY = "@Surround(method = \"run\") private void invalid$enter(int a) {}";
|
||||
static final String FINALLY = " @Surround.Finally private void invalid$always() {}";
|
||||
static final String RUN = cls("public int run(int a) { return Math.abs(a); }");
|
||||
static final String IN_NEW = cls("public int run(int a) { return new StringBuilder(Math.abs(a)).length(); }");
|
||||
|
||||
static final Case[] CASES = {
|
||||
new Case("Initializer", "@Surround invalid$enter cannot target the initializer", RUN, mixin("@Surround(method = \"<init>()V\") private void invalid$enter() {}")),
|
||||
new Case("Staticness", "must be non-static to match the entry handler", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally private static void invalid$always() {}")),
|
||||
new Case("CarriedThrowable", "cannot mark its caught Throwable parameter @Surround.Carry", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " @Surround.Carry Throwable state = null; }" + " @Surround.Catch private void invalid$caught(@Surround.Carry Throwable e) {}")),
|
||||
new Case("LocalCall", "is @Surround.Local java.lang.String, which matches no single local at the call", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a, @Surround.Local String s) {}")),
|
||||
new Case("HandleInsideNew", "declares handle = true, but the call is inside a constructor call's arguments", IN_NEW, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {}" + " @Surround.Catch(handle = true) private int invalid$caught(Throwable error) { return 0; }")),
|
||||
new Case("SkippedRequired", "can skip a int result but has no @Surround.Skipped", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {" + " @Surround.Skip boolean skip = a > 0; }")),
|
||||
new Case("Shift", "@Surround invalid$enter has @At(\"INVOKE\") with a non-default shift or by, which moves the match off the call", RUN, mixin("@Surround(method = \"run\", at = @At(value = \"INVOKE\", target = \"Ljava/lang/Math;abs(I)I\", shift = At.Shift.AFTER)) private void invalid$enter() {}")),
|
||||
new Case("AbstractTarget", "cannot target the abstract or native method", "public abstract class $T { public abstract int run(int a); }", "@Mixin(value = $T.class, remap = false) public abstract class $M {" + " @Surround(method = \"run\") private void invalid$enter(int a) {} }"),
|
||||
new Case("InitAt", "at must select a method call, not the constructor call java/lang/StringBuilder.<init>()V", cls("public Object run(int a) { return new StringBuilder(); }"), mixin("@Surround(method = \"run\", at = @At(value = \"INVOKE\", target = \"Ljava/lang/StringBuilder;<init>()V\")) private void invalid$enter() {}")),
|
||||
new Case("CatchingEntry", "must not catch exceptions when it surrounds a call inside a constructor call's arguments", IN_NEW, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {" + " try { Math.abs(a); } catch (RuntimeException e) { Math.abs(0); } }" + FINALLY)),
|
||||
new Case("TooManyOperands", "entry handler has more parameters than call java/lang/Math.abs(I)I", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a, int b) {}")),
|
||||
new Case("SkipNotAssigned", "has a @Surround.Skip local in slot 2 that is not definitely assigned", cls("public void run(int a) {}"), mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " @Surround.Skip boolean skip; if (a > 0) { skip = true; String.valueOf(a); } }")),
|
||||
new Case("Collision", "exit handler 'surround$exit' collides with surround$exit()V in " + PACKAGE + "/Collision, which is merged from " + PACKAGE + ".mixin.CollisionMixin2", RUN, "@Mixin(value = $T.class, priority = 900, remap = false) public class $M {" + " @Surround(method = \"run\") private void surround$enter(int a) {}" + " @Surround.Finally private void surround$exit() {} }", "@Mixin(value = $T.class, priority = 1100, remap = false) public class $M {" + " private void surround$exit() {} }"),
|
||||
new Case("MultipleRoles", "can be only one of @Surround.Catch, @Surround.Finally, @Surround.Skipped and @Surround.Return", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch @Surround.Finally private void invalid$exit(Throwable e) {}")),
|
||||
new Case("DuplicateId", "duplicate @Surround the default id; ids must be unique per mixin", RUN, mixin(WHOLE_ENTRY + " @Surround(method = \"run\") private void invalid$enter2(int a) {}")),
|
||||
new Case("OrphanExit", "no @Surround in this mixin declares it", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally(\"typo\") private void invalid$always() {}")),
|
||||
new Case("CarryOnEntryParameter", "only the entry handler's own locals can be carried", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(@Surround.Carry int a) {}")),
|
||||
new Case("CarryIdNotDeclared", "asks for the carried local 'frist'", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry(\"first\") int first = 1; }" + " @Surround.Finally private void invalid$always(@Surround.Carry(\"frist\") int typo) {}")),
|
||||
new Case("NoCarryOfKind", "matches no carried local of that kind", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry int count = 1; }" + " @Surround.Finally private void invalid$always(@Surround.Carry float missing) {}")),
|
||||
new Case("AmbiguousCarry", "matches 2 carried locals of that kind", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry int first = 1; @Surround.Carry int second = 2; }" + " @Surround.Finally private void invalid$always(@Surround.Carry int either) {}")),
|
||||
new Case("CarryReadAsDifferentType", "but another exit handler reads it as java.lang.String", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry Object value = null; }" + " @Surround.Catch private void invalid$caught(java.io.IOException e, @Surround.Carry String value) {}" + " @Surround.Finally private void invalid$always(@Surround.Carry Integer value) {}")),
|
||||
new Case("AmbiguousWideLocal", "shares its scope with another", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry long first; long second; try { first = a; second = a + 1L; } catch (RuntimeException e) { throw e; } }")),
|
||||
new Case("DuplicateCarryId", "declares two @Surround.Carry locals named 'x'", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Carry(\"x\") int first = 1; @Surround.Carry(\"x\") long second = 2L; }")),
|
||||
new Case("CarryInsideType", "@Surround.Carry must annotate the local itself, not part of its type", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { java.util.List<@Surround.Carry String> l = null; }")),
|
||||
new Case("DuplicateSkip", "declares two @Surround.Skip locals", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Skip boolean first = a > 0; @Surround.Skip boolean second = a < 0; }")),
|
||||
new Case("SkipNotIntLike", "it must be boolean or int-like", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { @Surround.Skip long skip = a; }")),
|
||||
new Case("SkippedWithoutSkip", "but declares no @Surround.Skip local, so nothing is ever skipped", RUN, mixin(WHOLE_ENTRY + " @Surround.Skipped private int invalid$skipped(int a) { return 0; }")),
|
||||
new Case("CarryScopedEarly", "goes out of scope before the entry handler ends", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " { @Surround.Carry int scoped = a; String.valueOf(scoped); } int reused = a + 1; String.valueOf(reused); }" + " @Surround.Finally private void invalid$always(@Surround.Carry int value) {}")),
|
||||
new Case("EarlyReturn", "every path falls through to its end", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " if (a > 0) { return; } String.valueOf(a); }" + FINALLY)),
|
||||
new Case("PartialAfterInt", "is not definitely assigned when the entry handler ends", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) {" + " { int tmp = a + 5; String.valueOf(tmp); }" + " @Surround.Carry int flag; if (a > 0) { flag = 1; String.valueOf(flag); } }" + " @Surround.Finally private void invalid$always(@Surround.Carry int flag) {}")),
|
||||
new Case("AssignsParameter", "must not assign its parameters", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { a = a + 1; }" + FINALLY)),
|
||||
new Case("LocalOnWholeMethod", "is @Surround.Local, which only applies when @Surround.at selects a call", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally private void invalid$always(@Surround.Local int b) {}")),
|
||||
new Case("IncrementsParameter", "must not assign its parameters", RUN, mixin("@Surround(method = \"run\") private void invalid$enter(int a) { a++; }" + FINALLY)),
|
||||
new Case("NonCallAt", "@Surround invalid$enter has @At(\"HEAD\"), which does not select a method call", RUN, mixin("@Surround(method = \"run\", at = @At(\"HEAD\")) private void invalid$enter() {}")),
|
||||
new Case("CancellableExit", "parameter 0 ('ci') of 'invalid$always' is annotated @Cancellable, which a @Surround handler cannot resolve", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally private void invalid$always(@Cancellable Object ci) {}")),
|
||||
new Case("DuplicateFinally", "declares more than one @Surround.Finally", RUN, mixin(WHOLE_ENTRY + FINALLY + " @Surround.Finally private void invalid$always2() {}")),
|
||||
new Case("ReturnWrongType", "must return java.lang.String to match what it surrounds, not int", cls("public String run(String s) { return s.trim(); }"), mixin("@Surround(method = \"run\", at = @At(value = \"INVOKE\", target = \"Ljava/lang/String;trim()Ljava/lang/String;\")) private void invalid$enter(String s) {}" + " @Surround.Return private int invalid$ret(int result) { return result; }")),
|
||||
new Case("ReturnResultNotFirst", "must declare the int result as its first parameter", RUN, mixin(WHOLE_ENTRY + " @Surround.Return private int invalid$ret(String wrong) { return 0; }")),
|
||||
new Case("ReturnCarriedResult", "cannot mark its result parameter @Surround.Carry", RUN, mixin(WHOLE_ENTRY + " @Surround.Return private int invalid$ret(@Surround.Carry int result) { return result; }")),
|
||||
new Case("NonVoidFinally", "must return void; an exit handler cannot suppress or replace anything", RUN, mixin(WHOLE_ENTRY + " @Surround.Finally private int invalid$always() { return 1; }")),
|
||||
new Case("NonVoidEntry", "must return void; carry state out with @Surround.Carry locals instead", RUN, mixin("@Surround(method = \"run\") private long invalid$enter(int a) { return 0L; }")),
|
||||
new Case("CatchNotThrowable", "parameter 0 ('notAnError') catches java.lang.String, which is not a java.lang.Throwable subtype", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch private void invalid$caught(String notAnError) {}")),
|
||||
new Case("DuplicateCatch", "is a second @Surround.Catch for java.io.IOException", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch private void invalid$first(java.io.IOException e) {} @Surround.Catch private void invalid$second(java.io.IOException e) {}")),
|
||||
new Case("CatchWithoutParameters", "must declare the Throwable it catches as its first parameter", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch private void invalid$caught() {}")),
|
||||
new Case("AbstractEntry", "must not be abstract", RUN, "@Mixin(value = $T.class, remap = false) public abstract class $M { @Surround(method = \"run\") protected abstract void invalid$enter(int a); }"),
|
||||
new Case("AbstractCatch", "must not be abstract", RUN, "@Mixin(value = $T.class, remap = false) public abstract class $M { " + WHOLE_ENTRY + " @Surround.Catch protected abstract void invalid$caught(Throwable t); }"),
|
||||
new Case("MisorderedLocalAfterCarry", "parameter 1 ('y') is out of order; declare operands first, then @Surround.Local, then @Surround.Carry parameters", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {}" + " @Surround.Finally private void invalid$always(@Surround.Carry int x, @Surround.Local int y) {}")),
|
||||
new Case("LocalAndCarry", "parameter 0 ('a') cannot be both @Surround.Local and @Surround.Carry", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {}" + " @Surround.Finally private void invalid$always(@Surround.Local @Surround.Carry int a) {}")),
|
||||
new Case("LocalCaught", "cannot mark its caught Throwable parameter @Surround.Local", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int a) {}" + " @Surround.Catch private void invalid$caught(@Surround.Local Throwable e) {}")),
|
||||
new Case("HandleWrongReturn", "must return java.lang.String to match what it surrounds, not int", cls("public String run(String s) { return s.trim(); }"), mixin("@Surround(method = \"run\", at = @At(value = \"INVOKE\", target = \"Ljava/lang/String;trim()Ljava/lang/String;\")) private void invalid$enter(String s) {}" + " @Surround.Catch(handle = true) private int invalid$caught(Throwable e, String s) { return 0; }")),
|
||||
new Case("HandleWithoutAt", "declares handle = true, which only applies when @Surround.at selects a call", RUN, mixin(WHOLE_ENTRY + " @Surround.Catch(handle = true) private void invalid$caught(Throwable e) {}")),
|
||||
new Case("SliceWithoutAt", "declares @Surround.slice, which only applies when @Surround.at selects a call", RUN, mixin("@Surround(method = \"run\", slice = @Slice(from = @At(\"HEAD\"))) private void invalid$enter(int a) {}")),
|
||||
new Case("EntryOperandMismatch", "parameter 0 ('notAnInt') is java.lang.String, which does not match the int operand of call java/lang/Math.abs(I)I", RUN, mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(String notAnInt) {}")),
|
||||
new Case("OrphanWithoutSurround", "@Surround exit handler 'invalid$always' refers to the default id but no @Surround in this mixin declares it; this mixin has no @Surround at all", RUN, mixin(WHOLE_ENTRY), mixin("@Surround.Finally private void invalid$always() {}"))
|
||||
};
|
||||
|
||||
static final Case SUGAR = new Case("Sugar", "@Surround invalid$enter one of its parameters is annotated @Local, @Share or @Cancellable, which a @Surround handler cannot resolve", cls("public int run(int a) { int local = a + 1; return Math.abs(local); }"), mixin("@Surround(method = \"run\", " + ABS + ") private void invalid$enter(int local, @Local(ordinal = 0) int a) {}"));
|
||||
|
||||
static final Case REQUIRE_ZERO_MISS = new Case("RequireZeroMiss", "", RUN, mixin("@Surround(method = \"run\", require = 0, at = @At(value = \"INVOKE\", target = \"Ljava/lang/Math;max(II)I\")) private void invalid$enter(int a, int b) {}" + FINALLY));
|
||||
|
||||
static String compileConfig(boolean mixinExtras) {
|
||||
final List<Case> cases = new ArrayList<>();
|
||||
if (mixinExtras) {
|
||||
cases.add(SUGAR);
|
||||
} else {
|
||||
cases.addAll(Arrays.asList(CASES));
|
||||
cases.add(REQUIRE_ZERO_MISS);
|
||||
}
|
||||
final List<Unit> units = new ArrayList<>();
|
||||
final List<String> mixins = new ArrayList<>();
|
||||
for (Case c : cases) {
|
||||
units.add(new Unit(PACKAGE + "." + c.name, "package " + PACKAGE + "; " + c.target.replace("$T", c.name)));
|
||||
for (int i = 0; i < c.mixins.length; i++) {
|
||||
final String name = c.name + "Mixin" + (i + 1);
|
||||
mixins.add('"' + name + '"');
|
||||
units.add(new Unit(PACKAGE + ".mixin." + name, "package " + PACKAGE + ".mixin; import " + PACKAGE + ".*;" + " import com.gtnewhorizons.angelica.experimental.surround.Surround;" + " import com.llamalad7.mixinextras.sugar.Cancellable;" + " import com.llamalad7.mixinextras.sugar.Local;" + " import org.spongepowered.asm.mixin.Mixin;" + " import org.spongepowered.asm.mixin.injection.At;" + " import org.spongepowered.asm.mixin.injection.Slice;" + c.mixins[i].replace("$T", c.name).replace("$M", name)));
|
||||
}
|
||||
}
|
||||
try {
|
||||
final Path dir = Paths.get("rejection-classes").toAbsolutePath();
|
||||
if (Files.exists(dir)) {
|
||||
try (Stream<Path> stale = Files.walk(dir)) {
|
||||
stale.sorted(Comparator.reverseOrder()).forEach(path -> path.toFile().delete());
|
||||
}
|
||||
}
|
||||
final Path sources = Files.createDirectories(dir.resolve("src"));
|
||||
final List<String> command = new ArrayList<>(Arrays.asList(System.getProperty("surround.javac"), "-classpath", System.getProperty("java.class.path"), "-d", dir.toString(), "-proc:none", "-g", "--release", "8", "-Xlint:-options"));
|
||||
for (Unit unit : units) {
|
||||
final Path file = sources.resolve(unit.className().replace('.', '/') + ".java");
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.write(file, unit.source().getBytes(StandardCharsets.UTF_8));
|
||||
command.add(file.toString());
|
||||
}
|
||||
final Process javac = new ProcessBuilder(command).redirectErrorStream(true).start();
|
||||
final String output = new String(javac.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
if (javac.waitFor() != 0) {
|
||||
throw new IllegalStateException(output);
|
||||
}
|
||||
Files.write(dir.resolve(CONFIG), ("{\"required\": true, \"minVersion\": \"0.8\", \"package\": \"" + PACKAGE + ".mixin\"," + " \"compatibilityLevel\": \"JAVA_8\", \"mixins\": " + mixins + "}").getBytes(StandardCharsets.UTF_8));
|
||||
Launch.classLoader.addURL(dir.toUri().toURL());
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return CONFIG;
|
||||
}
|
||||
|
||||
private static String cls(String members) {
|
||||
return "public class $T { " + members + " }";
|
||||
}
|
||||
|
||||
private static String mixin(String members) {
|
||||
return "@Mixin(value = $T.class, remap = false) public class $M { " + members + " }";
|
||||
}
|
||||
|
||||
private record Unit(String className, String source) {
|
||||
}
|
||||
|
||||
static final class Case {
|
||||
|
||||
final String name;
|
||||
final String expected;
|
||||
final String target;
|
||||
final String[] mixins;
|
||||
|
||||
Case(String name, String expected, String target, String... mixins) {
|
||||
this.name = name;
|
||||
this.expected = expected;
|
||||
this.target = target;
|
||||
this.mixins = mixins;
|
||||
}
|
||||
|
||||
void assertRejected() {
|
||||
final String messages = causeChain(transformExpectingFailure(PACKAGE + "." + this.name));
|
||||
assertTrue(messages.contains(this.expected), messages);
|
||||
}
|
||||
|
||||
private static Throwable transformExpectingFailure(String binaryName) {
|
||||
try {
|
||||
MixinTestBootstrap.transformer().transformClassBytes(binaryName, binaryName, MixinTestBootstrap.read(binaryName));
|
||||
} catch (Throwable t) {
|
||||
return t;
|
||||
}
|
||||
throw new AssertionError(binaryName + " transformed without the expected failure");
|
||||
}
|
||||
|
||||
private static String causeChain(Throwable thrown) {
|
||||
final StringBuilder all = new StringBuilder();
|
||||
for (Throwable t = thrown; t != null; t = t.getCause()) {
|
||||
all.append(t).append('\n');
|
||||
}
|
||||
return all.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.junit.jupiter.api.Named;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
class RejectionTest {
|
||||
|
||||
static Stream<Named<RejectionCases.Case>> rejections() {
|
||||
return Arrays.stream(RejectionCases.CASES).map(c -> Named.of(c.name, c));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("rejections")
|
||||
void anInvalidMixinIsRejectedAtLoad(RejectionCases.Case rejection) {
|
||||
rejection.assertRejected();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aRequireZeroCallSurroundThatMatchesNoCallIsNotRejected() {
|
||||
assertDoesNotThrow(() -> MixinTestBootstrap.transform(RejectionCases.PACKAGE + "." + RejectionCases.REQUIRE_ZERO_MISS.name));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("mixinextras")
|
||||
void mixinExtrasSugarOnACallSiteEntryHandlerIsRejected() {
|
||||
RejectionCases.SUGAR.assertRejected();
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.CALL;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.VOID;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.WHOLE;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.args;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.of;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.scenario;
|
||||
|
||||
class ScenarioTest {
|
||||
|
||||
static Stream<Arguments> scenarios() {
|
||||
return Stream.of(
|
||||
scenario(WHOLE, "a surround encloses the body and every other injector", "compute", of(7), of(14), "enter 7 1", "head 7", "body 7", "tail 14", "exit 1"),
|
||||
scenario(WHOLE, "the narrowest catch handler wins and finally runs when the body throws", "boom", of(args()), of(IllegalStateException.class), "enterBoom 1", "boomHead", "boom", "caught 1 boom", "exitBoom 1"),
|
||||
scenario(WHOLE, "an instance monitor covers the handlers and the body", "lockedInstance", of(7), of(14), "enterInstance true", "body true", "exitInstance true"),
|
||||
scenario(WHOLE, "a unique exit handler renamed on collision still binds", "run", of(args()), of(VOID), "enter", "run", "target exit", "handler exit"),
|
||||
scenario(WHOLE, "a lower priority mixin is the inner surround", "priority", of(5), of(60), "enterHigh", "enterLow", "body 5", "returnLow 5", "exitLow", "returnHigh 6", "exitHigh"),
|
||||
scenario(WHOLE, "static wide arguments", "wideBody", of(args(2.5d, 5L)), of(7.5d), "enter 1", "body", "exit 1"),
|
||||
scenario(WHOLE, "a throwing entry handler skips the body and the exit handler", "throwingEntry", of(5), of(IllegalArgumentException.class), "enterThrows 5"),
|
||||
scenario(WHOLE, "a throwing finally replaces the return value", "throwingFinally", of(args(9, 4)), of(UnsupportedOperationException.class), "enter 1", "gt", "exitThrows 1"),
|
||||
scenario(WHOLE, "pre-existing exception handlers still fire", "caught", of(4, -4), of(4, -1), "enter 1", "inner-finally", "exit 1", "enter 2", "caught neg", "inner-finally", "exit 2"),
|
||||
scenario(WHOLE, "three kinds bind by kind, not position", "carryThree", of(7), of(14), "enter 8 107 L7", "body 7", "exit 8 107 L7"),
|
||||
scenario(WHOLE, "frame naming an unbuilt object", "carryUninitialized", of(5), of(10), "enter pos", "body 5", "exit pos"),
|
||||
scenario(WHOLE, "entry handler's own try-catch", "carryGuarded", of(-3), of(-6), "recovered", "enter -1", "body -3", "exit -1"),
|
||||
scenario(WHOLE, "assigned in both branches at the end", "carrySplitAtEnd", of(-3), of(-6), "body -3", "exit 2"),
|
||||
scenario(WHOLE, "wide carry ahead of an int across a frame", "carryWideBeforeFrame", of(3), of(6), "positive", "body 3", "exit 3 4"),
|
||||
scenario(WHOLE, "array locals", "carryArrays", of(3), of(6), "body 3", "exit n3 3"),
|
||||
scenario(WHOLE, "widths and an uncarried local between carried ones survive the handler frame", "throwWide", of(3), of(IllegalStateException.class), "enter 4 1.5 3 s3", "body 3", "caught 4 1.5 3", "exit 4 1.5 3"),
|
||||
scenario(WHOLE, "a reference carry is cast to each exit handler's type and a throwing catch handler still runs finally", "throwObject", of(3), of(UnsupportedOperationException.class), "enter o3", "body 3", "caught 2 o3", "exit 2 o3"),
|
||||
scenario(WHOLE, "a reused slot carries the local's value", "reused", of(3), of(6), "tmp 8", "enter 10", "body 3", "exit 10"),
|
||||
scenario(CALL, "a static target takes static handlers around a static callee", "staticTarget", of(4), of(8), "enterStaticTarget 4", "twiceStatic 4", "exitStaticTarget 4 8"),
|
||||
scenario(CALL, "an interface callee is typed as the interface or a supertype", "interfaceCall", of(3), of(4), "enterInterface 3 true", "apply 3", "exitInterface true"),
|
||||
scenario(CALL, "a private callee's receiver is the target itself", "privateCall", of(3), of(4), "enterPrivate true 3", "priv 3", "exitPrivate true 3"),
|
||||
scenario(CALL, "every match is surrounded separately", "all", of(2), of(18), "enterAll 2 1", "twice 2", "exitAll 2 1", "enterAll 3 2", "twice 3", "exitAll 3 2", "enterAll 4 3", "twice 4", "exitAll 4 3"),
|
||||
scenario(CALL, "a slice restricts the matches", "sliced", of(2), of(10), "twice 2", "marker", "enterSliced 3", "twice 3", "exitSliced 3"),
|
||||
scenario(CALL, "a throwing entry handler runs no exit handler and skips the call", "throwingEntry", of(args()), of(IllegalArgumentException.class), "enterThrowingEntry 1"),
|
||||
scenario(CALL, "inject before, modify arg and inject after stay outside", "composed", of(5), of(210), "before 5", "modifyArg 5", "enterComposed 105", "twice 105", "exitComposed 105", "after 5"),
|
||||
scenario(WHOLE, "a void target skips without a skipped handler", "skippedVoid", of(-1, 1), of(VOID, VOID), "enterSkippedVoid -1", "exitSkippedVoid", "enterSkippedVoid 1", "skippedVoid 1", "exitSkippedVoid"),
|
||||
scenario(WHOLE, "an entry handler may call the target directly and then skip", "reentrant", of(1), of(24), "enterReentrant 11 false", "reentrant 11", "exitReentrant 11", "enterReentrant 1 true", "exitReentrant 1"),
|
||||
scenario(WHOLE, "return does not run after a skip and a reference skipped value is returned", "skipAndReturn", of("", "a", "x"), of(null, "a!?", IllegalStateException.class), "enterSkipAndReturn ", "skippedSkipAndReturn", "exitSkipAndReturn ", "enterSkipAndReturn a", "skipAndReturn a", "returnSkipAndReturn a!", "exitSkipAndReturn a", "enterSkipAndReturn x", "skipAndReturn x", "returnSkipAndReturn x!", "exitSkipAndReturn x"),
|
||||
scenario(WHOLE, "an entry handler may catch and rethrow around a direct call", "entryCatches", of(3, -1), of(6, IllegalStateException.class), "entryCatches 3", "exitEntryCatches false", "enterEntryCatches 3", "entryCatches 3", "exitEntryCatches true", "entryCatches -1", "exitEntryCatches false", "entryRethrow negative -1"),
|
||||
scenario(CALL, "a local is selected by name between operands and carries", "byName", of(3), of(49), "enterByName 23 1", "twice 13", "exitByName 13 23 1"),
|
||||
scenario(CALL, "wide locals bind", "wideLocals", of(3), of(31L), "enterWideLocals 30 1.5", "wide 30 1.5", "exitWideLocals 1.5 30"),
|
||||
scenario(CALL, "a handling catch replaces the result over the values below", "valueBelow", of(-3, 4), of(67, 84), "enterValueBelow -3 1", "explode -3", "handleValueBelow -3 1", "exitValueBelow -3 1", "enterValueBelow 4 2", "explode 4", "exitValueBelow 4 2"),
|
||||
scenario(CALL, "a skipped handler supplies the value of a skipped call", "valueSkip", of(-3, 3), of(-300, 6), "enterValueSkip -3 1", "skippedValueSkip -3 1", "exitValueSkip 1", "enterValueSkip 3 2", "twice 3", "exitValueSkip 2"),
|
||||
scenario(CALL, "a void return handler observes and does not run after a handled exception", "observed", of(2, -1), of(VOID, VOID), "enterObserved 2", "maybeBoom 2", "observe 2", "exitObserved 2", "enterObserved -1", "maybeBoom -1", "handleObserved -1", "exitObserved -1"),
|
||||
scenario(CALL, "a handled value bypasses the return handler", "returnAndHandle", of(4, -1), of(80, 7), "enterReturnAndHandle 4", "explode 4", "returnReturnAndHandle 8", "exitReturnAndHandle", "enterReturnAndHandle -1", "explode -1", "handleReturnAndHandle", "exitReturnAndHandle"),
|
||||
scenario(CALL, "the skip path is covered by catch and finally but not by return", "skipFinallyCatch", of(2, -3, 0), of(1004, 3, IllegalStateException.class), "enterSfc 2", "explode 2", "returnSfc 4", "exitSfc 2", "enterSfc -3", "skippedSfc -3", "exitSfc -3", "enterSfc 0", "skippedSfc 0", "caughtSfc zero", "exitSfc 0"),
|
||||
scenario(CALL, "an outer skip runs no inner handler and return handlers chain inner to outer", "nestedSkip", of(-2, 2), of(-1, 50), "enterNestedOuter -2", "skippedNestedOuter", "exitNestedOuter -2", "enterNestedOuter 2", "enterNestedInner 2", "twice 2", "returnNestedInner 4", "exitNestedInner 2", "returnNestedOuter 5", "exitNestedOuter 2"),
|
||||
scenario(CALL, "a skip skips the redirect handler inside", "redirectedSkip", of(-1, 3), of(0, 7), "enterRedirectedSkip -1", "exitRedirectedSkip -1", "enterRedirectedSkip 3", "redirectSkip 3", "twice 3", "exitRedirectedSkip 3"),
|
||||
scenario(CALL, "a final skip derived from a carry computed by a unique helper binds", "helperSkip", of(-4, 4), of(-1, 8), "part -4", "enterHelper -1", "skippedHelper", "exitHelper -1", "part 4", "enterHelper 4", "twice 4", "exitHelper 4"),
|
||||
scenario(CALL, "wide operands skip and return with an int skip flag", "wideSkip", of(args(-2L, 3.0d), args(5L, 2.5d)), of(-19L, 108L), "enterWideSkip -2 3.0", "skippedWideSkip -2 1.5", "exitWideSkip -2 3.0", "enterWideSkip 5 2.5", "wide 5 2.5", "returnWideSkip 7 1.25", "exitWideSkip 5 2.5"),
|
||||
scenario(CALL, "an entry-only call surround needs no exit handler even with a skip", "entryOnlySkip", of(1, -1), of(1, -1), "enterOnlySkip 1", "enterOnlySkip -1", "render -1"),
|
||||
scenario(CALL, "an entry-only whole-method surround", "entryOnlyWhole", of(args()), of(VOID), "enterOnlyWhole", "entryOnlyWhole"),
|
||||
scenario(CALL, "a skip restores the values below the call when the entry catches", "skipBelowRestored", of(-2, 3, 200), of(980, 1006, 1400), "enterSkipBelowRestored -2", "skippedSkipBelowRestored -2", "exitSkipBelowRestored -2", "enterSkipBelowRestored 3", "twice 3", "exitSkipBelowRestored 3", "entryCaughtSkipBelowRestored 200", "twice 200", "exitSkipBelowRestored 200"),
|
||||
scenario(CALL, "an inner skip completes normally for an outer return handler", "innerSkipOuterReturn", of(-2, 3), of(-199, 7), "enterOuterReturn -2", "enterInnerSkip -2", "skippedInnerSkip -2", "exitInnerSkip -2", "returnOuterReturn -200", "exitOuterReturn -2", "enterOuterReturn 3", "enterInnerSkip 3", "twice 3", "exitInnerSkip 3", "returnOuterReturn 6", "exitOuterReturn 3"),
|
||||
scenario(CALL, "a call inside a constructor call's arguments can be skipped", "skipInsideNew", of(-2, 3), of(-20, 6), "enterSkipInsideNew -2", "skippedSkipInsideNew -2", "exitSkipInsideNew -2", "enterSkipInsideNew 3", "twice 3", "exitSkipInsideNew 3"),
|
||||
scenario(CALL, "a call surround skips inside a body moved by a whole-method surround", "movedBody", of(-2, 3), of(990, 1008), "enterMovedWhole -2", "enterMovedCall -1 -2", "skippedMovedCall -1 -2", "exitMovedCall -1 -2", "exitMovedWhole -2", "enterMovedWhole 3", "enterMovedCall 4 3", "twice 4", "exitMovedCall 4 3", "exitMovedWhole 3"),
|
||||
scenario(WHOLE, "a branching entry handler around the whole method", "branching", of(-3), of(-6), "enterBranching negative", "body -3", "exitBranching negative")
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("scenarios")
|
||||
void scenarios(String method, String target, Object[] inputs, Object[] outputs, String[] events) throws Exception {
|
||||
MixinTestBootstrap.assertScenario(target, method, inputs, outputs, events);
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.lib.tree.LabelNode;
|
||||
import org.spongepowered.asm.lib.tree.LineNumberNode;
|
||||
import org.spongepowered.asm.lib.tree.LocalVariableNode;
|
||||
import org.spongepowered.asm.lib.tree.MethodNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.WHOLE;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.MixinTestBootstrap.call;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class WholeMethodTest {
|
||||
|
||||
@BeforeEach
|
||||
void resetTrace() {
|
||||
Trace.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneEntryHandlerCanSurroundSeveralTargets() throws Exception {
|
||||
call(WHOLE, "alpha");
|
||||
call(WHOLE, "beta");
|
||||
Trace.assertEvents("enterBoth 1", "alpha", "exitBoth 1", "enterBoth 2", "beta", "exitBoth 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theWrapperKeepsTheEntryHandlersLinesAndTheBodyKeepsItsOwn() {
|
||||
final ClassNode merged = MixinTestBootstrap.transformedNode(WHOLE);
|
||||
final List<Integer> entryLines = lines(bySuffix(merged, "$surround$enterBranching"));
|
||||
assertFalse(entryLines.isEmpty(), "fixture compiled without line numbers");
|
||||
assertEquals(entryLines, lines(MixinTestBootstrap.method(merged, "branching")));
|
||||
assertEquals(lines(MixinTestBootstrap.method(MixinTestBootstrap.originalNode(WHOLE), "branching")), lines(MixinTestBootstrap.method(merged, "branching$surround")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aReusedSlotNamesTheCarriedLocal() {
|
||||
final MethodNode wrapper = MixinTestBootstrap.method(MixinTestBootstrap.transformedNode(WHOLE), "reused");
|
||||
LabelNode last = null;
|
||||
for (AbstractInsnNode insn = wrapper.instructions.getLast(); last == null; insn = insn.getPrevious()) {
|
||||
last = insn instanceof LabelNode label ? label : null;
|
||||
}
|
||||
final List<String> toEnd = new ArrayList<>();
|
||||
for (LocalVariableNode local : wrapper.localVariables) {
|
||||
if (local.end == last) {
|
||||
toEnd.add(local.name);
|
||||
}
|
||||
}
|
||||
assertTrue(toEnd.contains("prev") && !toEnd.contains("tmp"), "locals live to the end: " + toEnd);
|
||||
}
|
||||
|
||||
@Test
|
||||
void theWrapperMatchesTheGoldens() {
|
||||
MixinTestBootstrap.assertGolden(WHOLE, "skipAndReturn", "whole-skip-return");
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledIfSystemProperty(named = "surround.test.obf", matches = "true")
|
||||
void theLaunchClassLoaderTransformersSeeTheMixinClass() throws Exception {
|
||||
MixinTestBootstrap.transform(WHOLE);
|
||||
final Set<?> seen = (Set<?>) Launch.classLoader.loadClass(LocalSortingTransformer.class.getName()).getField("SEEN").get(null);
|
||||
assertTrue(seen.contains("com.gtnewhorizons.angelica.experimental.surround.integration.mixin.MixinWholeTarget"), seen.toString());
|
||||
}
|
||||
|
||||
private static MethodNode bySuffix(ClassNode node, String suffix) {
|
||||
for (MethodNode method : node.methods) {
|
||||
if (method.name.endsWith(suffix)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
final List<String> names = new ArrayList<>();
|
||||
for (MethodNode method : node.methods) {
|
||||
names.add(method.name);
|
||||
}
|
||||
throw new AssertionError("no method ending " + suffix + " in " + names);
|
||||
}
|
||||
|
||||
private static List<Integer> lines(MethodNode method) {
|
||||
final List<Integer> lines = new ArrayList<>();
|
||||
for (AbstractInsnNode insn : method.instructions.toArray()) {
|
||||
if (insn instanceof LineNumberNode ln) {
|
||||
lines.add(ln.line);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
|
||||
public class CallTarget {
|
||||
|
||||
public static final String CALLEE = "Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;";
|
||||
public static final String TWICE = CALLEE + "twice(I)I";
|
||||
|
||||
private final Callee callee = new Callee();
|
||||
private final IntUnaryOperator op = new Callee();
|
||||
private final Render renderer = new Render();
|
||||
|
||||
public static int staticTarget(int a) {
|
||||
return Callee.twiceStatic(a);
|
||||
}
|
||||
|
||||
public int interfaceCall(int a) {
|
||||
return this.op.applyAsInt(a);
|
||||
}
|
||||
|
||||
public int privateCall(int a) {
|
||||
return this.priv(a);
|
||||
}
|
||||
|
||||
private int priv(int a) {
|
||||
Trace.add("priv " + a);
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
public int all(int a) {
|
||||
return this.callee.twice(a) + this.callee.twice(a + 1) + this.callee.twice(a + 2);
|
||||
}
|
||||
|
||||
public int sliced(int a) {
|
||||
final int first = this.callee.twice(a);
|
||||
Callee.marker();
|
||||
return first + this.callee.twice(a + 1);
|
||||
}
|
||||
|
||||
public void throwingEntry() {
|
||||
this.callee.twice(1);
|
||||
}
|
||||
|
||||
public int composed(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public boolean render(Entity entity, double x, double y, double z, float yaw, float partial, boolean flag) {
|
||||
Render render = null;
|
||||
try {
|
||||
render = this.renderer;
|
||||
if (render != null && entity != null) {
|
||||
try {
|
||||
render.doRender(entity, x, y, z, yaw, partial);
|
||||
} catch (Throwable throwable1) {
|
||||
throw new ReportedException("Rendering entity in world", throwable1);
|
||||
}
|
||||
try {
|
||||
render.doRenderShadowAndFire(entity, x, y, z, yaw, partial);
|
||||
} catch (Throwable throwable2) {
|
||||
throw new ReportedException("Post-rendering entity in world", throwable2);
|
||||
}
|
||||
if (flag) {
|
||||
Trace.add("boundingBox " + entity.name);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (Throwable throwable) {
|
||||
throw new ReportedException("Rendering entity (outer)", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public int byName(int a) {
|
||||
final int first = a + 10;
|
||||
final int second = a + 20;
|
||||
return this.callee.twice(first) + second;
|
||||
}
|
||||
|
||||
public long wideLocals(int a) {
|
||||
final long big = a * 10L;
|
||||
final double half = a / 2.0;
|
||||
return this.callee.wide(big, half);
|
||||
}
|
||||
|
||||
public int valueBelow(int a) {
|
||||
return a + this.callee.explode(a) * 10;
|
||||
}
|
||||
|
||||
public int valueSkip(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public void observed(int a) {
|
||||
this.callee.maybeBoom(a);
|
||||
}
|
||||
|
||||
public int returnAndHandle(int a) {
|
||||
return this.callee.explode(a);
|
||||
}
|
||||
|
||||
public int skipFinallyCatch(int a) {
|
||||
return this.callee.explode(a);
|
||||
}
|
||||
|
||||
public int nestedSkip(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int redirectedSkip(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int helperSkip(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public long wideSkip(long a, double b) {
|
||||
return this.callee.wide(a, b) + 1L;
|
||||
}
|
||||
|
||||
public int entryOnlySkip(int a) {
|
||||
this.callee.render(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
public void entryOnlyWhole() {
|
||||
Trace.add("entryOnlyWhole");
|
||||
}
|
||||
|
||||
public int skipBelowRestored(int a) {
|
||||
return 1000 + this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int innerSkipOuterReturn(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int skipInsideNew(int a) {
|
||||
return new AtomicInteger(this.callee.twice(a)).get();
|
||||
}
|
||||
|
||||
public int movedBody(int a) {
|
||||
final int b = a + 1;
|
||||
return 1000 + this.callee.twice(b);
|
||||
}
|
||||
|
||||
public static class Callee implements IntUnaryOperator {
|
||||
|
||||
public int twice(int a) {
|
||||
Trace.add("twice " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public static int twiceStatic(int a) {
|
||||
Trace.add("twiceStatic " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public static void marker() {
|
||||
Trace.add("marker");
|
||||
}
|
||||
|
||||
public long wide(long a, double b) {
|
||||
Trace.add("wide " + a + " " + b);
|
||||
return a + (long) b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int applyAsInt(int a) {
|
||||
Trace.add("apply " + a);
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
public int explode(int a) {
|
||||
Trace.add("explode " + a);
|
||||
if (a < 0) {
|
||||
throw new IllegalStateException("negative " + a);
|
||||
}
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public void maybeBoom(int a) {
|
||||
Trace.add("maybeBoom " + a);
|
||||
if (a < 0) {
|
||||
throw new IllegalStateException("boom " + a);
|
||||
}
|
||||
}
|
||||
|
||||
public void render(int a) {
|
||||
Trace.add("render " + a);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Entity {
|
||||
|
||||
public final String name;
|
||||
public boolean explode;
|
||||
|
||||
public Entity(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Render {
|
||||
|
||||
public void doRender(Entity entity, double x, double y, double z, float yaw, float partial) {
|
||||
Trace.add("doRender " + entity.name + " " + x + " " + partial);
|
||||
if (entity.explode) {
|
||||
throw new RuntimeException("gpu");
|
||||
}
|
||||
}
|
||||
|
||||
public void doRenderShadowAndFire(Entity entity, double x, double y, double z, float yaw, float partial) {
|
||||
Trace.add("shadow " + entity.name);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReportedException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ReportedException(String description, Throwable cause) {
|
||||
super(description, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
|
||||
public class WholeTarget {
|
||||
|
||||
public static boolean guard;
|
||||
|
||||
public int compute(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public void boom() {
|
||||
Trace.add("boom");
|
||||
throw new IllegalStateException("boom");
|
||||
}
|
||||
|
||||
public void alpha() {
|
||||
Trace.add("alpha");
|
||||
}
|
||||
|
||||
public void beta() {
|
||||
Trace.add("beta");
|
||||
}
|
||||
|
||||
public synchronized int lockedInstance(int a) {
|
||||
Trace.add("body " + Thread.holdsLock(this));
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Trace.add("run");
|
||||
exit();
|
||||
}
|
||||
|
||||
private void exit() {
|
||||
Trace.add("target exit");
|
||||
}
|
||||
|
||||
public int priority(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a;
|
||||
}
|
||||
|
||||
public int branching(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public void skippedVoid(int a) {
|
||||
Trace.add("skippedVoid " + a);
|
||||
}
|
||||
|
||||
public int reentrant(int a) {
|
||||
Trace.add("reentrant " + a);
|
||||
return a + 1;
|
||||
}
|
||||
|
||||
public String skipAndReturn(String a) {
|
||||
Trace.add("skipAndReturn " + a);
|
||||
return a + "!";
|
||||
}
|
||||
|
||||
public static int entryCatches(int a) {
|
||||
Trace.add("entryCatches " + a);
|
||||
if (a < 0) {
|
||||
throw new IllegalStateException("negative " + a);
|
||||
}
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public int throwingFinally(int a, int b) {
|
||||
return pick(a, b);
|
||||
}
|
||||
|
||||
private static int pick(int a, int b) {
|
||||
if (a > b) {
|
||||
Trace.add("gt");
|
||||
return a;
|
||||
}
|
||||
if (a < b) {
|
||||
Trace.add("lt");
|
||||
return b;
|
||||
}
|
||||
Trace.add("eq");
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static double wideBody(double a, long b) {
|
||||
Trace.add("body");
|
||||
return a + b;
|
||||
}
|
||||
|
||||
public void throwWide(int a) {
|
||||
thrown(a);
|
||||
}
|
||||
|
||||
public void throwObject(int a) {
|
||||
thrown(a);
|
||||
}
|
||||
|
||||
private static void thrown(int a) {
|
||||
Trace.add("body " + a);
|
||||
throw new IllegalStateException("boom " + a);
|
||||
}
|
||||
|
||||
public void throwingEntry(int a) {
|
||||
Trace.add("body " + a);
|
||||
}
|
||||
|
||||
public int caught(int a) {
|
||||
try {
|
||||
if (a < 0) {
|
||||
throw new IllegalArgumentException("neg");
|
||||
}
|
||||
return a;
|
||||
} catch (IllegalArgumentException e) {
|
||||
Trace.add("caught " + e.getMessage());
|
||||
return -1;
|
||||
} finally {
|
||||
Trace.add("inner-finally");
|
||||
}
|
||||
}
|
||||
|
||||
public int carryThree(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carryUninitialized(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carryGuarded(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carrySplitAtEnd(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carryWideBeforeFrame(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int carryArrays(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
public int reused(int a) {
|
||||
return carried(a);
|
||||
}
|
||||
|
||||
private static int carried(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public static int risky(int a) {
|
||||
if (a < 0) {
|
||||
throw new IllegalArgumentException("neg");
|
||||
}
|
||||
return a * 3;
|
||||
}
|
||||
|
||||
public static Object asObject(Object value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Callee;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
|
||||
public class WrapTarget {
|
||||
|
||||
private final Callee callee = new Callee();
|
||||
|
||||
public int wrapped(int a) {
|
||||
Trace.add("body " + a);
|
||||
return a * 2;
|
||||
}
|
||||
|
||||
public int originalOperands(int a) {
|
||||
return this.callee.twice(a);
|
||||
}
|
||||
|
||||
public int shifted(int a) {
|
||||
final int local = a + 1;
|
||||
final String label = "L" + local;
|
||||
return this.callee.twice(local) + label.length();
|
||||
}
|
||||
}
|
||||
+504
@@ -0,0 +1,504 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration.mixin;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Callee;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Entity;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Render;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.Slice;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.function.IntUnaryOperator;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.CALLEE;
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.TWICE;
|
||||
|
||||
@Mixin(value = CallTarget.class, remap = false)
|
||||
public class MixinCallTarget {
|
||||
|
||||
private static final String WIDE = CALLEE + "wide(JD)J";
|
||||
private static final String EXPLODE = CALLEE + "explode(I)I";
|
||||
private static final String MAYBE_BOOM = CALLEE + "maybeBoom(I)V";
|
||||
private static final String DO_RENDER = "Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Render;" + "doRender(Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Entity;DDDFF)V";
|
||||
private static final long QUEUED = -1L;
|
||||
|
||||
@Surround(method = "staticTarget", at = @At(value = "INVOKE", target = CALLEE + "twiceStatic(I)I"), id = "staticTarget")
|
||||
private static void surround$enterStaticTarget(int a) {
|
||||
@Surround.Carry int doubled = a * 2;
|
||||
Trace.add("enterStaticTarget " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("staticTarget")
|
||||
private static void surround$exitStaticTarget(int a, @Surround.Carry int doubled) {
|
||||
Trace.add("exitStaticTarget " + a + " " + doubled);
|
||||
}
|
||||
|
||||
@Surround(method = "interfaceCall", at = @At(value = "INVOKE", target = "Ljava/util/function/IntUnaryOperator;applyAsInt(I)I"), id = "interface")
|
||||
private void surround$enterInterface(IntUnaryOperator op, int a) {
|
||||
Trace.add("enterInterface " + a + " " + (op instanceof Callee));
|
||||
}
|
||||
|
||||
@Surround.Finally("interface")
|
||||
private void surround$exitInterface(Object op) {
|
||||
Trace.add("exitInterface " + (op instanceof Callee));
|
||||
}
|
||||
|
||||
@Surround(method = "privateCall", at = @At(value = "INVOKE", target = "Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget;priv(I)I"), id = "private")
|
||||
private void surround$enterPrivate(CallTarget self, int a) {
|
||||
Trace.add("enterPrivate " + (self == (Object) this) + " " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("private")
|
||||
private void surround$exitPrivate(CallTarget self, int a) {
|
||||
Trace.add("exitPrivate " + (self == (Object) this) + " " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "all", at = @At(value = "INVOKE", target = TWICE), id = "all", require = 3)
|
||||
private void surround$enterAll(Callee callee, int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterAll " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("all")
|
||||
private void surround$exitAll(Callee callee, int a, @Surround.Carry long token) {
|
||||
Trace.add("exitAll " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "sliced", at = @At(value = "INVOKE", target = TWICE), slice = @Slice(from = @At(value = "INVOKE", target = CALLEE + "marker()V")), id = "sliced", require = 1, allow = 1)
|
||||
private void surround$enterSliced(Callee callee, int a) {
|
||||
Trace.add("enterSliced " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("sliced")
|
||||
private void surround$exitSliced(Callee callee, int a) {
|
||||
Trace.add("exitSliced " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "throwingEntry", at = @At(value = "INVOKE", target = TWICE), id = "throwingEntry")
|
||||
private void surround$enterThrowingEntry(Callee callee, int a) {
|
||||
Trace.add("enterThrowingEntry " + a);
|
||||
if (a > 0) {
|
||||
throw new IllegalArgumentException("entry");
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Catch("throwingEntry")
|
||||
private void surround$caughtThrowingEntry(Throwable error) {
|
||||
Trace.add("caughtThrowingEntry");
|
||||
}
|
||||
|
||||
@Surround.Finally("throwingEntry")
|
||||
private void surround$exitThrowingEntry() {
|
||||
Trace.add("exitThrowingEntry");
|
||||
}
|
||||
|
||||
@Inject(method = "composed", at = @At(value = "INVOKE", target = TWICE))
|
||||
private void surround$before(int a, CallbackInfoReturnable<Integer> cir) {
|
||||
Trace.add("before " + a);
|
||||
}
|
||||
|
||||
@Inject(method = "composed", at = @At(value = "INVOKE", target = TWICE, shift = At.Shift.AFTER))
|
||||
private void surround$after(int a, CallbackInfoReturnable<Integer> cir) {
|
||||
Trace.add("after " + a);
|
||||
}
|
||||
|
||||
@ModifyArg(method = "composed", at = @At(value = "INVOKE", target = TWICE))
|
||||
private int surround$plusHundred(int a) {
|
||||
Trace.add("modifyArg " + a);
|
||||
return a + 100;
|
||||
}
|
||||
|
||||
@Surround(method = "composed", at = @At(value = "INVOKE", target = TWICE), id = "composed")
|
||||
private void surround$enterComposed(Callee callee, int a) {
|
||||
Trace.add("enterComposed " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("composed")
|
||||
private void surround$exitComposed(Callee callee, int a) {
|
||||
Trace.add("exitComposed " + a);
|
||||
}
|
||||
|
||||
@Redirect(method = "render", at = @At(value = "INVOKE", target = DO_RENDER))
|
||||
private void surround$redirectDoRender(Render render, Entity entity, double x, double y, double z, float yaw, float partial) {
|
||||
Trace.add("redirect " + entity.name);
|
||||
render.doRender(entity, x, y, z, yaw, partial);
|
||||
}
|
||||
|
||||
@Surround(method = "render", at = @At(value = "INVOKE", target = DO_RENDER), id = "render", require = 1)
|
||||
private void surround$enterDoRender(Render render, Entity entity, double x, double y, double z, float yaw, float partial) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterDoRender " + entity.name + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Catch("render")
|
||||
private void surround$caughtDoRender(Throwable error, Render render, Entity entity, @Surround.Carry long token) {
|
||||
Trace.add("caughtDoRender " + entity.name + " " + error.getMessage() + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("render")
|
||||
private void surround$exitDoRender(Render render, Entity entity, @Surround.Carry long token) {
|
||||
Trace.add("exitDoRender " + entity.name + " " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "byName", at = @At(value = "INVOKE", target = TWICE), id = "byName")
|
||||
private void surround$enterByName(@Surround.Local(name = "second") int second) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterByName " + second + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("byName")
|
||||
private void surround$exitByName(Callee callee, int first, @Surround.Local(name = "second") int second, @Surround.Carry long token) {
|
||||
Trace.add("exitByName " + first + " " + second + " " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "wideLocals", at = @At(value = "INVOKE", target = WIDE), id = "wideLocals")
|
||||
private void surround$enterWideLocals(Callee callee, long a, double b, @Surround.Local long big, @Surround.Local double half) {
|
||||
Trace.add("enterWideLocals " + big + " " + half);
|
||||
}
|
||||
|
||||
@Surround.Finally("wideLocals")
|
||||
private void surround$exitWideLocals(@Surround.Local(index = 4) double half, @Surround.Local(index = 2) long big) {
|
||||
Trace.add("exitWideLocals " + half + " " + big);
|
||||
}
|
||||
|
||||
@Surround(method = "valueBelow", at = @At(value = "INVOKE", target = EXPLODE), id = "valueBelow")
|
||||
private void surround$enterValueBelow(Callee callee, int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterValueBelow " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Catch(value = "valueBelow", handle = true)
|
||||
private int surround$handleValueBelow(IllegalStateException error, Callee callee, int a, @Surround.Carry long token) {
|
||||
Trace.add("handleValueBelow " + a + " " + token);
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Surround.Finally("valueBelow")
|
||||
private void surround$exitValueBelow(Callee callee, int a, @Surround.Carry long token) {
|
||||
Trace.add("exitValueBelow " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "valueSkip", at = @At(value = "INVOKE", target = TWICE), id = "valueSkip")
|
||||
private void surround$enterValueSkip(Callee callee, int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterValueSkip " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Skipped("valueSkip")
|
||||
private int surround$skippedValueSkip(Callee callee, int a, @Surround.Carry long token) {
|
||||
Trace.add("skippedValueSkip " + a + " " + token);
|
||||
return a * 100;
|
||||
}
|
||||
|
||||
@Surround.Finally("valueSkip")
|
||||
private void surround$exitValueSkip(@Surround.Carry long token) {
|
||||
Trace.add("exitValueSkip " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "observed", at = @At(value = "INVOKE", target = MAYBE_BOOM), id = "observed")
|
||||
private void surround$enterObserved(Callee callee, int a) {
|
||||
Trace.add("enterObserved " + a);
|
||||
}
|
||||
|
||||
@Surround.Return("observed")
|
||||
private void surround$observe(Callee callee, int a) {
|
||||
Trace.add("observe " + a);
|
||||
}
|
||||
|
||||
@Surround.Catch(value = "observed", handle = true)
|
||||
private void surround$handleObserved(IllegalStateException error, Callee callee, int a) {
|
||||
Trace.add("handleObserved " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("observed")
|
||||
private void surround$exitObserved(Callee callee, int a) {
|
||||
Trace.add("exitObserved " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "returnAndHandle", at = @At(value = "INVOKE", target = EXPLODE), id = "returnAndHandle")
|
||||
private void surround$enterReturnAndHandle(Callee callee, int a) {
|
||||
Trace.add("enterReturnAndHandle " + a);
|
||||
}
|
||||
|
||||
@Surround.Return("returnAndHandle")
|
||||
private int surround$returnReturnAndHandle(int result) {
|
||||
Trace.add("returnReturnAndHandle " + result);
|
||||
return result * 10;
|
||||
}
|
||||
|
||||
@Surround.Catch(value = "returnAndHandle", handle = true)
|
||||
private int surround$handleReturnAndHandle(IllegalStateException error) {
|
||||
Trace.add("handleReturnAndHandle");
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Surround.Finally("returnAndHandle")
|
||||
private void surround$exitReturnAndHandle() {
|
||||
Trace.add("exitReturnAndHandle");
|
||||
}
|
||||
|
||||
@Surround(method = "skipFinallyCatch", at = @At(value = "INVOKE", target = EXPLODE), id = "sfc")
|
||||
private void surround$enterSfc(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a <= 0;
|
||||
Trace.add("enterSfc " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("sfc")
|
||||
private int surround$skippedSfc(Callee callee, int a) {
|
||||
Trace.add("skippedSfc " + a);
|
||||
if (a == 0) {
|
||||
throw new IllegalStateException("zero");
|
||||
}
|
||||
return -a;
|
||||
}
|
||||
|
||||
@Surround.Return("sfc")
|
||||
private int surround$returnSfc(int result, Callee callee, int a) {
|
||||
Trace.add("returnSfc " + result);
|
||||
return result + 1000;
|
||||
}
|
||||
|
||||
@Surround.Catch("sfc")
|
||||
private void surround$caughtSfc(IllegalStateException error, Callee callee, int a) {
|
||||
Trace.add("caughtSfc " + error.getMessage());
|
||||
}
|
||||
|
||||
@Surround.Finally("sfc")
|
||||
private void surround$exitSfc(Callee callee, int a) {
|
||||
Trace.add("exitSfc " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "nestedSkip", at = @At(value = "INVOKE", target = TWICE), id = "nestedInner")
|
||||
private void surround$enterNestedInner(Callee callee, int a) {
|
||||
Trace.add("enterNestedInner " + a);
|
||||
}
|
||||
|
||||
@Surround.Return("nestedInner")
|
||||
private int surround$returnNestedInner(int result) {
|
||||
Trace.add("returnNestedInner " + result);
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
@Surround.Finally("nestedInner")
|
||||
private void surround$exitNestedInner(Callee callee, int a) {
|
||||
Trace.add("exitNestedInner " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "nestedSkip", at = @At(value = "INVOKE", target = TWICE), id = "nestedOuter")
|
||||
private void surround$enterNestedOuter(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterNestedOuter " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("nestedOuter")
|
||||
private int surround$skippedNestedOuter() {
|
||||
Trace.add("skippedNestedOuter");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Surround.Return("nestedOuter")
|
||||
private int surround$returnNestedOuter(int result) {
|
||||
Trace.add("returnNestedOuter " + result);
|
||||
return result * 10;
|
||||
}
|
||||
|
||||
@Surround.Finally("nestedOuter")
|
||||
private void surround$exitNestedOuter(Callee callee, int a) {
|
||||
Trace.add("exitNestedOuter " + a);
|
||||
}
|
||||
|
||||
@Redirect(method = "redirectedSkip", at = @At(value = "INVOKE", target = TWICE))
|
||||
private int surround$redirectSkip(Callee callee, int a) {
|
||||
Trace.add("redirectSkip " + a);
|
||||
return callee.twice(a) + 1;
|
||||
}
|
||||
|
||||
@Surround(method = "redirectedSkip", at = @At(value = "INVOKE", target = TWICE), id = "redirectedSkip")
|
||||
private void surround$enterRedirectedSkip(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterRedirectedSkip " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("redirectedSkip")
|
||||
private int surround$skippedRedirectedSkip(Callee callee, int a) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Surround.Finally("redirectedSkip")
|
||||
private void surround$exitRedirectedSkip(Callee callee, int a) {
|
||||
Trace.add("exitRedirectedSkip " + a);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static long surround$part(int a) {
|
||||
Trace.add("part " + a);
|
||||
return a < 0 ? QUEUED : a;
|
||||
}
|
||||
|
||||
@Surround(method = "helperSkip", at = @At(value = "INVOKE", target = TWICE), id = "helper")
|
||||
private void surround$enterHelper(Callee callee, int a) {
|
||||
@Surround.Carry final long part = surround$part(a);
|
||||
@Surround.Skip final boolean queued = part == QUEUED;
|
||||
Trace.add("enterHelper " + part);
|
||||
}
|
||||
|
||||
@Surround.Skipped("helper")
|
||||
private int surround$skippedHelper(Callee callee, int a, @Surround.Carry long part) {
|
||||
Trace.add("skippedHelper");
|
||||
return (int) part;
|
||||
}
|
||||
|
||||
@Surround.Finally("helper")
|
||||
private void surround$exitHelper(@Surround.Carry long part) {
|
||||
Trace.add("exitHelper " + part);
|
||||
}
|
||||
|
||||
@Surround(method = "wideSkip", at = @At(value = "INVOKE", target = WIDE), id = "wideSkip")
|
||||
private void surround$enterWideSkip(Callee callee, long a, double b) {
|
||||
@Surround.Carry double halved = b / 2;
|
||||
@Surround.Skip int skip = a < 0 ? 1 : 0;
|
||||
Trace.add("enterWideSkip " + a + " " + b);
|
||||
}
|
||||
|
||||
@Surround.Skipped("wideSkip")
|
||||
private long surround$skippedWideSkip(Callee callee, long a, @Surround.Carry double halved) {
|
||||
Trace.add("skippedWideSkip " + a + " " + halved);
|
||||
return a * 10L;
|
||||
}
|
||||
|
||||
@Surround.Return("wideSkip")
|
||||
private long surround$returnWideSkip(long result, Callee callee, long a, double b, @Surround.Carry double halved) {
|
||||
Trace.add("returnWideSkip " + result + " " + halved);
|
||||
return result + 100L;
|
||||
}
|
||||
|
||||
@Surround.Finally("wideSkip")
|
||||
private void surround$exitWideSkip(Callee callee, long a, double b) {
|
||||
Trace.add("exitWideSkip " + a + " " + b);
|
||||
}
|
||||
|
||||
@Surround(method = "entryOnlySkip", at = @At(value = "INVOKE", target = CALLEE + "render(I)V"), id = "entryOnlySkip")
|
||||
private void surround$enterOnlySkip(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a > 0;
|
||||
Trace.add("enterOnlySkip " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "entryOnlyWhole", id = "entryOnlyWhole")
|
||||
private void surround$enterOnlyWhole() {
|
||||
Trace.add("enterOnlyWhole");
|
||||
}
|
||||
|
||||
@Surround(method = "skipBelowRestored", at = @At(value = "INVOKE", target = TWICE), id = "skipBelowRestored")
|
||||
private void surround$enterSkipBelowRestored(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
try {
|
||||
if (a > 100) {
|
||||
throw new IllegalArgumentException("big");
|
||||
}
|
||||
Trace.add("enterSkipBelowRestored " + a);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Trace.add("entryCaughtSkipBelowRestored " + a);
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Skipped("skipBelowRestored")
|
||||
private int surround$skippedSkipBelowRestored(Callee callee, int a) {
|
||||
Trace.add("skippedSkipBelowRestored " + a);
|
||||
return a * 10;
|
||||
}
|
||||
|
||||
@Surround.Finally("skipBelowRestored")
|
||||
private void surround$exitSkipBelowRestored(Callee callee, int a) {
|
||||
Trace.add("exitSkipBelowRestored " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "innerSkipOuterReturn", at = @At(value = "INVOKE", target = TWICE), id = "innerSkip")
|
||||
private void surround$enterInnerSkip(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterInnerSkip " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("innerSkip")
|
||||
private int surround$skippedInnerSkip(Callee callee, int a) {
|
||||
Trace.add("skippedInnerSkip " + a);
|
||||
return a * 100;
|
||||
}
|
||||
|
||||
@Surround.Finally("innerSkip")
|
||||
private void surround$exitInnerSkip(Callee callee, int a) {
|
||||
Trace.add("exitInnerSkip " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "innerSkipOuterReturn", at = @At(value = "INVOKE", target = TWICE), id = "outerReturn")
|
||||
private void surround$enterOuterReturn(Callee callee, int a) {
|
||||
Trace.add("enterOuterReturn " + a);
|
||||
}
|
||||
|
||||
@Surround.Return("outerReturn")
|
||||
private int surround$returnOuterReturn(int result) {
|
||||
Trace.add("returnOuterReturn " + result);
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
@Surround.Finally("outerReturn")
|
||||
private void surround$exitOuterReturn(Callee callee, int a) {
|
||||
Trace.add("exitOuterReturn " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "skipInsideNew", at = @At(value = "INVOKE", target = TWICE), id = "skipInsideNew")
|
||||
private void surround$enterSkipInsideNew(Callee callee, int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterSkipInsideNew " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("skipInsideNew")
|
||||
private int surround$skippedSkipInsideNew(Callee callee, int a) {
|
||||
Trace.add("skippedSkipInsideNew " + a);
|
||||
return a * 10;
|
||||
}
|
||||
|
||||
@Surround.Catch("skipInsideNew")
|
||||
private void surround$caughtSkipInsideNew(Throwable error) {
|
||||
Trace.add("caughtSkipInsideNew");
|
||||
}
|
||||
|
||||
@Surround.Finally("skipInsideNew")
|
||||
private void surround$exitSkipInsideNew(Callee callee, int a) {
|
||||
Trace.add("exitSkipInsideNew " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "movedBody", id = "movedWhole")
|
||||
private void surround$enterMovedWhole(int a) {
|
||||
Trace.add("enterMovedWhole " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("movedWhole")
|
||||
private void surround$exitMovedWhole(int a) {
|
||||
Trace.add("exitMovedWhole " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "movedBody", at = @At(value = "INVOKE", target = TWICE), id = "movedCall")
|
||||
private void surround$enterMovedCall(Callee callee, int b, @Surround.Local(argsOnly = true) int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterMovedCall " + b + " " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("movedCall")
|
||||
private int surround$skippedMovedCall(Callee callee, int b, @Surround.Local(argsOnly = true) int a) {
|
||||
Trace.add("skippedMovedCall " + b + " " + a);
|
||||
return b * 10;
|
||||
}
|
||||
|
||||
@Surround.Finally("movedCall")
|
||||
private void surround$exitMovedCall(Callee callee, int b, @Surround.Local(argsOnly = true) int a) {
|
||||
Trace.add("exitMovedCall " + b + " " + a);
|
||||
}
|
||||
}
|
||||
+389
@@ -0,0 +1,389 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration.mixin;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.WholeTarget;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(value = WholeTarget.class, remap = false)
|
||||
public class MixinWholeTarget {
|
||||
|
||||
@Surround(method = "compute(I)I", id = "compute")
|
||||
private void surround$enter(int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enter " + a + " " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("compute")
|
||||
private void surround$exit(@Surround.Carry long token) {
|
||||
Trace.add("exit " + token);
|
||||
}
|
||||
|
||||
@Inject(method = "compute(I)I", at = @At("HEAD"))
|
||||
private void surround$head(int a, CallbackInfoReturnable<Integer> cir) {
|
||||
Trace.add("head " + a);
|
||||
}
|
||||
|
||||
@Inject(method = "compute(I)I", at = @At("RETURN"))
|
||||
private void surround$tail(int a, CallbackInfoReturnable<Integer> cir) {
|
||||
Trace.add("tail " + cir.getReturnValue());
|
||||
}
|
||||
|
||||
@Surround(method = "boom()V", id = "boom")
|
||||
private void surround$enterBoom() {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterBoom " + token);
|
||||
}
|
||||
|
||||
@Surround.Catch("boom")
|
||||
private void surround$caughtBoomBroad(Throwable e, @Surround.Carry long token) {
|
||||
Trace.add("caughtBroad " + token + " " + e.getMessage());
|
||||
}
|
||||
|
||||
@Surround.Catch("boom")
|
||||
private void surround$caughtBoom(IllegalStateException e, @Surround.Carry long token) {
|
||||
Trace.add("caught " + token + " " + e.getMessage());
|
||||
}
|
||||
|
||||
@Surround.Finally("boom")
|
||||
private void surround$exitBoom(@Surround.Carry long token) {
|
||||
Trace.add("exitBoom " + token);
|
||||
}
|
||||
|
||||
@Inject(method = "boom()V", at = @At("HEAD"))
|
||||
private void surround$boomHead(CallbackInfo ci) {
|
||||
Trace.add("boomHead");
|
||||
}
|
||||
|
||||
@Surround(method = {"alpha()V", "beta()V"}, id = "both")
|
||||
private void surround$enterBoth() {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enterBoth " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("both")
|
||||
private void surround$exitBoth(@Surround.Carry long token) {
|
||||
Trace.add("exitBoth " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "lockedInstance(I)I", id = "instance")
|
||||
private void surround$enterInstance(int a) {
|
||||
Trace.add("enterInstance " + Thread.holdsLock(this));
|
||||
}
|
||||
|
||||
@Surround.Finally("instance")
|
||||
private void surround$exitInstance() {
|
||||
Trace.add("exitInstance " + Thread.holdsLock(this));
|
||||
}
|
||||
|
||||
@Surround(method = "run()V", id = "run")
|
||||
private void surround$enterRun() {
|
||||
Trace.add("enter");
|
||||
}
|
||||
|
||||
@Unique
|
||||
@Surround.Finally("run")
|
||||
private void exit() {
|
||||
Trace.add("handler exit");
|
||||
}
|
||||
|
||||
@Surround(method = "priority(I)I", id = "priority")
|
||||
private void surround$enterLow(int a) {
|
||||
Trace.add("enterLow");
|
||||
}
|
||||
|
||||
@Surround.Return("priority")
|
||||
private int surround$returnLow(int result) {
|
||||
Trace.add("returnLow " + result);
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
@Surround.Finally("priority")
|
||||
private void surround$exitLow() {
|
||||
Trace.add("exitLow");
|
||||
}
|
||||
|
||||
@Surround(method = "branching(I)I", id = "branching")
|
||||
private void surround$enterBranching(int a) {
|
||||
@Surround.Carry String sign = a < 0 ? "negative" : "positive";
|
||||
Trace.add("enterBranching " + sign);
|
||||
}
|
||||
|
||||
@Surround.Finally("branching")
|
||||
private void surround$exitBranching(int a, @Surround.Carry String sign) {
|
||||
Trace.add("exitBranching " + sign);
|
||||
}
|
||||
|
||||
@Surround(method = "skippedVoid(I)V", id = "skippedVoid")
|
||||
private void surround$enterSkippedVoid(int a) {
|
||||
@Surround.Skip boolean skip = a < 0;
|
||||
Trace.add("enterSkippedVoid " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("skippedVoid")
|
||||
private void surround$exitSkippedVoid() {
|
||||
Trace.add("exitSkippedVoid");
|
||||
}
|
||||
|
||||
@Surround(method = "reentrant(I)I", id = "reentrant")
|
||||
private void surround$enterReentrant(int a) {
|
||||
@Surround.Carry int direct = 0;
|
||||
@Surround.Skip boolean skip = false;
|
||||
if (!WholeTarget.guard) {
|
||||
WholeTarget.guard = true;
|
||||
direct = ((WholeTarget) (Object) this).reentrant(a + 10);
|
||||
WholeTarget.guard = false;
|
||||
skip = true;
|
||||
}
|
||||
Trace.add("enterReentrant " + a + " " + skip);
|
||||
}
|
||||
|
||||
@Surround.Skipped("reentrant")
|
||||
private int surround$skippedReentrant(int a, @Surround.Carry int direct) {
|
||||
return direct * 2;
|
||||
}
|
||||
|
||||
@Surround.Finally("reentrant")
|
||||
private void surround$exitReentrant(int a) {
|
||||
Trace.add("exitReentrant " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "skipAndReturn(Ljava/lang/String;)Ljava/lang/String;", id = "skipAndReturn")
|
||||
private void surround$enterSkipAndReturn(String a) {
|
||||
@Surround.Skip boolean skip = a.isEmpty();
|
||||
Trace.add("enterSkipAndReturn " + a);
|
||||
}
|
||||
|
||||
@Surround.Skipped("skipAndReturn")
|
||||
private String surround$skippedSkipAndReturn(String a) {
|
||||
Trace.add("skippedSkipAndReturn");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Surround.Return("skipAndReturn")
|
||||
private String surround$returnSkipAndReturn(String result, String a) {
|
||||
Trace.add("returnSkipAndReturn " + result);
|
||||
if (result.equals("x!")) {
|
||||
throw new IllegalStateException("return");
|
||||
}
|
||||
return result + "?";
|
||||
}
|
||||
|
||||
@Surround.Catch("skipAndReturn")
|
||||
private void surround$caughtSkipAndReturn(Throwable e) {
|
||||
Trace.add("caughtSkipAndReturn " + e.getMessage());
|
||||
}
|
||||
|
||||
@Surround.Finally("skipAndReturn")
|
||||
private void surround$exitSkipAndReturn(String a) {
|
||||
Trace.add("exitSkipAndReturn " + a);
|
||||
}
|
||||
|
||||
@Surround(method = "entryCatches(I)I", id = "entryCatches")
|
||||
private static void surround$enterEntryCatches(int a) {
|
||||
@Surround.Carry final boolean direct = !WholeTarget.guard;
|
||||
if (direct) {
|
||||
WholeTarget.guard = true;
|
||||
try {
|
||||
WholeTarget.entryCatches(a);
|
||||
} catch (Throwable t) {
|
||||
WholeTarget.guard = false;
|
||||
Trace.add("entryRethrow " + t.getMessage());
|
||||
throw t;
|
||||
}
|
||||
Trace.add("enterEntryCatches " + a);
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("entryCatches")
|
||||
private static void surround$exitEntryCatches(@Surround.Carry boolean direct) {
|
||||
if (direct) {
|
||||
WholeTarget.guard = false;
|
||||
}
|
||||
Trace.add("exitEntryCatches " + direct);
|
||||
}
|
||||
|
||||
@Surround(method = "wideBody(DJ)D", id = "wideBody")
|
||||
private static void surround$enterWideBody(double a, long b) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enter " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("wideBody")
|
||||
private static void surround$exitWideBody(@Surround.Carry long token) {
|
||||
Trace.add("exit " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "throwingEntry(I)V", id = "throwingEntry")
|
||||
private void surround$enterThrowingEntry(int a) {
|
||||
Trace.add("enterThrows " + a);
|
||||
if (a > 0) {
|
||||
throw new IllegalArgumentException("entry failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("throwingEntry")
|
||||
private void surround$exitThrowingEntry() {
|
||||
Trace.add("exitNothing");
|
||||
}
|
||||
|
||||
@Surround(method = "throwingFinally(II)I", id = "throwingFinally")
|
||||
private void surround$enterThrowingFinally(int a, int b) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enter " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("throwingFinally")
|
||||
private void surround$exitThrowingFinally(@Surround.Carry long token) {
|
||||
Trace.add("exitThrows " + token);
|
||||
throw new UnsupportedOperationException("finally failed");
|
||||
}
|
||||
|
||||
@Surround(method = "caught(I)I", id = "caught")
|
||||
private void surround$enterCaught(int a) {
|
||||
@Surround.Carry long token = Trace.nextToken();
|
||||
Trace.add("enter " + token);
|
||||
}
|
||||
|
||||
@Surround.Finally("caught")
|
||||
private void surround$exitCaught(@Surround.Carry long token) {
|
||||
Trace.add("exit " + token);
|
||||
}
|
||||
|
||||
@Surround(method = "carryThree(I)I", id = "carryThree")
|
||||
private void surround$enterCarryThree(int a) {
|
||||
@Surround.Carry int count = a + 1;
|
||||
@Surround.Carry long stamp = 100L + a;
|
||||
@Surround.Carry String label = "L" + a;
|
||||
Trace.add("enter " + count + " " + stamp + " " + label);
|
||||
}
|
||||
|
||||
@Surround.Finally("carryThree")
|
||||
private void surround$exitCarryThree(@Surround.Carry String label, @Surround.Carry long stamp, @Surround.Carry int count) {
|
||||
Trace.add("exit " + count + " " + stamp + " " + label);
|
||||
}
|
||||
|
||||
@Surround(method = "carryUninitialized(I)I", id = "carryUninitialized")
|
||||
private void surround$enterCarryUninitialized(int a) {
|
||||
@Surround.Carry String label = new String(a > 0 ? "pos" : "neg");
|
||||
Trace.add("enter " + label);
|
||||
}
|
||||
|
||||
@Surround.Finally("carryUninitialized")
|
||||
private void surround$exitCarryUninitialized(@Surround.Carry String label) {
|
||||
Trace.add("exit " + label);
|
||||
}
|
||||
|
||||
@Surround(method = "carryGuarded(I)I", id = "carryGuarded")
|
||||
private void surround$enterCarryGuarded(int a) {
|
||||
@Surround.Carry int state = 0;
|
||||
try {
|
||||
state = WholeTarget.risky(a);
|
||||
} catch (IllegalArgumentException e) {
|
||||
state = -1;
|
||||
Trace.add("recovered");
|
||||
}
|
||||
Trace.add("enter " + state);
|
||||
}
|
||||
|
||||
@Surround.Finally("carryGuarded")
|
||||
private void surround$exitCarryGuarded(@Surround.Carry int flag) {
|
||||
Trace.add("exit " + flag);
|
||||
}
|
||||
|
||||
@Surround(method = "carrySplitAtEnd(I)I", id = "carrySplitAtEnd")
|
||||
private void surround$enterCarrySplitAtEnd(int a) {
|
||||
@Surround.Carry int flag;
|
||||
if (a > 0) {
|
||||
flag = 1;
|
||||
} else {
|
||||
flag = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("carrySplitAtEnd")
|
||||
private void surround$exitCarrySplitAtEnd(@Surround.Carry int flag) {
|
||||
Trace.add("exit " + flag);
|
||||
}
|
||||
|
||||
@Surround(method = "carryWideBeforeFrame(I)I", id = "carryWideBeforeFrame")
|
||||
private void surround$enterCarryWideBeforeFrame(int a) {
|
||||
@Surround.Carry long stamp = a;
|
||||
@Surround.Carry int count = a + 1;
|
||||
if (a > 0) {
|
||||
Trace.add("positive");
|
||||
}
|
||||
}
|
||||
|
||||
@Surround.Finally("carryWideBeforeFrame")
|
||||
private void surround$exitCarryWideBeforeFrame(@Surround.Carry long stamp, @Surround.Carry int count) {
|
||||
Trace.add("exit " + stamp + " " + count);
|
||||
}
|
||||
|
||||
@Surround(method = "carryArrays(I)I", id = "carryArrays")
|
||||
private void surround$enterCarryArrays(int a) {
|
||||
@Surround.Carry("elements") int[] elements = {a};
|
||||
String @Surround.Carry("names") [] names = {"n" + a};
|
||||
}
|
||||
|
||||
@Surround.Finally("carryArrays")
|
||||
private void surround$exitCarryArrays(@Surround.Carry("names") String[] names, @Surround.Carry("elements") int[] elements) {
|
||||
Trace.add("exit " + names[0] + " " + elements[0]);
|
||||
}
|
||||
|
||||
@Surround(method = "reused(I)I", id = "reused")
|
||||
private void surround$enterReused(int a) {
|
||||
{
|
||||
int tmp = a + 5;
|
||||
Trace.add("tmp " + tmp);
|
||||
}
|
||||
@Surround.Carry long prev = a + 7;
|
||||
Trace.add("enter " + prev);
|
||||
}
|
||||
|
||||
@Surround.Finally("reused")
|
||||
private void surround$exitPrev(@Surround.Carry long prev) {
|
||||
Trace.add("exit " + prev);
|
||||
}
|
||||
|
||||
@Surround(method = "throwWide(I)V", id = "throwWide")
|
||||
private void surround$enterThrowWide(int a) {
|
||||
@Surround.Carry long first = a + 1L;
|
||||
String scratch = "s" + a;
|
||||
@Surround.Carry double second = a / 2.0d;
|
||||
@Surround.Carry int third = a;
|
||||
Trace.add("enter " + first + " " + second + " " + third + " " + scratch);
|
||||
}
|
||||
|
||||
@Surround.Catch("throwWide")
|
||||
private void surround$caughtThrowWide(Throwable e, @Surround.Carry long first, @Surround.Carry double second, @Surround.Carry int third) {
|
||||
Trace.add("caught " + first + " " + second + " " + third);
|
||||
}
|
||||
|
||||
@Surround.Finally("throwWide")
|
||||
private void surround$exitThrowWide(@Surround.Carry double second, @Surround.Carry int third, @Surround.Carry long first) {
|
||||
Trace.add("exit " + first + " " + second + " " + third);
|
||||
}
|
||||
|
||||
@Surround(method = "throwObject(I)V", id = "throwObject")
|
||||
private void surround$enterThrowObject(int a) {
|
||||
@Surround.Carry Object prev = WholeTarget.asObject("o" + a);
|
||||
Trace.add("enter " + prev);
|
||||
}
|
||||
|
||||
@Surround.Catch("throwObject")
|
||||
private void surround$caughtThrowObject(IllegalStateException e, @Surround.Carry String prev) {
|
||||
Trace.add("caught " + prev.length() + " " + prev);
|
||||
throw new UnsupportedOperationException("replaced", e);
|
||||
}
|
||||
|
||||
@Surround.Finally("throwObject")
|
||||
private void surround$exitThrowObject(@Surround.Carry String prev) {
|
||||
Trace.add("exit " + prev.length() + " " + prev);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration.mixin;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.WholeTarget;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(value = WholeTarget.class, priority = 1100, remap = false)
|
||||
public class MixinWholeTargetHigh {
|
||||
|
||||
@Surround(method = "priority(I)I")
|
||||
private void surround$enterHigh(int a) {
|
||||
Trace.add("enterHigh");
|
||||
}
|
||||
|
||||
@Surround.Return
|
||||
private int surround$returnHigh(int result) {
|
||||
Trace.add("returnHigh " + result);
|
||||
return result * 10;
|
||||
}
|
||||
|
||||
@Surround.Finally
|
||||
private void surround$exitHigh() {
|
||||
Trace.add("exitHigh");
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.integration.mixinextras;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.Callee;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.integration.WrapTarget;
|
||||
import com.gtnewhorizons.angelica.experimental.surround.targets.Trace;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalIntRef;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
import static com.gtnewhorizons.angelica.experimental.surround.integration.CallTarget.TWICE;
|
||||
|
||||
@Mixin(value = WrapTarget.class, remap = false)
|
||||
public class MixinWrapTarget {
|
||||
|
||||
@Surround(method = "wrapped", id = "wrapped")
|
||||
private void surround$enterWrapped(int a) {
|
||||
Trace.add("entry " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("wrapped")
|
||||
private void surround$exitWrapped() {
|
||||
Trace.add("finally");
|
||||
}
|
||||
|
||||
@WrapMethod(method = "wrapped")
|
||||
private int wrapWrapped(int a, Operation<Integer> original) {
|
||||
Trace.add("wrap-before " + a);
|
||||
final int result = original.call(a);
|
||||
Trace.add("wrap-after " + result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@WrapOperation(method = "originalOperands", at = @At(value = "INVOKE", target = TWICE))
|
||||
private int wrapOperation$shiftArgument(Callee callee, int a, Operation<Integer> original) {
|
||||
Trace.add("wrap " + a);
|
||||
return original.call(callee, a + 100);
|
||||
}
|
||||
|
||||
@Surround(method = "originalOperands", at = @At(value = "INVOKE", target = TWICE), id = "originalOperands")
|
||||
private void surround$enterOriginalOperands(Callee callee, int a) {
|
||||
Trace.add("enter " + a);
|
||||
}
|
||||
|
||||
@Surround.Finally("originalOperands")
|
||||
private void surround$exitOriginalOperands(Callee callee, int a) {
|
||||
Trace.add("exit " + a);
|
||||
}
|
||||
|
||||
@WrapMethod(method = "shifted")
|
||||
private int wrapShifted(int a, Operation<Integer> original, @Share("seen") LocalIntRef seen) {
|
||||
seen.set(a);
|
||||
Trace.add("wrap " + a);
|
||||
return original.call(a);
|
||||
}
|
||||
|
||||
@Surround(method = "shifted", at = @At(value = "INVOKE", target = TWICE), id = "shifted")
|
||||
private void surround$enterShifted(Callee callee, int value, @Surround.Local(ordinal = 1) int local, @Surround.Local String label) {
|
||||
Trace.add("enterShifted " + value + " " + local + " " + label);
|
||||
}
|
||||
|
||||
@Surround.Finally("shifted")
|
||||
private void surround$exitShifted(@Surround.Local(name = "local") int local, @Surround.Local String label) {
|
||||
Trace.add("exitShifted " + local + " " + label);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.gtnewhorizons.angelica.experimental.surround.targets;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
public final class Trace {
|
||||
|
||||
private static final List<String> EVENTS = new ArrayList<>();
|
||||
private static long counter;
|
||||
|
||||
private Trace() {
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
EVENTS.clear();
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
public static void add(String event) {
|
||||
EVENTS.add(event);
|
||||
}
|
||||
|
||||
public static void assertEvents(String... expected) {
|
||||
assertArrayEquals(expected, EVENTS.toArray(new String[EVENTS.size()]), EVENTS.toString());
|
||||
}
|
||||
|
||||
public static long nextToken() {
|
||||
return ++counter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// class version 52.0 (52)
|
||||
// access flags 0x21
|
||||
public class com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget {
|
||||
|
||||
|
||||
// access flags 0x1
|
||||
public valueBelow(I)I
|
||||
TRYCATCHBLOCK L0 L1 L2 java/lang/IllegalStateException
|
||||
TRYCATCHBLOCK L3 L4 L5 null
|
||||
TRYCATCHBLOCK L0 L1 L5 null
|
||||
L6
|
||||
ILOAD 1
|
||||
ALOAD 0
|
||||
GETFIELD com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.callee : Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;
|
||||
ILOAD 1
|
||||
ISTORE 3
|
||||
ASTORE 2
|
||||
ISTORE 4
|
||||
L7
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.nextToken ()J
|
||||
LSTORE 5
|
||||
L8
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.md$jvmdowngrader$concat$surround$enterValueBelow$1$3f (IJ)Ljava/lang/String;
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.add (Ljava/lang/String;)V
|
||||
L9
|
||||
ILOAD 4
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
L0
|
||||
INVOKEVIRTUAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee.explode (I)I
|
||||
L1
|
||||
ALOAD 0
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueBelow (Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)V
|
||||
GOTO L10
|
||||
L2
|
||||
FRAME FULL [com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget I com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee I I J] [java/lang/IllegalStateException]
|
||||
ASTORE 7
|
||||
ILOAD 4
|
||||
L3
|
||||
ALOAD 0
|
||||
ALOAD 7
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$handleValueBelow (Ljava/lang/IllegalStateException;Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)I
|
||||
L4
|
||||
ALOAD 0
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueBelow (Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)V
|
||||
GOTO L10
|
||||
L5
|
||||
FRAME SAME1 java/lang/Throwable
|
||||
ASTORE 7
|
||||
ALOAD 0
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 5
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueBelow (Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)V
|
||||
ALOAD 7
|
||||
ATHROW
|
||||
L10
|
||||
FRAME FULL [com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget I com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee I I J] [I I]
|
||||
BIPUSH 10
|
||||
IMUL
|
||||
IADD
|
||||
IRETURN
|
||||
L11
|
||||
LOCALVARIABLE this Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget; L6 L11 0
|
||||
LOCALVARIABLE a I L6 L11 1
|
||||
MAXSTACK = 7
|
||||
MAXLOCALS = 8
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// class version 52.0 (52)
|
||||
// access flags 0x21
|
||||
public class com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget {
|
||||
|
||||
|
||||
// access flags 0x1
|
||||
public valueSkip(I)I
|
||||
TRYCATCHBLOCK L0 L1 L2 null
|
||||
L3
|
||||
ALOAD 0
|
||||
GETFIELD com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.callee : Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;
|
||||
ILOAD 1
|
||||
ISTORE 3
|
||||
ASTORE 2
|
||||
L4
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.nextToken ()J
|
||||
LSTORE 4
|
||||
L5
|
||||
ILOAD 3
|
||||
IFGE L6
|
||||
ICONST_1
|
||||
GOTO L7
|
||||
L6
|
||||
FRAME APPEND [com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee I J]
|
||||
ICONST_0
|
||||
L7
|
||||
FRAME SAME1 I
|
||||
ISTORE 6
|
||||
L8
|
||||
ILOAD 3
|
||||
LLOAD 4
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.md$jvmdowngrader$concat$surround$enterValueSkip$1$50 (IJ)Ljava/lang/String;
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.add (Ljava/lang/String;)V
|
||||
L9
|
||||
ILOAD 6
|
||||
IFEQ L10
|
||||
L0
|
||||
ALOAD 0
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
LLOAD 4
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$skippedValueSkip (Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee;IJ)I
|
||||
GOTO L1
|
||||
L10
|
||||
FRAME APPEND [I]
|
||||
ALOAD 2
|
||||
ILOAD 3
|
||||
INVOKEVIRTUAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget$Callee.twice (I)I
|
||||
L1
|
||||
FRAME SAME1 I
|
||||
ALOAD 0
|
||||
LLOAD 4
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueSkip (J)V
|
||||
GOTO L11
|
||||
L2
|
||||
FRAME SAME1 java/lang/Throwable
|
||||
ASTORE 7
|
||||
ALOAD 0
|
||||
LLOAD 4
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/CallTarget.surround$exitValueSkip (J)V
|
||||
ALOAD 7
|
||||
ATHROW
|
||||
L11
|
||||
FRAME SAME1 I
|
||||
IRETURN
|
||||
L12
|
||||
LOCALVARIABLE this Lcom/gtnewhorizons/angelica/experimental/surround/integration/CallTarget; L3 L12 0
|
||||
LOCALVARIABLE a I L3 L12 1
|
||||
MAXSTACK = 5
|
||||
MAXLOCALS = 8
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// class version 52.0 (52)
|
||||
// access flags 0x21
|
||||
public class com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget {
|
||||
|
||||
|
||||
// access flags 0x1
|
||||
public skipAndReturn(Ljava/lang/String;)Ljava/lang/String;
|
||||
TRYCATCHBLOCK L0 L1 L2 java/lang/Throwable
|
||||
TRYCATCHBLOCK L3 L4 L5 null
|
||||
TRYCATCHBLOCK L1 L6 L5 null
|
||||
TRYCATCHBLOCK L0 L1 L5 null
|
||||
L7
|
||||
ALOAD 1
|
||||
INVOKEVIRTUAL java/lang/String.isEmpty ()Z
|
||||
ISTORE 2
|
||||
L8
|
||||
ALOAD 1
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.md$jvmdowngrader$concat$surround$enterSkipAndReturn$1 (Ljava/lang/String;)Ljava/lang/String;
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.add (Ljava/lang/String;)V
|
||||
L9
|
||||
ILOAD 2
|
||||
IFEQ L10
|
||||
L0
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$skippedSkipAndReturn (Ljava/lang/String;)Ljava/lang/String;
|
||||
GOTO L6
|
||||
L10
|
||||
FRAME APPEND [I]
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.skipAndReturn$surround (Ljava/lang/String;)Ljava/lang/String;
|
||||
L1
|
||||
ASTORE 4
|
||||
ALOAD 0
|
||||
ALOAD 4
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$returnSkipAndReturn (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
|
||||
L6
|
||||
FRAME SAME1 java/lang/String
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$exitSkipAndReturn (Ljava/lang/String;)V
|
||||
GOTO L11
|
||||
L2
|
||||
FRAME SAME1 java/lang/Throwable
|
||||
ASTORE 3
|
||||
L3
|
||||
ALOAD 0
|
||||
ALOAD 3
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$caughtSkipAndReturn (Ljava/lang/Throwable;)V
|
||||
L4
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$exitSkipAndReturn (Ljava/lang/String;)V
|
||||
ALOAD 3
|
||||
ATHROW
|
||||
L5
|
||||
FRAME SAME1 java/lang/Throwable
|
||||
ASTORE 3
|
||||
ALOAD 0
|
||||
ALOAD 1
|
||||
INVOKESPECIAL com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.surround$exitSkipAndReturn (Ljava/lang/String;)V
|
||||
ALOAD 3
|
||||
ATHROW
|
||||
L11
|
||||
FRAME SAME1 java/lang/String
|
||||
ARETURN
|
||||
L12
|
||||
LOCALVARIABLE this Lcom/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget; L7 L12 0
|
||||
LOCALVARIABLE a Ljava/lang/String; L7 L12 1
|
||||
LOCALVARIABLE skip Z L8 L9 2
|
||||
LOCALVARIABLE surround$error Ljava/lang/Throwable; L2 L12 3
|
||||
LOCALVARIABLE surround$result Ljava/lang/String; L1 L6 4
|
||||
MAXSTACK = 3
|
||||
MAXLOCALS = 5
|
||||
|
||||
// access flags 0x2
|
||||
private skipAndReturn$surround(Ljava/lang/String;)Ljava/lang/String;
|
||||
L0
|
||||
ALOAD 1
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.jvmdowngrader$concat$skipAndReturn$1 (Ljava/lang/String;)Ljava/lang/String;
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/targets/Trace.add (Ljava/lang/String;)V
|
||||
L1
|
||||
ALOAD 1
|
||||
INVOKESTATIC com/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget.jvmdowngrader$concat$skipAndReturn$2 (Ljava/lang/String;)Ljava/lang/String;
|
||||
ARETURN
|
||||
L2
|
||||
LOCALVARIABLE this Lcom/gtnewhorizons/angelica/experimental/surround/integration/WholeTarget; L0 L2 0
|
||||
LOCALVARIABLE a Ljava/lang/String; L0 L2 1
|
||||
MAXSTACK = 1
|
||||
MAXLOCALS = 2
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "com.gtnewhorizons.angelica.experimental.surround.integration.mixinextras",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"MixinWrapTarget"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "com.gtnewhorizons.angelica.experimental.surround.integration.mixin",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"MixinCallTarget",
|
||||
"MixinWholeTarget",
|
||||
"MixinWholeTargetHigh"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
extensions.getByType<JavaPluginExtension>().apply {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "GTNH Maven"
|
||||
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
|
||||
}
|
||||
maven {
|
||||
name = "Minecraft Libraries"
|
||||
url = uri("https://libraries.minecraft.net")
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
dependencies {
|
||||
add("shadowImplementation", project(":experimental-surround-runtime")) { isTransitive = false }
|
||||
add("mixinAnnotationProcessor", project(":experimental-surround-ap"))
|
||||
}
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
minimize {
|
||||
exclude(project(":experimental-surround-runtime"))
|
||||
}
|
||||
relocate("com.gtnewhorizons.angelica.experimental.surround", "com.gtnewhorizons.angelica.experimental.surround")
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
include("experimental-surround-runtime", "experimental-surround-ap")
|
||||
project(":experimental-surround-runtime").projectDir = file("runtime")
|
||||
project(":experimental-surround-ap").projectDir = file("ap")
|
||||
@@ -0,0 +1,366 @@
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`java-test-fixtures`
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
apply(plugin = "xyz.wagyourtail.jvmdowngrader")
|
||||
|
||||
version = rootProject.version
|
||||
|
||||
val lwjglNatives: String by extra
|
||||
|
||||
java {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
// RFG variant attributes: the root Angelica project uses RetroFuturaGradle which expects
|
||||
// these attributes on dependencies. Without them, Gradle can't resolve the subproject variant.
|
||||
val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation", String::class.java)
|
||||
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
|
||||
|
||||
configurations {
|
||||
listOf("apiElements", "runtimeElements", "testFixturesApiElements", "testFixturesRuntimeElements").forEach { name ->
|
||||
named(name) {
|
||||
attributes {
|
||||
attribute(rfgObfAttr, "mcp")
|
||||
attribute(rfgTransformedAttr, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compile stubs: MC class signatures needed to resolve GTNHLib's type hierarchy.
|
||||
// Not included in the published jar - real MC classes are on the classpath at runtime.
|
||||
sourceSets {
|
||||
create("stubs") {
|
||||
java.srcDir("src/stubs/java")
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude stubs from all output jars
|
||||
tasks.withType<Jar>().configureEach {
|
||||
exclude("net/minecraft/**")
|
||||
}
|
||||
|
||||
val stubsElements by configurations.creating {
|
||||
isCanBeConsumed = true
|
||||
isCanBeResolved = false
|
||||
attributes {
|
||||
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, Usage.JAVA_RUNTIME))
|
||||
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category::class.java, Category.LIBRARY))
|
||||
attribute(rfgObfAttr, "mcp")
|
||||
attribute(rfgTransformedAttr, true)
|
||||
}
|
||||
outgoing {
|
||||
capability("${project.group}:glsm-stubs:${project.version}")
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
add(stubsElements.name, sourceSets["stubs"].java.classesDirectory) {
|
||||
type = "java-classes-directory"
|
||||
builtBy(tasks.named("stubsClasses"))
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "Mojang"
|
||||
url = uri("https://libraries.minecraft.net")
|
||||
}
|
||||
maven {
|
||||
name = "GTNH Maven"
|
||||
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
|
||||
}
|
||||
maven {
|
||||
name = "Forge"
|
||||
url = uri("https://maven.minecraftforge.net/")
|
||||
}
|
||||
maven {
|
||||
name = "taumc"
|
||||
url = uri("https://maven.taumc.org/releases")
|
||||
}
|
||||
maven {
|
||||
name = "WagYourTail"
|
||||
url = uri("https://maven.wagyourtail.xyz/releases")
|
||||
content { includeGroup("xyz.wagyourtail.jvmdowngrader") }
|
||||
}
|
||||
maven {
|
||||
name = "Sonatype Snapshots"
|
||||
url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
}
|
||||
maven {
|
||||
name = "Maven Central Snapshots"
|
||||
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
|
||||
}
|
||||
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
val spirvTestRuntime by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// MC compile stubs
|
||||
compileOnly(sourceSets["stubs"].output)
|
||||
|
||||
// LWJGL
|
||||
compileOnly(libs.lwjgl2)
|
||||
compileOnly(libs.lwjgl2.util)
|
||||
|
||||
compileOnly(libs.asm.tree.compile)
|
||||
|
||||
// Our Deps
|
||||
api(libs.gtnhlib) { artifact { classifier = "dev" } }
|
||||
api(libs.eventbus)
|
||||
|
||||
compileOnly(libs.celeritas.common) { isTransitive = false }
|
||||
api(libs.glsl.transformation.lib) { exclude(module = "antlr4") }
|
||||
api(libs.antlr4.runtime)
|
||||
implementation(libs.jcpp)
|
||||
|
||||
// shaderc + SPIRV-Cross for the SpirvShaderTranslator (GLSL -> SPIR-V -> GLSL ES).
|
||||
// TODO: Remove
|
||||
compileOnly(libs.lwjgl3.shaderc)
|
||||
compileOnly(libs.lwjgl3.spvc)
|
||||
testImplementation(libs.lwjgl3.shaderc)
|
||||
testImplementation(libs.lwjgl3.spvc)
|
||||
// Host natives so the JNI wrapper can find libshaderc / libspirv-cross / liblwjgl.
|
||||
testRuntimeOnly(libs.lwjgl3.shaderc) { artifact { classifier = lwjglNatives } }
|
||||
testRuntimeOnly(libs.lwjgl3.spvc) { artifact { classifier = lwjglNatives } }
|
||||
testRuntimeOnly(libs.lwjgl3)
|
||||
testRuntimeOnly(libs.lwjgl3) { artifact { classifier = lwjglNatives } }
|
||||
spirvTestRuntime(libs.lwjgl3.opengl)
|
||||
// @Lwjgl3Aware annotation
|
||||
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
|
||||
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
|
||||
|
||||
compileOnly(libs.lombok) { isTransitive = false }
|
||||
annotationProcessor(libs.lombok)
|
||||
compileOnly(libs.annotations)
|
||||
compileOnly(libs.log4j.api)
|
||||
|
||||
// Deps (normally from MC) - compile only, but needed at test runtime
|
||||
compileOnly(libs.guava)
|
||||
testRuntimeOnly(libs.guava)
|
||||
testRuntimeOnly(libs.fastutil)
|
||||
testRuntimeOnly(libs.joml) { isTransitive = false }
|
||||
|
||||
testFixturesCompileOnly(sourceSets["stubs"].output)
|
||||
testFixturesApi(platform(libs.junit.bom))
|
||||
testFixturesApi(libs.junit.jupiter.api)
|
||||
testFixturesApi(libs.lwjgl2)
|
||||
|
||||
// Test
|
||||
testImplementation(sourceSets["stubs"].output)
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(libs.asm.tree.test)
|
||||
testImplementation(libs.lwjgl2)
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testRuntimeOnly(libs.log4j.core)
|
||||
testImplementation(libs.celeritas.common) { isTransitive = false }
|
||||
testRuntimeOnly(libs.jvmdowngrader.java.api) { artifact { classifier = "downgraded-8" } }
|
||||
testRuntimeOnly(libs.commons.lang3)
|
||||
}
|
||||
|
||||
val depsToDowngrade by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
depsToDowngrade(libs.eventbus)
|
||||
depsToDowngrade(libs.celeritas.common) { isTransitive = false }
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeFiles>().configureEach {
|
||||
outputs.cacheIf { true }
|
||||
downgradeTo.set(JavaVersion.VERSION_1_8)
|
||||
multiReleaseOriginal.set(false)
|
||||
multiReleaseVersions.set(emptySet())
|
||||
logLevel.set("FATAL")
|
||||
}
|
||||
|
||||
val downgradeDepsForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = depsToDowngrade
|
||||
}
|
||||
|
||||
val downgradeMainClasses by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = sourceSets["main"].output.classesDirs.plus(sourceSets["stubs"].output.classesDirs)
|
||||
classpath = sourceSets["main"].compileClasspath
|
||||
(sourceSets["main"].output.classesDirs.files + sourceSets["stubs"].output.classesDirs.files)
|
||||
.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
|
||||
dependsOn(tasks.named("classes"), tasks.named("stubsClasses"))
|
||||
}
|
||||
|
||||
val downgradeTestFixturesClasses by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = sourceSets["testFixtures"].output.classesDirs
|
||||
classpath = sourceSets["testFixtures"].compileClasspath
|
||||
sourceSets["testFixtures"].output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
|
||||
dependsOn(tasks.named("testFixturesClasses"))
|
||||
}
|
||||
|
||||
val downgradeTestClasses by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = sourceSets["test"].output.classesDirs
|
||||
classpath = sourceSets["test"].compileClasspath
|
||||
sourceSets["test"].output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
|
||||
dependsOn(tasks.named("testClasses"))
|
||||
}
|
||||
|
||||
val GL_TASK_TAGS = mapOf(
|
||||
"glCompatTest" to "gl-compat",
|
||||
"glCoreTest" to "gl-core",
|
||||
"glSharedTest" to "gl-shared",
|
||||
)
|
||||
|
||||
fun Test.configureGlsmJava8() {
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(8)
|
||||
if (System.getProperty("os.name").lowercase().contains("mac")
|
||||
&& System.getProperty("os.arch") == "aarch64") {
|
||||
vendor = JvmVendorSpec.AZUL
|
||||
}
|
||||
}
|
||||
|
||||
dependsOn(downgradeMainClasses, downgradeTestClasses, downgradeTestFixturesClasses, downgradeDepsForTest)
|
||||
|
||||
val mainClassesDirs = sourceSets["main"].output.classesDirs
|
||||
.plus(sourceSets["stubs"].output.classesDirs)
|
||||
val testClassesDirs = sourceSets["test"].output.classesDirs
|
||||
|
||||
val downgradedTest = files(downgradeTestClasses.map { it.outputCollection })
|
||||
val downgradedMain = files(downgradeMainClasses.map { it.outputCollection })
|
||||
val downgradedDeps = files(downgradeDepsForTest.map { it.outputCollection })
|
||||
|
||||
setTestClassesDirs(downgradedTest)
|
||||
classpath = downgradedTest
|
||||
.plus(downgradedMain)
|
||||
.plus(downgradedDeps)
|
||||
.plus(files(downgradeTestFixturesClasses.map { it.outputCollection }))
|
||||
.plus(sourceSets["test"].runtimeClasspath.minus(mainClassesDirs).minus(testClassesDirs)
|
||||
.minus(sourceSets["testFixtures"].output.classesDirs).minus(depsToDowngrade))
|
||||
|
||||
val extractNatives = rootProject.tasks.named("extractNatives2")
|
||||
dependsOn(extractNatives)
|
||||
jvmArgs("-Djava.library.path=${extractNatives.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}")
|
||||
if (System.getProperty("os.name").lowercase().contains("mac")) {
|
||||
jvmArgs("-Dapple.awt.UIElement=true")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform {
|
||||
excludeTags = GL_TASK_TAGS.values.toSet() + "lwjgl3"
|
||||
}
|
||||
filter {
|
||||
excludeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
|
||||
}
|
||||
configureGlsmJava8()
|
||||
}
|
||||
|
||||
val glTestTasks = GL_TASK_TAGS.map { (taskName, tag) ->
|
||||
tasks.register<Test>(taskName) {
|
||||
description = "Runs the $tag GL tests in their own JVM, so they get their own Display and GL profile."
|
||||
group = "verification"
|
||||
useJUnitPlatform {
|
||||
includeTags = setOf(tag)
|
||||
}
|
||||
configureGlsmJava8()
|
||||
}
|
||||
}
|
||||
|
||||
(listOf(tasks.test) + glTestTasks).zipWithNext { earlier, later ->
|
||||
later.configure { mustRunAfter(earlier) }
|
||||
}
|
||||
tasks.test { finalizedBy(glTestTasks) }
|
||||
|
||||
val verifyTestsRan by tasks.registering {
|
||||
dependsOn(tasks.test, glTestTasks)
|
||||
}
|
||||
(listOf(tasks.test) + glTestTasks).forEach { testTask ->
|
||||
val name = testTask.name
|
||||
val verify = tasks.register("verify${name.replaceFirstChar { it.uppercase() }}Ran") {
|
||||
val resultsDir = layout.buildDirectory.dir("test-results/$name")
|
||||
dependsOn(testTask)
|
||||
doLast {
|
||||
val dir = resultsDir.get().asFile
|
||||
val xmls = dir.listFiles { f -> f.name.startsWith("TEST-") && f.name.endsWith(".xml") } ?: emptyArray()
|
||||
check(xmls.isNotEmpty()) {
|
||||
":glsm:$name produced no TEST-*.xml in $dir - test task likely went NO-SOURCE"
|
||||
}
|
||||
}
|
||||
}
|
||||
verifyTestsRan.configure { dependsOn(verify) }
|
||||
}
|
||||
tasks.check { dependsOn(verifyTestsRan) }
|
||||
|
||||
val unpackLwjgl3Natives = registerLwjgl3NativesUnpack()
|
||||
|
||||
val spirvTest by tasks.registering(Test::class) {
|
||||
description = "Runs SpirvShaderTranslator* tests against native shaderc + spvc on Java 21."
|
||||
group = "verification"
|
||||
useJUnitPlatform()
|
||||
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
|
||||
testClassesDirs = sourceSets["test"].output.classesDirs
|
||||
classpath = sourceSets["test"].runtimeClasspath.filter { file ->
|
||||
// Strip the LWJGL2 jars - they collide with LWJGL3's sealed org.lwjgl package.
|
||||
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
|
||||
}.plus(spirvTestRuntime)
|
||||
|
||||
filter {
|
||||
includeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
|
||||
includeTestsMatching("com.gtnewhorizons.angelica.glsm.ffp.FFPUniformBlockSpirvLayoutTest")
|
||||
includeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvCompilerDiskCacheTest")
|
||||
}
|
||||
|
||||
// libshaderc.so / libspirv-cross.so are extracted by extractNatives3.
|
||||
val extractNatives3 = rootProject.tasks.named("extractNatives3")
|
||||
dependsOn(extractNatives3)
|
||||
jvmArgs(
|
||||
"-Djava.library.path=${extractNatives3.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}",
|
||||
"--enable-native-access=ALL-UNNAMED"
|
||||
)
|
||||
loadLwjgl3NativesFrom(unpackLwjgl3Natives)
|
||||
|
||||
testLogging {
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("check") { dependsOn(spirvTest) }
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("mavenJava") {
|
||||
groupId = "com.gtnewhorizons.angelica"
|
||||
artifactId = "glsm"
|
||||
version = rootProject.version.toString()
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
if (System.getenv("MAVEN_USER") != null) {
|
||||
maven {
|
||||
name = "GTNHMaven"
|
||||
url = uri(rootProject.findProperty("mavenPublishUrl")?.toString() ?: "https://nexus.gtnewhorizons.com/repository/releases/")
|
||||
credentials {
|
||||
username = System.getenv("MAVEN_USER")
|
||||
password = System.getenv("MAVEN_PASSWORD")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class SystemProperties {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("SystemProperties");
|
||||
|
||||
// Backend selection
|
||||
public static final String KEY_USE_SDL_GPU = "angelica.sdlgpu.enable";
|
||||
public static final Boolean SDL_GPU_OVERRIDE = parseBooleanOverride(KEY_USE_SDL_GPU);
|
||||
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
|
||||
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
|
||||
public static final boolean DISABLE_LTW_WORKAROUND = Boolean.getBoolean("angelica.disableLtwWorkaround");
|
||||
|
||||
// Redirector
|
||||
public static final UnmappedGLMode UNMAPPED_GL = parseEnum("angelica.unmappedGL", UnmappedGLMode.WARN, UnmappedGLMode.WARN, UnmappedGLMode.class);
|
||||
public static final boolean FFP_WARN_ON_UNSUPPORTED = Boolean.getBoolean("angelica.ffp.warnOnUnsupported");
|
||||
|
||||
// SDL-GPU
|
||||
public static final String KEY_SDL_GPU_DRIVER = "angelica.sdlgpu.driver";
|
||||
public static final String SDL_GPU_DRIVER_OVERRIDE = parseStringOverride(KEY_SDL_GPU_DRIVER);
|
||||
public static final String MOLTENVK_DIR = System.getProperty("angelica.moltenvk.dir", "angelica" + File.separator + "natives" + File.separator + "moltenvk");
|
||||
public static final boolean SDL_GPU_DEBUG = Boolean.getBoolean("angelica.sdlgpu.debug");
|
||||
private static final SdlAssertionMode ENCODER_ASSERTIONS = parseEnum("angelica.sdlgpu.encoderAssertions", SdlAssertionMode.OFF, SdlAssertionMode.WARN, SdlAssertionMode.class);
|
||||
public static final boolean SDL_ENCODER_ASSERTIONS = ENCODER_ASSERTIONS != SdlAssertionMode.OFF;
|
||||
public static final boolean SDL_ENCODER_ASSERTIONS_FATAL = ENCODER_ASSERTIONS == SdlAssertionMode.FATAL;
|
||||
public static final boolean SDL_VERIFY_STATE_SYNC = Boolean.getBoolean("angelica.sdlgpu.verifyStateSync");
|
||||
public static final boolean SDL_VERIFY_PER_FRAME_UNIFORM_BLOCK = Boolean.getBoolean("angelica.sdlgpu.verifyPerFrameUniformBlock");
|
||||
public static final int SDL_FRAMES_IN_FLIGHT = Integer.getInteger("angelica.sdlgpu.framesInFlight", 2);
|
||||
public static final boolean DISABLE_SDL_PRESENTER_THREAD = Boolean.getBoolean("angelica.sdlgpu.disablePresenterThread");
|
||||
public static final boolean SDL_DISABLE_IN_PASS_CLEAR = Boolean.getBoolean("angelica.sdlgpu.disableInPassClear");
|
||||
|
||||
// Tracy
|
||||
public static final Boolean TRACY_OVERRIDE = parseBooleanOverride("angelica.tracy");
|
||||
public static final Boolean TRACY_FINE_ZONES_OVERRIDE = parseBooleanOverride("angelica.tracy.fineZones");
|
||||
public static final String TRACY_DIR = System.getProperty("angelica.tracy.dir", "angelica" + File.separator + "natives" + File.separator + "tracy");
|
||||
public static final Integer TRACY_MAX_SRC_LOCS_OVERRIDE = Integer.getInteger("angelica.tracy.maxSrcLocs");
|
||||
|
||||
// Profiling
|
||||
public static final String PROFILE_OPTS = System.getProperty("angelica.profile.opts", "event=wall,interval=5ms,alloc=512k,lock=10ms");
|
||||
public static final String PROFILE_DIR = System.getProperty("angelica.profile.dir", "angelica" + File.separator + "profiles");
|
||||
public static final String PROFILE_OUTPUT = System.getProperty("angelica.profile.output", "");
|
||||
|
||||
// Flyby
|
||||
public static final String FLYBY_ROUTE = System.getProperty("angelica.flyby.route", "");
|
||||
public static final boolean FLYBY_WAIT_FOR_TRACY = Boolean.getBoolean("angelica.flyby.waitForTracy");
|
||||
public static final boolean FLYBY_WAIT_FOR_FOCUS = Boolean.getBoolean("angelica.flyby.waitForFocus");
|
||||
public static final int FLYBY_WARMUP_TICKS = Integer.getInteger("angelica.flyby.warmupTicks", 400);
|
||||
public static final int FLYBY_LENGTH = Integer.getInteger("angelica.flyby.length", 0); // Blocks for moving routes, ticks for stationary
|
||||
public static final double FLYBY_SPEED = parseDouble("angelica.flyby.speed"); // Travel speed in blocks per tick
|
||||
public static final boolean FLYBY_EXIT_WHEN_DONE = Boolean.getBoolean("angelica.flyby.exitWhenDone");
|
||||
public static final long FLYBY_TIME_OF_DAY = Math.floorMod(Long.getLong("angelica.flyby.timeOfDay", 6000L), 24000L);
|
||||
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
|
||||
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
|
||||
public static final Integer FLYBY_DIMENSION = Integer.getInteger("angelica.flyby.dimension");
|
||||
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
|
||||
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
|
||||
public static final FlybyCamera FLYBY_CAMERA = parseEnum("angelica.flyby.camera", FlybyCamera.FIRST_PERSON, FlybyCamera.FIRST_PERSON, FlybyCamera.class);
|
||||
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
|
||||
public static final float FLYBY_PITCH = parseFloat("angelica.flyby.pitch");
|
||||
public static final int FLYBY_SCREENSHOTS = Integer.getInteger("angelica.flyby.screenshots", 0);
|
||||
public static final boolean FLYBY_DEBUG_HUD = Boolean.getBoolean("angelica.flyby.debugHud");
|
||||
public static final String FLYBY_CRASH_TEST = System.getProperty("angelica.flyby.crashTest", "");
|
||||
|
||||
// Debug
|
||||
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
|
||||
public static final boolean DEBUG_MARKERS = Boolean.getBoolean("angelica.debug.markers");
|
||||
public static final boolean DEBUG_F3_DETAIL = Boolean.getBoolean("angelica.debug.f3Detail");
|
||||
public static final boolean ENABLE_TEST_BLOCKS = Boolean.getBoolean("angelica.debug.testBlocks");
|
||||
public static final boolean DUMP_CLASS = Boolean.getBoolean("angelica.debug.dumpClass");
|
||||
private static final boolean DUMP_SHADERS_PROP = Boolean.getBoolean("angelica.debug.dumpShaders");
|
||||
private static final boolean DEBUG_TOOLING_PROP = Boolean.getBoolean("angelica.debug.tooling");
|
||||
public static final boolean REDIRECTOR_LOGSPAM = Boolean.getBoolean("angelica.debug.redirectorLogspam");
|
||||
public static final boolean DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debug.displayLists");
|
||||
public static final boolean FFP_TRACE = Boolean.getBoolean("angelica.debug.ffpTrace");
|
||||
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
|
||||
public static final boolean FORCE_ORPHAN_STREAMING = Boolean.getBoolean("angelica.debug.forceOrphanStreaming");
|
||||
public static final boolean WEATHER_REBUILD_ALWAYS = Boolean.getBoolean("angelica.debug.weatherRebuildAlways");
|
||||
public static final String SHADER_DUMP_ROOT = "angelica_dumps";
|
||||
|
||||
// Set by us, read by celeritas
|
||||
public static final String KEY_CELERITAS_ENABLE_GL_DEBUG = "celeritas.enableGLDebug";
|
||||
public static final String KEY_CELERITAS_LWJGL_SERVICE = "celeritas.lwjglService";
|
||||
|
||||
public static boolean dumpShaders() {
|
||||
return DUMP_SHADERS_PROP || isDeobf();
|
||||
}
|
||||
|
||||
public static boolean debugTooling() {
|
||||
return DEBUG_TOOLING_PROP || isDeobf();
|
||||
}
|
||||
|
||||
public static Path shaderDumpDir(String phase) {
|
||||
return dumpShaders() ? Paths.get(SHADER_DUMP_ROOT, phase) : null;
|
||||
}
|
||||
|
||||
public enum UnmappedGLMode {
|
||||
OFF,
|
||||
WARN,
|
||||
FAIL,
|
||||
STRICT;
|
||||
|
||||
public boolean detects() {
|
||||
return this != OFF;
|
||||
}
|
||||
|
||||
public boolean failsUnmapped() {
|
||||
return this == FAIL || this == STRICT;
|
||||
}
|
||||
|
||||
public boolean failsAwareUnroutable() {
|
||||
return this == STRICT;
|
||||
}
|
||||
}
|
||||
|
||||
private enum SdlAssertionMode {
|
||||
OFF,
|
||||
WARN,
|
||||
FATAL
|
||||
}
|
||||
|
||||
public enum FlybyPacing {
|
||||
UNCAPPED,
|
||||
CONFIGURED
|
||||
}
|
||||
|
||||
public enum FlybyCamera {
|
||||
FIRST_PERSON,
|
||||
THIRD_PERSON_BACK,
|
||||
THIRD_PERSON_FRONT
|
||||
}
|
||||
|
||||
public enum FlybyWeather {
|
||||
CLEAR,
|
||||
RAIN,
|
||||
THUNDER;
|
||||
|
||||
public boolean isRaining() {
|
||||
return this != CLEAR;
|
||||
}
|
||||
|
||||
public boolean isThundering() {
|
||||
return this == THUNDER;
|
||||
}
|
||||
}
|
||||
|
||||
private static <E extends Enum<E>> E parseEnum(String key, E whenAbsent, E whenInvalid, Class<E> type) {
|
||||
final String raw = System.getProperty(key);
|
||||
if (raw == null || raw.isEmpty()) return whenAbsent;
|
||||
try {
|
||||
return Enum.valueOf(type, raw.trim().toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.warn("Unrecognized value '{}' for -D{}; using {}. Valid values: {}", raw, key, whenInvalid, Arrays.toString(type.getEnumConstants()));
|
||||
return whenInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
private static Boolean parseBooleanOverride(String key) {
|
||||
final String raw = System.getProperty(key);
|
||||
return raw == null || raw.isEmpty() ? null : Boolean.parseBoolean(raw);
|
||||
}
|
||||
|
||||
private static String parseStringOverride(String key) {
|
||||
final String raw = System.getProperty(key);
|
||||
return raw == null || raw.isEmpty() ? null : raw;
|
||||
}
|
||||
|
||||
private static double parseDouble(String key) {
|
||||
final String raw = System.getProperty(key);
|
||||
if (raw == null || raw.isEmpty()) return 0.0D;
|
||||
try {
|
||||
return Double.parseDouble(raw);
|
||||
} catch (NumberFormatException e) {
|
||||
return 0.0D;
|
||||
}
|
||||
}
|
||||
|
||||
private static float parseFloat(String key) {
|
||||
final String raw = System.getProperty(key);
|
||||
if (raw == null || raw.isEmpty()) return Float.NaN;
|
||||
try {
|
||||
return Float.parseFloat(raw);
|
||||
} catch (NumberFormatException e) {
|
||||
return Float.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDeobf() {
|
||||
return Launch.blackboard != null && Boolean.TRUE.equals(Launch.blackboard.get("fml.deobfuscatedEnvironment"));
|
||||
}
|
||||
|
||||
private SystemProperties() {}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/** Decides whether instrumentation is worth emitting this frame. */
|
||||
public final class CaptureGate {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
private static final boolean IS_LINUX = System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("linux");
|
||||
private static final Path PROC_SELF_MAPS = Paths.get("/proc/self/maps");
|
||||
private static final String[] CAPTURE_ENV = { "RENDERDOC_CAPTURE_KEYPRESS", "ENABLE_VULKAN_RENDERDOC_CAPTURE", "RENDERDOC_HOOK_EGL" };
|
||||
private static final String[] CAPTURE_LIBS = { "renderdoc", "glxtrace", "egltrace", "apitrace" };
|
||||
public static final boolean FORCED = SystemProperties.DEBUG_MARKERS || SystemProperties.LWJGL_DEBUG;
|
||||
public static final boolean TOOL_ATTACHED = detectCaptureTool();
|
||||
public static boolean markersThisFrame = true;
|
||||
|
||||
private CaptureGate() {}
|
||||
|
||||
public static void refresh() {
|
||||
markersThisFrame = gateValue(FORCED, TOOL_ATTACHED, Tracy.isConnected());
|
||||
}
|
||||
|
||||
static boolean gateValue(boolean forced, boolean toolAttached, boolean tracyConnected) {
|
||||
return forced || toolAttached || tracyConnected;
|
||||
}
|
||||
|
||||
public static boolean enabledAtStartup() {
|
||||
return FORCED || TOOL_ATTACHED;
|
||||
}
|
||||
|
||||
private static boolean detectCaptureTool() {
|
||||
if (detectFromEnv(System::getenv)) {
|
||||
LOGGER.info("Capture tool detected via environment; instrumentation enabled");
|
||||
return true;
|
||||
}
|
||||
if (!IS_LINUX) return false;
|
||||
try {
|
||||
if (detectFromMaps(Files.readAllLines(PROC_SELF_MAPS, StandardCharsets.ISO_8859_1))) {
|
||||
LOGGER.info("Capture tool library detected in process map; instrumentation enabled");
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not read {} for capture tool detection: {}", PROC_SELF_MAPS, e.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static boolean detectFromEnv(UnaryOperator<String> env) {
|
||||
for (String key : CAPTURE_ENV) {
|
||||
if (env.apply(key) != null) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static boolean detectFromMaps(List<String> mapLines) {
|
||||
for (int i = 0; i < mapLines.size(); i++) {
|
||||
final String line = mapLines.get(i).toLowerCase(Locale.ROOT);
|
||||
for (String lib : CAPTURE_LIBS) {
|
||||
if (line.contains(lib)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
|
||||
public final class ClipPlaneBooleanState extends BooleanStateStack {
|
||||
|
||||
private final GLContextState owner;
|
||||
private final int index;
|
||||
|
||||
ClipPlaneBooleanState(GLContextState owner, int index, int glCap) {
|
||||
super(glCap);
|
||||
this.owner = owner;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnabledChanged() {
|
||||
if (isEnabled()) {
|
||||
owner.clipPlaneEnabledMask |= (1 << index);
|
||||
} else {
|
||||
owner.clipPlaneEnabledMask &= ~(1 << index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,682 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderDiskCache;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import org.taumc.glsl.ShaderParser;
|
||||
import org.taumc.glsl.Transformer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* Compat transformation for mod shaders running under core profile.
|
||||
*
|
||||
* <p>Handles:
|
||||
* <ul>
|
||||
* <li>Matrix builtin replacement (gl_ModelViewMatrix, gl_ProjectionMatrix, etc.)</li>
|
||||
* <li>Vertex attribute replacement (gl_Vertex, gl_Color, gl_MultiTexCoord0/1, gl_Normal)</li>
|
||||
* <li>gl_TexCoord[N] varying array -> per-index in/out declarations</li>
|
||||
* <li>Texture function renames (texture2D -> texture, etc.)</li>
|
||||
* <li>Fragment output handling (gl_FragColor -> layout-qualified out declarations)</li>
|
||||
* <li>Fog builtins (gl_Fog, gl_FogFragCoord)</li>
|
||||
* <li>gl_FrontColor -> local variable in vertex shaders</li>
|
||||
* <li>shadow2D/shadow2DLod -> texture/textureLod with vec4 wrapping</li>
|
||||
* <li>Version upgrade to 330 core minimum</li>
|
||||
* <li>Reserved word pre-parse renaming (texture-as-variable, sample, etc.)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CompatShaderTransformer {
|
||||
|
||||
private static final Pattern VERSION_PATTERN = Pattern.compile("#version[ \\t]+(\\d+)(?:[ \\t]+(\\w+))?");
|
||||
|
||||
private static final Pattern DEFINE_PATTERN = Pattern.compile("^\\s*#\\s*define\\s+.+$", Pattern.MULTILINE);
|
||||
|
||||
/** Compat builtins that trigger AST transformation. */
|
||||
private static final Set<String> COMPAT_BUILTINS = Set.of(
|
||||
"gl_ModelView", "gl_Projection", "gl_NormalMatrix", "gl_TextureMatrix",
|
||||
"gl_FragColor", "gl_Fog", "gl_FrontColor", "gl_Color",
|
||||
"gl_Vertex", "gl_MultiTexCoord", "gl_TexCoord", "gl_Normal", "ftransform",
|
||||
"texture2D", "texture3D", "texelFetch2D", "texelFetch3D", "textureSize2D",
|
||||
"shadow2D", "gl_FrontLightModelProduct",
|
||||
"gl_LightSource", "gl_FrontMaterial"
|
||||
);
|
||||
|
||||
private static final Pattern NEEDS_TRANSFORM_PATTERN = Pattern.compile(
|
||||
String.join("|", COMPAT_BUILTINS) + "|\\b(?:attribute|varying)\\b"
|
||||
);
|
||||
|
||||
private static final Map<String, String> MATRIX_RENAMES = Map.of(
|
||||
"gl_ModelViewMatrix", "angelica_ModelViewMatrix",
|
||||
"gl_ModelViewMatrixInverse", "angelica_ModelViewMatrixInverse",
|
||||
"gl_ProjectionMatrix", "angelica_ProjectionMatrix",
|
||||
"gl_ProjectionMatrixInverse", "angelica_ProjectionMatrixInverse",
|
||||
"gl_NormalMatrix", "angelica_NormalMatrix"
|
||||
);
|
||||
|
||||
private static final AtomicInteger dumpCounter = new AtomicInteger(0);
|
||||
|
||||
private static final int CACHE_SIZE = 256;
|
||||
private record CacheKey(String source, boolean isFragment) {}
|
||||
|
||||
private static final Map<CacheKey, String> cache = Collections.synchronizedMap(
|
||||
new LinkedHashMap<>(CACHE_SIZE, 0.75f, true) {
|
||||
@Override protected boolean removeEldestEntry(Map.Entry<CacheKey, String> eldest) {
|
||||
return size() > CACHE_SIZE;
|
||||
}
|
||||
});
|
||||
|
||||
private record EsCacheKey(String source, int glShaderType, boolean isFragment) {}
|
||||
private static final Map<EsCacheKey, String> esCache = Collections.synchronizedMap(
|
||||
new LinkedHashMap<>(CACHE_SIZE, 0.75f, true) {
|
||||
@Override protected boolean removeEldestEntry(Map.Entry<EsCacheKey, String> eldest) {
|
||||
return size() > CACHE_SIZE;
|
||||
}
|
||||
});
|
||||
|
||||
public static void clearCache() {
|
||||
cache.clear();
|
||||
esCache.clear();
|
||||
}
|
||||
|
||||
public static void prewarm(String source, int glShaderType, boolean isFragment) {
|
||||
if (!RenderSystem.isGLES()) return;
|
||||
final EsCacheKey key = new EsCacheKey(source, glShaderType, isFragment);
|
||||
if (esCache.containsKey(key)) return;
|
||||
transform(source, glShaderType, isFragment);
|
||||
}
|
||||
|
||||
/** Transform a mod shader for core profile; under GLES, also rewrites to ES 320. */
|
||||
public static String transform(String source, boolean isFragment) {
|
||||
return transform(source, isFragment ? GL20.GL_FRAGMENT_SHADER : GL20.GL_VERTEX_SHADER, isFragment);
|
||||
}
|
||||
|
||||
/** Variant carrying the exact GL shader type so the GLES re-emit dispatches to the right shaderc kind (geometry/tess/compute). */
|
||||
public static String transform(String source, int glShaderType, boolean isFragment) {
|
||||
if (!source.isEmpty() && source.charAt(source.length() - 1) == '\0') {
|
||||
source = source.substring(0, source.length() - 1);
|
||||
}
|
||||
final boolean gles = RenderSystem.isGLES();
|
||||
|
||||
if (gles) {
|
||||
final EsCacheKey esKey = new EsCacheKey(source, glShaderType, isFragment);
|
||||
final String esCached = esCache.get(esKey);
|
||||
if (esCached != null) {
|
||||
dumpShader(source, esCached, isFragment, needsTransformation(source));
|
||||
return esCached;
|
||||
}
|
||||
}
|
||||
|
||||
final boolean needsTransform = needsTransformation(source);
|
||||
String result;
|
||||
if (!needsTransform) {
|
||||
result = fixupVersion(source);
|
||||
} else {
|
||||
final CacheKey key = new CacheKey(source, isFragment);
|
||||
final String cached = cache.get(key);
|
||||
if (cached != null) {
|
||||
result = cached;
|
||||
} else {
|
||||
final ShaderDiskCache.Key dk = ShaderDiskCache.isEnabled() ? diskKey(source, isFragment) : null;
|
||||
final String disk = dk != null ? ShaderDiskCache.getString(dk) : null;
|
||||
if (disk != null) {
|
||||
result = disk;
|
||||
cache.put(key, result);
|
||||
} else {
|
||||
try {
|
||||
result = transformInternal(source, isFragment);
|
||||
cache.put(key, result);
|
||||
if (dk != null) ShaderDiskCache.putString(dk, result);
|
||||
} catch (Exception e) {
|
||||
GLStateManager.LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
|
||||
result = fixupVersion(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gles) {
|
||||
final String preGles = result;
|
||||
try {
|
||||
result = toGLES(result, glShaderType, isFragment);
|
||||
} catch (RuntimeException e) {
|
||||
dumpShader(source, preGles, isFragment, needsTransform);
|
||||
throw e;
|
||||
}
|
||||
esCache.put(new EsCacheKey(source, glShaderType, isFragment), result);
|
||||
}
|
||||
|
||||
dumpShader(source, result, isFragment, needsTransform);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int minGlslVersion() {
|
||||
return RENDER_BACKEND != null ? RENDER_BACKEND.getMinGLSLVersion() : 330;
|
||||
}
|
||||
|
||||
static ShaderDiskCache.Key diskKey(String source, boolean isFragment) {
|
||||
return ShaderDiskCache.key("compat").str(source).b(isFragment).i(minGlslVersion());
|
||||
}
|
||||
|
||||
public static boolean isCoreShader(String source) {
|
||||
final Matcher vm = VERSION_PATTERN.matcher(source);
|
||||
if (vm.find()) {
|
||||
final int version = Integer.parseInt(vm.group(1));
|
||||
return version >= 330 && "core".equals(vm.group(2));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean needsTransformation(String source) {
|
||||
if (isCoreShader(source)) return false;
|
||||
|
||||
return NEEDS_TRANSFORM_PATTERN.matcher(source).find();
|
||||
}
|
||||
|
||||
private static String transformInternal(String source, boolean isFragment) {
|
||||
final Matcher versionMatcher = VERSION_PATTERN.matcher(source);
|
||||
int declaredVersion = 110;
|
||||
if (versionMatcher.find()) {
|
||||
declaredVersion = Integer.parseInt(versionMatcher.group(1));
|
||||
}
|
||||
|
||||
final int targetVersion = Math.max(declaredVersion, minGlslVersion());
|
||||
|
||||
// Pre-parse reserved word renaming - prevents ANTLR parse failures
|
||||
source = GlslTransformUtils.replaceTexture(source);
|
||||
source = GlslTransformUtils.renameReservedWords(source, targetVersion);
|
||||
|
||||
final GlslTransformUtils.QuietParse parsed = GlslTransformUtils.parseBothQuiet(source);
|
||||
final Transformer transformer = new Transformer(parsed.full());
|
||||
|
||||
injectMatrixUniforms(transformer);
|
||||
|
||||
transformFog(transformer, isFragment, source);
|
||||
|
||||
// gl_FrontLightModelProduct.sceneColor -> angelica_SceneColor uniform
|
||||
if (source.contains("gl_FrontLightModelProduct")) {
|
||||
transformer.injectVariable("uniform vec4 angelica_SceneColor;");
|
||||
transformer.replaceExpression("gl_FrontLightModelProduct.sceneColor", "angelica_SceneColor");
|
||||
}
|
||||
|
||||
// gl_LightSource[i] -> struct + uniform array (both via injectFunction to keep struct before uniform)
|
||||
if (source.contains("gl_LightSource")) {
|
||||
transformer.injectFunction("struct angelica_LightSourceParameters {"
|
||||
+ "vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;"
|
||||
+ "vec3 spotDirection;float spotExponent;float spotCutoff;float spotCosCutoff;"
|
||||
+ "float constantAttenuation;float linearAttenuation;float quadraticAttenuation;"
|
||||
+ "};");
|
||||
transformer.injectFunction("uniform angelica_LightSourceParameters angelica_LightSource[2];");
|
||||
transformer.rename("gl_LightSource", "angelica_LightSource");
|
||||
}
|
||||
|
||||
// gl_FrontMaterial -> struct + uniform (both via injectFunction to keep struct before uniform)
|
||||
if (source.contains("gl_FrontMaterial")) {
|
||||
transformer.injectFunction("struct angelica_MaterialParameters {"
|
||||
+ "vec4 emission;vec4 ambient;vec4 diffuse;vec4 specular;float shininess;"
|
||||
+ "};");
|
||||
transformer.injectFunction("uniform angelica_MaterialParameters angelica_FrontMaterial;");
|
||||
transformer.rename("gl_FrontMaterial", "angelica_FrontMaterial");
|
||||
}
|
||||
|
||||
// gl_FrontColor (vertex) -> gl_Color (fragment) varying chain
|
||||
// Vertex side is unconditional: fragment may read gl_Color without vertex writing gl_FrontColor.
|
||||
// Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
|
||||
if (!isFragment) {
|
||||
transformer.injectVariable("out vec4 angelica_FrontColor;");
|
||||
transformer.rename("gl_FrontColor", "angelica_FrontColor");
|
||||
transformer.prependMain("angelica_FrontColor = vec4(1.0);");
|
||||
|
||||
final FFPInputs ffp = FFPInputs.detect(source);
|
||||
assignVertexInputLocations(transformer, ffp);
|
||||
transformVertexAttributes(transformer, source, ffp);
|
||||
} else {
|
||||
if (source.contains("gl_Color")) {
|
||||
transformer.injectVariable("in vec4 angelica_FrontColor;");
|
||||
transformer.rename("gl_Color", "angelica_FrontColor");
|
||||
}
|
||||
}
|
||||
|
||||
// gl_TexCoord[N] varying array -> per-index in/out declarations
|
||||
final Set<Integer> texCoordIndices = new HashSet<>();
|
||||
transformer.renameArray("gl_TexCoord", "angelica_TexCoord", texCoordIndices);
|
||||
for (Integer i : texCoordIndices) {
|
||||
final String qualifier = isFragment ? "in" : "out";
|
||||
transformer.injectVariable(qualifier + " vec4 angelica_TexCoord" + i + ";");
|
||||
}
|
||||
|
||||
// Fragment output handling + alpha test discard
|
||||
if (isFragment) {
|
||||
transformFragmentOutputs(transformer);
|
||||
}
|
||||
|
||||
// texture-as-variable collision handling
|
||||
if (transformer.containsCall("texture") && transformer.hasVariable("texture")) {
|
||||
transformer.rename("texture", "gtexture");
|
||||
}
|
||||
if (transformer.hasVariable("angelica_renamed_texture")) {
|
||||
transformer.rename("angelica_renamed_texture", "gtexture");
|
||||
}
|
||||
|
||||
transformer.renameFunctionCall(GlslTransformUtils.TEXTURE_RENAMES);
|
||||
|
||||
transformer.renameAndWrapShadow("shadow2D", "texture");
|
||||
transformer.renameAndWrapShadow("shadow2DLod", "textureLod");
|
||||
|
||||
final String versionDirective = "#version " + targetVersion + " core\n";
|
||||
final String extensions = VERSION_PATTERN.matcher(GlslTransformUtils.getFormattedShader(parsed.pre(), "")).replaceFirst("").trim();
|
||||
|
||||
// Preserve #define directives
|
||||
final StringBuilder defines = new StringBuilder();
|
||||
final Matcher defineMatcher = DEFINE_PATTERN.matcher(source);
|
||||
while (defineMatcher.find()) {
|
||||
defines.append(defineMatcher.group().trim()).append('\n');
|
||||
}
|
||||
|
||||
final String header = versionDirective + (extensions.isEmpty() ? "" : "\n" + extensions) + (defines.isEmpty() ? "" : "\n" + defines);
|
||||
final StringBuilder result = new StringBuilder();
|
||||
transformer.mutateTree(tree -> result.append(GlslTransformUtils.getFormattedShader(tree, header)));
|
||||
|
||||
// Restore pre-parse renames
|
||||
String output = GlslTransformUtils.restoreReservedWords(result.toString());
|
||||
|
||||
// Core profile: attribute -> in, varying -> out (vertex) / in (fragment)
|
||||
output = fixupQualifiers(output, isFragment);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject matrix uniforms and rename compat builtins.
|
||||
*/
|
||||
private static void injectMatrixUniforms(Transformer transformer) {
|
||||
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat3 angelica_NormalMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_LightmapTextureMatrix;");
|
||||
|
||||
transformer.rename(MATRIX_RENAMES);
|
||||
|
||||
// Expression replacements
|
||||
transformer.replaceExpression("gl_ModelViewProjectionMatrix", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix)");
|
||||
transformer.replaceExpression("gl_TextureMatrix[0]", "mat4(1.0)");
|
||||
transformer.replaceExpression("gl_TextureMatrix[1]", "angelica_LightmapTextureMatrix");
|
||||
transformer.replaceExpression(
|
||||
"gl_TextureMatrix",
|
||||
"mat4[8](mat4(1.0), angelica_LightmapTextureMatrix, mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0))");
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform fragment outputs for core profile.
|
||||
*/
|
||||
private static void transformFragmentOutputs(Transformer transformer) {
|
||||
if (transformer.containsCall("gl_FragColor")) {
|
||||
transformer.replaceExpression("gl_FragColor", "gl_FragData[0]");
|
||||
}
|
||||
|
||||
final Set<Integer> found = new HashSet<>();
|
||||
transformer.renameArray("gl_FragData", "angelica_FragData", found);
|
||||
|
||||
for (Integer i : found) {
|
||||
transformer.injectVariable("layout (location = " + i + ") out vec4 angelica_FragData" + i + ";");
|
||||
}
|
||||
|
||||
// Core profile: GL_ALPHA_TEST is removed - inject runtime discard using GLSM-tracked alpha reference (uploaded by CompatUniformManager).
|
||||
if (found.contains(0)) {
|
||||
transformer.injectVariable("uniform float angelica_currentAlphaTest;");
|
||||
transformer.appendMain("if (angelica_FragData0.a <= angelica_currentAlphaTest) discard;");
|
||||
}
|
||||
}
|
||||
|
||||
private static void transformFog(Transformer transformer, boolean isFragment, String source) {
|
||||
// Vertex side is unconditional: fragment may read gl_FogFragCoord without vertex writing it
|
||||
transformer.rename("gl_FogFragCoord", "angelica_FogFragCoord");
|
||||
if (!isFragment) {
|
||||
transformer.injectVariable("out float angelica_FogFragCoord;");
|
||||
transformer.prependMain("angelica_FogFragCoord = 0.0;");
|
||||
} else {
|
||||
if (source.contains("gl_FogFragCoord")) {
|
||||
transformer.injectVariable("in float angelica_FogFragCoord;");
|
||||
}
|
||||
}
|
||||
|
||||
transformer.rename("gl_Fog", "angelica_Fog");
|
||||
transformer.injectVariable("uniform float angelica_FogDensity;");
|
||||
transformer.injectVariable("uniform float angelica_FogStart;");
|
||||
transformer.injectVariable("uniform float angelica_FogEnd;");
|
||||
transformer.injectVariable("uniform vec4 angelica_FogColor;");
|
||||
transformer.injectFunction("struct angelica_FogParameters {vec4 color;float density;float start;float end;float scale;};");
|
||||
transformer.injectFunction("angelica_FogParameters angelica_Fog;");
|
||||
transformer.prependMain("angelica_Fog = angelica_FogParameters("
|
||||
+ "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
|
||||
+ "1.0 / (angelica_FogEnd - angelica_FogStart));");
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace removed FFP vertex attributes with explicit {@code in} declarations at core profile attribute locations.
|
||||
*/
|
||||
private static void transformVertexAttributes(Transformer transformer, String source, FFPInputs ffp) {
|
||||
if (ffp.position()) {
|
||||
transformer.injectVariable("layout(location = 0) in vec4 angelica_Vertex;");
|
||||
transformer.rename("gl_Vertex", "angelica_Vertex");
|
||||
}
|
||||
// gl_Color in vertex shaders is the per-vertex color attribute, distinct from the fragment gl_Color (interpolated gl_FrontColor) handled above
|
||||
if (ffp.color()) {
|
||||
transformer.injectVariable("layout(location = 1) in vec4 angelica_Color;");
|
||||
transformer.rename("gl_Color", "angelica_Color");
|
||||
}
|
||||
if (ffp.uv0()) {
|
||||
transformer.injectVariable("layout(location = 2) in vec4 angelica_MultiTexCoord0;");
|
||||
transformer.rename("gl_MultiTexCoord0", "angelica_MultiTexCoord0");
|
||||
}
|
||||
if (ffp.uv1()) {
|
||||
transformer.injectVariable("layout(location = 3) in vec4 angelica_MultiTexCoord1;");
|
||||
transformer.rename("gl_MultiTexCoord1", "angelica_MultiTexCoord1");
|
||||
}
|
||||
if (ffp.normal()) {
|
||||
transformer.injectVariable("layout(location = 4) in vec3 angelica_Normal;");
|
||||
transformer.rename("gl_Normal", "angelica_Normal");
|
||||
}
|
||||
if (source.contains("ftransform")) {
|
||||
transformer.replaceExpression("ftransform()", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix * angelica_Vertex)");
|
||||
}
|
||||
}
|
||||
|
||||
private static final int MAX_VERTEX_ATTRIBS = 16;
|
||||
|
||||
private record FFPInputs(boolean position, boolean color, boolean uv0, boolean uv1, boolean normal) {
|
||||
static FFPInputs detect(String source) {
|
||||
return new FFPInputs(
|
||||
source.contains("gl_Vertex") || source.contains("ftransform"),
|
||||
source.contains("gl_Color"),
|
||||
source.contains("gl_MultiTexCoord0"),
|
||||
source.contains("gl_MultiTexCoord1"),
|
||||
source.contains("gl_Normal"));
|
||||
}
|
||||
}
|
||||
|
||||
/** Adaptation of Mesa's assign_attribute_or_color_locations */
|
||||
private static void assignVertexInputLocations(Transformer transformer, FFPInputs ffp) {
|
||||
int used = 0;
|
||||
if (ffp.position()) used |= 1 << Usage.POSITION.getAttributeLocation();
|
||||
if (ffp.color()) used |= 1 << Usage.COLOR.getAttributeLocation();
|
||||
if (ffp.uv0()) used |= 1 << Usage.PRIMARY_UV.getAttributeLocation();
|
||||
if (ffp.uv1()) used |= 1 << Usage.SECONDARY_UV.getAttributeLocation();
|
||||
if (ffp.normal()) used |= 1 << Usage.NORMAL.getAttributeLocation();
|
||||
|
||||
record Input(String name, String baseType, String arraySuffix, int slots, int index) {}
|
||||
final List<Input> toAssign = new ArrayList<>();
|
||||
int index = 0;
|
||||
|
||||
for (TerminalNode storage : transformer.collectStorage()) {
|
||||
final int tokenType = storage.getSymbol().getType();
|
||||
if (tokenType != GLSLLexer.ATTRIBUTE && tokenType != GLSLLexer.IN) continue;
|
||||
|
||||
ParseTree node = storage.getParent();
|
||||
while (node != null && !(node instanceof GLSLParser.Single_declarationContext)) {
|
||||
node = node.getParent();
|
||||
}
|
||||
if (!(node instanceof GLSLParser.Single_declarationContext decl)) continue;
|
||||
if (decl.typeless_declaration() == null) continue;
|
||||
|
||||
final GLSLParser.Type_specifierContext typeSpec = decl.fully_specified_type().type_specifier();
|
||||
final String baseType = GlslTransformUtils.getFormattedShader(typeSpec.type_specifier_nonarray(), "").trim();
|
||||
final int typeArraySlots = arraySlots(typeSpec.array_specifier());
|
||||
final String typeArray = arraySuffix(typeSpec.array_specifier());
|
||||
|
||||
if (hasLayoutQualifier(decl)) {
|
||||
final int loc = explicitLocation(decl);
|
||||
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(decl.typeless_declaration().array_specifier());
|
||||
if (loc >= 0) used |= slotMask(slots) << loc;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (GLSLParser.Typeless_declarationContext d : ShaderIndex.declarators((GLSLParser.Init_declarator_listContext) decl.getParent())) {
|
||||
if (d.IDENTIFIER() == null) continue;
|
||||
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(d.array_specifier());
|
||||
final String arraySuffix = typeArray + arraySuffix(d.array_specifier());
|
||||
toAssign.add(new Input(d.IDENTIFIER().getText(), baseType, arraySuffix, slots, index++));
|
||||
}
|
||||
}
|
||||
|
||||
toAssign.sort((a, b) -> a.slots() != b.slots() ? b.slots() - a.slots() : a.index() - b.index());
|
||||
|
||||
for (Input in : toAssign) {
|
||||
final int loc = findAvailableSlots(used, in.slots());
|
||||
if (loc < 0) continue;
|
||||
used |= slotMask(in.slots()) << loc;
|
||||
transformer.removeVariable(in.name());
|
||||
transformer.variable = null;
|
||||
transformer.injectVariable("layout(location = " + loc + ") in " + in.baseType() + " " + in.name() + in.arraySuffix() + ";");
|
||||
}
|
||||
}
|
||||
|
||||
private static String arraySuffix(GLSLParser.Array_specifierContext arraySpec) {
|
||||
return arraySpec != null ? GlslTransformUtils.getFormattedShader(arraySpec, "").trim() : "";
|
||||
}
|
||||
|
||||
private static int arraySlots(GLSLParser.Array_specifierContext arraySpec) {
|
||||
if (arraySpec == null) return 1;
|
||||
int product = 1;
|
||||
for (GLSLParser.DimensionContext dim : arraySpec.dimension()) {
|
||||
if (dim.constant_expression() == null) continue;
|
||||
try {
|
||||
product *= Integer.decode(GlslTransformUtils.getFormattedShader(dim.constant_expression(), "").trim());
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
private static int baseSlots(String type) {
|
||||
final String dims = type.startsWith("dmat") ? type.substring(4) : type.startsWith("mat") ? type.substring(3) : null;
|
||||
if (dims != null && !dims.isEmpty() && Character.isDigit(dims.charAt(0))) {
|
||||
return dims.charAt(0) - '0';
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int slotMask(int slots) {
|
||||
return (slots >= 32) ? -1 : (1 << slots) - 1;
|
||||
}
|
||||
|
||||
private static int findAvailableSlots(int used, int needed) {
|
||||
if (needed <= 0) return -1;
|
||||
final int mask = slotMask(needed);
|
||||
for (int i = 0; i <= MAX_VERTEX_ATTRIBS - needed; i++) {
|
||||
if (((mask << i) & ~used) == (mask << i)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int explicitLocation(GLSLParser.Single_declarationContext decl) {
|
||||
final GLSLParser.Type_qualifierContext qualifier = decl.fully_specified_type().type_qualifier();
|
||||
if (qualifier == null) return -1;
|
||||
for (GLSLParser.Single_type_qualifierContext single : qualifier.single_type_qualifier()) {
|
||||
final GLSLParser.Layout_qualifierContext layout = single.layout_qualifier();
|
||||
if (layout == null) continue;
|
||||
for (GLSLParser.Layout_qualifier_idContext id : layout.layout_qualifier_id_list().layout_qualifier_id()) {
|
||||
if (id.IDENTIFIER() != null && "location".equals(id.IDENTIFIER().getText()) && id.constant_expression() != null) {
|
||||
try {
|
||||
return Integer.decode(GlslTransformUtils.getFormattedShader(id.constant_expression(), "").trim());
|
||||
} catch (NumberFormatException ignored) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static boolean hasLayoutQualifier(GLSLParser.Single_declarationContext decl) {
|
||||
final GLSLParser.Type_qualifierContext qualifier = decl.fully_specified_type().type_qualifier();
|
||||
if (qualifier == null) return false;
|
||||
for (GLSLParser.Single_type_qualifierContext single : qualifier.single_type_qualifier()) {
|
||||
if (single.layout_qualifier() != null) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
|
||||
final Path dir = SystemProperties.shaderDumpDir("compat");
|
||||
if (dir == null) return;
|
||||
final int id = dumpCounter.getAndIncrement();
|
||||
final String suffix = isFragment ? ".frag.glsl" : ".vert.glsl";
|
||||
try {
|
||||
Files.createDirectories(dir);
|
||||
// Capture caller info for identification
|
||||
final String caller = identifyCaller();
|
||||
final String header = "// Compat shader dump #" + id + " (" + (isFragment ? "fragment" : "vertex") + ")"
|
||||
+ "\n// Transformed: " + wasTransformed
|
||||
+ "\n// Caller: " + caller + "\n\n";
|
||||
Files.writeString(dir.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8);
|
||||
Files.writeString(dir.resolve(id + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
GLStateManager.LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static String identifyCaller() {
|
||||
for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
|
||||
final String cls = frame.getClassName();
|
||||
if (!cls.startsWith("com.gtnewhorizons.angelica.glsm.")
|
||||
&& !cls.startsWith("java.")
|
||||
&& !cls.equals("org.lwjgl.opengl.GL20")) {
|
||||
return cls + "." + frame.getMethodName() + ":" + frame.getLineNumber();
|
||||
}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\battribute\\b");
|
||||
private static final Pattern VARYING_PATTERN = Pattern.compile("\\bvarying\\b");
|
||||
|
||||
/** Replace legacy storage qualifiers removed in core profile. Safe on AST-serialized output (no comments). */
|
||||
public static String fixupQualifiers(String source, boolean isFragment) {
|
||||
source = ATTRIBUTE_PATTERN.matcher(source).replaceAll("in");
|
||||
source = VARYING_PATTERN.matcher(source).replaceAll(isFragment ? "in" : "out");
|
||||
return source;
|
||||
}
|
||||
|
||||
private static final String PRECISION_QUALIFIER = "(?:highp\\s+|mediump\\s+|lowp\\s+)?";
|
||||
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+" + PRECISION_QUALIFIER + "(vec[234])\\s+angelica_FrontColor\\b");
|
||||
private static final Pattern FRAG_IN_TEXCOORD = Pattern.compile("\\bin\\s+" + PRECISION_QUALIFIER + "(vec[234])\\s+angelica_TexCoord(\\d+)\\b");
|
||||
private static final Pattern FRAG_IN_FOGCOORD = Pattern.compile("\\bin\\s+" + PRECISION_QUALIFIER + "float\\s+angelica_FogFragCoord\\b");
|
||||
|
||||
/**
|
||||
* Generate a passthrough vertex shader for a fragment-only program.
|
||||
*
|
||||
* @param fragmentSource the transformed fragment shader source
|
||||
* @return a complete vertex shader source string
|
||||
*/
|
||||
public static String generatePassthroughVertexShader(String fragmentSource) {
|
||||
final StringBuilder sb = new StringBuilder(512);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
// Always have position
|
||||
sb.append("layout(location = 0) in vec4 a_Position;\n");
|
||||
|
||||
// Matrix uniforms for position transform
|
||||
sb.append("uniform mat4 angelica_ModelViewMatrix;\n");
|
||||
sb.append("uniform mat4 angelica_ProjectionMatrix;\n");
|
||||
|
||||
final StringBuilder varyings = new StringBuilder();
|
||||
final StringBuilder assignments = new StringBuilder();
|
||||
final Matcher colorMatcher = FRAG_IN_COLOR.matcher(fragmentSource);
|
||||
if (colorMatcher.find()) {
|
||||
final String colorType = colorMatcher.group(1);
|
||||
sb.append("layout(location = 1) in ").append(colorType).append(" a_Color;\n");
|
||||
varyings.append("out ").append(colorType).append(" angelica_FrontColor;\n");
|
||||
assignments.append(" angelica_FrontColor = a_Color;\n");
|
||||
}
|
||||
|
||||
final Matcher texCoordMatcher = FRAG_IN_TEXCOORD.matcher(fragmentSource);
|
||||
final Map<Integer, String> texCoordTypes = new HashMap<>();
|
||||
while (texCoordMatcher.find()) {
|
||||
texCoordTypes.putIfAbsent(Integer.parseInt(texCoordMatcher.group(2)), texCoordMatcher.group(1));
|
||||
}
|
||||
for (Map.Entry<Integer, String> e : texCoordTypes.entrySet()) {
|
||||
final int i = e.getKey();
|
||||
final String type = e.getValue();
|
||||
// PRIMARY_UV=2, SECONDARY_UV=3. Only indices 0 and 1 are supported; higher indices collide at location 3.
|
||||
final int location = i == 0 ? 2 : 3;
|
||||
sb.append("layout(location = ").append(location).append(") in ").append(type).append(" a_TexCoord").append(i).append(";\n");
|
||||
varyings.append("out ").append(type).append(" angelica_TexCoord").append(i).append(";\n");
|
||||
assignments.append(" angelica_TexCoord").append(i).append(" = a_TexCoord").append(i).append(";\n");
|
||||
}
|
||||
|
||||
if (FRAG_IN_FOGCOORD.matcher(fragmentSource).find()) {
|
||||
varyings.append("out float angelica_FogFragCoord;\n");
|
||||
assignments.append(" angelica_FogFragCoord = abs((angelica_ModelViewMatrix * a_Position).z);\n");
|
||||
}
|
||||
|
||||
sb.append(varyings);
|
||||
sb.append("\nvoid main() {\n");
|
||||
sb.append(" gl_Position = angelica_ProjectionMatrix * angelica_ModelViewMatrix * a_Position;\n");
|
||||
sb.append(assignments);
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** 330-core -> GLSL ES 320 via shaderc + SPIRV-Cross. Throws on shaderc failure. */
|
||||
public static String toGLES(String source, int glShaderType, boolean isFragment) {
|
||||
final Matcher m = VERSION_PATTERN.matcher(source);
|
||||
if (m.find() && "es".equalsIgnoreCase(m.group(2))) return source;
|
||||
if (glShaderType == 0) throw new IllegalArgumentException("toGLES requires a valid GL shader type");
|
||||
final String translated = SpirvShaderTranslator.glslToGlslEs(source, glShaderType, "compat");
|
||||
if (translated == null) {
|
||||
throw new IllegalStateException("SpirvShaderTranslator failed for shader kind 0x"
|
||||
+ Integer.toHexString(glShaderType) + "; see preceding shaderc error + run/client/angelica_dumps/compat/");
|
||||
}
|
||||
return translated;
|
||||
}
|
||||
|
||||
public static String toGLES(String source, boolean isFragment) {
|
||||
return toGLES(source, isFragment ? GL20.GL_FRAGMENT_SHADER : GL20.GL_VERTEX_SHADER, isFragment);
|
||||
}
|
||||
|
||||
/** Ensure #version is at least 330 core, strip 'compatibility' profile. */
|
||||
private static String fixupVersion(String source) {
|
||||
final Matcher m = VERSION_PATTERN.matcher(source);
|
||||
if (!m.find()) {
|
||||
return "#version 330 core\n" + source;
|
||||
}
|
||||
|
||||
final int version = Integer.parseInt(m.group(1));
|
||||
final String profile = m.group(2);
|
||||
|
||||
if (version >= 330 && !"compatibility".equals(profile)) {
|
||||
if ("core".equals(profile)) return source;
|
||||
return m.replaceFirst("#version " + version + " core");
|
||||
}
|
||||
|
||||
final int targetVersion = Math.max(version, 330);
|
||||
return m.replaceFirst("#version " + targetVersion + " core");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* Per-program compat uniform location cache and upload manager.
|
||||
*
|
||||
* <p>Manages {@code angelica_*} uniforms injected by {@link CompatShaderTransformer} and {@code iris_*} matrix uniforms used by Iris/celeritas shader
|
||||
* programs.
|
||||
*/
|
||||
public class CompatUniformManager {
|
||||
|
||||
// Uniform location indices within the cached array (package-private for tests)
|
||||
static final int LOC_MODELVIEW = 0;
|
||||
static final int LOC_MODELVIEW_INVERSE = 1;
|
||||
static final int LOC_PROJECTION = 2;
|
||||
static final int LOC_PROJECTION_INVERSE = 3;
|
||||
static final int LOC_NORMAL = 4;
|
||||
static final int LOC_LIGHTMAP_TEXTURE_MATRIX = 5;
|
||||
static final int LOC_FOG_DENSITY = 6;
|
||||
static final int LOC_FOG_START = 7;
|
||||
static final int LOC_FOG_END = 8;
|
||||
static final int LOC_FOG_COLOR = 9;
|
||||
static final int LOC_IRIS_MODELVIEW = 10;
|
||||
static final int LOC_IRIS_PROJECTION = 11;
|
||||
static final int LOC_IRIS_NORMAL = 12;
|
||||
static final int LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX = 13;
|
||||
static final int LOC_IRIS_TEXTURE_MATRIX = 14;
|
||||
static final int LOC_ALPHA_TEST_REF = 15;
|
||||
static final int LOC_SCENE_COLOR = 16;
|
||||
static final int LOC_CLIP_PLANES = 17;
|
||||
static final int LOC_CLIP_PLANES_ENABLED = 18;
|
||||
|
||||
// Light source fields: 12 per light × 2 lights = 24 locations
|
||||
static final int LIGHT_FIELDS = 12;
|
||||
static final int LF_AMBIENT = 0, LF_DIFFUSE = 1, LF_SPECULAR = 2, LF_POSITION = 3;
|
||||
static final int LF_HALF_VECTOR = 4, LF_SPOT_DIRECTION = 5, LF_SPOT_EXPONENT = 6;
|
||||
static final int LF_SPOT_CUTOFF = 7, LF_SPOT_COS_CUTOFF = 8;
|
||||
static final int LF_CONSTANT_ATTEN = 9, LF_LINEAR_ATTEN = 10, LF_QUADRATIC_ATTEN = 11;
|
||||
static final int LOC_LIGHT_BASE = 19;
|
||||
|
||||
// Material fields: 5 locations
|
||||
static final int MF_EMISSION = 0, MF_AMBIENT = 1, MF_DIFFUSE = 2, MF_SPECULAR = 3, MF_SHININESS = 4;
|
||||
static final int MAT_FIELDS = 5;
|
||||
static final int LOC_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 43
|
||||
|
||||
static final int LOC_IRIS_MODELVIEW_INVERSE = LOC_MAT_BASE + MAT_FIELDS; // 48
|
||||
static final int LOC_IRIS_PROJECTION_INVERSE = LOC_IRIS_MODELVIEW_INVERSE + 1; // 49
|
||||
|
||||
static final int LOC_COUNT = LOC_IRIS_PROJECTION_INVERSE + 1; // 50
|
||||
|
||||
private static final String[] LIGHT_FIELD_NAMES = {
|
||||
"ambient", "diffuse", "specular", "position", "halfVector",
|
||||
"spotDirection", "spotExponent", "spotCutoff", "spotCosCutoff",
|
||||
"constantAttenuation", "linearAttenuation", "quadraticAttenuation",
|
||||
};
|
||||
private static final String[] MAT_FIELD_NAMES = {
|
||||
"emission", "ambient", "diffuse", "specular", "shininess",
|
||||
};
|
||||
|
||||
private static final String[] UNIFORM_NAMES;
|
||||
|
||||
static {
|
||||
UNIFORM_NAMES = new String[LOC_COUNT];
|
||||
UNIFORM_NAMES[LOC_MODELVIEW] = "angelica_ModelViewMatrix";
|
||||
UNIFORM_NAMES[LOC_MODELVIEW_INVERSE] = "angelica_ModelViewMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_PROJECTION] = "angelica_ProjectionMatrix";
|
||||
UNIFORM_NAMES[LOC_PROJECTION_INVERSE] = "angelica_ProjectionMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_NORMAL] = "angelica_NormalMatrix";
|
||||
UNIFORM_NAMES[LOC_LIGHTMAP_TEXTURE_MATRIX] = "angelica_LightmapTextureMatrix";
|
||||
UNIFORM_NAMES[LOC_FOG_DENSITY] = "angelica_FogDensity";
|
||||
UNIFORM_NAMES[LOC_FOG_START] = "angelica_FogStart";
|
||||
UNIFORM_NAMES[LOC_FOG_END] = "angelica_FogEnd";
|
||||
UNIFORM_NAMES[LOC_FOG_COLOR] = "angelica_FogColor";
|
||||
UNIFORM_NAMES[LOC_IRIS_MODELVIEW] = "iris_ModelViewMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_PROJECTION] = "iris_ProjectionMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_TEXTURE_MATRIX] = "iris_TextureMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_MODELVIEW_INVERSE] = "iris_ModelViewMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_IRIS_PROJECTION_INVERSE] = "iris_ProjectionMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
|
||||
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
|
||||
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
|
||||
UNIFORM_NAMES[LOC_CLIP_PLANES_ENABLED] = "angelica_ClipPlanesEnabled";
|
||||
for (int li = 0; li < 2; li++) {
|
||||
for (int fi = 0; fi < LIGHT_FIELDS; fi++) {
|
||||
UNIFORM_NAMES[LOC_LIGHT_BASE + li * LIGHT_FIELDS + fi] = "angelica_LightSource[" + li + "]." + LIGHT_FIELD_NAMES[fi];
|
||||
}
|
||||
}
|
||||
for (int fi = 0; fi < MAT_FIELDS; fi++) {
|
||||
UNIFORM_NAMES[LOC_MAT_BASE + fi] = "angelica_FrontMaterial." + MAT_FIELD_NAMES[fi];
|
||||
}
|
||||
}
|
||||
|
||||
/** Per-program cached uniform locations + last-uploaded generations. Maps program ID -> state. */
|
||||
private static final Int2ObjectOpenHashMap<ProgramUniforms> programUniforms = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
private static final class ProgramUniforms {
|
||||
final int[] locs;
|
||||
GLContextState owner;
|
||||
int lastMvGen = -1, lastMvLinearGen = -1, lastProjGen = -1, lastTexMatGen = -1;
|
||||
int lastFragmentGen = -1, lastLightingGen = -1, lastClipPlaneGen = -1;
|
||||
ProgramUniforms(int[] locs) { this.locs = locs; }
|
||||
}
|
||||
|
||||
// Reusable NIO buffers for upload
|
||||
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
|
||||
private static final FloatBuffer mat3Buf = BufferUtils.createFloatBuffer(9);
|
||||
private static final FloatBuffer vec4Buf = BufferUtils.createFloatBuffer(4);
|
||||
private static final FloatBuffer clipPlaneBuf = BufferUtils.createFloatBuffer(32); // 8 planes * vec4
|
||||
private static final Matrix3f normalMatrix = new Matrix3f();
|
||||
private static final Matrix4f scratchMatrix = new Matrix4f();
|
||||
private static final Vector3f scratchCol = new Vector3f();
|
||||
|
||||
private static final float LIGHTMAP_SCALE = 1.0f / 256.0f;
|
||||
private static final FloatBuffer lightmapMatrixBuf;
|
||||
|
||||
static {
|
||||
lightmapMatrixBuf = BufferUtils.createFloatBuffer(16);
|
||||
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
|
||||
}
|
||||
|
||||
private CompatUniformManager() {}
|
||||
|
||||
/**
|
||||
* Normalize each column of a 3x3 matrix in place. Used to strip the residual 1/scale that
|
||||
* inverse-transpose introduces.
|
||||
*/
|
||||
private static void normalizeColumnsInPlace(Matrix3f m) {
|
||||
m.setColumn(0, m.getColumn(0, scratchCol).normalize());
|
||||
m.setColumn(1, m.getColumn(1, scratchCol).normalize());
|
||||
m.setColumn(2, m.getColumn(2, scratchCol).normalize());
|
||||
}
|
||||
|
||||
public static void onLinkProgram(int program) {
|
||||
int[] locs = new int[LOC_COUNT];
|
||||
boolean hasAny = false;
|
||||
|
||||
for (int i = 0; i < LOC_COUNT; i++) {
|
||||
locs[i] = RENDER_BACKEND.getUniformLocation(program, UNIFORM_NAMES[i]);
|
||||
if (locs[i] != -1) hasAny = true;
|
||||
}
|
||||
|
||||
if (hasAny) {
|
||||
final ProgramUniforms uniforms = new ProgramUniforms(locs);
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
programUniforms.put(program, uniforms);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the emulated uniforms.
|
||||
*
|
||||
* @return Whether the program needs emulation (FFP or Iris Program)
|
||||
*/
|
||||
public static boolean refreshCompatUniforms(int program, GLContextState glCtx) {
|
||||
final ProgramUniforms pu = programUniforms.get(program);
|
||||
if (pu == null) return false;
|
||||
final int[] locs = pu.locs;
|
||||
if (pu.owner != glCtx) {
|
||||
pu.owner = glCtx;
|
||||
pu.lastMvGen = pu.lastMvLinearGen = pu.lastProjGen = pu.lastTexMatGen = -1;
|
||||
pu.lastFragmentGen = pu.lastLightingGen = pu.lastClipPlaneGen = -1;
|
||||
}
|
||||
|
||||
// Matrix uniforms — skip if this program's storage already holds the current generation
|
||||
final int mvGen = glCtx.mvGeneration;
|
||||
final int mvLinearGen = glCtx.mvLinearGeneration;
|
||||
final int projGen = glCtx.projGeneration;
|
||||
final int texMatGen = glCtx.texMatrixGeneration;
|
||||
final boolean mvChanged = mvGen != pu.lastMvGen;
|
||||
final boolean mvLinearChanged = mvLinearGen != pu.lastMvLinearGen;
|
||||
final boolean projChanged = projGen != pu.lastProjGen;
|
||||
final boolean texMatChanged = texMatGen != pu.lastTexMatGen;
|
||||
if (mvChanged || projChanged || texMatChanged) {
|
||||
uploadMatrices(locs, mvChanged, mvLinearChanged, projChanged, texMatChanged);
|
||||
pu.lastMvGen = mvGen;
|
||||
pu.lastMvLinearGen = mvLinearGen;
|
||||
pu.lastProjGen = projGen;
|
||||
pu.lastTexMatGen = texMatGen;
|
||||
}
|
||||
|
||||
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
|
||||
final int fragGen = glCtx.fragmentGeneration;
|
||||
if (fragGen != pu.lastFragmentGen) {
|
||||
pu.lastFragmentGen = fragGen;
|
||||
uploadFragmentUniforms(locs);
|
||||
}
|
||||
|
||||
// Lighting-derived uniforms (scene color, light sources, material)
|
||||
final int litGen = glCtx.lightingGeneration;
|
||||
if (litGen != pu.lastLightingGen) {
|
||||
pu.lastLightingGen = litGen;
|
||||
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
|
||||
uploadLightSources(locs);
|
||||
uploadMaterial(locs);
|
||||
}
|
||||
|
||||
// Clip plane equations + enabled bool — uploaded when enable state or equations change
|
||||
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
final int cpGen = glCtx.clipPlaneGeneration;
|
||||
if (cpGen != pu.lastClipPlaneGen) {
|
||||
pu.lastClipPlaneGen = cpGen;
|
||||
uploadClipPlanes(locs);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean mvLinearChanged, boolean projChanged, boolean texMatChanged) {
|
||||
final Matrix4f mv = GLStateManager.getModelViewMatrix();
|
||||
final Matrix4f proj = GLStateManager.getProjectionMatrix();
|
||||
|
||||
if (mvChanged) {
|
||||
// ModelView
|
||||
if (locs[LOC_MODELVIEW] != -1 || locs[LOC_IRIS_MODELVIEW] != -1) {
|
||||
mv.get(mat4Buf);
|
||||
if (locs[LOC_MODELVIEW] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW], false, mat4Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_MODELVIEW] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_MODELVIEW], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// ModelView Inverse
|
||||
if (locs[LOC_MODELVIEW_INVERSE] != -1 || locs[LOC_IRIS_MODELVIEW_INVERSE] != -1) {
|
||||
mv.invert(scratchMatrix);
|
||||
scratchMatrix.get(mat4Buf);
|
||||
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_MODELVIEW_INVERSE] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_MODELVIEW_INVERSE], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mvLinearChanged) {
|
||||
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
|
||||
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
|
||||
mv.normal(normalMatrix);
|
||||
if (locs[LOC_NORMAL] != -1) {
|
||||
normalMatrix.get(mat3Buf);
|
||||
RENDER_BACKEND.uniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
|
||||
}
|
||||
// Column-normalized to unit magnitude
|
||||
if (locs[LOC_IRIS_NORMAL] != -1) {
|
||||
normalizeColumnsInPlace(normalMatrix);
|
||||
normalMatrix.get(mat3Buf);
|
||||
RENDER_BACKEND.uniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projChanged) {
|
||||
// Projection
|
||||
if (locs[LOC_PROJECTION] != -1 || locs[LOC_IRIS_PROJECTION] != -1) {
|
||||
proj.get(mat4Buf);
|
||||
if (locs[LOC_PROJECTION] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION], false, mat4Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_PROJECTION] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_PROJECTION], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Projection Inverse
|
||||
if (locs[LOC_PROJECTION_INVERSE] != -1 || locs[LOC_IRIS_PROJECTION_INVERSE] != -1) {
|
||||
proj.invert(scratchMatrix);
|
||||
scratchMatrix.get(mat4Buf);
|
||||
if (locs[LOC_PROJECTION_INVERSE] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_PROJECTION_INVERSE] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_PROJECTION_INVERSE], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (texMatChanged) {
|
||||
// Iris lightmap texture matrix (constant)
|
||||
if (locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] != -1) {
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX], false, lightmapMatrixBuf);
|
||||
}
|
||||
|
||||
// angelica_LightmapTextureMatrix — actual GLSM texture unit 1 matrix (set by enableLightmap())
|
||||
if (locs[LOC_LIGHTMAP_TEXTURE_MATRIX] != -1) {
|
||||
GLStateManager.getTextures().getTextureUnitMatrix(1).get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_LIGHTMAP_TEXTURE_MATRIX], false, mat4Buf);
|
||||
}
|
||||
|
||||
// iris_TextureMatrix — texture unit 0 matrix (animated by glints, etc.)
|
||||
if (locs[LOC_IRIS_TEXTURE_MATRIX] != -1) {
|
||||
GLStateManager.getTextures().getTextureUnitMatrix(0).get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_TEXTURE_MATRIX], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadFragmentUniforms(int[] locs) {
|
||||
// Fog uniforms
|
||||
FogStateStack fog = GLStateManager.getFogState();
|
||||
if (locs[LOC_FOG_DENSITY] != -1) {
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_FOG_DENSITY], Math.max(0.0f, fog.getDensity()));
|
||||
}
|
||||
if (locs[LOC_FOG_START] != -1) {
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_FOG_START], fog.getStart());
|
||||
}
|
||||
if (locs[LOC_FOG_END] != -1) {
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_FOG_END], fog.getEnd());
|
||||
}
|
||||
if (locs[LOC_FOG_COLOR] != -1) {
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y).put((float) fog.getFogColor().z).put(fog.getFogAlpha());
|
||||
vec4Buf.flip();
|
||||
RENDER_BACKEND.uniform4(locs[LOC_FOG_COLOR], vec4Buf);
|
||||
}
|
||||
|
||||
// Alpha test reference — core profile replacement for GL_ALPHA_TEST
|
||||
// Return -1.0f for ALWAYS or disabled so `alpha <= ref` never discards.
|
||||
if (locs[LOC_ALPHA_TEST_REF] != -1) {
|
||||
final float ref;
|
||||
if (!GLStateManager.getAlphaTest().isEnabled() || GLStateManager.getAlphaState().getFunction() == GL11.GL_ALWAYS) {
|
||||
ref = -1.0f;
|
||||
} else {
|
||||
ref = GLStateManager.getAlphaState().getReference();
|
||||
}
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_ALPHA_TEST_REF], ref);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scene color = emission + ambient * lightModel.ambient, alpha = diffuse.a Same computation as {@code Uniforms.uploadLightingPreComputed()}.
|
||||
*/
|
||||
private static void uploadSceneColor(int[] locs) {
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
final LightModelState lm = GLStateManager.getLightModel();
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(mat.emission.x + mat.ambient.x * lm.ambient.x)
|
||||
.put(mat.emission.y + mat.ambient.y * lm.ambient.y)
|
||||
.put(mat.emission.z + mat.ambient.z * lm.ambient.z)
|
||||
.put(mat.diffuse.w);
|
||||
vec4Buf.flip();
|
||||
RENDER_BACKEND.uniform4(locs[LOC_SCENE_COLOR], vec4Buf);
|
||||
}
|
||||
|
||||
private static void uploadLightSources(int[] locs) {
|
||||
// Early exit: if the first light field isn't present, no lighting uniforms exist
|
||||
if (locs[LOC_LIGHT_BASE] == -1) return;
|
||||
final LightStateStack[] lights = GLStateManager.getLightDataStates();
|
||||
for (int li = 0; li < 2; li++) {
|
||||
final int base = LOC_LIGHT_BASE + li * LIGHT_FIELDS;
|
||||
final LightState light = lights[li];
|
||||
|
||||
if (locs[base + LF_AMBIENT] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[base + LF_AMBIENT], light.ambient.x, light.ambient.y, light.ambient.z, light.ambient.w);
|
||||
if (locs[base + LF_DIFFUSE] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[base + LF_DIFFUSE], light.diffuse.x, light.diffuse.y, light.diffuse.z, light.diffuse.w);
|
||||
if (locs[base + LF_SPECULAR] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[base + LF_SPECULAR], light.specular.x, light.specular.y, light.specular.z, light.specular.w);
|
||||
if (locs[base + LF_POSITION] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[base + LF_POSITION], light.position.x, light.position.y, light.position.z, light.position.w);
|
||||
if (locs[base + LF_HALF_VECTOR] != -1) {
|
||||
// halfVector = normalize(normalize(position.xyz) + eye), eye=(0,0,1) for infinite viewer
|
||||
float hx, hy, hz;
|
||||
if (light.position.w == 0.0f) {
|
||||
final float len = (float) Math.sqrt(light.position.x * light.position.x
|
||||
+ light.position.y * light.position.y
|
||||
+ light.position.z * light.position.z);
|
||||
if (len > 0) {
|
||||
hx = light.position.x / len;
|
||||
hy = light.position.y / len;
|
||||
hz = light.position.z / len + 1.0f;
|
||||
} else {
|
||||
hx = 0; hy = 0; hz = 1;
|
||||
}
|
||||
} else {
|
||||
hx = 0; hy = 0; hz = 1;
|
||||
}
|
||||
final float hlen = (float) Math.sqrt(hx * hx + hy * hy + hz * hz);
|
||||
if (hlen > 0) { hx /= hlen; hy /= hlen; hz /= hlen; }
|
||||
RENDER_BACKEND.uniform4f(locs[base + LF_HALF_VECTOR], hx, hy, hz, 0.0f);
|
||||
}
|
||||
if (locs[base + LF_SPOT_DIRECTION] != -1) RENDER_BACKEND.uniform3f(locs[base + LF_SPOT_DIRECTION], light.spotDirection.x, light.spotDirection.y, light.spotDirection.z);
|
||||
if (locs[base + LF_SPOT_EXPONENT] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_EXPONENT], light.spotExponent);
|
||||
if (locs[base + LF_SPOT_CUTOFF] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_CUTOFF], light.spotCutoff);
|
||||
if (locs[base + LF_SPOT_COS_CUTOFF] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_COS_CUTOFF], light.spotCosCutoff);
|
||||
if (locs[base + LF_CONSTANT_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_CONSTANT_ATTEN], light.constantAttenuation);
|
||||
if (locs[base + LF_LINEAR_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_LINEAR_ATTEN], light.linearAttenuation);
|
||||
if (locs[base + LF_QUADRATIC_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_QUADRATIC_ATTEN], light.quadraticAttenuation);
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadMaterial(int[] locs) {
|
||||
// Early exit: if the first material field isn't present, no material uniforms exist
|
||||
if (locs[LOC_MAT_BASE] == -1) return;
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
if (locs[LOC_MAT_BASE + MF_EMISSION] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_EMISSION], mat.emission.x, mat.emission.y, mat.emission.z, mat.emission.w);
|
||||
if (locs[LOC_MAT_BASE + MF_AMBIENT] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_AMBIENT], mat.ambient.x, mat.ambient.y, mat.ambient.z, mat.ambient.w);
|
||||
if (locs[LOC_MAT_BASE + MF_DIFFUSE] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_DIFFUSE], mat.diffuse.x, mat.diffuse.y, mat.diffuse.z, mat.diffuse.w);
|
||||
if (locs[LOC_MAT_BASE + MF_SPECULAR] != -1)
|
||||
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_SPECULAR], mat.specular.x, mat.specular.y, mat.specular.z, mat.specular.w);
|
||||
if (locs[LOC_MAT_BASE + MF_SHININESS] != -1)
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
|
||||
}
|
||||
|
||||
private static void uploadClipPlanes(int[] locs) {
|
||||
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
|
||||
}
|
||||
if (locs[LOC_CLIP_PLANES] != -1) {
|
||||
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
}
|
||||
clipPlaneBuf.flip();
|
||||
RENDER_BACKEND.uniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onDeleteProgram(int program) {
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
programUniforms.remove(program);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasProgram(int program) {
|
||||
return programUniforms.containsKey(program);
|
||||
}
|
||||
|
||||
public static int[] getLocations(int program) {
|
||||
final ProgramUniforms pu = programUniforms.get(program);
|
||||
return pu == null ? null : pu.locs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
/*
|
||||
* Ported from Mesa's util_idalloc (src/util/u_idalloc.c).
|
||||
*
|
||||
* Original copyright and license:
|
||||
*
|
||||
* Copyright 2017 Valve Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sub license, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the
|
||||
* next paragraph) shall be included in all copies or substantial portions
|
||||
* of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Original author: Samuel Pitoiset <[email protected]>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Bitmap ID allocator for display list IDs.
|
||||
*
|
||||
* <p>Bitmap-based: each bit in the {@code data} array represents one ID.
|
||||
* Bit set = allocated, bit clear = free. ID 0 is reserved (never returned).
|
||||
*/
|
||||
public class DisplayListIDAllocator {
|
||||
|
||||
private int[] data;
|
||||
private int numElements; // number of uint32 words allocated
|
||||
private int numSetElements; // highest word index with any bit set + 1
|
||||
private int lowestFreeIdx; // optimization: skip fully-allocated words
|
||||
|
||||
public DisplayListIDAllocator() {
|
||||
this(32); // 1024 IDs initially
|
||||
}
|
||||
|
||||
public DisplayListIDAllocator(int initialWords) {
|
||||
if (initialWords < 1) initialWords = 1;
|
||||
this.data = new int[initialWords];
|
||||
this.numElements = initialWords;
|
||||
this.numSetElements = 0;
|
||||
this.lowestFreeIdx = 0;
|
||||
// Reserve ID 0 (never returned), matching Mesa's skip_zero pattern
|
||||
data[0] = 1;
|
||||
numSetElements = 1;
|
||||
}
|
||||
|
||||
public synchronized int allocRange(int count) {
|
||||
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
|
||||
if (count == 1) return allocSingle();
|
||||
return allocMultiple(count);
|
||||
}
|
||||
|
||||
private int allocSingle() {
|
||||
for (int i = lowestFreeIdx; i < numElements; i++) {
|
||||
if (data[i] == 0xFFFFFFFF) continue;
|
||||
|
||||
int bit = Integer.numberOfTrailingZeros(~data[i]);
|
||||
data[i] |= 1 << bit;
|
||||
lowestFreeIdx = i;
|
||||
numSetElements = Math.max(numSetElements, i + 1);
|
||||
return i * 32 + bit;
|
||||
}
|
||||
|
||||
// No slots available, grow. Record old end, then mark first bit there.
|
||||
int oldNumElements = numElements;
|
||||
resize(Math.max(numElements, 1) * 2);
|
||||
lowestFreeIdx = oldNumElements;
|
||||
data[oldNumElements] |= 1;
|
||||
numSetElements = Math.max(numSetElements, oldNumElements + 1);
|
||||
return oldNumElements * 32;
|
||||
}
|
||||
|
||||
private int allocMultiple(int num) {
|
||||
int numWords = divRoundUp(num, 32);
|
||||
int base = findFreeBlock(lowestFreeIdx);
|
||||
boolean found = false;
|
||||
|
||||
while (!found) {
|
||||
int i;
|
||||
for (i = base; i < numElements && i - base < numWords && data[i] == 0; i++)
|
||||
;
|
||||
|
||||
if (i - base == numWords) {
|
||||
found = true;
|
||||
} else if (i == numElements) {
|
||||
// Hit end — base is valid: data[base..end] are zero, resize extends the run into newly zeroed words.
|
||||
resize(numElements * 2 + numWords);
|
||||
found = true;
|
||||
} else {
|
||||
// Non-zero word at i, skip past it
|
||||
base = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark bits as used
|
||||
int fullWords = num / 32;
|
||||
int remainder = num % 32;
|
||||
for (int i = base; i < base + fullWords; i++) {
|
||||
data[i] = 0xFFFFFFFF;
|
||||
}
|
||||
if (remainder != 0) {
|
||||
data[base + fullWords] |= (1 << remainder) - 1;
|
||||
}
|
||||
|
||||
if (lowestFreeIdx == base) {
|
||||
lowestFreeIdx = base + num / 32;
|
||||
}
|
||||
numSetElements = Math.max(numSetElements, base + numWords);
|
||||
|
||||
return base * 32;
|
||||
}
|
||||
|
||||
private int findFreeBlock(int start) {
|
||||
for (int i = start; i < numElements; i++) {
|
||||
if (data[i] == 0) return i;
|
||||
}
|
||||
return numElements;
|
||||
}
|
||||
|
||||
public synchronized void free(int id) {
|
||||
if (id <= 0) return; // ID 0 is reserved
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) return;
|
||||
|
||||
lowestFreeIdx = Math.min(idx, lowestFreeIdx);
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
|
||||
if (numSetElements == idx + 1) {
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void freeRange(int base, int count) {
|
||||
if (count <= 0 || base <= 0) return;
|
||||
|
||||
int startIdx = base / 32;
|
||||
if (startIdx >= numElements) return;
|
||||
|
||||
int end = base + count;
|
||||
for (int id = base; id < end; id++) {
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) break;
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
}
|
||||
|
||||
lowestFreeIdx = Math.min(startIdx, lowestFreeIdx);
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean isAllocated(int id) {
|
||||
if (id <= 0) return false;
|
||||
int idx = id / 32;
|
||||
if (idx >= numSetElements) return false;
|
||||
return (data[idx] & (1 << (id % 32))) != 0;
|
||||
}
|
||||
|
||||
private void resize(int newNumElements) {
|
||||
if (newNumElements <= numElements) return;
|
||||
int[] newData = new int[newNumElements];
|
||||
System.arraycopy(data, 0, newData, 0, numElements);
|
||||
data = newData;
|
||||
numElements = newNumElements;
|
||||
}
|
||||
|
||||
private static int divRoundUp(int a, int b) {
|
||||
return (a + b - 1) / b;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,361 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class Feature {
|
||||
private static final int[] supportedAttribs = new int[] { GL11.GL_ACCUM_BUFFER_BIT, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_CURRENT_BIT, GL11.GL_DEPTH_BUFFER_BIT,
|
||||
GL11.GL_ENABLE_BIT, GL11.GL_EVAL_BIT, GL11.GL_FOG_BIT, GL11.GL_HINT_BIT, GL11.GL_LIGHTING_BIT, GL11.GL_LINE_BIT, GL11.GL_LIST_BIT,
|
||||
GL13.GL_MULTISAMPLE_BIT, GL11.GL_PIXEL_MODE_BIT, GL11.GL_POINT_BIT, GL11.GL_POLYGON_BIT, GL11.GL_POLYGON_STIPPLE_BIT, GL11.GL_SCISSOR_BIT,
|
||||
GL11.GL_STENCIL_BUFFER_BIT, GL11.GL_TEXTURE_BIT, GL11.GL_TRANSFORM_BIT, GL11.GL_VIEWPORT_BIT };
|
||||
|
||||
private static final long[] NO_MEMBERS = new long[0];
|
||||
|
||||
private static final Int2ObjectMap<long[]> maskToMembersMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
static long[] maskToMembers(int mask) {
|
||||
final long[] cached = maskToMembersMap.get(mask);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
long[] result = NO_MEMBERS;
|
||||
for (int attrib : Feature.supportedAttribs) {
|
||||
if ((mask & attrib) == attrib) {
|
||||
result = union(result, attribToMembers.get(attrib));
|
||||
}
|
||||
}
|
||||
|
||||
maskToMembersMap.put(mask, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static long[] union(long[] a, long[] b) {
|
||||
if (b == null || b.length == 0) return a;
|
||||
if (a.length == 0) return b.clone();
|
||||
final long[] result = new long[Math.max(a.length, b.length)];
|
||||
System.arraycopy(a, 0, result, 0, a.length);
|
||||
for (int i = 0; i < b.length; i++) result[i] |= b[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
private static long[] toMembers(Set<IStateStack<?>> stacks) {
|
||||
if (stacks.isEmpty()) return NO_MEMBERS;
|
||||
int max = 0;
|
||||
for (IStateStack<?> s : stacks) max = Math.max(max, ((CowStateStack<?>) s).stackId());
|
||||
final long[] bits = new long[(max >> 6) + 1];
|
||||
for (IStateStack<?> s : stacks) {
|
||||
final int id = ((CowStateStack<?>) s).stackId();
|
||||
bits[id >> 6] |= 1L << id;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
private static final Int2ObjectMap<long[]> attribToMembers = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
/**
|
||||
* Helper method to add all texture gen states (S, T, R, Q) for all texture units to a collection.
|
||||
* Used by both GL_ENABLE_BIT and GL_TEXTURE_BIT.
|
||||
*/
|
||||
private static void addTextureGenStates(Set<IStateStack<?>> collection) {
|
||||
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
collection.add(GLStateManager.getTextures().getTexGenSStates(i));
|
||||
collection.add(GLStateManager.getTextures().getTexGenTStates(i));
|
||||
collection.add(GLStateManager.getTextures().getTexGenRStates(i));
|
||||
collection.add(GLStateManager.getTextures().getTexGenQStates(i));
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
final Int2ObjectMap<Set<IStateStack<?>>> attribToFeatures = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
attribToFeatures.put(GL11.GL_COLOR_BUFFER_BIT, ImmutableSet.of(
|
||||
GLStateManager.getAlphaTest()
|
||||
, GLStateManager.getAlphaState()
|
||||
, GLStateManager.getBlendMode()
|
||||
, GLStateManager.getBlendState()
|
||||
, GLStateManager.getColorLogicOpState()
|
||||
, GLStateManager.getDitherState()
|
||||
, GLStateManager.getDrawBuffer()
|
||||
, GLStateManager.getIndexLogicOpState()
|
||||
, GLStateManager.getLogicOpMode()
|
||||
, GLStateManager.getColorMask()
|
||||
, GLStateManager.getClearColor()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_CURRENT_BIT, ImmutableSet.of(
|
||||
GLStateManager.getColor()
|
||||
, GLStateManager.getSecondaryColor() // Current secondary color
|
||||
, ShaderManager.getNormalStack() // Current normal vector
|
||||
, ShaderManager.getTexCoordStack() // Current texture coordinates
|
||||
, GLStateManager.getEdgeFlagState()
|
||||
// Current color index
|
||||
// Current raster position
|
||||
// GL_CURRENT_RASTER_POSITION_VALID flag
|
||||
// RGBA color associated with current raster position
|
||||
// Color index associated with current raster position
|
||||
// Texture coordinates associated with current raster position
|
||||
// GL_EDGE_FLAG flag
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_DEPTH_BUFFER_BIT, ImmutableSet.of(
|
||||
GLStateManager.getDepthTest()
|
||||
, GLStateManager.getDepthState()
|
||||
// Depth buffer clear value
|
||||
// GL_DEPTH_WRITEMASK enable bit
|
||||
));
|
||||
|
||||
final HashSet<IStateStack<?>> enableBits = new HashSet<>(ImmutableSet.of(
|
||||
GLStateManager.getAlphaTest()
|
||||
, GLStateManager.getAutoNormalState()
|
||||
, GLStateManager.getBlendMode()
|
||||
, GLStateManager.getColorLogicOpState()
|
||||
, GLStateManager.getColorMaterial()
|
||||
, GLStateManager.getColorSumState()
|
||||
, GLStateManager.getCullState()
|
||||
, GLStateManager.getDepthTest()
|
||||
, GLStateManager.getDitherState()
|
||||
, GLStateManager.getFogMode()
|
||||
, GLStateManager.getIndexLogicOpState()
|
||||
, GLStateManager.getLightStates()[0] // GL_LIGHT0
|
||||
, GLStateManager.getLightStates()[1] // GL_LIGHT1
|
||||
, GLStateManager.getLightStates()[2] // GL_LIGHT2
|
||||
, GLStateManager.getLightStates()[3] // GL_LIGHT3
|
||||
, GLStateManager.getLightStates()[4] // GL_LIGHT4
|
||||
, GLStateManager.getLightStates()[5] // GL_LIGHT5
|
||||
, GLStateManager.getLightStates()[6] // GL_LIGHT6
|
||||
, GLStateManager.getLightStates()[7] // GL_LIGHT7
|
||||
, GLStateManager.getLightingState()
|
||||
, GLStateManager.getLineSmoothState()
|
||||
, GLStateManager.getLineStippleState()
|
||||
, GLStateManager.getMap1Color4State()
|
||||
, GLStateManager.getMap1IndexState()
|
||||
, GLStateManager.getMap1NormalState()
|
||||
, GLStateManager.getMap1TextureCoord1State()
|
||||
, GLStateManager.getMap1TextureCoord2State()
|
||||
, GLStateManager.getMap1TextureCoord3State()
|
||||
, GLStateManager.getMap1TextureCoord4State()
|
||||
, GLStateManager.getMap1Vertex3State()
|
||||
, GLStateManager.getMap1Vertex4State()
|
||||
, GLStateManager.getMap2Color4State()
|
||||
, GLStateManager.getMap2IndexState()
|
||||
, GLStateManager.getMap2NormalState()
|
||||
, GLStateManager.getMap2TextureCoord1State()
|
||||
, GLStateManager.getMap2TextureCoord2State()
|
||||
, GLStateManager.getMap2TextureCoord3State()
|
||||
, GLStateManager.getMap2TextureCoord4State()
|
||||
, GLStateManager.getMap2Vertex3State()
|
||||
, GLStateManager.getMap2Vertex4State()
|
||||
, GLStateManager.getMultisampleState()
|
||||
, GLStateManager.getNormalizeState()
|
||||
, GLStateManager.getPointSmoothState()
|
||||
, GLStateManager.getPolygonOffsetPointState()
|
||||
, GLStateManager.getPolygonOffsetLineState()
|
||||
, GLStateManager.getPolygonOffsetFillState()
|
||||
, GLStateManager.getPointSpriteState()
|
||||
, GLStateManager.getVertexProgramTwoSideState()
|
||||
, GLStateManager.getPolygonSmoothState()
|
||||
, GLStateManager.getPolygonStippleState()
|
||||
, GLStateManager.getRescaleNormalState()
|
||||
, GLStateManager.getSampleAlphaToCoverageState()
|
||||
, GLStateManager.getSampleAlphaToOneState()
|
||||
, GLStateManager.getSampleCoverageState()
|
||||
, GLStateManager.getScissorTest()
|
||||
, GLStateManager.getStencilTest()
|
||||
));
|
||||
|
||||
// Enable bits for the user-definable clipping planes
|
||||
for(int i = 0; i < GLStateManager.getClipPlaneStates().length; i++) {
|
||||
enableBits.add(GLStateManager.getClipPlaneStates()[i]);
|
||||
}
|
||||
|
||||
// GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D flags
|
||||
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
enableBits.add(GLStateManager.getTextures().getTexture1DStates(i));
|
||||
enableBits.add(GLStateManager.getTextures().getTextureUnitStates(i));
|
||||
enableBits.add(GLStateManager.getTextures().getTexture3DStates(i));
|
||||
enableBits.add(GLStateManager.getTextures().getTextureCubeMapStates(i));
|
||||
}
|
||||
|
||||
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
|
||||
addTextureGenStates(enableBits);
|
||||
|
||||
attribToFeatures.put(GL11.GL_ENABLE_BIT, enableBits);
|
||||
attribToFeatures.put(GL11.GL_EVAL_BIT, ImmutableSet.of(
|
||||
GLStateManager.getAutoNormalState()
|
||||
, GLStateManager.getMap1Color4State()
|
||||
, GLStateManager.getMap1IndexState()
|
||||
, GLStateManager.getMap1NormalState()
|
||||
, GLStateManager.getMap1TextureCoord1State()
|
||||
, GLStateManager.getMap1TextureCoord2State()
|
||||
, GLStateManager.getMap1TextureCoord3State()
|
||||
, GLStateManager.getMap1TextureCoord4State()
|
||||
, GLStateManager.getMap1Vertex3State()
|
||||
, GLStateManager.getMap1Vertex4State()
|
||||
, GLStateManager.getMap2Color4State()
|
||||
, GLStateManager.getMap2IndexState()
|
||||
, GLStateManager.getMap2NormalState()
|
||||
, GLStateManager.getMap2TextureCoord1State()
|
||||
, GLStateManager.getMap2TextureCoord2State()
|
||||
, GLStateManager.getMap2TextureCoord3State()
|
||||
, GLStateManager.getMap2TextureCoord4State()
|
||||
, GLStateManager.getMap2Vertex3State()
|
||||
, GLStateManager.getMap2Vertex4State()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_FOG_BIT, ImmutableSet.of(
|
||||
GLStateManager.getFogMode()
|
||||
, GLStateManager.getColorSumState()
|
||||
, GLStateManager.getFogState()
|
||||
// ^^ Fog density
|
||||
// ^^ Linear fog start
|
||||
// ^^ Linear fog end
|
||||
// Fog index
|
||||
// ^^ GL_FOG_MODE value
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_HINT_BIT, ImmutableSet.of(
|
||||
GLStateManager.getHintState()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_LIGHTING_BIT, ImmutableSet.of(
|
||||
GLStateManager.getColorMaterial()
|
||||
, GLStateManager.getColorMaterialFace()
|
||||
, GLStateManager.getColorMaterialParameter()
|
||||
, GLStateManager.getLightModel()
|
||||
, GLStateManager.getLightingState()
|
||||
// Enable bit for each light
|
||||
, GLStateManager.getLightStates()[0] // GL_LIGHT0
|
||||
, GLStateManager.getLightStates()[1] // GL_LIGHT1
|
||||
, GLStateManager.getLightStates()[2] // GL_LIGHT2
|
||||
, GLStateManager.getLightStates()[3] // GL_LIGHT3
|
||||
, GLStateManager.getLightStates()[4] // GL_LIGHT4
|
||||
, GLStateManager.getLightStates()[5] // GL_LIGHT5
|
||||
, GLStateManager.getLightStates()[6] // GL_LIGHT6
|
||||
, GLStateManager.getLightStates()[7] // GL_LIGHT7
|
||||
// Ambient, diffuse, and specular intensity for each light
|
||||
// Direction, position, exponent, and cutoff angle for each light
|
||||
// Constant, linear, and quadratic attenuation factors for each light
|
||||
, GLStateManager.getLightDataStates()[0]
|
||||
, GLStateManager.getLightDataStates()[1]
|
||||
, GLStateManager.getLightDataStates()[2]
|
||||
, GLStateManager.getLightDataStates()[3]
|
||||
, GLStateManager.getLightDataStates()[4]
|
||||
, GLStateManager.getLightDataStates()[5]
|
||||
, GLStateManager.getLightDataStates()[6]
|
||||
, GLStateManager.getLightDataStates()[7]
|
||||
// Ambient, diffuse, specular, and emissive color for each material
|
||||
// Ambient, diffuse, and specular color indices for each material
|
||||
// Specular exponent for each material
|
||||
, GLStateManager.getFrontMaterial()
|
||||
, GLStateManager.getBackMaterial()
|
||||
, GLStateManager.getShadeModelState()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_LINE_BIT, ImmutableSet.of(
|
||||
GLStateManager.getLineSmoothState()
|
||||
, GLStateManager.getLineStippleState()
|
||||
, GLStateManager.getLineState()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_LIST_BIT, ImmutableSet.of(
|
||||
// GL_LIST_BASE setting
|
||||
));
|
||||
attribToFeatures.put(GL13.GL_MULTISAMPLE_BIT, ImmutableSet.of(
|
||||
GLStateManager.getMultisampleState()
|
||||
, GLStateManager.getSampleAlphaToCoverageState()
|
||||
, GLStateManager.getSampleAlphaToOneState()
|
||||
, GLStateManager.getSampleCoverageState()
|
||||
// GL_SAMPLE_COVERAGE_VALUE value
|
||||
// GL_SAMPLE_COVERAGE_INVERT value
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_PIXEL_MODE_BIT, ImmutableSet.of(
|
||||
// GL_RED_BIAS and GL_RED_SCALE settings
|
||||
// GL_GREEN_BIAS and GL_GREEN_SCALE values
|
||||
// GL_BLUE_BIAS and GL_BLUE_SCALE
|
||||
// GL_ALPHA_BIAS and GL_ALPHA_SCALE
|
||||
// GL_DEPTH_BIAS and GL_DEPTH_SCALE
|
||||
// GL_INDEX_OFFSET and GL_INDEX_SHIFT values
|
||||
// GL_MAP_COLOR and GL_MAP_STENCIL flags
|
||||
// GL_ZOOM_X and GL_ZOOM_Y factors
|
||||
// GL_READ_BUFFER setting
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
|
||||
GLStateManager.getPointSmoothState()
|
||||
, GLStateManager.getPointSpriteState()
|
||||
, GLStateManager.getPointState()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
|
||||
GLStateManager.getCullState()
|
||||
, GLStateManager.getPolygonSmoothState()
|
||||
, GLStateManager.getPolygonStippleState()
|
||||
, GLStateManager.getPolygonOffsetFillState()
|
||||
, GLStateManager.getPolygonOffsetLineState()
|
||||
, GLStateManager.getPolygonOffsetPointState()
|
||||
, GLStateManager.getPolygonState()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_POLYGON_STIPPLE_BIT, ImmutableSet.of(
|
||||
// Polygon stipple pattern
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_SCISSOR_BIT, ImmutableSet.of(
|
||||
GLStateManager.getScissorTest()
|
||||
, GLStateManager.getScissorState()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
|
||||
GLStateManager.getStencilTest()
|
||||
, GLStateManager.getStencilState()
|
||||
));
|
||||
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of(
|
||||
GLStateManager.getActiveTextureUnitStack()
|
||||
// GL_TEXTURE_ENV_MODE + GL_TEXTURE_ENV_COLOR — now per-unit in TexEnvState (added below)
|
||||
// Enable bits for the four texture coordinates
|
||||
|
||||
// Border color for each texture image
|
||||
// Minification function for each texture image
|
||||
// Magnification function for each texture image
|
||||
// Texture coordinates and wrap mode for each texture image
|
||||
|
||||
// Enable bits GL_TEXTURE_GEN_x, x is S, T, R, and Q
|
||||
// GL_TEXTURE_GEN_MODE setting for S, T, R, and Q
|
||||
// glTexGen plane equations for S, T, R, and Q
|
||||
// Current texture bindings (for example, GL_TEXTURE_BINDING_2D) - Below
|
||||
));
|
||||
|
||||
// Current Texture Bindings - GL_TEXTURE_BINDING_2D + per-unit TexEnvState
|
||||
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
textureAttribs.add(GLStateManager.getTextures().getTextureUnitBindings(i));
|
||||
textureAttribs.add(GLStateManager.getTextures().getTexEnvState(i));
|
||||
textureAttribs.add(GLStateManager.getTextures().getTexture1DStates(i));
|
||||
textureAttribs.add(GLStateManager.getTextures().getTextureUnitStates(i));
|
||||
textureAttribs.add(GLStateManager.getTextures().getTexture3DStates(i));
|
||||
textureAttribs.add(GLStateManager.getTextures().getTextureCubeMapStates(i));
|
||||
}
|
||||
|
||||
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
|
||||
addTextureGenStates(textureAttribs);
|
||||
|
||||
attribToFeatures.put(GL11.GL_TEXTURE_BIT, textureAttribs);
|
||||
|
||||
final Set<IStateStack<?>> transformAttribs = new HashSet<>(ImmutableSet.of(
|
||||
// Coefficients of the six clipping planes
|
||||
|
||||
GLStateManager.getMatrixMode()
|
||||
, GLStateManager.getNormalizeState()
|
||||
, GLStateManager.getRescaleNormalState()
|
||||
));
|
||||
for(int i = 0; i < GLStateManager.getClipPlaneStates().length; i++) {
|
||||
transformAttribs.add(GLStateManager.getClipPlaneStates()[i]);
|
||||
}
|
||||
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, transformAttribs);
|
||||
attribToFeatures.put(GL11.GL_VIEWPORT_BIT, ImmutableSet.of(
|
||||
|
||||
GLStateManager.getViewportState()
|
||||
));
|
||||
|
||||
for (Int2ObjectMap.Entry<Set<IStateStack<?>>> entry : attribToFeatures.int2ObjectEntrySet()) {
|
||||
attribToMembers.put(entry.getIntKey(), toMembers(entry.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* Software emulation of GL feedback mode
|
||||
*/
|
||||
public class FeedbackManager {
|
||||
|
||||
private static final int FB_3D = 0x01;
|
||||
private static final int FB_4D = 0x02;
|
||||
private static final int FB_COLOR = 0x04;
|
||||
private static final int FB_TEXTURE = 0x08;
|
||||
|
||||
private static int renderMode = GL11.GL_RENDER;
|
||||
private static FloatBuffer feedbackBuffer;
|
||||
private static int feedbackBufferSize;
|
||||
private static int feedbackMask;
|
||||
private static int feedbackCount;
|
||||
|
||||
private static final Matrix4f mvpMatrix = new Matrix4f();
|
||||
private static final Vector4f clipVec = new Vector4f();
|
||||
|
||||
private static ByteBuffer readbackBuf = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
|
||||
private static ByteBuffer indexReadbackBuf = ByteBuffer.allocateDirect(4 * 1024).order(ByteOrder.nativeOrder());
|
||||
|
||||
@FunctionalInterface
|
||||
private interface IndexReader {
|
||||
|
||||
int read(int i);
|
||||
}
|
||||
|
||||
public static boolean isFeedbackMode() {
|
||||
return renderMode == GL11.GL_FEEDBACK;
|
||||
}
|
||||
|
||||
public static int getRenderMode() {
|
||||
return renderMode;
|
||||
}
|
||||
|
||||
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
|
||||
if (renderMode == GL11.GL_FEEDBACK) {
|
||||
GLStateManager.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
|
||||
return;
|
||||
}
|
||||
feedbackBuffer = buffer;
|
||||
feedbackBufferSize = buffer.remaining();
|
||||
feedbackMask = computeMask(type);
|
||||
feedbackCount = 0;
|
||||
}
|
||||
|
||||
public static int glRenderMode(int mode) {
|
||||
int result = switch (renderMode) {
|
||||
case GL11.GL_FEEDBACK -> (feedbackCount > feedbackBufferSize) ? -1 : feedbackCount;
|
||||
case GL11.GL_RENDER, GL11.GL_SELECT -> 0;
|
||||
default -> 0;
|
||||
};
|
||||
|
||||
if (mode == GL11.GL_SELECT) {
|
||||
GLStateManager.LOGGER.warn("glRenderMode(GL_SELECT): selection mode not emulated");
|
||||
}
|
||||
|
||||
feedbackCount = 0;
|
||||
renderMode = mode;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void glPassThrough(float token) {
|
||||
if (renderMode == GL11.GL_FEEDBACK) {
|
||||
feedbackToken(GL11.GL_PASS_THROUGH_TOKEN);
|
||||
feedbackToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
private static void feedbackToken(float token) {
|
||||
if (feedbackCount < feedbackBufferSize) {
|
||||
feedbackBuffer.put(feedbackBuffer.position() + feedbackCount, token);
|
||||
}
|
||||
feedbackCount++;
|
||||
}
|
||||
|
||||
private static void feedbackVertex(float winX, float winY, float winZ, float winW) {
|
||||
feedbackToken(winX);
|
||||
feedbackToken(winY);
|
||||
if ((feedbackMask & FB_3D) != 0) feedbackToken(winZ);
|
||||
if ((feedbackMask & FB_4D) != 0) feedbackToken(winW);
|
||||
if ((feedbackMask & FB_COLOR) != 0) {
|
||||
feedbackToken(1f);
|
||||
feedbackToken(1f);
|
||||
feedbackToken(1f);
|
||||
feedbackToken(1f);
|
||||
}
|
||||
if ((feedbackMask & FB_TEXTURE) != 0) {
|
||||
feedbackToken(0f);
|
||||
feedbackToken(0f);
|
||||
feedbackToken(0f);
|
||||
feedbackToken(1f);
|
||||
}
|
||||
}
|
||||
|
||||
private static void transformAndEmitVertex(float x, float y, float z) {
|
||||
clipVec.set(x, y, z, 1.0f);
|
||||
mvpMatrix.transform(clipVec);
|
||||
|
||||
final float w = clipVec.w;
|
||||
final float ndcX = clipVec.x / w;
|
||||
final float ndcY = clipVec.y / w;
|
||||
final float ndcZ = clipVec.z / w;
|
||||
|
||||
final ViewportState vp = GLStateManager.getViewportState();
|
||||
final float winX = vp.x + vp.width * (ndcX + 1.0f) * 0.5f;
|
||||
final float winY = vp.y + vp.height * (ndcY + 1.0f) * 0.5f;
|
||||
final float winZ = (float) ((vp.depthRangeFar - vp.depthRangeNear) * ndcZ + (vp.depthRangeFar + vp.depthRangeNear)) * 0.5f;
|
||||
final float winW = 1.0f / w;
|
||||
|
||||
feedbackVertex(winX, winY, winZ, winW);
|
||||
}
|
||||
|
||||
private static int positionStride(GLContextState glCtx) {
|
||||
final int s = glCtx.vaos.attribEffectiveStride(0);
|
||||
return (s != 0) ? s : 12;
|
||||
}
|
||||
|
||||
private static long positionOffset(GLContextState glCtx) {
|
||||
return glCtx.vaos.attribOffset(0);
|
||||
}
|
||||
|
||||
public static void processDrawArrays(int mode, int first, int count) {
|
||||
computeMVP();
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (!glCtx.vaos.isAttribEnabled(0)) return;
|
||||
final int stride = positionStride(glCtx);
|
||||
final long posOffset = positionOffset(glCtx);
|
||||
final int byteCount = count * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
readPositionBuffer((long) first * stride + posOffset, readbackBuf, glCtx);
|
||||
|
||||
emitAllPrimitives(mode, count, i -> i, stride);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
|
||||
computeMVP();
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (!glCtx.vaos.isAttribEnabled(0)) return;
|
||||
final int stride = positionStride(glCtx);
|
||||
|
||||
final int elementSize = GLTypes.sizeBytes(type);
|
||||
final int indexByteCount = indexCount * elementSize;
|
||||
ensureIndexReadbackCapacity(indexByteCount);
|
||||
indexReadbackBuf.clear().limit(indexByteCount);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboOffset, indexReadbackBuf);
|
||||
final ByteBuffer indexBuf = indexReadbackBuf;
|
||||
|
||||
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < indexCount; i++) {
|
||||
final int idx = ImmediateModeRecorder.readIndex(indexBuf, type, 0, i);
|
||||
if (idx < minIdx) minIdx = idx;
|
||||
if (idx > maxIdx) maxIdx = idx;
|
||||
}
|
||||
|
||||
final long posOffset = positionOffset(glCtx);
|
||||
final int vertexCount = maxIdx - minIdx + 1;
|
||||
final int byteCount = vertexCount * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf, glCtx);
|
||||
|
||||
final int base = minIdx;
|
||||
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, ByteBuffer indices) {
|
||||
final int basePos = indices.position();
|
||||
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFF);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, ShortBuffer indices) {
|
||||
final int basePos = indices.position();
|
||||
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFFFF);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, IntBuffer indices) {
|
||||
final int basePos = indices.position();
|
||||
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i));
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, int count, int type, ByteBuffer indices) {
|
||||
final int pos = indices.position();
|
||||
final IndexReader reader = switch (type) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> i -> indices.get(pos + i) & 0xFF;
|
||||
case GL11.GL_UNSIGNED_SHORT -> i -> indices.getShort(pos + i * 2) & 0xFFFF;
|
||||
case GL11.GL_UNSIGNED_INT -> i -> indices.getInt(pos + i * 4);
|
||||
default -> i -> 0;
|
||||
};
|
||||
processDrawElementsImpl(mode, count, reader);
|
||||
}
|
||||
|
||||
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
|
||||
computeMVP();
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (!glCtx.vaos.isAttribEnabled(0)) return;
|
||||
final int stride = positionStride(glCtx);
|
||||
|
||||
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int idx = reader.read(i);
|
||||
if (idx < minIdx) minIdx = idx;
|
||||
if (idx > maxIdx) maxIdx = idx;
|
||||
}
|
||||
|
||||
readVerticesFromVBO(minIdx, maxIdx, stride, glCtx);
|
||||
final int base = minIdx;
|
||||
emitAllPrimitives(mode, count, i -> reader.read(i) - base, stride);
|
||||
}
|
||||
|
||||
private static void computeMVP() {
|
||||
mvpMatrix.set(GLStateManager.getProjectionMatrix()).mul(GLStateManager.getModelViewMatrix());
|
||||
}
|
||||
|
||||
private static int computeMask(int type) {
|
||||
return switch (type) {
|
||||
case GL11.GL_2D -> 0;
|
||||
case GL11.GL_3D -> FB_3D;
|
||||
case GL11.GL_3D_COLOR -> FB_3D | FB_COLOR;
|
||||
case GL11.GL_3D_COLOR_TEXTURE -> FB_3D | FB_COLOR | FB_TEXTURE;
|
||||
case GL11.GL_4D_COLOR_TEXTURE -> FB_3D | FB_4D | FB_COLOR | FB_TEXTURE;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static void ensureReadbackCapacity(int bytes) {
|
||||
if (readbackBuf.capacity() < bytes) {
|
||||
readbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureIndexReadbackCapacity(int bytes) {
|
||||
if (indexReadbackBuf.capacity() < bytes) {
|
||||
indexReadbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
}
|
||||
|
||||
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride, GLContextState glCtx) {
|
||||
final long posOffset = positionOffset(glCtx);
|
||||
final int vertexCount = maxIdx - minIdx + 1;
|
||||
final int byteCount = vertexCount * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf, glCtx);
|
||||
}
|
||||
|
||||
private static void readPositionBuffer(long offset, ByteBuffer destination, GLContextState glCtx) {
|
||||
final VAOManager.Attrib position = glCtx.vaos.attrib(0);
|
||||
if (position != null && position.clientPointer != null) {
|
||||
final ByteBuffer source = position.clientPointer.duplicate();
|
||||
source.position(source.position() + (int) offset);
|
||||
if (source.remaining() > destination.remaining()) source.limit(source.position() + destination.remaining());
|
||||
destination.duplicate().put(source);
|
||||
return;
|
||||
}
|
||||
final int positionVbo = glCtx.vaos.attribVBO(0);
|
||||
final int previousVbo = glCtx.boundVBO;
|
||||
|
||||
if (positionVbo != 0 && positionVbo != previousVbo) {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, positionVbo);
|
||||
}
|
||||
try {
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, offset, destination);
|
||||
} finally {
|
||||
if (positionVbo != 0 && positionVbo != previousVbo) {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, previousVbo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitVertex(int vertexIndex, int stride) {
|
||||
final float x = readbackBuf.getFloat(vertexIndex * stride);
|
||||
final float y = readbackBuf.getFloat(vertexIndex * stride + 4);
|
||||
final float z = readbackBuf.getFloat(vertexIndex * stride + 8);
|
||||
transformAndEmitVertex(x, y, z);
|
||||
}
|
||||
|
||||
private static void emitAllPrimitives(int mode, int count, IndexReader reader, int stride) {
|
||||
switch (mode) {
|
||||
case GL11.GL_POINTS:
|
||||
for (int i = 0; i < count; i++) {
|
||||
feedbackToken(GL11.GL_POINT_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_LINES:
|
||||
for (int i = 0; i + 1 < count; i += 2) {
|
||||
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_LINE_STRIP:
|
||||
for (int i = 0; i + 1 < count; i++) {
|
||||
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_LINE_LOOP:
|
||||
for (int i = 0; i + 1 < count; i++) {
|
||||
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
if (count > 2) {
|
||||
feedbackToken(GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(count - 1), stride);
|
||||
emitVertex(reader.read(0), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_TRIANGLES:
|
||||
for (int i = 0; i + 2 < count; i += 3) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(3);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_TRIANGLE_STRIP:
|
||||
for (int i = 0; i + 2 < count; i++) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(3);
|
||||
if ((i & 1) == 0) {
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
} else {
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i), stride);
|
||||
}
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_TRIANGLE_FAN:
|
||||
for (int i = 1; i + 1 < count; i++) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(3);
|
||||
emitVertex(reader.read(0), stride);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_QUADS:
|
||||
for (int i = 0; i + 3 < count; i += 4) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(4);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
emitVertex(reader.read(i + 3), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_QUAD_STRIP:
|
||||
for (int i = 0; i + 3 < count; i += 2) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(4);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i + 3), stride);
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_POLYGON:
|
||||
if (count >= 3) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
emitVertex(reader.read(i), stride);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Feedback mode: unsupported primitive mode 0x" + Integer.toHexString(mode));
|
||||
}
|
||||
}
|
||||
|
||||
static void reset() {
|
||||
renderMode = GL11.GL_RENDER;
|
||||
feedbackBuffer = null;
|
||||
feedbackBufferSize = 0;
|
||||
feedbackMask = 0;
|
||||
feedbackCount = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.Color4Stack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ColorMaskStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowDepths;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowDispatch;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.DepthStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.IntegerStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.LightModelStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.LineStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.RetainedState;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ScissorStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.StreamingVaos;
|
||||
import org.joml.Matrix4d;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fStack;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GLContextState {
|
||||
private final List<IStateStack<?>> allStacksBuilder = new ArrayList<>();
|
||||
public IStateStack<?>[] allStacks;
|
||||
final List<BooleanStateStack> allBooleanStates = new ArrayList<>();
|
||||
|
||||
private BooleanStateStack track(BooleanStateStack s) {
|
||||
s.setStackId(StackIdAllocator.nextId());
|
||||
allBooleanStates.add(s);
|
||||
allStacksBuilder.add(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
private <T extends IStateStack<?>> T member(T s) {
|
||||
allStacksBuilder.add(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
public int programGeneration;
|
||||
public int drawFramebufferGeneration;
|
||||
public boolean poppingAttributes;
|
||||
// modelview matrix changes (any: translation, rotation, scale)
|
||||
public int mvGeneration;
|
||||
public int mvLinearGeneration;
|
||||
// projection matrix changes
|
||||
public int projGeneration;
|
||||
// texture matrix changes
|
||||
public int texMatrixGeneration;
|
||||
public int lightingGeneration;
|
||||
// fog + alpha ref + overlay color
|
||||
public int fragmentGeneration;
|
||||
// current vertex color
|
||||
public int colorGeneration;
|
||||
// clip plane equation changes
|
||||
public int clipPlaneGeneration;
|
||||
public int clipPlaneEnabledMask;
|
||||
public float overlayR = 0.0f;
|
||||
public float overlayG = 0.0f;
|
||||
public float overlayB = 0.0f;
|
||||
public float overlayA = 0.0f;
|
||||
public float shaderColorR = 1.0f;
|
||||
public float shaderColorG = 1.0f;
|
||||
public float shaderColorB = 1.0f;
|
||||
public float shaderColorA = 1.0f;
|
||||
// Deferred vertex attribute upload flags - set when state changes, flushed before draw
|
||||
public boolean dirtyColorAttrib = true;
|
||||
public boolean dirtyNormalAttrib;
|
||||
public boolean dirtyTexCoordAttrib;
|
||||
public boolean dirtyLightmapAttrib = true;
|
||||
public boolean unit23TexCoordSetDuringDraw = false;
|
||||
public int maxBoundTextureUnit = 0;
|
||||
public int maxBoundImageUnit = 0;
|
||||
public final StateSet[] attribSets = new StateSet[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public RetainedState retainedOwner;
|
||||
public int attribDepth = 0;
|
||||
|
||||
public boolean savesState(int id) {
|
||||
for (int i = attribDepth - 1; i >= 0; i--) {
|
||||
if (attribSets[i].contains(id)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int batchStateGeneration;
|
||||
public final int[] savedBatchStateGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public CowStateStack<?>[] stackById;
|
||||
public CowDepths[] depthsById;
|
||||
public int[] restoreBitById;
|
||||
public int[] restoreUnitById;
|
||||
public byte[] kindById;
|
||||
public int[][] modifiedIds;
|
||||
public int[] modifiedCount;
|
||||
public final int[] savedMvGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedMvLinearGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedProjGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedTexMatGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedLightingGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedFragmentGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedColorGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedNormalGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedTexCoordGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final IntegerStateStack activeTextureUnit = member(new IntegerStateStack(0, StackIdAllocator.nextId()).restoreBit(StateSet.R_ACTIVE_UNIT));
|
||||
public int clientActiveTextureUnit = 0;
|
||||
public final IntegerStateStack shadeModelState = member(new IntegerStateStack(GL11.GL_SMOOTH, StackIdAllocator.nextId()));
|
||||
public final TextureUnitArray textures = new TextureUnitArray();
|
||||
public final ImageUnitArray imageUnits = new ImageUnitArray();
|
||||
public final SamplerUnitArray samplerUnits = new SamplerUnitArray();
|
||||
public final BlendStateStack blendState = member(new BlendStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack blendMode = track(new BooleanStateStack(GL11.GL_BLEND));
|
||||
public final BooleanStateStack scissorTest = track(new BooleanStateStack(GL11.GL_SCISSOR_TEST));
|
||||
public final BooleanStateStack rasterizerDiscard = track(new BooleanStateStack(GL30.GL_RASTERIZER_DISCARD));
|
||||
public final DepthStateStack depthState = member(new DepthStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack depthTest = track(new BooleanStateStack(GL11.GL_DEPTH_TEST));
|
||||
public final FogStateStack fogState = member(new FogStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack fogMode = track(new BooleanStateStack(GL11.GL_FOG, false, true));
|
||||
public final Color4Stack color = member(new Color4Stack(StackIdAllocator.nextId()));
|
||||
// GL_CURRENT_SECONDARY_COLOR - alpha is always 1 and unused, the color sum only adds RGB
|
||||
public final Color4Stack secondaryColor = member(new Color4Stack(StackIdAllocator.nextId(), new Color4(0.0F, 0.0F, 0.0F, 1.0F)).restoreBit(StateSet.R_SECONDARY_COLOR));
|
||||
public final BooleanStateStack colorSumState = track(new BooleanStateStack(GL14.GL_COLOR_SUM, false, true));
|
||||
public final Color4Stack clearColor = member(new Color4Stack(StackIdAllocator.nextId(), new Color4(0.0F, 0.0F, 0.0F, 0.0F)).restoreBit(StateSet.R_CLEAR_COLOR));
|
||||
public final ColorMaskStack colorMask = member(new ColorMaskStack(StackIdAllocator.nextId()));
|
||||
public final IntegerStateStack drawBuffer = member(new IntegerStateStack(GLStateManager.DEFAULT_DRAW_BUFFER, StackIdAllocator.nextId()).restoreBit(StateSet.R_DRAW_BUFFER));
|
||||
public final IntegerStateStack logicOpMode = member(new IntegerStateStack(GL11.GL_COPY, StackIdAllocator.nextId()).restoreBit(StateSet.R_LOGIC_OP));
|
||||
public final BooleanStateStack cullState = track(new BooleanStateStack(GL11.GL_CULL_FACE));
|
||||
public final AlphaState restoredAlphaScratch = new AlphaState();
|
||||
public final AlphaStateStack alphaState = member(new AlphaStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack alphaTest = track(new BooleanStateStack(GL11.GL_ALPHA_TEST, false, true));
|
||||
public final BooleanStateStack lightingState = track(new BooleanStateStack(GL11.GL_LIGHTING, false, true));
|
||||
public final BooleanStateStack rescaleNormalState = track(new BooleanStateStack(GL12.GL_RESCALE_NORMAL, false, true));
|
||||
public final BooleanStateStack normalizeState = track(new BooleanStateStack(GL11.GL_NORMALIZE, false, true));
|
||||
public final BooleanStateStack ditherState = track(new BooleanStateStack(GL11.GL_DITHER, true));
|
||||
public final BooleanStateStack stencilTest = track(new BooleanStateStack(GL11.GL_STENCIL_TEST));
|
||||
public final BooleanStateStack lineSmoothState = track(new BooleanStateStack(GL11.GL_LINE_SMOOTH));
|
||||
public final BooleanStateStack lineStippleState = track(new BooleanStateStack(GL11.GL_LINE_STIPPLE, false, true));
|
||||
public final BooleanStateStack pointSmoothState = track(new BooleanStateStack(GL11.GL_POINT_SMOOTH, false, true));
|
||||
public final BooleanStateStack pointSpriteState = track(new BooleanStateStack(GL20.GL_POINT_SPRITE, false, true));
|
||||
public final BooleanStateStack vertexProgramTwoSideState = track(new BooleanStateStack(GL20.GL_VERTEX_PROGRAM_TWO_SIDE, false, true));
|
||||
public final BooleanStateStack edgeFlagState = track(new BooleanStateStack(GL11.GL_EDGE_FLAG, true, true));
|
||||
public final BooleanStateStack polygonSmoothState = track(new BooleanStateStack(GL11.GL_POLYGON_SMOOTH));
|
||||
public final BooleanStateStack polygonStippleState = track(new BooleanStateStack(GL11.GL_POLYGON_STIPPLE, false, true));
|
||||
public final BooleanStateStack multisampleState = track(new BooleanStateStack(GL13.GL_MULTISAMPLE, true));
|
||||
public final BooleanStateStack sampleAlphaToCoverageState = track(new BooleanStateStack(GL13.GL_SAMPLE_ALPHA_TO_COVERAGE));
|
||||
public final BooleanStateStack sampleAlphaToOneState = track(new BooleanStateStack(GL13.GL_SAMPLE_ALPHA_TO_ONE));
|
||||
public final BooleanStateStack sampleCoverageState = track(new BooleanStateStack(GL13.GL_SAMPLE_COVERAGE));
|
||||
public final BooleanStateStack colorLogicOpState = track(new BooleanStateStack(GL11.GL_COLOR_LOGIC_OP));
|
||||
public final BooleanStateStack indexLogicOpState = track(new BooleanStateStack(GL11.GL_INDEX_LOGIC_OP, false, true));
|
||||
public final BooleanStateStack polygonOffsetPointState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_POINT));
|
||||
public final BooleanStateStack polygonOffsetLineState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_LINE));
|
||||
public final BooleanStateStack polygonOffsetFillState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_FILL));
|
||||
public final LineStateStack lineState = member(new LineStateStack(StackIdAllocator.nextId()));
|
||||
public final PointStateStack pointState = member(new PointStateStack(StackIdAllocator.nextId()));
|
||||
public final IntegerStateStack hintState = member(new IntegerStateStack(0, StackIdAllocator.nextId()).restoreBit(StateSet.R_HINT));
|
||||
public final PolygonStateStack polygonState = member(new PolygonStateStack(StackIdAllocator.nextId()));
|
||||
public final StencilStateStack stencilState = member(new StencilStateStack(StackIdAllocator.nextId()));
|
||||
public int stencilBitMask = 0xFFFFFFFF;
|
||||
public final BooleanStateStack autoNormalState = track(new BooleanStateStack(GL11.GL_AUTO_NORMAL, false, true));
|
||||
public final BooleanStateStack map1Color4State = track(new BooleanStateStack(GL11.GL_MAP1_COLOR_4, false, true));
|
||||
public final BooleanStateStack map1IndexState = track(new BooleanStateStack(GL11.GL_MAP1_INDEX, false, true));
|
||||
public final BooleanStateStack map1NormalState = track(new BooleanStateStack(GL11.GL_MAP1_NORMAL, false, true));
|
||||
public final BooleanStateStack map1TextureCoord1State = track(new BooleanStateStack(GL11.GL_MAP1_TEXTURE_COORD_1, false, true));
|
||||
public final BooleanStateStack map1TextureCoord2State = track(new BooleanStateStack(GL11.GL_MAP1_TEXTURE_COORD_2, false, true));
|
||||
public final BooleanStateStack map1TextureCoord3State = track(new BooleanStateStack(GL11.GL_MAP1_TEXTURE_COORD_3, false, true));
|
||||
public final BooleanStateStack map1TextureCoord4State = track(new BooleanStateStack(GL11.GL_MAP1_TEXTURE_COORD_4, false, true));
|
||||
public final BooleanStateStack map1Vertex3State = track(new BooleanStateStack(GL11.GL_MAP1_VERTEX_3, false, true));
|
||||
public final BooleanStateStack map1Vertex4State = track(new BooleanStateStack(GL11.GL_MAP1_VERTEX_4, false, true));
|
||||
public final BooleanStateStack map2Color4State = track(new BooleanStateStack(GL11.GL_MAP2_COLOR_4, false, true));
|
||||
public final BooleanStateStack map2IndexState = track(new BooleanStateStack(GL11.GL_MAP2_INDEX, false, true));
|
||||
public final BooleanStateStack map2NormalState = track(new BooleanStateStack(GL11.GL_MAP2_NORMAL, false, true));
|
||||
public final BooleanStateStack map2TextureCoord1State = track(new BooleanStateStack(GL11.GL_MAP2_TEXTURE_COORD_1, false, true));
|
||||
public final BooleanStateStack map2TextureCoord2State = track(new BooleanStateStack(GL11.GL_MAP2_TEXTURE_COORD_2, false, true));
|
||||
public final BooleanStateStack map2TextureCoord3State = track(new BooleanStateStack(GL11.GL_MAP2_TEXTURE_COORD_3, false, true));
|
||||
public final BooleanStateStack map2TextureCoord4State = track(new BooleanStateStack(GL11.GL_MAP2_TEXTURE_COORD_4, false, true));
|
||||
public final BooleanStateStack map2Vertex3State = track(new BooleanStateStack(GL11.GL_MAP2_VERTEX_3, false, true));
|
||||
public final BooleanStateStack map2Vertex4State = track(new BooleanStateStack(GL11.GL_MAP2_VERTEX_4, false, true));
|
||||
public final BooleanStateStack[] clipPlaneStates = new BooleanStateStack[GLStateManager.MAX_CLIP_PLANES];
|
||||
public final ClipPlaneState clipPlaneState = new ClipPlaneState();
|
||||
public final FloatBuffer queryScratch = BufferUtils.createFloatBuffer(16);
|
||||
public final DoubleBuffer queryScratchDouble = BufferUtils.createDoubleBuffer(16);
|
||||
public final MatrixModeStack matrixMode = member(new MatrixModeStack(StackIdAllocator.nextId()));
|
||||
public final Matrix4fStack modelViewMatrix = new Matrix4fStack(GLStateManager.MAX_MODELVIEW_STACK_DEPTH);
|
||||
public final Matrix4fStack projectionMatrix = new Matrix4fStack(GLStateManager.MAX_PROJECTION_STACK_DEPTH);
|
||||
public final BooleanStateStack[] lightStates = new BooleanStateStack[8];
|
||||
public final LightStateStack[] lightDataStates = new LightStateStack[8];
|
||||
public final BooleanStateStack colorMaterial = track(new BooleanStateStack(GL11.GL_COLOR_MATERIAL, false, true));
|
||||
public final IntegerStateStack colorMaterialFace = member(new IntegerStateStack(GL11.GL_FRONT_AND_BACK, StackIdAllocator.nextId()));
|
||||
public final IntegerStateStack colorMaterialParameter = member(new IntegerStateStack(GL11.GL_AMBIENT_AND_DIFFUSE, StackIdAllocator.nextId()));
|
||||
public final LightModelStateStack lightModel = member(new LightModelStateStack(StackIdAllocator.nextId()));
|
||||
public final MaterialStateStack frontMaterial = member(new MaterialStateStack(GL11.GL_FRONT, StackIdAllocator.nextId()));
|
||||
public final MaterialStateStack backMaterial = member(new MaterialStateStack(GL11.GL_BACK, StackIdAllocator.nextId()));
|
||||
public final ViewPortStateStack viewportState = member(new ViewPortStateStack(StackIdAllocator.nextId()));
|
||||
public final ScissorStateStack scissorState = member(new ScissorStateStack(StackIdAllocator.nextId()));
|
||||
public int activeProgram = 0;
|
||||
public final IntegerStateStack programStack = member(new IntegerStateStack(0, StackIdAllocator.nextId()));
|
||||
public int listBase = 0;
|
||||
public int boundVBO;
|
||||
public int boundVAO;
|
||||
public int boundPixelUnpackBuffer;
|
||||
public int boundPixelPackBuffer;
|
||||
public int boundCopyReadBuffer;
|
||||
public int boundCopyWriteBuffer;
|
||||
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
|
||||
public PixelStoreState pixelUnpackState = PixelStoreState.DEFAULT;
|
||||
public PixelStoreState pixelPackState = PixelStoreState.DEFAULT;
|
||||
public final int[] clientAttribSavedMask = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final PixelStoreState[] clientAttribSavedPixelUnpack = new PixelStoreState[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final PixelStoreState[] clientAttribSavedPixelPack = new PixelStoreState[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final int[] clientAttribSavedUnpackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final int[] clientAttribSavedPackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final VAOManager.ClientArrays[] clientAttribSavedArrays = new VAOManager.ClientArrays[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public int clientAttribStackPointer = 0;
|
||||
public float maxPointSize = Float.NaN;
|
||||
public int restoreChangedMask;
|
||||
public long restoreUnitChangedMask;
|
||||
public final BlendState vanillaBlendBefore = new BlendState();
|
||||
public final BlendState vanillaBlendAfter = new BlendState();
|
||||
public boolean vanillaBlendEnabledBefore;
|
||||
public int drawFramebuffer = 0;
|
||||
public int readFramebuffer = 0;
|
||||
public int texGenGeneration;
|
||||
public int defaultVAO;
|
||||
public final VAOManager vaos = new VAOManager();
|
||||
public final StreamingVaos streamingVaos = new StreamingVaos();
|
||||
public int fontVao;
|
||||
public boolean lockBindCallback;
|
||||
public int foreignDrawDepth;
|
||||
public int internalGlDepth;
|
||||
public int internalDrawDepth;
|
||||
public int savedFilterTextureId;
|
||||
public int savedFilterMin = -1;
|
||||
public int savedFilterMag = -1;
|
||||
public float savedFilterAniso = -1.0f;
|
||||
public boolean lineQuadsActive = false;
|
||||
public boolean lineStippleActive = false;
|
||||
public Instancing ffpInstancing = Instancing.NONE;
|
||||
public float lastBrightnessX;
|
||||
public float lastBrightnessY;
|
||||
public final Matrix4f multMatrix = new Matrix4f();
|
||||
public final Matrix4d conversionMatrix4d = new Matrix4d();
|
||||
public final Matrix4f conversionMatrix4f = new Matrix4f();
|
||||
public final Matrix4f gluMatrix = new Matrix4f();
|
||||
public final Matrix4f gluMatrix2 = new Matrix4f();
|
||||
public final Vector4f gluVec4 = new Vector4f();
|
||||
public final FloatBuffer gluBuffer = BufferUtils.createFloatBuffer(16);
|
||||
public final float[] texGenTempPlane = new float[4];
|
||||
public final IntBuffer shaderBuf = BufferUtils.createIntBuffer(8);
|
||||
public final FloatBuffer projectionMatrixBuffer = BufferUtils.createFloatBuffer(16);
|
||||
public final ShaderManager ffp = new ShaderManager();
|
||||
|
||||
GLContextState() {
|
||||
allStacksBuilder.addAll(textures.idStacks());
|
||||
for (int i = 0; i < lightStates.length; i++) {
|
||||
lightStates[i] = track(new BooleanStateStack(GL11.GL_LIGHT0 + i, false, true));
|
||||
}
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
clipPlaneStates[i] = track(new ClipPlaneBooleanState(this, i, GL11.GL_CLIP_PLANE0 + i));
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
for (int i = 0; i < lightDataStates.length; i++) {
|
||||
lightDataStates[i] = member(new LightStateStack(GL11.GL_LIGHT0 + i, StackIdAllocator.nextId()));
|
||||
}
|
||||
allStacksBuilder.add(ffp.normalStack);
|
||||
allStacksBuilder.add(ffp.texCoordStack);
|
||||
allStacks = allStacksBuilder.toArray(new IStateStack<?>[0]);
|
||||
|
||||
final int capacity = StackIdAllocator.allocated();
|
||||
stackById = new CowStateStack<?>[capacity];
|
||||
depthsById = new CowDepths[capacity];
|
||||
restoreBitById = new int[capacity];
|
||||
restoreUnitById = new int[capacity];
|
||||
kindById = new byte[capacity];
|
||||
for (final IStateStack<?> stack : allStacks) {
|
||||
final CowStateStack<?> s = (CowStateStack<?>) stack;
|
||||
final int id = s.stackId();
|
||||
stackById[id] = s;
|
||||
depthsById[id] = s.cowDepths();
|
||||
depthsById[id].owner = this;
|
||||
restoreBitById[id] = s.restoreBit();
|
||||
restoreUnitById[id] = s.restoreUnit();
|
||||
kindById[id] = CowDispatch.kindOf(s);
|
||||
}
|
||||
|
||||
modifiedIds = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH][allStacks.length];
|
||||
modifiedCount = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,655 @@
|
||||
/*
|
||||
* Copyright LWJGL. All rights reserved. Modified by IMS for use in Iris (net.coderbot.iris.gl).
|
||||
* License terms: https://www.lwjgl.org/license
|
||||
*/
|
||||
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
|
||||
import org.lwjgl.opengl.EXTBlendColor;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class GLDebug {
|
||||
|
||||
/**
|
||||
* Sets up debug callbacks
|
||||
*
|
||||
* @return 0 for failure, 1 for success, 2 for restart required.
|
||||
*/
|
||||
public static int setupDebugMessageCallback() {
|
||||
if (Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
GLStateManager.LOGGER.warn("setupDebugMessageCallback called from non-main thread!");
|
||||
return 0;
|
||||
}
|
||||
return setupDebugMessageCallbackImpl();
|
||||
}
|
||||
|
||||
public static Throwable filterStackTrace(Throwable throwable, int offset) {
|
||||
final StackTraceElement[] elems = throwable.getStackTrace();
|
||||
final StackTraceElement[] filtered = new StackTraceElement[elems.length];
|
||||
int j = 0;
|
||||
for (int i = offset; i < elems.length; i++) {
|
||||
String className = elems[i].getClassName();
|
||||
if (className == null) {
|
||||
className = "";
|
||||
}
|
||||
filtered[j++] = elems[i];
|
||||
}
|
||||
final StackTraceElement[] newElems = new StackTraceElement[j];
|
||||
System.arraycopy(filtered, 0, newElems, 0, j);
|
||||
throwable.setStackTrace(newElems);
|
||||
return throwable;
|
||||
}
|
||||
|
||||
private static String buildStackTrace() {
|
||||
final StackTraceElement[] elems = filterStackTrace(new Throwable(), 4).getStackTrace();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (StackTraceElement elem : elems) {
|
||||
sb.append("\n\t").append(elem.toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void logDebugMessage(int id, String source, String type, String severity, String message) {
|
||||
final String fullMessage = String.format("[GL] %s %s (0x%X) from %s: %s%s", severity, type, id, source, message, buildStackTrace());
|
||||
|
||||
if ("HIGH".equals(severity)) {
|
||||
GLStateManager.LOGGER.error(fullMessage);
|
||||
} else if ("MEDIUM".equals(severity)) {
|
||||
GLStateManager.LOGGER.warn(fullMessage);
|
||||
} else {
|
||||
GLStateManager.LOGGER.info(fullMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static int setupDebugMessageCallbackImpl() {
|
||||
return BackendManager.RENDER_BACKEND.setupDebugOutput(
|
||||
(source, type, id, severity, message) ->
|
||||
logDebugMessage(id, getDebugSource(source), getDebugType(type), getDebugSeverity(severity), message)
|
||||
);
|
||||
}
|
||||
|
||||
public static int disableDebugMessages() {
|
||||
return BackendManager.RENDER_BACKEND.disableDebugOutput();
|
||||
}
|
||||
|
||||
private static String getDebugSource(int source) {
|
||||
return switch (source) {
|
||||
case GL43.GL_DEBUG_SOURCE_API -> "API";
|
||||
case GL43.GL_DEBUG_SOURCE_WINDOW_SYSTEM -> "WINDOW SYSTEM";
|
||||
case GL43.GL_DEBUG_SOURCE_SHADER_COMPILER -> "SHADER COMPILER";
|
||||
case GL43.GL_DEBUG_SOURCE_THIRD_PARTY -> "THIRD PARTY";
|
||||
case GL43.GL_DEBUG_SOURCE_APPLICATION -> "APPLICATION";
|
||||
case GL43.GL_DEBUG_SOURCE_OTHER -> "OTHER";
|
||||
default -> String.format("Unknown [0x%X]", source);
|
||||
};
|
||||
}
|
||||
|
||||
private static String getDebugType(int type) {
|
||||
return switch (type) {
|
||||
case GL43.GL_DEBUG_TYPE_ERROR -> "ERROR";
|
||||
case GL43.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR -> "DEPRECATED BEHAVIOR";
|
||||
case GL43.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR -> "UNDEFINED BEHAVIOR";
|
||||
case GL43.GL_DEBUG_TYPE_PORTABILITY -> "PORTABILITY";
|
||||
case GL43.GL_DEBUG_TYPE_PERFORMANCE -> "PERFORMANCE";
|
||||
case GL43.GL_DEBUG_TYPE_OTHER -> "OTHER";
|
||||
case GL43.GL_DEBUG_TYPE_MARKER -> "MARKER";
|
||||
default -> String.format("Unknown [0x%X]", type);
|
||||
};
|
||||
}
|
||||
|
||||
private static String getDebugSeverity(int severity) {
|
||||
return switch (severity) {
|
||||
case GL43.GL_DEBUG_SEVERITY_NOTIFICATION -> "NOTIFICATION";
|
||||
case GL43.GL_DEBUG_SEVERITY_HIGH -> "HIGH";
|
||||
case GL43.GL_DEBUG_SEVERITY_MEDIUM -> "MEDIUM";
|
||||
case GL43.GL_DEBUG_SEVERITY_LOW -> "LOW";
|
||||
default -> String.format("Unknown [0x%X]", severity);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getMatrixModeName(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_MODELVIEW -> "MODELVIEW";
|
||||
case GL11.GL_PROJECTION -> "PROJECTION";
|
||||
case GL11.GL_TEXTURE -> "TEXTURE";
|
||||
case GL11.GL_COLOR -> "COLOR";
|
||||
default -> String.format("0x%X", mode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getCapabilityName(int cap) {
|
||||
return switch (cap) {
|
||||
case GL11.GL_ALPHA_TEST -> "ALPHA_TEST";
|
||||
case GL11.GL_BLEND -> "BLEND";
|
||||
case GL11.GL_COLOR_MATERIAL -> "COLOR_MATERIAL";
|
||||
case GL11.GL_CULL_FACE -> "CULL_FACE";
|
||||
case GL11.GL_DEPTH_TEST -> "DEPTH_TEST";
|
||||
case GL11.GL_FOG -> "FOG";
|
||||
case GL11.GL_LIGHTING -> "LIGHTING";
|
||||
case GL11.GL_LINE_SMOOTH -> "LINE_SMOOTH";
|
||||
case GL11.GL_NORMALIZE -> "NORMALIZE";
|
||||
case GL11.GL_POINT_SMOOTH -> "POINT_SMOOTH";
|
||||
case GL11.GL_POLYGON_OFFSET_FILL -> "POLYGON_OFFSET_FILL";
|
||||
case GL11.GL_POLYGON_OFFSET_LINE -> "POLYGON_OFFSET_LINE";
|
||||
case GL11.GL_POLYGON_SMOOTH -> "POLYGON_SMOOTH";
|
||||
case GL12.GL_RESCALE_NORMAL -> "RESCALE_NORMAL";
|
||||
case GL11.GL_SCISSOR_TEST -> "SCISSOR_TEST";
|
||||
case GL11.GL_STENCIL_TEST -> "STENCIL_TEST";
|
||||
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
|
||||
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
|
||||
case GL11.GL_LIGHT0 -> "LIGHT0";
|
||||
case GL11.GL_LIGHT1 -> "LIGHT1";
|
||||
case GL11.GL_LIGHT2 -> "LIGHT2";
|
||||
case GL11.GL_LIGHT3 -> "LIGHT3";
|
||||
case GL11.GL_LIGHT4 -> "LIGHT4";
|
||||
case GL11.GL_LIGHT5 -> "LIGHT5";
|
||||
case GL11.GL_LIGHT6 -> "LIGHT6";
|
||||
case GL11.GL_LIGHT7 -> "LIGHT7";
|
||||
default -> String.format("0x%X", cap);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getComparisonFuncName(int func) {
|
||||
return switch (func) {
|
||||
case GL11.GL_NEVER -> "NEVER";
|
||||
case GL11.GL_LESS -> "LESS";
|
||||
case GL11.GL_EQUAL -> "EQUAL";
|
||||
case GL11.GL_LEQUAL -> "LEQUAL";
|
||||
case GL11.GL_GREATER -> "GREATER";
|
||||
case GL11.GL_NOTEQUAL -> "NOTEQUAL";
|
||||
case GL11.GL_GEQUAL -> "GEQUAL";
|
||||
case GL11.GL_ALWAYS -> "ALWAYS";
|
||||
default -> String.format("0x%X", func);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getBlendFactorName(int factor) {
|
||||
return switch (factor) {
|
||||
case GL11.GL_ZERO -> "ZERO";
|
||||
case GL11.GL_ONE -> "ONE";
|
||||
case GL11.GL_SRC_COLOR -> "SRC_COLOR";
|
||||
case GL11.GL_ONE_MINUS_SRC_COLOR -> "ONE_MINUS_SRC_COLOR";
|
||||
case GL11.GL_DST_COLOR -> "DST_COLOR";
|
||||
case GL11.GL_ONE_MINUS_DST_COLOR -> "ONE_MINUS_DST_COLOR";
|
||||
case GL11.GL_SRC_ALPHA -> "SRC_ALPHA";
|
||||
case GL11.GL_ONE_MINUS_SRC_ALPHA -> "ONE_MINUS_SRC_ALPHA";
|
||||
case GL11.GL_DST_ALPHA -> "DST_ALPHA";
|
||||
case GL11.GL_ONE_MINUS_DST_ALPHA -> "ONE_MINUS_DST_ALPHA";
|
||||
case EXTBlendColor.GL_CONSTANT_COLOR_EXT -> "CONSTANT_COLOR";
|
||||
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_COLOR_EXT -> "ONE_MINUS_CONSTANT_COLOR";
|
||||
case EXTBlendColor.GL_CONSTANT_ALPHA_EXT -> "CONSTANT_ALPHA";
|
||||
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_ALPHA_EXT -> "ONE_MINUS_CONSTANT_ALPHA";
|
||||
case GL11.GL_SRC_ALPHA_SATURATE -> "SRC_ALPHA_SATURATE";
|
||||
default -> String.format("0x%X", factor);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getShadeModelName(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_FLAT -> "FLAT";
|
||||
case GL11.GL_SMOOTH -> "SMOOTH";
|
||||
default -> String.format("0x%X", mode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getTextureTargetName(int target) {
|
||||
return switch (target) {
|
||||
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
|
||||
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
|
||||
case GL12.GL_TEXTURE_3D -> "TEXTURE_3D";
|
||||
case GL13.GL_TEXTURE_CUBE_MAP -> "TEXTURE_CUBE_MAP";
|
||||
default -> String.format("0x%X", target);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getTexturePnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_MIN_FILTER -> "TEXTURE_MIN_FILTER";
|
||||
case GL11.GL_TEXTURE_MAG_FILTER -> "TEXTURE_MAG_FILTER";
|
||||
case GL11.GL_TEXTURE_WRAP_S -> "TEXTURE_WRAP_S";
|
||||
case GL11.GL_TEXTURE_WRAP_T -> "TEXTURE_WRAP_T";
|
||||
case GL12.GL_TEXTURE_WRAP_R -> "TEXTURE_WRAP_R";
|
||||
case GL12.GL_TEXTURE_MIN_LOD -> "TEXTURE_MIN_LOD";
|
||||
case GL12.GL_TEXTURE_MAX_LOD -> "TEXTURE_MAX_LOD";
|
||||
case GL12.GL_TEXTURE_BASE_LEVEL -> "TEXTURE_BASE_LEVEL";
|
||||
case GL12.GL_TEXTURE_MAX_LEVEL -> "TEXTURE_MAX_LEVEL";
|
||||
case GL11.GL_TEXTURE_BORDER_COLOR -> "TEXTURE_BORDER_COLOR";
|
||||
case GL11.GL_TEXTURE_PRIORITY -> "TEXTURE_PRIORITY";
|
||||
case GL14.GL_TEXTURE_LOD_BIAS -> "TEXTURE_LOD_BIAS";
|
||||
case GL14.GL_GENERATE_MIPMAP -> "GENERATE_MIPMAP";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getTextureFormatName(int format) {
|
||||
return switch (format) {
|
||||
case GL11.GL_RED -> "RED";
|
||||
case GL11.GL_GREEN -> "GREEN";
|
||||
case GL11.GL_BLUE -> "BLUE";
|
||||
case GL11.GL_ALPHA -> "ALPHA";
|
||||
case GL11.GL_RGB -> "RGB";
|
||||
case GL11.GL_RGBA -> "RGBA";
|
||||
case GL11.GL_LUMINANCE -> "LUMINANCE";
|
||||
case GL11.GL_LUMINANCE_ALPHA -> "LUMINANCE_ALPHA";
|
||||
case GL12.GL_BGR -> "BGR";
|
||||
case GL12.GL_BGRA -> "BGRA";
|
||||
case GL11.GL_DEPTH_COMPONENT -> "DEPTH_COMPONENT";
|
||||
case GL11.GL_STENCIL_INDEX -> "STENCIL_INDEX";
|
||||
case GL30.GL_DEPTH_STENCIL -> "DEPTH_STENCIL";
|
||||
// Internal formats
|
||||
case GL11.GL_RGB8 -> "RGB8";
|
||||
case GL11.GL_RGBA8 -> "RGBA8";
|
||||
case GL11.GL_ALPHA8 -> "ALPHA8";
|
||||
case GL11.GL_LUMINANCE8 -> "LUMINANCE8";
|
||||
case GL11.GL_LUMINANCE8_ALPHA8 -> "LUMINANCE8_ALPHA8";
|
||||
default -> String.format("0x%X", format);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLightName(int light) {
|
||||
return switch (light) {
|
||||
case GL11.GL_LIGHT0 -> "LIGHT0";
|
||||
case GL11.GL_LIGHT1 -> "LIGHT1";
|
||||
case GL11.GL_LIGHT2 -> "LIGHT2";
|
||||
case GL11.GL_LIGHT3 -> "LIGHT3";
|
||||
case GL11.GL_LIGHT4 -> "LIGHT4";
|
||||
case GL11.GL_LIGHT5 -> "LIGHT5";
|
||||
case GL11.GL_LIGHT6 -> "LIGHT6";
|
||||
case GL11.GL_LIGHT7 -> "LIGHT7";
|
||||
default -> String.format("0x%X", light);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLightPnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_AMBIENT -> "AMBIENT";
|
||||
case GL11.GL_DIFFUSE -> "DIFFUSE";
|
||||
case GL11.GL_SPECULAR -> "SPECULAR";
|
||||
case GL11.GL_POSITION -> "POSITION";
|
||||
case GL11.GL_SPOT_DIRECTION -> "SPOT_DIRECTION";
|
||||
case GL11.GL_SPOT_EXPONENT -> "SPOT_EXPONENT";
|
||||
case GL11.GL_SPOT_CUTOFF -> "SPOT_CUTOFF";
|
||||
case GL11.GL_CONSTANT_ATTENUATION -> "CONSTANT_ATTENUATION";
|
||||
case GL11.GL_LINEAR_ATTENUATION -> "LINEAR_ATTENUATION";
|
||||
case GL11.GL_QUADRATIC_ATTENUATION -> "QUADRATIC_ATTENUATION";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getMaterialPnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_AMBIENT -> "AMBIENT";
|
||||
case GL11.GL_DIFFUSE -> "DIFFUSE";
|
||||
case GL11.GL_SPECULAR -> "SPECULAR";
|
||||
case GL11.GL_EMISSION -> "EMISSION";
|
||||
case GL11.GL_SHININESS -> "SHININESS";
|
||||
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
|
||||
case GL11.GL_COLOR_INDEXES -> "COLOR_INDEXES";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getFaceName(int face) {
|
||||
return switch (face) {
|
||||
case GL11.GL_FRONT -> "FRONT";
|
||||
case GL11.GL_BACK -> "BACK";
|
||||
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
|
||||
default -> String.format("0x%X", face);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getColorMaterialModeName(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_AMBIENT -> "AMBIENT";
|
||||
case GL11.GL_DIFFUSE -> "DIFFUSE";
|
||||
case GL11.GL_SPECULAR -> "SPECULAR";
|
||||
case GL11.GL_EMISSION -> "EMISSION";
|
||||
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
|
||||
default -> String.format("0x%X", mode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getFogPnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_FOG_MODE -> "FOG_MODE";
|
||||
case GL11.GL_FOG_DENSITY -> "FOG_DENSITY";
|
||||
case GL11.GL_FOG_START -> "FOG_START";
|
||||
case GL11.GL_FOG_END -> "FOG_END";
|
||||
case GL11.GL_FOG_INDEX -> "FOG_INDEX";
|
||||
case GL11.GL_FOG_COLOR -> "FOG_COLOR";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLightModelPnameName(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_LIGHT_MODEL_AMBIENT -> "LIGHT_MODEL_AMBIENT";
|
||||
case GL11.GL_LIGHT_MODEL_LOCAL_VIEWER -> "LIGHT_MODEL_LOCAL_VIEWER";
|
||||
case GL11.GL_LIGHT_MODEL_TWO_SIDE -> "LIGHT_MODEL_TWO_SIDE";
|
||||
case GL12.GL_LIGHT_MODEL_COLOR_CONTROL -> "LIGHT_MODEL_COLOR_CONTROL";
|
||||
default -> String.format("0x%X", pname);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getClearMaskString(int mask) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean first = true;
|
||||
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
|
||||
sb.append("COLOR");
|
||||
first = false;
|
||||
}
|
||||
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
if (!first) sb.append("|");
|
||||
sb.append("DEPTH");
|
||||
first = false;
|
||||
}
|
||||
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
|
||||
if (!first) sb.append("|");
|
||||
sb.append("STENCIL");
|
||||
first = false;
|
||||
}
|
||||
if ((mask & GL11.GL_ACCUM_BUFFER_BIT) != 0) {
|
||||
if (!first) sb.append("|");
|
||||
sb.append("ACCUM");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getCullFaceName(int mode) {
|
||||
return switch (mode) {
|
||||
case GL11.GL_FRONT -> "FRONT";
|
||||
case GL11.GL_BACK -> "BACK";
|
||||
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
|
||||
default -> String.format("0x%X", mode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLogicOpName(int opcode) {
|
||||
return switch (opcode) {
|
||||
case GL11.GL_CLEAR -> "CLEAR";
|
||||
case GL11.GL_AND -> "AND";
|
||||
case GL11.GL_AND_REVERSE -> "AND_REVERSE";
|
||||
case GL11.GL_COPY -> "COPY";
|
||||
case GL11.GL_AND_INVERTED -> "AND_INVERTED";
|
||||
case GL11.GL_NOOP -> "NOOP";
|
||||
case GL11.GL_XOR -> "XOR";
|
||||
case GL11.GL_OR -> "OR";
|
||||
case GL11.GL_NOR -> "NOR";
|
||||
case GL11.GL_EQUIV -> "EQUIV";
|
||||
case GL11.GL_INVERT -> "INVERT";
|
||||
case GL11.GL_OR_REVERSE -> "OR_REVERSE";
|
||||
case GL11.GL_COPY_INVERTED -> "COPY_INVERTED";
|
||||
case GL11.GL_OR_INVERTED -> "OR_INVERTED";
|
||||
case GL11.GL_NAND -> "NAND";
|
||||
case GL11.GL_SET -> "SET";
|
||||
default -> String.format("0x%X", opcode);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getFramebufferStatusName(int status) {
|
||||
return switch (status) {
|
||||
case GL30.GL_FRAMEBUFFER_COMPLETE -> "COMPLETE";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT -> "INCOMPLETE_ATTACHMENT";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT -> "INCOMPLETE_MISSING_ATTACHMENT";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER -> "INCOMPLETE_DRAW_BUFFER";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER -> "INCOMPLETE_READ_BUFFER";
|
||||
case GL30.GL_FRAMEBUFFER_UNSUPPORTED -> "UNSUPPORTED";
|
||||
case GL30.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE -> "INCOMPLETE_MULTISAMPLE";
|
||||
case 0 -> "NO_FRAMEBUFFER_BOUND";
|
||||
default -> String.format("UNKNOWN(0x%X)", status);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getInternalFormatName(int format) {
|
||||
return switch (format) {
|
||||
// Basic
|
||||
case GL11.GL_RGBA -> "RGBA";
|
||||
case GL11.GL_RGB -> "RGB";
|
||||
// 8-bit normalized
|
||||
case GL30.GL_R8 -> "R8";
|
||||
case GL30.GL_RG8 -> "RG8";
|
||||
case GL11.GL_RGB8 -> "RGB8";
|
||||
case GL11.GL_RGBA8 -> "RGBA8";
|
||||
// 8-bit signed normalized
|
||||
case 0x8F94 -> "R8_SNORM";
|
||||
case 0x8F95 -> "RG8_SNORM";
|
||||
case 0x8F96 -> "RGB8_SNORM";
|
||||
case 0x8F97 -> "RGBA8_SNORM";
|
||||
// 16-bit normalized
|
||||
case GL30.GL_R16 -> "R16";
|
||||
case GL30.GL_RG16 -> "RG16";
|
||||
case GL11.GL_RGB16 -> "RGB16";
|
||||
case GL11.GL_RGBA16 -> "RGBA16";
|
||||
// 16-bit float
|
||||
case GL30.GL_R16F -> "R16F";
|
||||
case GL30.GL_RG16F -> "RG16F";
|
||||
case GL30.GL_RGB16F -> "RGB16F";
|
||||
case GL30.GL_RGBA16F -> "RGBA16F";
|
||||
// 32-bit float
|
||||
case GL30.GL_R32F -> "R32F";
|
||||
case GL30.GL_RG32F -> "RG32F";
|
||||
case GL30.GL_RGB32F -> "RGB32F";
|
||||
case GL30.GL_RGBA32F -> "RGBA32F";
|
||||
// Packed
|
||||
case GL30.GL_R11F_G11F_B10F -> "R11F_G11F_B10F";
|
||||
case GL11.GL_RGB10_A2 -> "RGB10_A2";
|
||||
default -> String.format("0x%X", format);
|
||||
};
|
||||
}
|
||||
|
||||
private static DebugState debugState;
|
||||
|
||||
private interface DebugState {
|
||||
|
||||
void nameObject(int id, int object, String name);
|
||||
|
||||
void pushGroup(String name);
|
||||
|
||||
void popGroup();
|
||||
|
||||
void debugMessage(String name);
|
||||
|
||||
String getObjectLabel(int glProgram, int program);
|
||||
}
|
||||
|
||||
private static class KHRDebugState implements DebugState {
|
||||
|
||||
private static final int ID = 0;
|
||||
private int depth = 0;
|
||||
private static final int maxDepth = BackendManager.RENDER_BACKEND.getInteger(GL43.GL_MAX_DEBUG_GROUP_STACK_DEPTH);
|
||||
private final String[] groupStack = new String[maxDepth + 1];
|
||||
|
||||
@Override
|
||||
public void nameObject(int id, int object, String name) {
|
||||
BackendManager.RENDER_BACKEND.objectLabel(id, object, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushGroup(String name) {
|
||||
if (depth + 1 > maxDepth) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Debug group stack overflow (maxDepth=").append(maxDepth).append("), current stack:\n");
|
||||
for (int i = 0; i < depth; i++) {
|
||||
sb.append(" [").append(i).append("] ").append(groupStack[i]).append('\n');
|
||||
}
|
||||
sb.append(" -> ").append(name).append(" (rejected)");
|
||||
throw new RuntimeException(sb.toString());
|
||||
}
|
||||
groupStack[depth] = name;
|
||||
BackendManager.RENDER_BACKEND.pushDebugGroup(GL43.GL_DEBUG_SOURCE_APPLICATION, ID, name);
|
||||
depth++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popGroup() {
|
||||
if (depth - 1 < 0) {
|
||||
throw new RuntimeException("Stack underflow");
|
||||
}
|
||||
BackendManager.RENDER_BACKEND.popDebugGroup();
|
||||
depth--;
|
||||
groupStack[depth] = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debugMessage(String message) {
|
||||
BackendManager.RENDER_BACKEND.debugMessageInsert(
|
||||
GL43.GL_DEBUG_SOURCE_APPLICATION,
|
||||
GL43.GL_DEBUG_TYPE_MARKER,
|
||||
ID,
|
||||
GL43.GL_DEBUG_SEVERITY_NOTIFICATION,
|
||||
message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getObjectLabel(int glProgram, int program) {
|
||||
if (program == 0) return "";
|
||||
|
||||
return BackendManager.RENDER_BACKEND.getObjectLabel(glProgram, program);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UnsupportedDebugState implements DebugState {
|
||||
|
||||
@Override
|
||||
public void nameObject(int id, int object, String name) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushGroup(String name) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popGroup() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debugMessage(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getObjectLabel(int glProgram, int program) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void initDebugState() {
|
||||
final GLSMInitConfig cfg = GLStateManager.getInitConfig();
|
||||
final boolean userRequested = cfg != null && (cfg.isLwjglDebug() || CaptureGate.enabledAtStartup());
|
||||
if (userRequested && BackendManager.RENDER_BACKEND.supportsDebugOutput()) {
|
||||
debugState = new KHRDebugState();
|
||||
} else {
|
||||
debugState = new UnsupportedDebugState();
|
||||
}
|
||||
}
|
||||
|
||||
public static void nameObject(int id, int object, String name) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.nameObject(id, object, name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload that lets you gate the String concat behind the debugState
|
||||
*/
|
||||
public static void pushGroup(String prefix, long value) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.pushGroup(prefix + value);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isActive() {
|
||||
return debugState instanceof KHRDebugState && CaptureGate.markersThisFrame && Thread.currentThread() == GLStateManager.getMainThread();
|
||||
}
|
||||
|
||||
public static void pushGroup(String group) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.pushGroup(group);
|
||||
}
|
||||
}
|
||||
|
||||
public static void popGroup() {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.popGroup();
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetGroupStack() {
|
||||
if (debugState instanceof KHRDebugState khr) {
|
||||
khr.depth = 0;
|
||||
Arrays.fill(khr.groupStack, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void debugMessage(String message) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.debugMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getObjectLabel(int glProgram, int program) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
return debugState.getObjectLabel(glProgram, program);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static final Scope NOOP_SCOPE = () -> {};
|
||||
private static final Scope ACTIVE_SCOPE = GLDebug::popGroup;
|
||||
|
||||
public static Scope scope(String name) {
|
||||
if (isActive()) {
|
||||
debugState.pushGroup(name);
|
||||
return ACTIVE_SCOPE;
|
||||
}
|
||||
return NOOP_SCOPE;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Scope extends AutoCloseable {
|
||||
@Override void close();
|
||||
}
|
||||
|
||||
private static MethodHandle khrCallbackInvoke;
|
||||
private static boolean khrCallbackInvokeResolved;
|
||||
|
||||
/// Use reflection because of lwjgl3ify
|
||||
static GLDebugMessageListener adaptDebugCallback(Object callback) {
|
||||
if (callback == null) return null;
|
||||
final MethodHandle invoke = resolveKhrCallbackInvoke(callback.getClass());
|
||||
if (invoke == null) return null;
|
||||
return (source, type, id, severity, length, message, userParam) -> {
|
||||
try {
|
||||
invoke.invoke(callback, source, type, id, severity, length, message, userParam);
|
||||
} catch (Throwable t) {
|
||||
GLStateManager.LOGGER.warn("Debug message callback threw", t);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static synchronized MethodHandle resolveKhrCallbackInvoke(Class<?> callbackClass) {
|
||||
if (!khrCallbackInvokeResolved) {
|
||||
khrCallbackInvokeResolved = true;
|
||||
try {
|
||||
khrCallbackInvoke = MethodHandles.publicLookup().findVirtual(callbackClass, "invoke", MethodType.methodType(void.class, int.class, int.class, int.class, int.class, int.class, long.class, long.class));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
GLStateManager.LOGGER.warn("{} has no public invoke(int,int,int,int,int,long,long); debug messages registered through it will not be delivered.", callbackClass.getName());
|
||||
}
|
||||
}
|
||||
return khrCallbackInvoke;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
public final class GLESCaps {
|
||||
|
||||
private GLESCaps() {}
|
||||
|
||||
public static boolean isCapAllowed(int cap, boolean isGLES, boolean hasClipCullDistance) {
|
||||
if (!isGLES) return true;
|
||||
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + GLStateManager.MAX_CLIP_PLANES) {
|
||||
return hasClipCullDistance;
|
||||
}
|
||||
return switch (cap) {
|
||||
case GL11.GL_LINE_SMOOTH,
|
||||
GL11.GL_POLYGON_SMOOTH,
|
||||
GL11.GL_COLOR_LOGIC_OP,
|
||||
GL11.GL_POLYGON_OFFSET_POINT,
|
||||
GL11.GL_POLYGON_OFFSET_LINE,
|
||||
GL13.GL_MULTISAMPLE,
|
||||
GL13.GL_SAMPLE_ALPHA_TO_ONE,
|
||||
GL20.GL_VERTEX_PROGRAM_POINT_SIZE,
|
||||
GL30.GL_FRAMEBUFFER_SRGB,
|
||||
GL31.GL_PRIMITIVE_RESTART,
|
||||
GL32.GL_DEPTH_CLAMP -> false;
|
||||
default -> true;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
public final class GLESFormatRemap {
|
||||
|
||||
private GLESFormatRemap() {}
|
||||
|
||||
public record Result(int internalFormat, int format, int type) {}
|
||||
|
||||
public static Result apply(int internalformat, int format, int type, boolean isGLES) {
|
||||
internalformat = promoteAlphaFormat(internalformat);
|
||||
if (isGLES) {
|
||||
internalformat = remapInternalFormat(internalformat);
|
||||
if (type == GL12.GL_UNSIGNED_INT_8_8_8_8_REV && (format == GL12.GL_BGRA || format == GL11.GL_RGBA)) {
|
||||
type = GL11.GL_UNSIGNED_BYTE;
|
||||
}
|
||||
if (isGenericPixelType(type)) {
|
||||
type = typeForInternalFormatES32(internalformat, type);
|
||||
}
|
||||
}
|
||||
return new Result(internalformat, format, type);
|
||||
}
|
||||
|
||||
public static int promoteAlphaFormat(int internalformat) {
|
||||
return switch (internalformat) {
|
||||
case GL11.GL_ALPHA4 -> GL11.GL_RGBA4;
|
||||
case GL11.GL_ALPHA8 -> GL11.GL_RGBA8;
|
||||
case GL11.GL_ALPHA12 -> GL11.GL_RGBA12;
|
||||
case GL11.GL_ALPHA16 -> GL11.GL_RGBA16;
|
||||
default -> internalformat;
|
||||
};
|
||||
}
|
||||
|
||||
public static int remapInternalFormat(int internalformat) {
|
||||
return switch (internalformat) {
|
||||
case GL11.GL_RGB16, GL11.GL_RGB12 -> GL30.GL_RGB16F;
|
||||
case GL11.GL_RGBA16, GL11.GL_RGBA12 -> GL30.GL_RGBA16F;
|
||||
case GL30.GL_R16 -> GL30.GL_R16F;
|
||||
case GL30.GL_RG16 -> GL30.GL_RG16F;
|
||||
case GL11.GL_RGB10, GL11.GL_R3_G3_B2 -> GL11.GL_RGB8;
|
||||
default -> internalformat;
|
||||
};
|
||||
}
|
||||
|
||||
public static int remapPixelType(int format, int type) {
|
||||
if (type == GL12.GL_UNSIGNED_INT_8_8_8_8_REV && (format == GL12.GL_BGRA || format == GL11.GL_RGBA)) {
|
||||
return GL11.GL_UNSIGNED_BYTE;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public static int typeForInternalFormatES32(int internalformat, int type) {
|
||||
return switch (internalformat) {
|
||||
case GL30.GL_R16F, GL30.GL_RG16F, GL30.GL_RGB16F, GL30.GL_RGBA16F -> GL30.GL_HALF_FLOAT;
|
||||
case GL30.GL_R32F, GL30.GL_RG32F, GL30.GL_RGB32F, GL30.GL_RGBA32F -> GL11.GL_FLOAT;
|
||||
case GL31.GL_R8_SNORM, GL31.GL_RG8_SNORM, GL31.GL_RGB8_SNORM, GL31.GL_RGBA8_SNORM -> GL11.GL_BYTE;
|
||||
case GL30.GL_R11F_G11F_B10F, GL30.GL_RGB9_E5 -> GL30.GL_HALF_FLOAT;
|
||||
case GL11.GL_RGB10_A2 -> GL12.GL_UNSIGNED_INT_2_10_10_10_REV;
|
||||
case GL30.GL_R8I, GL30.GL_RG8I, GL30.GL_RGB8I, GL30.GL_RGBA8I,
|
||||
GL30.GL_R16I, GL30.GL_RG16I, GL30.GL_RGB16I, GL30.GL_RGBA16I,
|
||||
GL30.GL_R32I, GL30.GL_RG32I, GL30.GL_RGB32I, GL30.GL_RGBA32I -> GL11.GL_INT;
|
||||
case GL30.GL_R8UI, GL30.GL_RG8UI, GL30.GL_RGB8UI, GL30.GL_RGBA8UI,
|
||||
GL30.GL_R16UI, GL30.GL_RG16UI, GL30.GL_RGB16UI, GL30.GL_RGBA16UI,
|
||||
GL30.GL_R32UI, GL30.GL_RG32UI, GL30.GL_RGB32UI, GL30.GL_RGBA32UI -> GL11.GL_UNSIGNED_INT;
|
||||
case GL14.GL_DEPTH_COMPONENT24 -> GL11.GL_UNSIGNED_INT;
|
||||
case GL30.GL_DEPTH_COMPONENT32F -> GL11.GL_FLOAT;
|
||||
case GL30.GL_DEPTH24_STENCIL8 -> GL30.GL_UNSIGNED_INT_24_8;
|
||||
case GL30.GL_DEPTH32F_STENCIL8 -> GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
|
||||
default -> type;
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean isGenericPixelType(int type) {
|
||||
return type == GL11.GL_UNSIGNED_BYTE
|
||||
|| type == GL11.GL_UNSIGNED_SHORT
|
||||
|| type == GL11.GL_UNSIGNED_INT
|
||||
|| type == GL11.GL_BYTE
|
||||
|| type == GL11.GL_SHORT
|
||||
|| type == GL11.GL_INT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.lwjgl.opengl.OpenGLException;
|
||||
|
||||
final class GLErrorReporter {
|
||||
|
||||
private GLErrorReporter() {}
|
||||
|
||||
static void reportError(int error) {
|
||||
throw new OpenGLException(error);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,57 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
public final class GLTypes {
|
||||
|
||||
private GLTypes() {}
|
||||
|
||||
public static int sizeBytes(int glType) {
|
||||
return switch (glType) {
|
||||
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
|
||||
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT, GL30.GL_HALF_FLOAT -> 2;
|
||||
case GL11.GL_INT, GL11.GL_UNSIGNED_INT, GL11.GL_FLOAT -> 4;
|
||||
case GL11.GL_DOUBLE -> 8;
|
||||
default -> throw new IllegalArgumentException("Unknown GL type: 0x" + Integer.toHexString(glType));
|
||||
};
|
||||
}
|
||||
|
||||
public static int packedTexelBytes(int glType) {
|
||||
return switch (glType) {
|
||||
case GL12.GL_UNSIGNED_BYTE_3_3_2, GL12.GL_UNSIGNED_BYTE_2_3_3_REV -> 1;
|
||||
case GL12.GL_UNSIGNED_SHORT_5_6_5, GL12.GL_UNSIGNED_SHORT_5_6_5_REV,
|
||||
GL12.GL_UNSIGNED_SHORT_4_4_4_4, GL12.GL_UNSIGNED_SHORT_4_4_4_4_REV,
|
||||
GL12.GL_UNSIGNED_SHORT_5_5_5_1, GL12.GL_UNSIGNED_SHORT_1_5_5_5_REV -> 2;
|
||||
case GL12.GL_UNSIGNED_INT_8_8_8_8, GL12.GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
GL12.GL_UNSIGNED_INT_10_10_10_2, GL12.GL_UNSIGNED_INT_2_10_10_10_REV,
|
||||
GL30.GL_UNSIGNED_INT_24_8, GL30.GL_UNSIGNED_INT_10F_11F_11F_REV,
|
||||
GL30.GL_UNSIGNED_INT_5_9_9_9_REV -> 4;
|
||||
case GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV -> 8;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
public static String name(int glType) {
|
||||
return switch (glType) {
|
||||
case GL11.GL_BYTE -> "BYTE";
|
||||
case GL11.GL_UNSIGNED_BYTE -> "UNSIGNED_BYTE";
|
||||
case GL11.GL_SHORT -> "SHORT";
|
||||
case GL11.GL_UNSIGNED_SHORT -> "UNSIGNED_SHORT";
|
||||
case GL11.GL_INT -> "INT";
|
||||
case GL11.GL_UNSIGNED_INT -> "UNSIGNED_INT";
|
||||
case GL11.GL_FLOAT -> "FLOAT";
|
||||
case GL11.GL_DOUBLE -> "DOUBLE";
|
||||
case GL30.GL_HALF_FLOAT -> "HALF_FLOAT";
|
||||
case GL12.GL_UNSIGNED_BYTE_3_3_2 -> "UNSIGNED_BYTE_3_3_2";
|
||||
case GL12.GL_UNSIGNED_SHORT_4_4_4_4 -> "UNSIGNED_SHORT_4_4_4_4";
|
||||
case GL12.GL_UNSIGNED_SHORT_5_5_5_1 -> "UNSIGNED_SHORT_5_5_5_1";
|
||||
case GL12.GL_UNSIGNED_INT_8_8_8_8 -> "UNSIGNED_INT_8_8_8_8";
|
||||
case GL12.GL_UNSIGNED_INT_8_8_8_8_REV -> "UNSIGNED_INT_8_8_8_8_REV";
|
||||
case GL12.GL_UNSIGNED_INT_10_10_10_2 -> "UNSIGNED_INT_10_10_10_2";
|
||||
case GL12.GL_UNSIGNED_INT_2_10_10_10_REV -> "UNSIGNED_INT_2_10_10_10_REV";
|
||||
default -> String.format("0x%X", glType);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.antlr.v4.runtime.BailErrorStrategy;
|
||||
import org.antlr.v4.runtime.BufferedTokenStream;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonToken;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.DefaultErrorStrategy;
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.WritableToken;
|
||||
import org.antlr.v4.runtime.atn.PredictionMode;
|
||||
import org.antlr.v4.runtime.misc.ParseCancellationException;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import org.taumc.glsl.grammar.GLSLPreParser;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Shared GLSL shader transformation utilities.
|
||||
*/
|
||||
public class GlslTransformUtils {
|
||||
|
||||
private static final String RENAMED_PREFIX = "angelica_renamed_";
|
||||
|
||||
/** Texture function renames */
|
||||
public static final Map<String, String> TEXTURE_RENAMES = Map.ofEntries(
|
||||
Map.entry("texture2D", "texture"),
|
||||
Map.entry("texture3D", "texture"),
|
||||
Map.entry("texture2DLod", "textureLod"),
|
||||
Map.entry("texture3DLod", "textureLod"),
|
||||
Map.entry("texture2DProj", "textureProj"),
|
||||
Map.entry("texture3DProj", "textureProj"),
|
||||
Map.entry("texture2DGrad", "textureGrad"),
|
||||
Map.entry("texture2DGradARB", "textureGrad"),
|
||||
Map.entry("texture3DGrad", "textureGrad"),
|
||||
Map.entry("texelFetch2D", "texelFetch"),
|
||||
Map.entry("texelFetch3D", "texelFetch"),
|
||||
Map.entry("textureSize2D", "textureSize")
|
||||
);
|
||||
|
||||
private static final Pattern TEXTURE_PATTERN = Pattern.compile("\\btexture\\s*\\(|(\\btexture\\b)");
|
||||
|
||||
/** Reserved words added in later GLSL versions that may appear as identifiers in older shaders. */
|
||||
private record ReservedWordRename(Pattern pattern, String replacement) {}
|
||||
private static final Map<Integer, List<ReservedWordRename>> VERSIONED_RESERVED_WORDS = Map.of(
|
||||
// Some reserved words to always rename
|
||||
0, List.of(
|
||||
new ReservedWordRename(Pattern.compile("sample(?<=\\bsample)\\b"), RENAMED_PREFIX + "sample"),
|
||||
new ReservedWordRename(Pattern.compile("new(?<=\\bnew)\\b"), RENAMED_PREFIX + "new")
|
||||
),
|
||||
400, List.of(
|
||||
new ReservedWordRename(Pattern.compile("sampler(?<=\\bsampler)\\b(?!\\d)"), RENAMED_PREFIX + "sampler")
|
||||
)
|
||||
);
|
||||
|
||||
public static String replaceTexture(String input) {
|
||||
final Matcher matcher = TEXTURE_PATTERN.matcher(input);
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
if (matcher.group(1) != null) {
|
||||
matcher.appendReplacement(builder, RENAMED_PREFIX + "texture");
|
||||
} else {
|
||||
matcher.appendReplacement(builder, Matcher.quoteReplacement(matcher.group(0)));
|
||||
}
|
||||
}
|
||||
matcher.appendTail(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String renameReservedWords(String source, int targetVersion) {
|
||||
if (!containsWholeWord(source, "sample") && !containsWholeWord(source, "new")
|
||||
&& (targetVersion < 400 || !containsWholeWord(source, "sampler"))) {
|
||||
return source;
|
||||
}
|
||||
for (var entry : VERSIONED_RESERVED_WORDS.entrySet()) {
|
||||
// Rename identifiers that become reserved words at or above this GLSL version
|
||||
if (targetVersion >= entry.getKey()) {
|
||||
for (var rename : entry.getValue()) {
|
||||
source = rename.pattern().matcher(source).replaceAll(rename.replacement());
|
||||
}
|
||||
}
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
static boolean containsWholeWord(String source, String word) {
|
||||
int from = 0;
|
||||
while (true) {
|
||||
final int match = source.indexOf(word, from);
|
||||
if (match < 0) return false;
|
||||
final int end = match + word.length();
|
||||
if ((match == 0 || !isAsciiWordChar(source.charAt(match - 1))) && (end == source.length() || !isAsciiWordChar(source.charAt(end)))) {
|
||||
return true;
|
||||
}
|
||||
from = match + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAsciiWordChar(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
|
||||
}
|
||||
|
||||
public static String restoreReservedWords(String source) {
|
||||
source = source.replace(RENAMED_PREFIX + "texture", "texture");
|
||||
source = source.replace(RENAMED_PREFIX + "sampler", "sampler");
|
||||
source = source.replace(RENAMED_PREFIX + "sample", "sample");
|
||||
return source;
|
||||
}
|
||||
|
||||
public static void restoreReservedWordsInTree(ParseTree tree) {
|
||||
if (tree instanceof TerminalNode terminal) {
|
||||
final Token token = terminal.getSymbol();
|
||||
final String text = token.getText();
|
||||
if (text != null && text.startsWith(RENAMED_PREFIX) && token instanceof WritableToken writable) {
|
||||
final String rest = text.substring(RENAMED_PREFIX.length());
|
||||
if (rest.startsWith("texture") || rest.startsWith("sample")) {
|
||||
writable.setText(rest);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < tree.getChildCount(); i++) {
|
||||
restoreReservedWordsInTree(tree.getChild(i));
|
||||
}
|
||||
}
|
||||
|
||||
public record QuietParse(GLSLParser.Translation_unitContext full, GLSLPreParser.Translation_unitContext pre) {}
|
||||
|
||||
public static GLSLParser.Translation_unitContext parseFullQuiet(String source) {
|
||||
return parseQuiet(new GLSLParser(new CommonTokenStream(quietLexer(source))), GLSLParser::translation_unit);
|
||||
}
|
||||
|
||||
public static QuietParse parseBothQuiet(String source) {
|
||||
final GLSLLexer lexer = quietLexer(source);
|
||||
final GLSLPreParser.Translation_unitContext pre = parseQuiet(new GLSLPreParser(new BufferedTokenStream(lexer)), GLSLPreParser::translation_unit);
|
||||
lexer.reset();
|
||||
return new QuietParse(parseQuiet(new GLSLParser(new CommonTokenStream(lexer)), GLSLParser::translation_unit), pre);
|
||||
}
|
||||
|
||||
private static <P extends Parser, T> T parseQuiet(P parser, Function<P, T> rule) {
|
||||
parser.removeErrorListeners();
|
||||
parser.setErrorHandler(new BailErrorStrategy());
|
||||
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
|
||||
try {
|
||||
return rule.apply(parser);
|
||||
} catch (ParseCancellationException e) {
|
||||
// Rewinds the token stream, which keeps every token SLL already pulled from the lexer.
|
||||
parser.reset();
|
||||
parser.setErrorHandler(new DefaultErrorStrategy());
|
||||
parser.getInterpreter().setPredictionMode(PredictionMode.LL);
|
||||
return rule.apply(parser);
|
||||
}
|
||||
}
|
||||
|
||||
public static GLSLLexer quietLexer(String source) {
|
||||
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
|
||||
lexer.removeErrorListeners();
|
||||
return lexer;
|
||||
}
|
||||
|
||||
public static String getFormattedShader(ParseTree tree, String header) {
|
||||
final StringBuilder sb = new StringBuilder(header + "\n");
|
||||
final String[] tabHolder = {""};
|
||||
getFormattedShader(tree, sb, tabHolder, false);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getFormattedShaderRebased(ParseTree tree, String header) {
|
||||
final StringBuilder sb = new StringBuilder(header + "\n");
|
||||
final String[] tabHolder = {""};
|
||||
getFormattedShader(tree, sb, tabHolder, true);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void getFormattedShader(ParseTree tree, StringBuilder stringBuilder, String[] tabHolder, boolean rebaseOffsets) {
|
||||
if (tree instanceof TerminalNode terminal) {
|
||||
final String text = tree.getText();
|
||||
if (text.equals("<EOF>")) {
|
||||
return;
|
||||
}
|
||||
if (text.equals("#")) {
|
||||
stringBuilder.append("\n#");
|
||||
rebaseAt(terminal, stringBuilder.length() - 1, text, rebaseOffsets);
|
||||
return;
|
||||
}
|
||||
stringBuilder.append(text);
|
||||
if (!text.equals("}")) {
|
||||
rebaseAt(terminal, stringBuilder.length() - text.length(), text, rebaseOffsets);
|
||||
}
|
||||
if (text.equals("{")) {
|
||||
stringBuilder.append(" \n\t");
|
||||
tabHolder[0] = "\t";
|
||||
}
|
||||
|
||||
if (text.equals("}")) {
|
||||
if (stringBuilder.length() >= 2) {
|
||||
stringBuilder.deleteCharAt(stringBuilder.length() - 2);
|
||||
}
|
||||
rebaseAt(terminal, stringBuilder.length() - 1, text, rebaseOffsets);
|
||||
tabHolder[0] = "";
|
||||
stringBuilder.append(" \n");
|
||||
} else {
|
||||
stringBuilder.append(text.equals(";") ? " \n" + tabHolder[0] : " ");
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < tree.getChildCount(); ++i) {
|
||||
getFormattedShader(tree.getChild(i), stringBuilder, tabHolder, rebaseOffsets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void rebaseAt(TerminalNode terminal, int startIndex, String text, boolean rebaseOffsets) {
|
||||
if (!rebaseOffsets) return;
|
||||
if (terminal.getSymbol() instanceof CommonToken common) {
|
||||
common.setText(text);
|
||||
common.setStartIndex(startIndex);
|
||||
common.setStopIndex(startIndex + text.length() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
public final class LTWWorkaround {
|
||||
|
||||
private LTWWorkaround() {}
|
||||
|
||||
private static ByteBuffer oneTexel;
|
||||
|
||||
public static boolean isLtwVersionString(String glVersion) {
|
||||
return glVersion != null && glVersion.contains("LTW");
|
||||
}
|
||||
|
||||
public static void onTexImage2D(int target, int level, int format, boolean hasPixels) {
|
||||
if (target != GL11.GL_TEXTURE_2D || level != 0 || format != GL12.GL_BGRA || hasPixels) return;
|
||||
if (oneTexel == null) {
|
||||
oneTexel = BufferUtils.createByteBuffer(4);
|
||||
}
|
||||
oneTexel.clear();
|
||||
GLStateManager.forcePixelUnpackState(PixelStoreState.DEFAULT);
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texSubImage2D(target, level, 0, 0, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, oneTexel);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
GLStateManager.restorePixelUnpackState(PixelStoreState.DEFAULT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
final class LegacyQueries {
|
||||
|
||||
static final int BOOL = 1;
|
||||
static final int INT = 2;
|
||||
static final int FLOAT = 3;
|
||||
static final int FLOATN = 4;
|
||||
|
||||
private static final int COUNT_SHIFT = 3;
|
||||
|
||||
private LegacyQueries() {}
|
||||
|
||||
static int desc(int pname) {
|
||||
return switch (pname) {
|
||||
case GL11.GL_ALPHA_TEST, GL11.GL_AUTO_NORMAL, GL11.GL_COLOR_MATERIAL, GL14.GL_COLOR_SUM, GL11.GL_FOG,
|
||||
GL11.GL_INDEX_LOGIC_OP, GL11.GL_LIGHTING, GL11.GL_LIGHT0, GL11.GL_LIGHT1, GL11.GL_LIGHT2, GL11.GL_LIGHT3,
|
||||
GL11.GL_LIGHT4, GL11.GL_LIGHT5, GL11.GL_LIGHT6, GL11.GL_LIGHT7, GL11.GL_LINE_STIPPLE, GL11.GL_MAP1_COLOR_4,
|
||||
GL11.GL_MAP1_INDEX, GL11.GL_MAP1_NORMAL, GL11.GL_MAP1_TEXTURE_COORD_1, GL11.GL_MAP1_TEXTURE_COORD_2,
|
||||
GL11.GL_MAP1_TEXTURE_COORD_3, GL11.GL_MAP1_TEXTURE_COORD_4, GL11.GL_MAP1_VERTEX_3, GL11.GL_MAP1_VERTEX_4,
|
||||
GL11.GL_MAP2_COLOR_4, GL11.GL_MAP2_INDEX, GL11.GL_MAP2_NORMAL, GL11.GL_MAP2_TEXTURE_COORD_1,
|
||||
GL11.GL_MAP2_TEXTURE_COORD_2, GL11.GL_MAP2_TEXTURE_COORD_3, GL11.GL_MAP2_TEXTURE_COORD_4,
|
||||
GL11.GL_MAP2_VERTEX_3, GL11.GL_MAP2_VERTEX_4, GL11.GL_NORMALIZE, GL11.GL_POINT_SMOOTH,
|
||||
GL11.GL_POLYGON_STIPPLE, GL12.GL_RESCALE_NORMAL, GL11.GL_TEXTURE_1D, GL12.GL_TEXTURE_3D,
|
||||
GL13.GL_TEXTURE_CUBE_MAP, GL11.GL_TEXTURE_GEN_S, GL11.GL_TEXTURE_GEN_T, GL11.GL_TEXTURE_GEN_R,
|
||||
GL11.GL_TEXTURE_GEN_Q, GL11.GL_LIGHT_MODEL_LOCAL_VIEWER, GL11.GL_LIGHT_MODEL_TWO_SIDE,
|
||||
GL11.GL_VERTEX_ARRAY, GL11.GL_NORMAL_ARRAY, GL11.GL_COLOR_ARRAY, GL11.GL_TEXTURE_COORD_ARRAY,
|
||||
GL11.GL_INDEX_ARRAY, GL11.GL_EDGE_FLAG_ARRAY, GL14.GL_SECONDARY_COLOR_ARRAY, GL14.GL_FOG_COORDINATE_ARRAY,
|
||||
GL11.GL_EDGE_FLAG, GL20.GL_POINT_SPRITE, GL20.GL_VERTEX_PROGRAM_TWO_SIDE -> BOOL;
|
||||
case GL11.GL_ALPHA_TEST_FUNC, GL11.GL_LIST_BASE, GL11.GL_MATRIX_MODE, GL11.GL_SHADE_MODEL,
|
||||
GL11.GL_COLOR_MATERIAL_FACE, GL11.GL_COLOR_MATERIAL_PARAMETER, GL12.GL_LIGHT_MODEL_COLOR_CONTROL,
|
||||
GL11.GL_FOG_MODE, GL11.GL_LINE_STIPPLE_PATTERN, GL11.GL_LINE_STIPPLE_REPEAT, GL13.GL_CLIENT_ACTIVE_TEXTURE,
|
||||
GL20.GL_MAX_TEXTURE_COORDS, GL11.GL_RENDER_MODE, GL11.GL_FOG_HINT, GL11.GL_PERSPECTIVE_CORRECTION_HINT,
|
||||
GL11.GL_POINT_SMOOTH_HINT, GL14.GL_GENERATE_MIPMAP_HINT, GL11.GL_LINE_SMOOTH_HINT, GL11.GL_POLYGON_SMOOTH_HINT,
|
||||
GL13.GL_TEXTURE_COMPRESSION_HINT, GL20.GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL20.GL_POINT_SPRITE_COORD_ORIGIN,
|
||||
GL11.GL_VERTEX_ARRAY_SIZE, GL11.GL_VERTEX_ARRAY_TYPE, GL11.GL_VERTEX_ARRAY_STRIDE, GL15.GL_VERTEX_ARRAY_BUFFER_BINDING,
|
||||
GL11.GL_NORMAL_ARRAY_TYPE, GL11.GL_NORMAL_ARRAY_STRIDE, GL15.GL_NORMAL_ARRAY_BUFFER_BINDING,
|
||||
GL11.GL_COLOR_ARRAY_SIZE, GL11.GL_COLOR_ARRAY_TYPE, GL11.GL_COLOR_ARRAY_STRIDE, GL15.GL_COLOR_ARRAY_BUFFER_BINDING,
|
||||
GL11.GL_INDEX_ARRAY_TYPE, GL11.GL_INDEX_ARRAY_STRIDE, GL15.GL_INDEX_ARRAY_BUFFER_BINDING,
|
||||
GL11.GL_TEXTURE_COORD_ARRAY_SIZE, GL11.GL_TEXTURE_COORD_ARRAY_TYPE, GL11.GL_TEXTURE_COORD_ARRAY_STRIDE,
|
||||
GL15.GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING, GL11.GL_EDGE_FLAG_ARRAY_STRIDE, GL15.GL_EDGE_FLAG_ARRAY_BUFFER_BINDING,
|
||||
GL14.GL_SECONDARY_COLOR_ARRAY_SIZE, GL14.GL_SECONDARY_COLOR_ARRAY_TYPE, GL14.GL_SECONDARY_COLOR_ARRAY_STRIDE,
|
||||
GL15.GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING, GL14.GL_FOG_COORDINATE_ARRAY_TYPE, GL14.GL_FOG_COORDINATE_ARRAY_STRIDE,
|
||||
GL15.GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING -> INT;
|
||||
case GL11.GL_FOG_DENSITY, GL11.GL_FOG_START, GL11.GL_FOG_END, GL14.GL_POINT_SIZE_MIN, GL14.GL_POINT_SIZE_MAX,
|
||||
GL14.GL_POINT_FADE_THRESHOLD_SIZE -> FLOAT;
|
||||
case GL11.GL_ALPHA_TEST_REF -> FLOATN;
|
||||
case GL14.GL_POINT_DISTANCE_ATTENUATION -> FLOAT | 2 << COUNT_SHIFT;
|
||||
case GL11.GL_CURRENT_TEXTURE_COORDS -> FLOAT | 3 << COUNT_SHIFT;
|
||||
case GL11.GL_MODELVIEW_MATRIX, GL11.GL_PROJECTION_MATRIX, GL11.GL_TEXTURE_MATRIX -> FLOATN | 15 << COUNT_SHIFT;
|
||||
case GL11.GL_CURRENT_NORMAL -> FLOATN | 2 << COUNT_SHIFT;
|
||||
case GL11.GL_CURRENT_COLOR, GL14.GL_CURRENT_SECONDARY_COLOR, GL11.GL_FOG_COLOR, GL11.GL_LIGHT_MODEL_AMBIENT -> FLOATN | 3 << COUNT_SHIFT;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
static int type(int desc) {
|
||||
return desc & 7;
|
||||
}
|
||||
|
||||
static int count(int desc) {
|
||||
return (desc >> COUNT_SHIFT) + 1;
|
||||
}
|
||||
|
||||
static int floatToInt(float value) {
|
||||
return (int) (2147483647.0 * value);
|
||||
}
|
||||
|
||||
static int lround(float value) {
|
||||
return value < 0 ? -Math.round(-value) : Math.round(value);
|
||||
}
|
||||
|
||||
static int toInt(int type, float value) {
|
||||
return type == FLOATN ? floatToInt(value) : lround(value);
|
||||
}
|
||||
|
||||
private static FloatBuffer fillScratch(GLContextState glCtx, int pname) {
|
||||
final FloatBuffer scratch = glCtx.queryScratch;
|
||||
scratch.clear();
|
||||
GLStateManager.glGetFloat(pname, scratch);
|
||||
return scratch;
|
||||
}
|
||||
|
||||
private static IllegalStateException uncached(int pname) {
|
||||
return new IllegalStateException("Legacy pname 0x" + Integer.toHexString(pname) + " has no cached answer for its declared type");
|
||||
}
|
||||
|
||||
static int getInteger(GLContextState glCtx, int pname, int desc) {
|
||||
final int type = type(desc);
|
||||
if (count(desc) > 1) {
|
||||
return toInt(type, fillScratch(glCtx, pname).get(0));
|
||||
}
|
||||
return switch (type) {
|
||||
case BOOL -> GLStateManager.glGetBoolean(glCtx, pname) ? GL11.GL_TRUE : GL11.GL_FALSE;
|
||||
case FLOAT -> lround(GLStateManager.glGetFloat(glCtx, pname));
|
||||
case FLOATN -> floatToInt(GLStateManager.glGetFloat(glCtx, pname));
|
||||
default -> throw uncached(pname);
|
||||
};
|
||||
}
|
||||
|
||||
static float getFloat(GLContextState glCtx, int pname, int desc) {
|
||||
if (count(desc) > 1) {
|
||||
return fillScratch(glCtx, pname).get(0);
|
||||
}
|
||||
return switch (type(desc)) {
|
||||
case BOOL -> GLStateManager.glGetBoolean(glCtx, pname) ? 1.0f : 0.0f;
|
||||
case INT -> (float) GLStateManager.glGetInteger(glCtx, pname);
|
||||
default -> throw uncached(pname);
|
||||
};
|
||||
}
|
||||
|
||||
static boolean getBoolean(GLContextState glCtx, int pname, int desc) {
|
||||
if (count(desc) > 1) {
|
||||
return fillScratch(glCtx, pname).get(0) != 0.0f;
|
||||
}
|
||||
return switch (type(desc)) {
|
||||
case INT -> GLStateManager.glGetInteger(glCtx, pname) != 0;
|
||||
case FLOAT, FLOATN -> GLStateManager.glGetFloat(glCtx, pname) != 0.0f;
|
||||
default -> throw uncached(pname);
|
||||
};
|
||||
}
|
||||
|
||||
static boolean getIntegerv(GLContextState glCtx, int pname, IntBuffer params) {
|
||||
final int desc = desc(pname);
|
||||
if (desc == 0) return false;
|
||||
final int pos = params.position();
|
||||
final int type = type(desc);
|
||||
final FloatBuffer scratch = fillScratch(glCtx, pname);
|
||||
for (int i = 0, count = count(desc); i < count; i++) {
|
||||
params.put(pos + i, toInt(type, scratch.get(i)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean getIntegerv(GLContextState glCtx, int pname, int[] params) {
|
||||
final int desc = desc(pname);
|
||||
if (desc == 0) return false;
|
||||
final int type = type(desc);
|
||||
final FloatBuffer scratch = fillScratch(glCtx, pname);
|
||||
for (int i = 0, count = Math.min(count(desc), params.length); i < count; i++) {
|
||||
params[i] = toInt(type, scratch.get(i));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean getBooleanv(GLContextState glCtx, int pname, ByteBuffer params) {
|
||||
final int desc = desc(pname);
|
||||
if (desc == 0) return false;
|
||||
final int pos = params.position();
|
||||
final FloatBuffer scratch = fillScratch(glCtx, pname);
|
||||
for (int i = 0, count = count(desc); i < count; i++) {
|
||||
params.put(pos + i, (byte) (scratch.get(i) != 0.0f ? GL11.GL_TRUE : GL11.GL_FALSE));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VertexKey;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class LightingSnapshot {
|
||||
final boolean[] lightEnabled = new boolean[VertexKey.FFP_LIGHT_COUNT];
|
||||
final LightState[] lights = new LightState[VertexKey.FFP_LIGHT_COUNT];
|
||||
final LightModelState lightModel = new LightModelState();
|
||||
final MaterialState frontMaterial = new MaterialState(GL11.GL_FRONT);
|
||||
final MaterialState backMaterial = new MaterialState(GL11.GL_BACK);
|
||||
boolean colorMaterial;
|
||||
int colorMaterialFace;
|
||||
int colorMaterialParameter;
|
||||
boolean normalize;
|
||||
boolean rescaleNormal;
|
||||
|
||||
public LightingSnapshot() {
|
||||
for (int i = 0; i < lights.length; i++) {
|
||||
lights[i] = new LightState(GL11.GL_LIGHT0 + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutShort;
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* Shared quad-to-triangle EBO for core profile GL_QUADS emulation.
|
||||
*
|
||||
* <p>Maintains a single growable element buffer with pre-computed indices mapping
|
||||
* GL_QUADS vertex order to GL_TRIANGLES: {@code (0,1,3, 1,2,3)} per quad.
|
||||
* Uses {@code GL_LAST_VERTEX_CONVENTION} winding (Mesa {@code do_quad} LAST path)
|
||||
* so both emitted triangles share the quad's provoking vertex (v3).
|
||||
*/
|
||||
public final class QuadConverter {
|
||||
public static final int INDEX_TYPE = GL11.GL_UNSIGNED_INT;
|
||||
|
||||
private static int eboId;
|
||||
private static int maxQuads;
|
||||
|
||||
/** Scratch EBO for converted quad-element draws (drawQuadElementsAsTriangles). */
|
||||
private static int scratchEboId;
|
||||
private static int scratchEboCapacity; // in bytes
|
||||
|
||||
private QuadConverter() {}
|
||||
|
||||
private static void ensureCapacity(int quadCount) {
|
||||
if (quadCount <= maxQuads) return;
|
||||
|
||||
// Round up to power of 2 for amortized growth
|
||||
int newMaxQuads = Math.max(256, maxQuads);
|
||||
while (newMaxQuads < quadCount) {
|
||||
newMaxQuads *= 2;
|
||||
}
|
||||
|
||||
if (eboId == 0) {
|
||||
eboId = RENDER_BACKEND.genBuffers();
|
||||
}
|
||||
|
||||
// Generate index data: 6 ints per quad
|
||||
final ByteBuffer indexData = memAlloc(newMaxQuads * 6 * 4);
|
||||
long ptr = memAddress0(indexData);
|
||||
for (int i = 0; i < newMaxQuads; i++) {
|
||||
final int base = i * 4;
|
||||
// GL_LAST_VERTEX_CONVENTION: both triangles share v3 as provoking vertex
|
||||
// Triangle 1: 0, 1, 3
|
||||
memPutInt(ptr, base);
|
||||
memPutInt(ptr + 4, base + 1);
|
||||
memPutInt(ptr + 8, base + 3);
|
||||
// Triangle 2: 1, 2, 3
|
||||
memPutInt(ptr + 12, base + 1);
|
||||
memPutInt(ptr + 16, base + 2);
|
||||
memPutInt(ptr + 20, base + 3);
|
||||
ptr += 24;
|
||||
}
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);
|
||||
|
||||
memFree(indexData);
|
||||
maxQuads = newMaxQuads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a GL_QUADS glDrawArrays call to indexed GL_TRIANGLES.
|
||||
* Binds the shared EBO to the current VAO and issues glDrawElements.
|
||||
*
|
||||
* @param first first vertex index (must be aligned to quad boundary, i.e. multiple of 4)
|
||||
* @param vertexCount number of vertices (must be multiple of 4)
|
||||
*/
|
||||
public static void drawQuadsAsTriangles(GLContextState glCtx, int first, int vertexCount) {
|
||||
drawSharedQuadEbo(glCtx, first, vertexCount, 1, false);
|
||||
}
|
||||
|
||||
public static void drawQuadsAsTrianglesInstanced(GLContextState glCtx, int first, int vertexCount, int primcount) {
|
||||
drawSharedQuadEbo(glCtx, first, vertexCount, primcount, true);
|
||||
}
|
||||
|
||||
private static int misalignedDraws;
|
||||
|
||||
private static void drawSharedQuadEbo(GLContextState glCtx, int first, int vertexCount, int primcount, boolean instanced) {
|
||||
final int quadCount = vertexCount / 4;
|
||||
if ((first & 3) != 0) {
|
||||
misalignedDraws++;
|
||||
if ((misalignedDraws & (misalignedDraws - 1)) == 0) {
|
||||
GLStateManager.LOGGER.warn("QuadConverter: quad draw with unaligned first={} count={} (#{}) -- using scratch indices", first, vertexCount, misalignedDraws);
|
||||
}
|
||||
final ByteBuffer dst = memAlloc(quadCount * 6 * 4);
|
||||
long ptr = memAddress0(dst);
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int base = first + i * 4;
|
||||
memPutInt(ptr, base);
|
||||
memPutInt(ptr + 4, base + 1);
|
||||
memPutInt(ptr + 8, base + 3);
|
||||
memPutInt(ptr + 12, base + 1);
|
||||
memPutInt(ptr + 16, base + 2);
|
||||
memPutInt(ptr + 20, base + 3);
|
||||
ptr += 24;
|
||||
}
|
||||
uploadAndDraw(glCtx, dst, quadCount * 6, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
|
||||
return;
|
||||
}
|
||||
GLStateManager.preDraw(glCtx, GL11.GL_TRIANGLES);
|
||||
final int prevEbo = glCtx.vaos.boundEBO;
|
||||
ensureCapacity(first / 4 + quadCount);
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
|
||||
final long indexOffset = (long) (first / 4) * 6 * 4;
|
||||
if (instanced) {
|
||||
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset, primcount);
|
||||
} else {
|
||||
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
|
||||
}
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload converted triangle indices to the scratch EBO and draw.
|
||||
* Saves/restores the caller's EBO binding so subsequent indexed draws
|
||||
* (or glGetBufferSubData reads) still see the original EBO.
|
||||
*
|
||||
* @param dst off-heap buffer with triangle index data (freed by this method)
|
||||
* @param triIndexCount number of triangle indices
|
||||
* @param indexType GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
|
||||
* @param bytesPerIndex bytes per index element
|
||||
*/
|
||||
private static void uploadAndDraw(GLContextState glCtx, ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
|
||||
uploadAndDraw(glCtx, dst, triIndexCount, indexType, bytesPerIndex, 1, false);
|
||||
}
|
||||
|
||||
private static void uploadAndDraw(GLContextState glCtx, ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
|
||||
GLStateManager.preDraw(glCtx, GL11.GL_TRIANGLES);
|
||||
final int needed = triIndexCount * bytesPerIndex;
|
||||
final int prevEbo = glCtx.vaos.boundEBO;
|
||||
|
||||
if (scratchEboId == 0) {
|
||||
scratchEboId = RENDER_BACKEND.genBuffers();
|
||||
}
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
|
||||
|
||||
if (needed > scratchEboCapacity) {
|
||||
// Power-of-2 growth -- allocate full capacity, upload actual data
|
||||
int newCap = Math.max(4096, scratchEboCapacity);
|
||||
while (newCap < needed) newCap *= 2;
|
||||
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
|
||||
scratchEboCapacity = newCap;
|
||||
}
|
||||
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
|
||||
|
||||
if (instanced) {
|
||||
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L, primcount);
|
||||
} else {
|
||||
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
|
||||
}
|
||||
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
memFree(dst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an indexed GL_QUADS draw to GL_TRIANGLES by reading the source EBO,
|
||||
* building a triangle index buffer, and issuing the converted draw.
|
||||
*
|
||||
* @param indexCount number of quad indices (must be multiple of 4)
|
||||
* @param type GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, or GL_UNSIGNED_BYTE
|
||||
* @param offset byte offset into the currently bound EBO
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(GLContextState glCtx, int indexCount, int type, long offset) {
|
||||
drawQuadElementsFromEbo(glCtx, indexCount, type, offset, 1, false);
|
||||
}
|
||||
|
||||
public static void drawQuadElementsAsTrianglesInstanced(GLContextState glCtx, int indexCount, int type, long offset, int primcount) {
|
||||
drawQuadElementsFromEbo(glCtx, indexCount, type, offset, primcount, true);
|
||||
}
|
||||
|
||||
private static void drawQuadElementsFromEbo(GLContextState glCtx, int indexCount, int type, long offset, int primcount, boolean instanced) {
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
|
||||
final int quadCount = indexCount / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final int bytesPerIndex = GLTypes.sizeBytes(type);
|
||||
|
||||
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
triangulateQuads(memAddress0(src), type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
|
||||
|
||||
memFree(src);
|
||||
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side IntBuffer of quad indices to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(GLContextState glCtx, IntBuffer indices) {
|
||||
final int indexCount = indices.remaining();
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0;
|
||||
final int quadCount = indexCount / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long srcAddr = memAddress0(indices) + (long) indices.position() * 4;
|
||||
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_INT, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
|
||||
|
||||
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side ShortBuffer of quad indices to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(GLContextState glCtx, ShortBuffer indices) {
|
||||
final int indexCount = indices.remaining();
|
||||
if (indexCount == 0) return;
|
||||
assert indexCount % 4 == 0;
|
||||
final int quadCount = indexCount / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 2);
|
||||
final long srcAddr = memAddress0(indices) + (long) indices.position() * 2;
|
||||
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_SHORT, memAddress0(dst), GL11.GL_UNSIGNED_SHORT, quadCount);
|
||||
|
||||
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a client-side ByteBuffer of quad indices (type-agnostic) to triangles.
|
||||
*/
|
||||
public static void drawQuadElementsAsTriangles(GLContextState glCtx, int count, int type, ByteBuffer indices) {
|
||||
if (count == 0) return;
|
||||
assert count % 4 == 0;
|
||||
final int quadCount = count / 4;
|
||||
final int triIndexCount = quadCount * 6;
|
||||
final long srcAddr = memAddress0(indices) + indices.position();
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
triangulateQuads(srcAddr, type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
|
||||
|
||||
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read one index at {@code srcAddr + i * sizeof(srcType)} and return its
|
||||
* zero-extended unsigned value.
|
||||
*/
|
||||
private static int readIndex(long srcAddr, int srcType, int i) {
|
||||
return switch (srcType) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> memGetByte(srcAddr + i) & 0xFF;
|
||||
case GL11.GL_UNSIGNED_SHORT -> memGetShort(srcAddr + i * 2L) & 0xFFFF;
|
||||
case GL11.GL_UNSIGNED_INT -> memGetInt(srcAddr + i * 4L);
|
||||
default -> throw new IllegalArgumentException("unsupported index type 0x" + Integer.toHexString(srcType));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Write one index at {@code dstAddr + i * sizeof(dstType)}.
|
||||
*/
|
||||
private static void writeIndex(long dstAddr, int dstType, int i, int value) {
|
||||
switch (dstType) {
|
||||
case GL11.GL_UNSIGNED_SHORT -> memPutShort(dstAddr + i * 2L, (short) value);
|
||||
case GL11.GL_UNSIGNED_INT -> memPutInt(dstAddr + i * 4L, value);
|
||||
default -> throw new IllegalArgumentException("unsupported index type 0x" + Integer.toHexString(dstType));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read {@code quadCount} quads from {@code srcAddr} and emit 6 triangle indices
|
||||
* per quad at {@code dstAddr} using {@code GL_LAST_VERTEX_CONVENTION}:
|
||||
* {@code (a,b,d), (b,c,d)}. v3 is the provoking vertex for both triangles;
|
||||
* diagonal runs b→d. Matches Mesa's {@code do_quad} LAST path.
|
||||
*
|
||||
* <p>Caller allocates dst of size {@code quadCount * 6 * sizeof(dstType)}.
|
||||
*
|
||||
* <p>Supported src types: {@code GL_UNSIGNED_BYTE} / {@code GL_UNSIGNED_SHORT}
|
||||
* / {@code GL_UNSIGNED_INT}. Supported dst types: {@code GL_UNSIGNED_SHORT} /
|
||||
* {@code GL_UNSIGNED_INT}. Widening only.
|
||||
*/
|
||||
public static void triangulateQuads(long srcAddr, int srcType, long dstAddr, int dstType, int quadCount) {
|
||||
triangulateQuads(srcAddr, srcType, dstAddr, dstType, quadCount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #triangulateQuads(long, int, long, int, int)} but subtracts
|
||||
* {@code minVtx} from every emitted index, rebasing to a 0-based buffer in
|
||||
* one pass.
|
||||
*/
|
||||
public static void triangulateQuads(long srcAddr, int srcType, long dstAddr, int dstType, int quadCount, int minVtx) {
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int a = readIndex(srcAddr, srcType, i * 4) - minVtx;
|
||||
final int b = readIndex(srcAddr, srcType, i * 4 + 1) - minVtx;
|
||||
final int c = readIndex(srcAddr, srcType, i * 4 + 2) - minVtx;
|
||||
final int d = readIndex(srcAddr, srcType, i * 4 + 3) - minVtx;
|
||||
final int o = i * 6;
|
||||
writeIndex(dstAddr, dstType, o, a);
|
||||
writeIndex(dstAddr, dstType, o + 1, b);
|
||||
writeIndex(dstAddr, dstType, o + 2, d);
|
||||
writeIndex(dstAddr, dstType, o + 3, b);
|
||||
writeIndex(dstAddr, dstType, o + 4, c);
|
||||
writeIndex(dstAddr, dstType, o + 5, d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy {@code count} indices from {@code srcAddr} to {@code dstAddr}, converting
|
||||
* from {@code srcType} to {@code dstType}.
|
||||
*
|
||||
* <p>Supported src types: {@code GL_UNSIGNED_BYTE} / {@code GL_UNSIGNED_SHORT} /
|
||||
* {@code GL_UNSIGNED_INT}. Supported dst types: {@code GL_UNSIGNED_SHORT} /
|
||||
* {@code GL_UNSIGNED_INT}. Widening only; narrowing is rejected by
|
||||
* {@link #writeIndex(long, int, int, int) writeIndex}.
|
||||
*/
|
||||
public static void widenIndices(long srcAddr, int srcType, long dstAddr, int dstType, int count) {
|
||||
widenIndices(srcAddr, srcType, dstAddr, dstType, count, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #widenIndices(long, int, long, int, int)} but subtracts
|
||||
* {@code minVtx} from every written index in one pass.
|
||||
*/
|
||||
public static void widenIndices(long srcAddr, int srcType, long dstAddr, int dstType, int count, int minVtx) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
writeIndex(dstAddr, dstType, i, readIndex(srcAddr, srcType, i) - minVtx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan {@code count} indices at {@code srcAddr} of {@code srcType}. Returns
|
||||
* {@code (max << 32) | (min & 0xFFFFFFFFL)} or {@code -1L} for an unsupported
|
||||
* type or empty range.
|
||||
*/
|
||||
public static long scanMinMaxIndex(long srcAddr, int srcType, int count) {
|
||||
if (count <= 0) return -1L;
|
||||
long min = Long.MAX_VALUE;
|
||||
long max = Long.MIN_VALUE;
|
||||
switch (srcType) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> {
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int v = memGetByte(srcAddr + i) & 0xFF;
|
||||
if (v < min) min = v;
|
||||
if (v > max) max = v;
|
||||
}
|
||||
}
|
||||
case GL11.GL_UNSIGNED_SHORT -> {
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int v = memGetShort(srcAddr + i * 2L) & 0xFFFF;
|
||||
if (v < min) min = v;
|
||||
if (v > max) max = v;
|
||||
}
|
||||
}
|
||||
case GL11.GL_UNSIGNED_INT -> {
|
||||
for (int i = 0; i < count; i++) {
|
||||
final long v = memGetInt(srcAddr + i * 4L) & 0xFFFFFFFFL;
|
||||
if (v < min) min = v;
|
||||
if (v > max) max = v;
|
||||
}
|
||||
// readIndex / writeIndex / rebase all route UINT through signed int.
|
||||
// Reject the > 2^31 slice so the caller bails rather than silently
|
||||
// emitting negative indices. MC never hits this in practice.
|
||||
if (max > Integer.MAX_VALUE) return -1L;
|
||||
}
|
||||
default -> {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
return (max << 32) | (min & 0xFFFFFFFFL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,699 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSACore;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import org.taumc.celeritas.lwjgl.LWJGLServiceProvider;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3i;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.ARBShaderImageLoadStore;
|
||||
import org.lwjgl.opengl.ARBShaderStorageBufferObject;
|
||||
import org.lwjgl.opengl.EXTShaderImageLoadStore;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.opengl.NVXGpuMemoryInfo;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* This class is responsible for abstracting calls to OpenGL and asserting that calls are run on the render thread.
|
||||
*/
|
||||
public class RenderSystem {
|
||||
private static DSAAccess dsaState;
|
||||
private static boolean supportsCompute;
|
||||
private static boolean supportsImageLoadStore;
|
||||
private static boolean supportsSSBO;
|
||||
private static boolean supportsMultiDrawIndirect;
|
||||
private static boolean supportsBufferStorage;
|
||||
private static boolean supportsClearTexture;
|
||||
private static boolean supportsTesselation;
|
||||
private static boolean supportsSamplerObjects;
|
||||
private static boolean supportsProgramBinary;
|
||||
private static int maxImageUnits;
|
||||
@Getter private static int maxCombinedTextureImageUnits;
|
||||
private static int maxSSBOBindings;
|
||||
private static int maxGlslVersion;
|
||||
private static boolean supportsGpuShader4;
|
||||
@Getter private static int glMajor;
|
||||
@Getter private static int glMinor;
|
||||
@Getter private static boolean coreProfile;
|
||||
|
||||
private static volatile boolean isGLES;
|
||||
private static volatile int glesVersion;
|
||||
private static volatile boolean glesDetected;
|
||||
private static volatile boolean hasClipCullDistance;
|
||||
|
||||
private static volatile boolean isLTW;
|
||||
private static volatile boolean ltwDetected;
|
||||
|
||||
private static final Pattern SEMVER_PATTERN = Pattern.compile("(?<major>\\d+)\\.(?<minor>\\d+)\\.*(?<bugfix>\\d*)(.*)");
|
||||
|
||||
private RenderSystem() {}
|
||||
|
||||
private static boolean rendererInitialized;
|
||||
|
||||
public static void initRenderer() {
|
||||
if (rendererInitialized) return;
|
||||
rendererInitialized = true;
|
||||
|
||||
final boolean wasDetected = glesDetected;
|
||||
if (isGLES() && !wasDetected) {
|
||||
GLStateManager.LOGGER.info("OpenGL ES {}.{} detected", glesVersion / 100, (glesVersion / 10) % 10);
|
||||
}
|
||||
|
||||
if (!isGLES) {
|
||||
try {
|
||||
if (GLStateManager.vendorIsIntel() && GLStateManager.isWindows()) {
|
||||
dsaState = new DSAUnsupported();
|
||||
GLStateManager.LOGGER.info("Detected Intel drivers on Windows, disabling DSA.");
|
||||
} else if (!GLStateManager.getInitConfig().isDSAEnabled()) {
|
||||
dsaState = new DSAUnsupported();
|
||||
GLStateManager.LOGGER.info("enableDSA is set to false, disabling DSA.");
|
||||
} else if (GLStateManager.capabilities.OpenGL45) {
|
||||
dsaState = (Runtime.version().feature() > 8 && GLStateManager.capabilities.GL_EXT_direct_state_access) ? new DSAEXT() : new DSACore();
|
||||
GLStateManager.LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
|
||||
}
|
||||
|
||||
} catch (NoSuchFieldError ignored) {
|
||||
}
|
||||
try {
|
||||
if (dsaState == null && GLStateManager.capabilities.GL_ARB_direct_state_access) {
|
||||
dsaState = new DSAARB();
|
||||
GLStateManager.LOGGER.info("ARB_direct_state_access detected, enabling DSA.");
|
||||
}
|
||||
} catch (NoSuchFieldError ignored) {
|
||||
}
|
||||
}
|
||||
if (dsaState == null) {
|
||||
dsaState = new DSAUnsupported();
|
||||
GLStateManager.LOGGER.info("{}", isGLES ? "GLES context, using DSAUnsupported (bind-based) fallback." : "No DSA support detected, falling back to legacy OpenGL.");
|
||||
}
|
||||
|
||||
BackendManager.init();
|
||||
|
||||
try {
|
||||
// Force init on client thread
|
||||
GLStateManager.LOGGER.info("Celeritas LWJGL service: {}", LWJGLServiceProvider.LWJGL.getClass().getName());
|
||||
} catch (Throwable t) {
|
||||
GLStateManager.LOGGER.debug("Could not resolve the celeritas LWJGL service: {}", t.toString());
|
||||
}
|
||||
|
||||
supportsCompute = supportsCompute();
|
||||
supportsTesselation = GLStateManager.capabilities.GL_ARB_tessellation_shader || GLStateManager.capabilities.OpenGL40;
|
||||
|
||||
supportsImageLoadStore = GLStateManager.capabilities.OpenGL42
|
||||
|| GLStateManager.capabilities.GL_ARB_shader_image_load_store
|
||||
|| GLStateManager.capabilities.GL_EXT_shader_image_load_store;
|
||||
supportsSSBO = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_shader_storage_buffer_object;
|
||||
supportsMultiDrawIndirect = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_multi_draw_indirect;
|
||||
supportsBufferStorage = !isGLES && (GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage);
|
||||
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
|
||||
hasClipCullDistance = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
|
||||
|
||||
// Cache maximum image units
|
||||
if (supportsImageLoadStore) {
|
||||
if (GLStateManager.capabilities.OpenGL42) {
|
||||
maxImageUnits = RENDER_BACKEND.getInteger(GL42.GL_MAX_IMAGE_UNITS);
|
||||
} else if (GLStateManager.capabilities.GL_ARB_shader_image_load_store) {
|
||||
maxImageUnits = RENDER_BACKEND.getInteger(ARBShaderImageLoadStore.GL_MAX_IMAGE_UNITS);
|
||||
} else {
|
||||
maxImageUnits = RENDER_BACKEND.getInteger(EXTShaderImageLoadStore.GL_MAX_IMAGE_UNITS_EXT);
|
||||
}
|
||||
} else {
|
||||
maxImageUnits = 0;
|
||||
}
|
||||
|
||||
// Cache maximum SSBO bindings
|
||||
if (supportsSSBO) {
|
||||
if (GLStateManager.capabilities.OpenGL43) {
|
||||
maxSSBOBindings = RENDER_BACKEND.getInteger(GL43.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
|
||||
} else {
|
||||
maxSSBOBindings = RENDER_BACKEND.getInteger(ARBShaderStorageBufferObject.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
|
||||
}
|
||||
} else {
|
||||
maxSSBOBindings = 0;
|
||||
}
|
||||
|
||||
// Check for sampler objects support (GL 3.3+ or ARB extension)
|
||||
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
|
||||
if (supportsSamplerObjects) {
|
||||
maxCombinedTextureImageUnits = RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
|
||||
}
|
||||
|
||||
supportsProgramBinary = RENDER_BACKEND.supportsProgramBinary();
|
||||
GLStateManager.LOGGER.info("Program Binary: {}", supportsProgramBinary);
|
||||
|
||||
maxGlslVersion = Integer.parseInt(parseGlVersionString(RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION)));
|
||||
supportsGpuShader4 = GLStateManager.capabilities.GL_EXT_gpu_shader4;
|
||||
|
||||
GLStateManager.LOGGER.info("Max GLSL version: {}, GPU Shader4: {}", maxGlslVersion, supportsGpuShader4);
|
||||
GLStateManager.LOGGER.info("Image Load/Store: {}, Max Image Units: {}", supportsImageLoadStore, maxImageUnits);
|
||||
GLStateManager.LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings);
|
||||
GLStateManager.LOGGER.info("Buffer Storage: {}, Clear Texture: {}, Sampler Objects: {}", supportsBufferStorage, supportsClearTexture, supportsSamplerObjects);
|
||||
|
||||
if (isGLES) {
|
||||
GLStateManager.LOGGER.info("GL ES context detected, enabling shader transformer.");
|
||||
ShaderManager.enable();
|
||||
} else if (GLStateManager.capabilities.OpenGL32) {
|
||||
final int profileMask = RENDER_BACKEND.getInteger(GL32.GL_CONTEXT_PROFILE_MASK);
|
||||
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
|
||||
coreProfile = true;
|
||||
GLStateManager.LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
|
||||
ShaderManager.enable();
|
||||
} else {
|
||||
throw new IllegalStateException("Non-core GL context (profile mask 0x" + Integer.toHexString(profileMask)
|
||||
+ "); FFP emulation requires a core profile and nothing would render. Context creation should have rejected this.");
|
||||
}
|
||||
}
|
||||
|
||||
final Matcher glVerMatcher = SEMVER_PATTERN.matcher(RENDER_BACKEND.getString(GL11.GL_VERSION));
|
||||
if (glVerMatcher.matches()) {
|
||||
glMajor = Integer.parseInt(glVerMatcher.group("major"));
|
||||
glMinor = Integer.parseInt(glVerMatcher.group("minor"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateMipmaps(int texture, int mipmapTarget) {
|
||||
dsaState.generateMipmaps(texture, mipmapTarget);
|
||||
}
|
||||
|
||||
public static void bindAttributeLocation(int program, int index, CharSequence name) {
|
||||
RENDER_BACKEND.bindAttribLocation(program, index, name);
|
||||
}
|
||||
|
||||
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
|
||||
final GLStateManager.GLESTexImageRemap remap = GLStateManager.remapTexImageForGLES(internalformat, format, type);
|
||||
internalformat = remap.internalFormat();
|
||||
format = remap.format();
|
||||
type = remap.type();
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
if (target == GL11.GL_TEXTURE_2D && level == 0) {
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
|
||||
RENDER_BACKEND.uniformMatrix4(location, transpose, matrix);
|
||||
}
|
||||
|
||||
public static void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {
|
||||
GLStateManager.glCopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
|
||||
}
|
||||
|
||||
public static void uniform1f(int location, float v0) {
|
||||
RENDER_BACKEND.uniform1f(location, v0);
|
||||
}
|
||||
|
||||
public static void uniform1i(int location, int v0) {
|
||||
RENDER_BACKEND.uniform1i(location, v0);
|
||||
}
|
||||
|
||||
public static void uniform2f(int location, float v0, float v1) {
|
||||
RENDER_BACKEND.uniform2f(location, v0, v1);
|
||||
}
|
||||
|
||||
public static void uniform2i(int location, int v0, int v1) {
|
||||
RENDER_BACKEND.uniform2i(location, v0, v1);
|
||||
}
|
||||
|
||||
public static void uniform3f(int location, float v0, float v1, float v2) {
|
||||
RENDER_BACKEND.uniform3f(location, v0, v1, v2);
|
||||
}
|
||||
|
||||
public static void uniform3i(int location, int v0, int v1, int v2) {RENDER_BACKEND.uniform3i(location, v0, v1, v2);}
|
||||
|
||||
public static void uniform4f(int location, float v0, float v1, float v2, float v3) {
|
||||
RENDER_BACKEND.uniform4f(location, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
public static void uniform4i(int location, int v0, int v1, int v2, int v3) {
|
||||
RENDER_BACKEND.uniform4i(location, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
public static int getAttribLocation(int programId, String name) {
|
||||
return RENDER_BACKEND.getAttribLocation(programId, name);
|
||||
}
|
||||
|
||||
public static int getUniformLocation(int programId, String name) {
|
||||
return RENDER_BACKEND.getUniformLocation(programId, name);
|
||||
}
|
||||
|
||||
public static void texParameteriv(int texture, int target, int pname, IntBuffer params) {
|
||||
dsaState.texParameteriv(texture, target, pname, params);
|
||||
}
|
||||
|
||||
public static void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
|
||||
dsaState.copyTexSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
|
||||
}
|
||||
|
||||
public static void texParameteri(int texture, int target, int pname, int param) {
|
||||
dsaState.texParameteri(texture, target, pname, param);
|
||||
}
|
||||
|
||||
public static void texParameterf(int texture, int target, int pname, float param) {
|
||||
dsaState.texParameterf(texture, target, pname, param);
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
}
|
||||
|
||||
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
}
|
||||
|
||||
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
}
|
||||
|
||||
public static String getProgramInfoLog(int program) {
|
||||
return RENDER_BACKEND.getProgramInfoLog(program, RENDER_BACKEND.getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
|
||||
}
|
||||
|
||||
public static String getShaderInfoLog(int shader) {
|
||||
return RENDER_BACKEND.getShaderInfoLog(shader, RENDER_BACKEND.getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
|
||||
}
|
||||
|
||||
public static void drawBuffers(int framebuffer, IntBuffer buffers) {
|
||||
dsaState.drawBuffers(framebuffer, buffers);
|
||||
}
|
||||
|
||||
public static void readBuffer(int framebuffer, int buffer) {
|
||||
dsaState.readBuffer(framebuffer, buffer);
|
||||
}
|
||||
|
||||
public static String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType) {
|
||||
return RENDER_BACKEND.getActiveUniform(program, index, maxLength, sizeType);
|
||||
}
|
||||
|
||||
public static void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
|
||||
GLStateManager.suspendPixelPackBuffer();
|
||||
RENDER_BACKEND.readPixels(x, y, width, height, format, type, pixels);
|
||||
GLStateManager.restorePixelPackBuffer();
|
||||
}
|
||||
|
||||
public static void bufferData(int target, FloatBuffer data, int usage) {
|
||||
GLStateManager.glBufferData(target, data, usage);
|
||||
}
|
||||
|
||||
public static int bufferStorage(int target, FloatBuffer data, int usage) {
|
||||
return dsaState.bufferStorage(target, data, usage);
|
||||
}
|
||||
|
||||
public static void vertexAttrib4f(int index, float v0, float v1, float v2, float v3) {
|
||||
RENDER_BACKEND.vertexAttrib4f(index, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
public static void detachShader(int program, int shader) {
|
||||
RENDER_BACKEND.detachShader(program, shader);
|
||||
}
|
||||
|
||||
public static void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
|
||||
dsaState.framebufferTexture2D(fb, fbtarget, attachment, target, texture, levels);
|
||||
}
|
||||
|
||||
public static int getTexParameteri(int texture, int target, int pname) {
|
||||
return dsaState.getTexParameteri(texture, target, pname);
|
||||
}
|
||||
|
||||
public static float getTexParameterf(int texture, int target, int pname) {
|
||||
return dsaState.getTexParameterf(texture, target, pname);
|
||||
}
|
||||
|
||||
public static int getTexLevelParameteri(int texture, int level, int pname) {
|
||||
return dsaState.getTexLevelParameteri(texture, level, pname);
|
||||
}
|
||||
|
||||
public static void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) {
|
||||
GLStateManager.glBindImageTexture(unit, texture, level, layered, layer, access, format);
|
||||
}
|
||||
|
||||
public static int getMaxImageUnits() {
|
||||
return maxImageUnits;
|
||||
}
|
||||
|
||||
public static boolean supportsImageLoadStore() {
|
||||
if (RENDER_BACKEND.isSDLGPU()) return true;
|
||||
return supportsImageLoadStore;
|
||||
}
|
||||
|
||||
public static boolean hasClipCullDistance() {
|
||||
return hasClipCullDistance;
|
||||
}
|
||||
|
||||
private static boolean hasExtension(String name) {
|
||||
final int count = GLStateManager.glGetInteger(GL30.GL_NUM_EXTENSIONS);
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (name.equals(GLStateManager.glGetStringi(GL11.GL_EXTENSIONS, i))) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isGLES() {
|
||||
if (!glesDetected) detectGLES();
|
||||
return isGLES;
|
||||
}
|
||||
|
||||
private static synchronized void detectGLES() {
|
||||
if (glesDetected) return;
|
||||
try {
|
||||
final String v = RENDER_BACKEND.getString(GL11.GL_VERSION);
|
||||
if (v != null && v.startsWith("OpenGL ES ")) {
|
||||
isGLES = true;
|
||||
glesVersion = Integer.parseInt(parseGlVersionString(v));
|
||||
}
|
||||
glesDetected = true;
|
||||
} catch (Throwable ignored) {
|
||||
// No GL context on this thread yet (splash); retry on next call.
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isLTW() {
|
||||
if (!ltwDetected) detectLTW();
|
||||
return isLTW;
|
||||
}
|
||||
|
||||
private static synchronized void detectLTW() {
|
||||
if (ltwDetected) return;
|
||||
if (SystemProperties.DISABLE_LTW_WORKAROUND) {
|
||||
ltwDetected = true;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final String v = RENDER_BACKEND.getString(GL11.GL_VERSION);
|
||||
if (v == null) return;
|
||||
isLTW = LTWWorkaround.isLtwVersionString(v);
|
||||
ltwDetected = true;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean supportsSSBO() {
|
||||
return supportsSSBO;
|
||||
}
|
||||
|
||||
public static boolean supportsMultiDrawIndirect() {
|
||||
return supportsMultiDrawIndirect;
|
||||
}
|
||||
|
||||
public static boolean supportsBufferStorage() {
|
||||
return supportsBufferStorage;
|
||||
}
|
||||
|
||||
public static boolean supportsClearTexture() {
|
||||
return supportsClearTexture;
|
||||
}
|
||||
|
||||
public static int getMaxSSBOBindings() {
|
||||
return maxSSBOBindings;
|
||||
}
|
||||
|
||||
public static void getProgramiv(int program, int value, IntBuffer storage) {
|
||||
RENDER_BACKEND.getProgramiv(program, value, storage);
|
||||
}
|
||||
|
||||
public static void dispatchCompute(int workX, int workY, int workZ) {
|
||||
RENDER_BACKEND.dispatchCompute(workX, workY, workZ);
|
||||
}
|
||||
|
||||
public static void dispatchCompute(Vector3i workGroups) {
|
||||
RENDER_BACKEND.dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
|
||||
}
|
||||
|
||||
public static void dispatchComputeIndirect(long offset) {
|
||||
RENDER_BACKEND.dispatchComputeIndirect(offset);
|
||||
}
|
||||
|
||||
public static void bindBuffer(int target, int buffer) {
|
||||
GLStateManager.glBindBuffer(target, buffer);
|
||||
}
|
||||
|
||||
public static void memoryBarrier(int barriers) {
|
||||
if (supportsCompute) {
|
||||
RENDER_BACKEND.memoryBarrier(barriers);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean supportsBufferBlending() {
|
||||
return GLStateManager.capabilities.GL_ARB_draw_buffers_blend || GLStateManager.capabilities.OpenGL40;
|
||||
}
|
||||
|
||||
public static void disableBufferBlend(int buffer) {
|
||||
GLStateManager.glDisablei(GL11.GL_BLEND, buffer);
|
||||
}
|
||||
|
||||
public static void enableBufferBlend(int buffer) {
|
||||
GLStateManager.glEnablei(GL11.GL_BLEND, buffer);
|
||||
}
|
||||
|
||||
public static void blendFuncSeparatei(int buffer, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
GLStateManager.glBlendFuncSeparatei(buffer, srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
}
|
||||
|
||||
public static void bindTextureToUnit(int unit, int texture) {
|
||||
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
|
||||
dsaState.bindTextureToUnit(unit, texture);
|
||||
}
|
||||
|
||||
public static void bindTextureToUnit(int target, int unit, int texture) {
|
||||
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
|
||||
dsaState.bindTextureToUnit(target, unit, texture);
|
||||
}
|
||||
|
||||
public static void setupProjectionMatrix(Matrix4f matrix) {
|
||||
final FloatBuffer projectionMatrixBuffer = GLStateManager.ctx().projectionMatrixBuffer;
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glPushMatrix();
|
||||
matrix.get(0, projectionMatrixBuffer);
|
||||
GLStateManager.glLoadMatrix(projectionMatrixBuffer);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
public static void restoreProjectionMatrix() {
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
public static void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2, int bufferChoice, int filter) {
|
||||
final boolean scissor = GLStateManager.glIsEnabled(GL11.GL_SCISSOR_TEST);
|
||||
if (scissor) GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
try {
|
||||
dsaState.blitFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
|
||||
} finally {
|
||||
if (scissor) GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
public static int createFramebuffer() {
|
||||
return dsaState.createFramebuffer();
|
||||
}
|
||||
|
||||
public static int createTexture(int target) {
|
||||
return dsaState.createTexture(target);
|
||||
}
|
||||
|
||||
// TODO: Proper notification of compute support
|
||||
public static boolean supportsCompute() {
|
||||
try {
|
||||
return GLStateManager.capabilities.GL_ARB_compute_shader || GLStateManager.capabilities.OpenGL43;
|
||||
} catch (Exception ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static int createBuffers() {
|
||||
return GLStateManager.glGenBuffers();
|
||||
}
|
||||
|
||||
public static int getIndexedBufferBinding(int target, int index) {
|
||||
return RENDER_BACKEND.getIndexedBufferBinding(target, index);
|
||||
}
|
||||
|
||||
public static void bindBufferBase(int target, int index, int buffer) {
|
||||
GLStateManager.glBindBufferBase(target, index, buffer);
|
||||
}
|
||||
|
||||
public static void bufferStorage(int target, long size, int flags) {
|
||||
GLStateManager.glBufferStorage(target, size, flags);
|
||||
}
|
||||
|
||||
public static void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, int[] data) {
|
||||
final ByteBuffer buf = BufferUtils.createByteBuffer(data.length * 4);
|
||||
buf.asIntBuffer().put(data);
|
||||
GLStateManager.glClearBufferSubData(target, internalFormat, offset, size, format, type, buf);
|
||||
}
|
||||
|
||||
public static void deleteBuffers(int buffer) {
|
||||
GLStateManager.glDeleteBuffers(buffer);
|
||||
}
|
||||
|
||||
public static long getVRAM() {
|
||||
if (GLStateManager.capabilities.GL_NVX_gpu_memory_info) {
|
||||
return RENDER_BACKEND.getInteger(NVXGpuMemoryInfo.GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX) * 1024L;
|
||||
} else {
|
||||
// Default to 4GB if we can't query VRAM
|
||||
return 4294967296L;
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearTexImage(int texture, int target, int level, int format, int type) {
|
||||
RENDER_BACKEND.clearTexImage(texture, level, format, type);
|
||||
}
|
||||
|
||||
public static int getMaxGlslVersion() {
|
||||
return maxGlslVersion;
|
||||
}
|
||||
|
||||
public static boolean supportsGpuShader4() {
|
||||
return supportsGpuShader4;
|
||||
}
|
||||
|
||||
public static boolean supportsSnormFormats() {
|
||||
if (isGLES()) return false;
|
||||
return GLStateManager.capabilities.OpenGL31;
|
||||
}
|
||||
|
||||
public static boolean supportsPackedFloatRenderable() {
|
||||
return GLStateManager.capabilities.OpenGL30 && maxGlslVersion >= 130;
|
||||
}
|
||||
|
||||
public static boolean supportsTesselation() {
|
||||
return supportsTesselation;
|
||||
}
|
||||
|
||||
public static boolean supportsSamplerObjects() {
|
||||
return supportsSamplerObjects;
|
||||
}
|
||||
|
||||
public static boolean supportsProgramBinary() {
|
||||
return supportsProgramBinary;
|
||||
}
|
||||
|
||||
public static int genSampler() {
|
||||
if (!supportsSamplerObjects) return 0;
|
||||
return GLStateManager.glGenSamplers();
|
||||
}
|
||||
|
||||
public static void destroySampler(int sampler) {
|
||||
if (!supportsSamplerObjects || sampler == 0) return;
|
||||
GLStateManager.glDeleteSamplers(sampler);
|
||||
}
|
||||
|
||||
public static void samplerParameteri(int sampler, int pname, int param) {
|
||||
if (!supportsSamplerObjects || sampler == 0) return;
|
||||
GLStateManager.glSamplerParameteri(sampler, pname, param);
|
||||
}
|
||||
|
||||
public static void samplerParameterf(int sampler, int pname, float param) {
|
||||
if (!supportsSamplerObjects || sampler == 0) return;
|
||||
GLStateManager.glSamplerParameterf(sampler, pname, param);
|
||||
}
|
||||
|
||||
public static void bindSamplerToUnit(int unit, int sampler) {
|
||||
if (!supportsSamplerObjects) return;
|
||||
GLStateManager.glBindSampler(unit, sampler);
|
||||
}
|
||||
|
||||
public static void unbindAllSamplers() {
|
||||
if (!supportsSamplerObjects) return;
|
||||
final int count = GLStateManager.getSamplerUnitCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (GLStateManager.getSamplerBinding(i) != 0) {
|
||||
GLStateManager.glBindSampler(i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Strips
|
||||
* "OpenGL ES " / "OpenGL ES GLSL ES " prefixes so ES contexts parse too. */
|
||||
public static String parseGlVersionString(String info) {
|
||||
Objects.requireNonNull(info);
|
||||
String stripped = info;
|
||||
if (stripped.startsWith("OpenGL ES GLSL ES ")) {
|
||||
stripped = stripped.substring("OpenGL ES GLSL ES ".length());
|
||||
} else if (stripped.startsWith("OpenGL ES ")) {
|
||||
stripped = stripped.substring("OpenGL ES ".length());
|
||||
}
|
||||
final Matcher matcher = SEMVER_PATTERN.matcher(stripped);
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalStateException("Could not parse GL version from \"" + info + "\"");
|
||||
}
|
||||
final String major = matcher.group("major");
|
||||
final String minor = matcher.group("minor");
|
||||
String bugfix = matcher.group("bugfix");
|
||||
if (bugfix == null || bugfix.isEmpty()) bugfix = "0";
|
||||
return major + minor + bugfix;
|
||||
}
|
||||
|
||||
/** Parse a GL version string into packed major*10+minor (e.g. "4.3.0 - Build ..." -> 43), or -1 if unparseable. */
|
||||
public static int parseMajorMinor(String glVersion) {
|
||||
if (glVersion == null) return -1;
|
||||
final Matcher matcher = SEMVER_PATTERN.matcher(glVersion.trim());
|
||||
if (!matcher.matches()) return -1;
|
||||
return Integer.parseInt(matcher.group("major")) * 10 + Integer.parseInt(matcher.group("minor"));
|
||||
}
|
||||
|
||||
public static boolean isContextValid(int requestedMajor, int requestedMinor, int actualVersion, boolean coreBitSet) {
|
||||
if (!coreBitSet) return false;
|
||||
if (actualVersion < 0) return true;
|
||||
return actualVersion >= requestedMajor * 10 + requestedMinor;
|
||||
}
|
||||
|
||||
public static final int MIN_CORE_VERSION = 33;
|
||||
|
||||
public static int probeCapAfterRejection(int requested, int actual) {
|
||||
if (actual >= MIN_CORE_VERSION && actual < requested) return actual;
|
||||
if (actual >= 0 && actual < MIN_CORE_VERSION && requested > MIN_CORE_VERSION) return MIN_CORE_VERSION;
|
||||
return requested - 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class StateSet {
|
||||
|
||||
public static final int R_DEPTH = 1;
|
||||
public static final int R_BLEND = 1 << 1;
|
||||
public static final int R_COLOR_MASK = 1 << 2;
|
||||
public static final int R_CLEAR_COLOR = 1 << 3;
|
||||
public static final int R_DRAW_BUFFER = 1 << 4;
|
||||
public static final int R_LOGIC_OP = 1 << 5;
|
||||
public static final int R_SECONDARY_COLOR = 1 << 6;
|
||||
public static final int R_STENCIL = 1 << 7;
|
||||
public static final int R_VIEWPORT = 1 << 8;
|
||||
public static final int R_LINE = 1 << 9;
|
||||
public static final int R_POINT = 1 << 10;
|
||||
public static final int R_POLYGON = 1 << 11;
|
||||
public static final int R_TEXTURE = 1 << 12;
|
||||
public static final int R_ACTIVE_UNIT = 1 << 13;
|
||||
public static final int R_PROGRAM = 1 << 14;
|
||||
public static final int R_SCISSOR = 1 << 15;
|
||||
public static final int R_HINT = 1 << 16;
|
||||
|
||||
public static final int B_FRAG_COLORBUF = 1;
|
||||
public static final int B_FRAG_TEXTURE = 1 << 1;
|
||||
public static final int B_FRAG_FOG = 1 << 2;
|
||||
public static final int B_CURRENT = 1 << 3;
|
||||
public static final int B_LIGHTING = 1 << 4;
|
||||
public static final int B_TRANSFORM = 1 << 5;
|
||||
|
||||
private static final long[] NO_MEMBERS = new long[0];
|
||||
private static final Int2ObjectMap<StateSet> maskToStateSetMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
public final int glMask;
|
||||
public final long[] members;
|
||||
public final int restore;
|
||||
public final int bump;
|
||||
public final boolean blendSnapshot;
|
||||
public final DisplayListCommand pushCommand;
|
||||
|
||||
StateSet(int glMask, long[] members, int restore, int bump, boolean blendSnapshot) {
|
||||
this.glMask = glMask;
|
||||
this.members = members;
|
||||
this.restore = restore;
|
||||
this.bump = bump;
|
||||
this.blendSnapshot = blendSnapshot;
|
||||
this.pushCommand = () -> GLStateManager.pushStateLive(this);
|
||||
}
|
||||
|
||||
public boolean contains(int id) {
|
||||
if (id < 0) return false;
|
||||
final int word = id >> 6;
|
||||
return word < members.length && (members[word] & (1L << id)) != 0;
|
||||
}
|
||||
|
||||
private static long[] ids(CowStateStack<?>... stacks) {
|
||||
if (stacks.length == 0) return NO_MEMBERS;
|
||||
int max = 0;
|
||||
for (CowStateStack<?> s : stacks) max = Math.max(max, s.stackId());
|
||||
final long[] bits = new long[(max >> 6) + 1];
|
||||
for (CowStateStack<?> s : stacks) {
|
||||
final int id = s.stackId();
|
||||
bits[id >> 6] |= 1L << id;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static final StateSet BATCH = buildBatch();
|
||||
public static final StateSet FONT = buildFont();
|
||||
public static final StateSet FONT_PIPELINE = buildFontPipeline();
|
||||
public static final StateSet HAND = buildHand();
|
||||
public static final StateSet BLEND = buildBlend();
|
||||
public static final StateSet ALPHA = buildAlpha();
|
||||
public static final StateSet CUTOUT = buildCutout();
|
||||
public static final StateSet MASKS = buildMasks();
|
||||
public static final StateSet CULL = buildCull();
|
||||
|
||||
private static StateSet buildBatch() {
|
||||
return new StateSet(0,
|
||||
ids(
|
||||
GLStateManager.getBlendState(), GLStateManager.getAlphaState(), GLStateManager.getDepthState(),
|
||||
GLStateManager.getColorMask(), GLStateManager.getPolygonState(),
|
||||
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
|
||||
GLStateManager.getActiveTextureUnitStack(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
|
||||
GLStateManager.getLightingState(), GLStateManager.getCullState(), GLStateManager.getPolygonOffsetFillState(),
|
||||
GLStateManager.getTextures().getTextureUnitStates(0), GLStateManager.getTextures().getTextureUnitStates(1),
|
||||
GLStateManager.getLightStates()[0], GLStateManager.getLightStates()[1],
|
||||
GLStateManager.getLightDataStates()[0], GLStateManager.getLightDataStates()[1],
|
||||
GLStateManager.getColorMaterial(), GLStateManager.getColorMaterialFace(), GLStateManager.getColorMaterialParameter(),
|
||||
GLStateManager.getLightModel(), GLStateManager.getFrontMaterial(), GLStateManager.getBackMaterial(),
|
||||
GLStateManager.getNormalizeState(), GLStateManager.getRescaleNormalState()
|
||||
),
|
||||
R_BLEND | R_DEPTH | R_COLOR_MASK | R_POLYGON | R_TEXTURE | R_ACTIVE_UNIT,
|
||||
B_FRAG_COLORBUF | B_LIGHTING,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildFont() {
|
||||
return new StateSet(0,
|
||||
ids(
|
||||
GLStateManager.getBlendState(), GLStateManager.getDepthState(), GLStateManager.getShadeModelState(),
|
||||
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
|
||||
GLStateManager.getActiveTextureUnitStack(), GLStateManager.getProgramStack(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
|
||||
GLStateManager.getTextures().getTextureUnitStates(0),
|
||||
GLStateManager.getColorMask(), GLStateManager.getCullState(),
|
||||
GLStateManager.getPolygonState(), GLStateManager.getPolygonOffsetFillState()
|
||||
),
|
||||
R_BLEND | R_DEPTH | R_COLOR_MASK | R_TEXTURE | R_ACTIVE_UNIT | R_PROGRAM | R_POLYGON,
|
||||
0,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildFontPipeline() {
|
||||
return new StateSet(0,
|
||||
ids(
|
||||
GLStateManager.getBlendState(), GLStateManager.getDepthState(),
|
||||
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
|
||||
GLStateManager.getActiveTextureUnitStack(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
|
||||
GLStateManager.getTextures().getTextureUnitStates(0),
|
||||
GLStateManager.getColorMask(), GLStateManager.getCullState(),
|
||||
GLStateManager.getPolygonState(), GLStateManager.getPolygonOffsetFillState()
|
||||
),
|
||||
R_BLEND | R_DEPTH | R_COLOR_MASK | R_TEXTURE | R_ACTIVE_UNIT | R_POLYGON,
|
||||
0,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildHand() {
|
||||
return new StateSet(0,
|
||||
ids(
|
||||
GLStateManager.getBlendState(), GLStateManager.getAlphaState(), GLStateManager.getDepthState(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getTextures().getTextureUnitStates(1)
|
||||
),
|
||||
R_BLEND | R_DEPTH,
|
||||
B_FRAG_COLORBUF,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildBlend() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getBlendState(), GLStateManager.getBlendMode()),
|
||||
R_BLEND,
|
||||
0,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildAlpha() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getAlphaState(), GLStateManager.getAlphaTest()),
|
||||
0,
|
||||
B_FRAG_COLORBUF,
|
||||
false);
|
||||
}
|
||||
|
||||
private static StateSet buildCutout() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getAlphaState(), GLStateManager.getAlphaTest(), GLStateManager.getTextures().getTextureUnitStates(1)),
|
||||
0,
|
||||
B_FRAG_COLORBUF,
|
||||
false);
|
||||
}
|
||||
|
||||
private static StateSet buildMasks() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getDepthState(), GLStateManager.getColorMask()),
|
||||
R_DEPTH | R_COLOR_MASK,
|
||||
0,
|
||||
false);
|
||||
}
|
||||
|
||||
private static StateSet buildCull() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getPolygonState(), GLStateManager.getCullState()),
|
||||
R_POLYGON,
|
||||
0,
|
||||
false);
|
||||
}
|
||||
|
||||
public static StateSet forMask(int mask) {
|
||||
StateSet cached = maskToStateSetMap.get(mask);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
cached = buildForMask(mask);
|
||||
maskToStateSetMap.put(mask, cached);
|
||||
return cached;
|
||||
}
|
||||
|
||||
private static StateSet buildForMask(int mask) {
|
||||
int restore = 0;
|
||||
int bump = 0;
|
||||
final boolean blendSnapshot = (mask & (GL11.GL_COLOR_BUFFER_BIT | GL11.GL_ENABLE_BIT)) != 0;
|
||||
|
||||
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
restore |= R_DEPTH;
|
||||
}
|
||||
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
|
||||
restore |= R_BLEND | R_COLOR_MASK | R_CLEAR_COLOR | R_DRAW_BUFFER | R_LOGIC_OP;
|
||||
bump |= B_FRAG_COLORBUF;
|
||||
}
|
||||
if ((mask & GL11.GL_CURRENT_BIT) != 0) {
|
||||
restore |= R_SECONDARY_COLOR;
|
||||
bump |= B_CURRENT;
|
||||
}
|
||||
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
|
||||
restore |= R_STENCIL;
|
||||
}
|
||||
if ((mask & GL11.GL_VIEWPORT_BIT) != 0) {
|
||||
restore |= R_VIEWPORT;
|
||||
}
|
||||
if ((mask & GL11.GL_SCISSOR_BIT) != 0) {
|
||||
restore |= R_SCISSOR;
|
||||
}
|
||||
if ((mask & GL11.GL_LINE_BIT) != 0) {
|
||||
restore |= R_LINE;
|
||||
}
|
||||
if ((mask & GL11.GL_POINT_BIT) != 0) {
|
||||
restore |= R_POINT;
|
||||
}
|
||||
if ((mask & GL11.GL_HINT_BIT) != 0) {
|
||||
restore |= R_HINT;
|
||||
}
|
||||
if ((mask & GL11.GL_POLYGON_BIT) != 0) {
|
||||
restore |= R_POLYGON;
|
||||
}
|
||||
if ((mask & GL11.GL_TEXTURE_BIT) != 0) {
|
||||
restore |= R_TEXTURE | R_ACTIVE_UNIT;
|
||||
bump |= B_FRAG_TEXTURE;
|
||||
}
|
||||
if ((mask & GL11.GL_LIGHTING_BIT) != 0) {
|
||||
bump |= B_LIGHTING;
|
||||
}
|
||||
if ((mask & GL11.GL_FOG_BIT) != 0) {
|
||||
bump |= B_FRAG_FOG;
|
||||
}
|
||||
if ((mask & GL11.GL_TRANSFORM_BIT) != 0) {
|
||||
bump |= B_TRANSFORM;
|
||||
}
|
||||
|
||||
final long[] members = Feature.maskToMembers(mask);
|
||||
|
||||
return new StateSet(mask, members, restore, bump, blendSnapshot);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
/// Matches
|
||||
public enum Vendor {
|
||||
AMD("amd"),
|
||||
INTEL("intel"),
|
||||
MESA("mesa"),
|
||||
NVIDIA("nvidia"),
|
||||
UNKNOWN("");
|
||||
|
||||
final String[] names;
|
||||
|
||||
Vendor(String... names) {
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
public static Vendor getVendor(String vendorString) {
|
||||
for (var v : values()) {
|
||||
for (var name : v.names) {
|
||||
if (vendorString.contains(name)) return v;
|
||||
}
|
||||
}
|
||||
|
||||
// Shouldn't be needed as "" matches everything, but sanity checks good.
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
public final class BackendManager {
|
||||
private static final Logger LOGGER = LogManager.getLogger("BackendManager");
|
||||
|
||||
public static final RenderBackend RENDER_BACKEND = loadService();
|
||||
|
||||
private BackendManager() {}
|
||||
|
||||
public static void init() {
|
||||
RENDER_BACKEND.init();
|
||||
LOGGER.info("Initialized backend: {} (priority {})", RENDER_BACKEND.getName(), RENDER_BACKEND.getPriority());
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
RENDER_BACKEND.shutdown();
|
||||
}
|
||||
|
||||
private static RenderBackend loadService() {
|
||||
final ServiceLoader<RenderBackend> loader = ServiceLoader.load(RenderBackend.class, RenderBackend.class.getClassLoader());
|
||||
|
||||
RenderBackend best = null;
|
||||
final Iterator<RenderBackend> iterator = loader.iterator();
|
||||
int attempts = 0;
|
||||
|
||||
while (attempts < 16) {
|
||||
try {
|
||||
attempts++;
|
||||
if (!iterator.hasNext()) break;
|
||||
final RenderBackend backend = iterator.next();
|
||||
LOGGER.info("Found backend: {} (priority {})", backend.getName(), backend.getPriority());
|
||||
if (backend.isAvailable() && (best == null || backend.getPriority() > best.getPriority())) {
|
||||
best = backend;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
LOGGER.debug("Skipping unavailable backend: {}", t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (best == null) {
|
||||
throw new IllegalStateException("No available render backend!");
|
||||
}
|
||||
|
||||
LOGGER.info("Selected backend: {} (priority {})", best.getName(), best.getPriority());
|
||||
return best;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
|
||||
public final class BackendOptions {
|
||||
|
||||
private static boolean configSdlGpu;
|
||||
private static String configDriver = "";
|
||||
private static int configGlVersion;
|
||||
|
||||
private BackendOptions() {}
|
||||
|
||||
public static void latch(boolean sdlGpu, String driver, int glVersion) {
|
||||
configSdlGpu = sdlGpu;
|
||||
configDriver = driver;
|
||||
configGlVersion = glVersion;
|
||||
}
|
||||
|
||||
public static boolean sdlGpuRequested() {
|
||||
return SystemProperties.SDL_GPU_OVERRIDE != null ? SystemProperties.SDL_GPU_OVERRIDE : configSdlGpu;
|
||||
}
|
||||
|
||||
public static String sdlGpuDriver() {
|
||||
return SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SystemProperties.SDL_GPU_DRIVER_OVERRIDE : configDriver;
|
||||
}
|
||||
|
||||
public static boolean configSdlGpu() {
|
||||
return configSdlGpu;
|
||||
}
|
||||
|
||||
public static String configDriver() {
|
||||
return configDriver;
|
||||
}
|
||||
|
||||
public static int configGlVersion() {
|
||||
return configGlVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public final class BackendStartGuard {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica/BackendStartGuard");
|
||||
|
||||
private static File pending;
|
||||
private static boolean tripped;
|
||||
|
||||
private BackendStartGuard() {}
|
||||
|
||||
public static boolean begin(File marker, boolean sdlGpuConfigured, boolean flagForced) {
|
||||
pending = null;
|
||||
tripped = false;
|
||||
if (!sdlGpuConfigured || flagForced) {
|
||||
delete(marker);
|
||||
return false;
|
||||
}
|
||||
if (marker.isFile()) {
|
||||
delete(marker);
|
||||
tripped = true;
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
final File dir = marker.getParentFile();
|
||||
if (dir != null && !dir.isDirectory() && !dir.mkdirs()) throw new IOException("cannot create " + dir);
|
||||
if (marker.createNewFile()) pending = marker;
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not write {}: {}", marker, e.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void finish() {
|
||||
if (pending == null) return;
|
||||
delete(pending);
|
||||
pending = null;
|
||||
}
|
||||
|
||||
public static boolean tripped() {
|
||||
return tripped;
|
||||
}
|
||||
|
||||
private static void delete(File marker) {
|
||||
if (marker.isFile() && !marker.delete()) {
|
||||
LOGGER.warn("Could not delete {}", marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DebugMessageHandler {
|
||||
void handleMessage(int source, int type, int id, int severity, String message);
|
||||
}
|
||||
Loaded 100 of 2595 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user