Compare commits
919 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e065e4df4 | |||
| 4c0399ca40 | |||
| 49e92b4190 | |||
| fa751fd473 | |||
| 0ccd024f37 | |||
| 2236bf19a5 | |||
|
|
c5e447125b | ||
|
|
cff4866499 | ||
|
|
935685ad08 | ||
|
|
848efe60a8 | ||
|
|
470534831b | ||
|
|
df90323fc7 | ||
|
|
f703d83b91 | ||
|
|
51f1197c9f | ||
|
|
1e92902724 | ||
|
|
0f6a099345 | ||
|
|
a7dc2112fa | ||
|
|
837b779aa9 | ||
|
|
692ac95549 | ||
|
|
3f0a1466ac | ||
|
|
7bc2c2b960 | ||
|
|
1e7b199660 | ||
|
|
61da430895 | ||
|
|
263f93215a | ||
|
|
79890fde1e | ||
|
|
3cc5afc1e1 | ||
|
|
3bcdbbec90 | ||
|
|
6212d95cdd | ||
|
|
b086832333 | ||
|
|
e86beefbc7 | ||
|
|
45ff6c4414 | ||
|
|
ee7ec50d44 | ||
|
|
f272042a76 | ||
|
|
b5c31478fb | ||
|
|
b68d5b3c66 | ||
|
|
342043674d | ||
|
|
d30ea7ecc7 | ||
|
|
bca46143fb | ||
|
|
1e4500a912 | ||
|
|
bc995f9c0f | ||
|
|
86ce0c0f98 | ||
|
|
4ffb7583a7 | ||
|
|
ef1a296998 | ||
|
|
0428153cf5 | ||
|
|
fb9b7923b0 | ||
|
|
5b697ddb04 | ||
|
|
25463a4c11 | ||
|
|
8247248c62 | ||
|
|
6beb6b2037 | ||
|
|
65e10c2c68 | ||
|
|
362998cc5f | ||
|
|
76cfef5b3c | ||
|
|
15604c16bf | ||
|
|
85638fce98 | ||
|
|
2bf9af00e5 | ||
|
|
6724157f9a | ||
|
|
bbf7d60abe | ||
|
|
b7f5798ecd | ||
|
|
c4f799ff53 | ||
|
|
561337e10c | ||
|
|
4ca9cdbaa8 | ||
|
|
a7ea5b2f99 | ||
|
|
c7cf4a74d5 | ||
|
|
03d971385c | ||
|
|
afe41a10b5 | ||
|
|
2458a4a3f6 | ||
|
|
edd0ce33ef | ||
|
|
f713ef2e8f | ||
|
|
0f9287adcb | ||
|
|
823babef81 | ||
|
|
66a2061813 | ||
|
|
26189d4739 | ||
|
|
53f857771e | ||
|
|
aa314781d6 | ||
|
|
7f49a84215 | ||
|
|
43ed8145ff | ||
|
|
37b7feecd5 | ||
|
|
e7deeed34e | ||
|
|
18b13370d8 | ||
|
|
d371e80e3b | ||
|
|
f887b1ac60 | ||
|
|
e873ce76c6 | ||
|
|
b688dc38d6 | ||
|
|
9d8ade88f3 | ||
|
|
4d8de7eb3d | ||
|
|
560ba41fa5 | ||
|
|
937eaa5fb1 | ||
|
|
bf5b8d76d2 | ||
|
|
1c1d812bbf | ||
|
|
8426006435 | ||
|
|
1c9ef1ea16 | ||
|
|
4d5de7aa36 | ||
|
|
8c6e6f75c6 | ||
|
|
9aebf20aad | ||
|
|
5baf4ce2db | ||
|
|
86882d5ac9 | ||
|
|
601c2a2fea | ||
|
|
141c2b1f98 | ||
|
|
96bd651f5c | ||
|
|
f91f59f1f8 | ||
|
|
e81d442b8b | ||
|
|
a8075158ba | ||
|
|
36325a0946 | ||
|
|
c8f552f389 | ||
|
|
b27d6323e7 | ||
|
|
a17711429c | ||
|
|
31cbe85588 | ||
|
|
4172ba357e | ||
|
|
b6741241f6 | ||
|
|
d909d82c33 | ||
|
|
8d4d823537 | ||
|
|
ec221ed820 | ||
|
|
72fd785855 | ||
|
|
45f3397ca9 | ||
|
|
703bf5f1d8 | ||
|
|
2534740cda | ||
|
|
85ce43be12 | ||
|
|
3df37b1102 | ||
|
|
4863f607b8 | ||
|
|
7b1b08c997 | ||
|
|
e3f3a7536d | ||
|
|
210ed1f8af | ||
|
|
dd854b5478 | ||
|
|
c739e545f2 | ||
|
|
d2a5b1e607 | ||
|
|
6ffa90f064 | ||
|
|
3de5740790 | ||
|
|
146cffc8d9 | ||
|
|
cb084e116e | ||
|
|
bc590c9d81 | ||
|
|
842987e891 | ||
|
|
c4769c4f1a | ||
|
|
4a98df4740 | ||
|
|
fbe001f559 | ||
|
|
b15b70860b | ||
|
|
01fe58172c | ||
|
|
a09708bb30 | ||
|
|
b465871bd7 | ||
|
|
b59cf5d700 | ||
|
|
06537a8815 | ||
|
|
46a0c982e1 | ||
|
|
87c7f37bb1 | ||
|
|
a54b97d04b | ||
|
|
2c62c876fa | ||
|
|
a0512ef044 | ||
|
|
ddbc1c27e4 | ||
|
|
2f49d03953 | ||
|
|
3350e01eca | ||
|
|
eb8cd8ba62 | ||
|
|
dbac73048f | ||
|
|
c9eddb2cbb | ||
|
|
8f34fc24fb | ||
|
|
f4509d6f0d | ||
|
|
0d79e316e3 | ||
|
|
12b0c29c01 | ||
|
|
4dcb7cad6d | ||
|
|
e2beabe2d3 | ||
|
|
34caf07d8c | ||
|
|
ab1c8b0f71 | ||
|
|
b2d62ec807 | ||
|
|
72d1fccacf | ||
|
|
241edb946b | ||
|
|
e855f87011 | ||
|
|
792f248c38 | ||
|
|
7cf33bc0cd | ||
|
|
4d4f4abcfc | ||
|
|
5ca07e574a | ||
|
|
02c9d0aa5e | ||
|
|
69511b6d1f | ||
|
|
8e111aa7e4 | ||
|
|
f846ec0825 | ||
|
|
1d53f658a7 | ||
|
|
303d3f0f46 | ||
|
|
35bd5fa414 | ||
|
|
c2f6fda5c8 | ||
|
|
83eea856e1 | ||
|
|
da5d7bc95e | ||
|
|
1438c16558 | ||
|
|
0968b25968 | ||
|
|
b364713268 | ||
|
|
ff7aecadb2 | ||
|
|
02d0d024d6 | ||
|
|
471d00b534 | ||
|
|
0cf46b3d5d | ||
|
|
bc4bf03a64 | ||
|
|
77d51dd27d | ||
|
|
474a8a7e3c | ||
|
|
ae7004f5d8 | ||
|
|
944e1c3c7f | ||
|
|
83975c8a98 | ||
|
|
6f748cbe12 | ||
|
|
7ca910e35a | ||
|
|
3f94bba49e | ||
|
|
301d587535 | ||
|
|
7b15dbbea3 | ||
|
|
e0d125906b | ||
|
|
d5ab22c7f3 | ||
|
|
000fa24b2f | ||
|
|
b1582e5a1f | ||
|
|
4ba2b1ca1f | ||
|
|
7ec41006f7 | ||
|
|
25ac827865 | ||
|
|
a6710c3e2e | ||
|
|
6539d67087 | ||
|
|
5781f17858 | ||
|
|
3ceba131f1 | ||
|
|
f0efd36674 | ||
|
|
d78653a76f | ||
|
|
b86546a178 | ||
|
|
465a55a77e | ||
|
|
1b023f859b | ||
|
|
9edb680114 | ||
|
|
a47876aca8 | ||
|
|
ad182d4170 | ||
|
|
2a4d6d085c | ||
|
|
7605ebf48d | ||
|
|
9f4282e37b | ||
|
|
81741974bb | ||
|
|
a2722b98de | ||
|
|
a012cead98 | ||
|
|
3513192907 | ||
|
|
99ee9a5ad2 | ||
|
|
cb9b41baf6 | ||
|
|
effabf95fd | ||
|
|
8615f29132 | ||
|
|
dc94b60121 | ||
|
|
8c5672d5fb | ||
|
|
75a9df969b | ||
|
|
c268306bd6 | ||
|
|
e2342a75dd | ||
|
|
37323f9170 | ||
|
|
aa48dbbf86 | ||
|
|
ae54c6ebea | ||
|
|
8384b9f88b | ||
|
|
ea884f1583 | ||
|
|
c506865d7b | ||
|
|
920d7db5f6 | ||
|
|
35f93122e5 | ||
|
|
ed04e21861 | ||
|
|
c25643525a | ||
|
|
f031d761b3 | ||
|
|
f3fa371ef9 | ||
|
|
0d09f4c11f | ||
|
|
dd4f1de695 | ||
|
|
269c7da0ac | ||
|
|
0ed453fe1d | ||
|
|
f2bcfca8e8 | ||
|
|
d8324dacd4 | ||
|
|
544c1df366 | ||
|
|
63a969e5df | ||
|
|
552a6e33a6 | ||
|
|
3533d2356c | ||
|
|
120f1e2018 | ||
|
|
c92c1d5b4a | ||
|
|
2fd686a5e6 | ||
|
|
aa185f11d7 | ||
|
|
7fab36ff3e | ||
|
|
cc609bbb07 | ||
|
|
9c74f92147 | ||
|
|
3b113e4914 | ||
|
|
33d50aed67 | ||
|
|
a9c2d57bc5 | ||
|
|
3d2693796d | ||
|
|
70a937bcaa | ||
|
|
3e50c95c91 | ||
|
|
a0f9b78162 | ||
|
|
d507429b9b | ||
|
|
634d187c11 | ||
|
|
11f7041df0 | ||
|
|
2956872970 | ||
|
|
c8b0df6ff9 | ||
|
|
16952e13e4 | ||
|
|
e9fba367c0 | ||
|
|
db06516f97 | ||
|
|
ff8e96e293 | ||
|
|
454a5a0e11 | ||
|
|
7b456e3d98 | ||
|
|
0514528a4c | ||
|
|
bdcdae791e | ||
|
|
4d59d05ad6 | ||
|
|
00928fdb88 | ||
|
|
4a34b29b46 | ||
|
|
f590ad704e | ||
|
|
99a1bb1dc9 | ||
|
|
5fe5ebc0a2 | ||
|
|
aff30961bd | ||
|
|
64d211b333 | ||
|
|
606d3b2282 | ||
|
|
132c6aa2e8 | ||
|
|
4a140c110f | ||
|
|
1c8d052544 | ||
|
|
3199b77ae5 | ||
|
|
f0e1f18379 | ||
|
|
492e2a707a | ||
|
|
7551ca3484 | ||
|
|
8f3fa2e7f2 | ||
|
|
936619ce12 | ||
|
|
d6a42f8ef3 | ||
|
|
bf43e405ff | ||
|
|
0c7c33304d | ||
|
|
f9b1d8a9e1 | ||
|
|
7b4fe4bd5c | ||
|
|
6ba3111ada | ||
|
|
258ccf89e0 | ||
|
|
3e193bb675 | ||
|
|
69b96eee96 | ||
|
|
e1ba2c4ebb | ||
|
|
dfce9dae46 | ||
|
|
f4fca865bb | ||
|
|
08fa0725d3 | ||
|
|
b92b769f7b | ||
|
|
726517a8b6 | ||
|
|
51f54c6edd | ||
|
|
f7f260777a | ||
|
|
dd9ac2819d | ||
|
|
1a7bb8498e | ||
|
|
fb2d26153d | ||
|
|
a640c0e62c | ||
|
|
784322db6f | ||
|
|
355a63c46f | ||
|
|
155eb75b82 | ||
|
|
64d4ef0c03 | ||
|
|
edb15db8fa | ||
|
|
883f140b41 | ||
|
|
90a6765e8a | ||
|
|
b8ede978c2 | ||
|
|
c1091acc6b | ||
|
|
0034940082 | ||
|
|
4d35fad772 | ||
|
|
d86c3b2eb8 | ||
|
|
b3556813a9 | ||
|
|
a94dcf1949 | ||
|
|
7fa07ae5ea | ||
|
|
cf60d31b75 | ||
|
|
e1b4e1ea6a | ||
|
|
4f6b0aa04d | ||
|
|
8b5e2780c7 | ||
|
|
0dd730d8de | ||
|
|
0f865c7afb | ||
|
|
688f24a409 | ||
|
|
dcacd279b3 | ||
|
|
37d0b755af | ||
|
|
26672ce34b | ||
|
|
d1be49f474 | ||
|
|
87072a4edc | ||
|
|
5f8679e5d2 | ||
|
|
1a7cd37741 | ||
|
|
ed181c1dcd | ||
|
|
4d839e3662 | ||
|
|
156b30756d | ||
|
|
6326870525 | ||
|
|
a360c9349a | ||
|
|
9e6276e0fa | ||
|
|
2bbc7a8999 | ||
|
|
fc3e05434f | ||
|
|
388764e9c8 | ||
|
|
3fb8323dd0 | ||
|
|
2327c6baf8 | ||
|
|
144faf5b21 | ||
|
|
dc6dd4bb11 | ||
|
|
84482e8998 | ||
|
|
072ece7a3d | ||
|
|
22d557ed01 | ||
|
|
b454d54a99 | ||
|
|
341119386a | ||
|
|
1575d7319c | ||
|
|
950e92d7c7 | ||
|
|
0e98f52580 | ||
|
|
caf2703102 | ||
|
|
b79923de3d | ||
|
|
ee6d171ef6 | ||
|
|
1c30198347 | ||
|
|
4ed9199e1c | ||
|
|
2027cb064c | ||
|
|
a00eec69b7 | ||
|
|
3aa1c94c6a | ||
|
|
84c07c4115 | ||
|
|
6398164d42 | ||
|
|
fa42ad5a03 | ||
|
|
22553eb1f9 | ||
|
|
cb599eea0b | ||
|
|
f73413e7c0 | ||
|
|
5b752d3f87 | ||
|
|
4f37d3b597 | ||
|
|
21b497d2d4 | ||
|
|
3bfc0c266d | ||
|
|
f252fa3a7a | ||
|
|
66266fb426 | ||
|
|
225e2d9d1a | ||
|
|
3b4aa75890 | ||
|
|
0c1917d56e | ||
|
|
35850082d5 | ||
|
|
d24b719a93 | ||
|
|
a0c33a439b | ||
|
|
6bbd2c521a | ||
|
|
7575c35b02 | ||
|
|
f78a8df275 | ||
|
|
8462dde374 | ||
|
|
075e8f2897 | ||
|
|
204989b909 | ||
|
|
c023e3b4f2 | ||
|
|
e7c4d6f132 | ||
|
|
9d0cf33a45 | ||
|
|
34c5c71d77 | ||
|
|
03bede4067 | ||
|
|
f624f85698 | ||
|
|
985fa4b53c | ||
|
|
6c6c08d188 | ||
|
|
fe2e6522ed | ||
|
|
ea930ad917 | ||
|
|
f9d1d9961b | ||
|
|
efda204001 | ||
|
|
0c5882af8e | ||
|
|
56b75e38e2 | ||
|
|
b899dde5fe | ||
|
|
50e0634a94 | ||
|
|
28f2e1e881 | ||
|
|
e8e89f022b | ||
|
|
5d8cc2b3c4 | ||
|
|
3b261d9989 | ||
|
|
4660ab927c | ||
|
|
c283f6eac4 | ||
|
|
d7d930c4fa | ||
|
|
b8a16fb087 | ||
|
|
74dac668fb | ||
|
|
a1ace12042 | ||
|
|
90a3a16cc2 | ||
|
|
0882b71a9f | ||
|
|
cdfa15c1f6 | ||
|
|
a314c26b89 | ||
|
|
5c2024bab4 | ||
|
|
5c8f2a43cb | ||
|
|
055be2aa2a | ||
|
|
973f61e428 | ||
|
|
3a13071edb | ||
|
|
68e6621e7e | ||
|
|
d808486cd0 | ||
|
|
128f8eda98 | ||
|
|
4a5961d656 | ||
|
|
8adc708e4d | ||
|
|
2962b47939 | ||
|
|
fd22ea4153 | ||
|
|
9be68ac2f4 | ||
|
|
fc612c608f | ||
|
|
40c6d50d5e | ||
|
|
5d91a5bc09 | ||
|
|
2249e96496 | ||
|
|
5bee5dd1d1 | ||
|
|
0b1d8b9fd9 | ||
|
|
f3aecbe944 | ||
|
|
b9e5870a7f | ||
|
|
f872b995c3 | ||
|
|
74ecc39921 | ||
|
|
947802d5ed | ||
|
|
95fbd77a0e | ||
|
|
2618c6a978 | ||
|
|
717def2b1a | ||
|
|
a8e2876586 | ||
|
|
802b853b75 | ||
|
|
9bc0f46be9 | ||
|
|
73f27b65b1 | ||
|
|
28ef19a1dc | ||
|
|
7d9b735f8b | ||
|
|
e58cbaa6de | ||
|
|
48799d8ea1 | ||
|
|
c49f5309cd | ||
|
|
566e1da6e7 | ||
|
|
88b166d297 | ||
|
|
c2be58b0f2 | ||
|
|
3221702fc2 | ||
|
|
0d27de5ba7 | ||
|
|
3ade56c582 | ||
|
|
0028e5ec8f | ||
|
|
d1957680e8 | ||
|
|
1acb063b83 | ||
|
|
86ff9cebd2 | ||
|
|
93ea97b8dc | ||
|
|
ef8a5af94b | ||
|
|
4aa04c7cce | ||
|
|
d9c4116449 | ||
|
|
a10aa444bb | ||
|
|
aa4120433e | ||
|
|
79f42760b0 | ||
|
|
cbc8428e23 | ||
|
|
28507cc8b0 | ||
|
|
af863ae608 | ||
|
|
6a42bcccc7 | ||
|
|
09666e2dfa | ||
|
|
1b5c787b3a | ||
|
|
ff4c1b267e | ||
|
|
ec866fa1b8 | ||
|
|
3851b07294 | ||
|
|
e42802a2bd | ||
|
|
fb52dea6fa | ||
|
|
d072f474a4 | ||
|
|
6087dba91b | ||
|
|
7a4e01faab | ||
|
|
45aaf9b981 | ||
|
|
553935626e | ||
|
|
636b680c87 | ||
|
|
da36c6abd1 | ||
|
|
99f0335d36 | ||
|
|
94223738ec | ||
|
|
74bda196f0 | ||
|
|
365ae58c70 | ||
|
|
834253f0ae | ||
|
|
86c4c8f09e | ||
|
|
79cd18d310 | ||
|
|
fce99fbde3 | ||
|
|
917e308ece | ||
|
|
f74992a37a | ||
|
|
7c4a3fe7b4 | ||
|
|
e82d2a875c | ||
|
|
8b04be72ee | ||
|
|
6aebddb773 | ||
|
|
b78d389a06 | ||
|
|
b064032d8f | ||
|
|
ad0d1ede41 | ||
|
|
cc1f63b443 | ||
|
|
da1c382259 | ||
|
|
50cc7d983c | ||
|
|
7ad3d8c1c7 | ||
|
|
2829ddb085 | ||
|
|
d2ec4c359f | ||
|
|
84a32581b7 | ||
|
|
3fa50a8965 | ||
|
|
0b88a6f824 | ||
|
|
fbcef60f67 | ||
|
|
1d7e985593 | ||
|
|
ba788062c5 | ||
|
|
8a8cc8c423 | ||
|
|
fce9ca3824 | ||
|
|
d12a0f1ba4 | ||
|
|
dd46c43c73 | ||
|
|
b526d5a51a | ||
|
|
c209cc82af | ||
|
|
fc439459cb | ||
|
|
a2cfe942c6 | ||
|
|
7a88e1f893 | ||
|
|
d91d41de79 | ||
|
|
aed8366206 | ||
|
|
6d99f45412 | ||
|
|
4a3291f4a6 | ||
|
|
9cdf1282da | ||
|
|
a5361eaad2 | ||
|
|
65480c5e9f | ||
|
|
75685d57d6 | ||
|
|
b6aa53483d | ||
|
|
e032ea43e2 | ||
|
|
ec42f47d9e | ||
|
|
5f1c89144c | ||
|
|
71c181fb2c | ||
|
|
b34792ff45 | ||
|
|
985e0beafd | ||
|
|
7c27a4d8fd | ||
|
|
bbe7cd2099 | ||
|
|
862afc498e | ||
|
|
f023f8e8f1 | ||
|
|
987427b893 | ||
|
|
03e79db0ba | ||
|
|
f7e8ceaa3f | ||
|
|
07bb8e0475 | ||
|
|
306951ebee | ||
|
|
3684174b67 | ||
|
|
c2e95876d0 | ||
|
|
0c7421f36c | ||
|
|
d3bf885042 | ||
|
|
4bed271258 | ||
|
|
bbfc451956 | ||
|
|
539ac56548 | ||
|
|
b00223a351 | ||
|
|
c81a1d015e | ||
|
|
543a683a5f | ||
|
|
89eddde7d4 | ||
|
|
67b4aab39b | ||
|
|
68aaa878d3 | ||
|
|
4629a9f96c | ||
|
|
e7c123fcf8 | ||
|
|
1456756ea7 | ||
|
|
5d01bc8f03 | ||
|
|
7d0cbefe17 | ||
|
|
1794b1ac4b | ||
|
|
62f4116cc8 | ||
|
|
550a75460f | ||
|
|
d226146213 | ||
|
|
b9d4a4116a | ||
|
|
becd958c5e | ||
|
|
d1322990b3 | ||
|
|
325983152a | ||
|
|
d847f7f7b0 | ||
|
|
0234c09495 | ||
|
|
3b2614631d | ||
|
|
432d078212 | ||
|
|
1ffcaea80f | ||
|
|
8ef53b3c4f | ||
|
|
b0bd063ed6 | ||
|
|
99cfbb07b9 | ||
|
|
7e3a73671b | ||
|
|
523cb55889 | ||
|
|
1121b5e6dd | ||
|
|
79b7ddda8d | ||
|
|
d6c49ea142 | ||
|
|
a521e34f49 | ||
|
|
c6aa690eee | ||
|
|
5163cbd26e | ||
|
|
f98ee30491 | ||
|
|
2a128bd69c | ||
|
|
198f0f9c31 | ||
|
|
b7713815a4 | ||
|
|
47592b4e6d | ||
|
|
802cea001c | ||
|
|
c5cf08365f | ||
|
|
7af77296ed | ||
|
|
6ccef6d1a5 | ||
|
|
4fd84cb811 | ||
|
|
7cd2ecfcde | ||
|
|
3569e1d889 | ||
|
|
89dfc3c9f2 | ||
|
|
a7cf766c27 | ||
|
|
63983f6c26 | ||
|
|
183746ba4d | ||
|
|
4d77096589 | ||
|
|
1dc6d2a6bf | ||
|
|
7027c34941 | ||
|
|
2ff6f4cb4e | ||
|
|
d7d9365a63 | ||
|
|
7cc92a533d | ||
|
|
9e5e5e654d | ||
|
|
856b5b5b56 | ||
|
|
3c079397cd | ||
|
|
3f972856ad | ||
|
|
74630504ae | ||
|
|
e67ed69cc9 | ||
|
|
9e442ef854 | ||
|
|
7f3be8c1ac | ||
|
|
0b86620a4e | ||
|
|
b85e6367b4 | ||
|
|
40c4101bec | ||
|
|
aba9129460 | ||
|
|
9cf7652ae3 | ||
|
|
9f604dfc2e | ||
|
|
12404348e5 | ||
|
|
e0314de4f6 | ||
|
|
8b3f8812de | ||
|
|
426ae35a44 | ||
|
|
e812b13ef7 | ||
|
|
d73f844b10 | ||
|
|
76bad0be68 | ||
|
|
560ca9092c | ||
|
|
3702467178 | ||
|
|
f6eefdb336 | ||
|
|
33f5772ecd | ||
|
|
1d7e24537f | ||
|
|
02b4fee507 | ||
|
|
b387ab069c | ||
|
|
6cadf85b6a | ||
|
|
4a613ff145 | ||
|
|
68a17f0578 | ||
|
|
8b7a3c4bb1 | ||
|
|
a992a44f40 | ||
|
|
5e5c63d109 | ||
|
|
cca9ac0e56 | ||
|
|
7a2fe748ea | ||
|
|
2d95cdfd01 | ||
|
|
1c23394415 | ||
|
|
41c31976d6 | ||
|
|
eb7172aaba | ||
|
|
ac1427d125 | ||
|
|
755d410430 | ||
|
|
78b2f09e76 | ||
|
|
4858ad586b | ||
|
|
daa3cab313 | ||
|
|
ba30742630 | ||
|
|
ef389b7ed0 | ||
|
|
104bdf070c | ||
|
|
aea98cb6e4 | ||
|
|
0dd1eb443f | ||
|
|
77e3b95d6b | ||
|
|
4368f5bc4d | ||
|
|
699ccb90b2 | ||
|
|
b6a9adce28 | ||
|
|
7b11670d55 | ||
|
|
c6fd3b19f9 | ||
|
|
aae089404d | ||
|
|
fd2ee3f71f | ||
|
|
60b878d53c | ||
|
|
fd4d1d7910 | ||
|
|
c0a578cdd5 | ||
|
|
ff82c2d761 | ||
|
|
743c423ba5 | ||
|
|
fc55d5fc3b | ||
|
|
a8971166b4 | ||
|
|
ce5f792500 | ||
|
|
a6ed760304 | ||
|
|
d475b114ee | ||
|
|
6e05679b3e | ||
|
|
241897dbd9 | ||
|
|
f37c5353d0 | ||
|
|
5d187589b4 | ||
|
|
6d683503cd | ||
|
|
d77addb416 | ||
|
|
49aa9c2dd9 | ||
|
|
b849686564 | ||
|
|
57a8661b67 | ||
|
|
e336cee557 | ||
|
|
cf0afbb545 | ||
|
|
db43e26b8a | ||
|
|
9bd84ae3e8 | ||
|
|
613c4c744a | ||
|
|
4ff28bc323 | ||
|
|
8f1d89cb95 | ||
|
|
34666c7994 | ||
|
|
ac135d1907 | ||
|
|
f29f626a2f | ||
|
|
bc72eaeb0f | ||
|
|
d373b806ed | ||
|
|
64067d9ba5 | ||
|
|
4d20f4747a | ||
|
|
72ef321bfb | ||
|
|
ec67222855 | ||
|
|
a0132b8c67 | ||
|
|
71349d7404 | ||
|
|
28af11ca2e | ||
|
|
9e20eac7ab | ||
|
|
772ec27ea1 | ||
|
|
5421452429 | ||
|
|
900e72d1fc | ||
|
|
b6289a901c | ||
|
|
4d6859db43 | ||
|
|
aac3db90d3 | ||
|
|
1048d38b4d | ||
|
|
9ded4d4b13 | ||
|
|
d0e969dcf6 | ||
|
|
783f87f22d | ||
|
|
402d682d6e | ||
|
|
d6500d2227 | ||
|
|
d0b5f32e2d | ||
|
|
7454179313 | ||
|
|
194cf672ee | ||
|
|
5a3ef0fe69 | ||
|
|
7f62b76691 | ||
|
|
75ad35e057 | ||
|
|
44c66d5c26 | ||
|
|
3dcfd196a1 | ||
|
|
b7d8f82a3d | ||
|
|
dbf1188b12 | ||
|
|
6539e69fb0 | ||
|
|
07b00bc710 | ||
|
|
d090703d32 | ||
|
|
b08ba23889 | ||
|
|
b8fda63ef3 | ||
|
|
316f42cf03 | ||
|
|
7b9c31b770 | ||
|
|
67347048b5 | ||
|
|
73cc91fa0d | ||
|
|
a1ed14e8cb | ||
|
|
8321d3feee | ||
|
|
46283fec0e | ||
|
|
65244bc43d | ||
|
|
ed0bd1a2b5 | ||
|
|
7def1b487c | ||
|
|
2f7f7f4f6b | ||
|
|
1a3ad7d701 | ||
|
|
5242104d87 | ||
|
|
a369d61024 | ||
|
|
df2611f0b7 | ||
|
|
663a352586 | ||
|
|
412768c57e | ||
|
|
5bb91bb1eb | ||
|
|
d9d433f47c | ||
|
|
c3701ad903 | ||
|
|
6ed4c92c94 | ||
|
|
fdeed5c257 | ||
|
|
aa1ba08f70 | ||
|
|
a62413ed0b | ||
|
|
2057ced33d | ||
|
|
f8d9cb8855 | ||
|
|
09b1029030 | ||
|
|
2c778ce5ec | ||
|
|
029a7df71a | ||
|
|
8a3d7a9da5 | ||
|
|
69f0c63831 | ||
|
|
523fd89f97 | ||
|
|
d9fc4b3c05 | ||
|
|
8c36021746 | ||
|
|
1b82b3020f | ||
|
|
eeeecb982c | ||
|
|
70a1fe36c7 | ||
|
|
622c40f188 | ||
|
|
3b25d2d535 | ||
|
|
017bf78d4c | ||
|
|
775bf46b8e | ||
|
|
9c05fdc11e | ||
|
|
58ae91afbe | ||
|
|
acb7f3aa17 | ||
|
|
3c5d79ce77 | ||
|
|
812342b4da | ||
|
|
c6fe0a1bed | ||
|
|
00a123b150 | ||
|
|
56d96ddd84 | ||
|
|
5a6819757b | ||
|
|
3618c136f7 | ||
|
|
4312472534 | ||
|
|
bdf3319204 | ||
|
|
53672d3ace | ||
|
|
fac00a81d3 | ||
|
|
c0be96e797 | ||
|
|
5979b17891 | ||
|
|
5e5d0c8051 | ||
|
|
0a7c0c573c | ||
|
|
a15978d1fb | ||
|
|
7fa62375c6 | ||
|
|
182e66daff | ||
|
|
640da6c1be | ||
|
|
3d2129c1d4 | ||
|
|
5e72b945c4 | ||
|
|
3a800ec9ca | ||
|
|
10391c48c7 | ||
|
|
00cf0c73e3 | ||
|
|
f53a81099e | ||
|
|
f9a8f9b1c2 | ||
|
|
27ed49fcd9 | ||
|
|
ad7660e4d6 | ||
|
|
7eaa8483b8 | ||
|
|
87238bdb45 | ||
|
|
aafc475843 | ||
|
|
78d5c224a8 | ||
|
|
b875d5fc25 | ||
|
|
df5c34c626 | ||
|
|
0aeb0fbf21 | ||
|
|
d5045731ad | ||
|
|
1c0e0cc666 | ||
|
|
b9a3d18b56 | ||
|
|
76aaf3824d | ||
|
|
756431b581 | ||
|
|
c0cc236c40 | ||
|
|
1a42f115fb | ||
|
|
fbdd65bc00 | ||
|
|
306956839a | ||
|
|
d4714989b4 | ||
|
|
1d1e244f03 | ||
|
|
f055234463 | ||
|
|
6450069d8a | ||
|
|
c8bcfc3b6d | ||
|
|
3ba1c76414 | ||
|
|
7f25f5ff5d | ||
|
|
15391a6f91 | ||
|
|
b81fd46929 | ||
|
|
fce77cbd5f | ||
|
|
163d1f9f43 | ||
|
|
d2483283f5 | ||
|
|
f536b0cd74 | ||
|
|
445c583a5b | ||
|
|
a5122d31a4 | ||
|
|
0135c63b88 | ||
|
|
395112fc40 | ||
|
|
323cd95bff | ||
|
|
4bb05252aa | ||
|
|
8797ee56a8 | ||
|
|
6eed71656d | ||
|
|
ee5bca7db5 | ||
|
|
082143ed15 | ||
|
|
377c51f365 | ||
|
|
cd3b40399c | ||
|
|
10ff5dda7d | ||
|
|
403317fd29 | ||
|
|
351fac9052 | ||
|
|
2d6d948e80 | ||
|
|
593f222760 | ||
|
|
0fdba1bf64 | ||
|
|
e4fe056586 | ||
|
|
0bb04b2f8e | ||
|
|
264e26cb90 | ||
|
|
0366c42cf3 | ||
|
|
7d883913bd | ||
|
|
57ad9f56be | ||
|
|
082885a5eb | ||
|
|
945cd68672 | ||
|
|
3194f1d23e | ||
|
|
154f016a96 | ||
|
|
0362399ba7 | ||
|
|
7dc8bbcd9a | ||
|
|
5225bca007 | ||
|
|
9b08c6e5ff | ||
|
|
7f5b84e6e7 | ||
|
|
2f07b8420b | ||
|
|
6fdcde856b | ||
|
|
004bd1e751 | ||
|
|
aa65197749 | ||
|
|
7703e2fb0f | ||
|
|
433f3ace9f | ||
|
|
87f7d71c94 | ||
|
|
ca899dd506 | ||
|
|
25712a850e | ||
|
|
9329a7885d | ||
|
|
c04bf8b351 | ||
|
|
d5cf684215 | ||
|
|
4f02eca7c9 | ||
|
|
d7b555fca6 | ||
|
|
9d4f912808 | ||
|
|
26825e358f | ||
|
|
5d58e9e4da | ||
|
|
72e35557a4 | ||
|
|
0ff2db1881 | ||
|
|
4cbd7af3d0 | ||
|
|
5b5939855f | ||
|
|
91e93dea2b | ||
|
|
37b85057c3 | ||
|
|
3cec8b281c | ||
|
|
86f1770af3 | ||
|
|
707c0b1c85 | ||
|
|
6ba56a133e | ||
|
|
1e855a0ed0 | ||
|
|
716bf097bf | ||
|
|
8ed121f71e | ||
|
|
e5333cb2e9 | ||
|
|
a7c6768449 | ||
|
|
e1c7bb18a9 | ||
|
|
12b2dce779 |
24
.github/workflows/check-does-build-pr.yml
vendored
Normal file
24
.github/workflows/check-does-build-pr.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: check-does-build-pr
|
||||
|
||||
on: [ pull_request ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: true
|
||||
|
||||
- name: Gradle build
|
||||
run: ./gradlew build
|
||||
32
.github/workflows/check-does-build.yml
vendored
Normal file
32
.github/workflows/check-does-build.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: check-does-build
|
||||
|
||||
on: [ push ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
- name: Loom Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/.gradle/loom-cache"
|
||||
key: "${{ runner.os }}-gradle-${{ hashFiles('**/libs.versions.*', '**/*.gradle*', '**/gradle-wrapper.properties') }}"
|
||||
restore-keys: "${{ runner.os }}-gradle-"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: false
|
||||
cache-cleanup: never
|
||||
|
||||
- name: Gradle build
|
||||
run: ./gradlew -I init.gradle build
|
||||
31
.github/workflows/manual-artifact.yml
vendored
Normal file
31
.github/workflows/manual-artifact.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: manual-artifact
|
||||
|
||||
on: [ workflow_dispatch ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: false
|
||||
cache-cleanup: always
|
||||
|
||||
- name: Gradle build
|
||||
run: ./gradlew build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: voxy-artifacts
|
||||
path: build/libs/*.jar
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
# Project exclude paths
|
||||
/.gradle/
|
||||
/.idea/
|
||||
/build/
|
||||
/run/
|
||||
/out/
|
||||
/logs/
|
||||
|
||||
5
LICENSE.md
Normal file
5
LICENSE.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copyright 2025 MCRcortex
|
||||
All rights reserved.
|
||||
Do not redistribute.
|
||||
|
||||
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 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 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.
|
||||
237
build.gradle
237
build.gradle
@@ -1,11 +1,17 @@
|
||||
plugins {
|
||||
id 'fabric-loom' version "1.7.1"
|
||||
id 'fabric-loom' version "1.14-SNAPSHOT"
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
def isInGHA = System.getenv("GITHUB_ACTIONS") == "true"
|
||||
|
||||
version = project.mod_version
|
||||
group = project.maven_group
|
||||
|
||||
base {
|
||||
archivesName = project.archives_base_name
|
||||
}
|
||||
|
||||
repositories {
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
@@ -18,15 +24,72 @@ repositories {
|
||||
includeGroup "maven.modrinth"
|
||||
}
|
||||
}
|
||||
maven { url "https://maven.shedaniel.me/" }
|
||||
maven { url "https://maven.terraformersmc.com/releases/" }
|
||||
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
maven {
|
||||
name "caffeinemcRepository"
|
||||
url "https://maven.caffeinemc.net/snapshots"
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeGroup "net.caffeinemc"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
maven { url = "https://maven.shedaniel.me/" }
|
||||
maven { url = "https://maven.terraformersmc.com/releases/" }
|
||||
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
ivy {
|
||||
name = "github"
|
||||
url = "https://github.com/"
|
||||
patternLayout {
|
||||
artifact '/[organisation]/[module]/releases/download/[revision]/[module]-[revision]-[classifier].[ext]'
|
||||
}
|
||||
metadataSources {
|
||||
artifact()
|
||||
}
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeModuleByRegex("[^\\.]+", "nvidium")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def gitCommitHash = { ->
|
||||
try {
|
||||
ExecOutput result = providers.exec {
|
||||
commandLine = ['git', 'rev-parse', '--short', 'HEAD']
|
||||
ignoreExitValue = true
|
||||
}
|
||||
|
||||
if (result.getResult().get().getExitValue() != 0) {
|
||||
return "<UnknownCommit>";
|
||||
} else {
|
||||
return result.standardOutput.asText.get().strip();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace()
|
||||
return "<UnknownCommit>";
|
||||
}
|
||||
}
|
||||
|
||||
def buildtime = {System.currentTimeSeconds()}
|
||||
|
||||
processResources {
|
||||
inputs.property "version", project.version
|
||||
archivesBaseName = "voxy"
|
||||
def time = buildtime()
|
||||
def hash = gitCommitHash()
|
||||
def version = project.version
|
||||
|
||||
inputs.properties("version": version, "commit": hash, "buildtime": time)
|
||||
filesMatching("fabric.mod.json") {
|
||||
expand "version": project.version
|
||||
expand "version": version, "commit": hash, "buildtime": time
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,56 +97,70 @@ loom {
|
||||
accessWidenerPath = file("src/main/resources/voxy.accesswidener")
|
||||
}
|
||||
|
||||
def modRuntimeOnlyMsk = {arg->}
|
||||
if (!isInGHA) {
|
||||
modRuntimeOnlyMsk = { arg ->
|
||||
dependencies {
|
||||
modRuntimeOnly(arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// To change the versions see the gradle.properties file
|
||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
||||
mappings loom.officialMojangMappings()
|
||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||
|
||||
modImplementation(fabricApi.module("fabric-api-base", project.fabric_version))
|
||||
modImplementation(fabricApi.module("fabric-rendering-fluids-v1", project.fabric_version))
|
||||
modImplementation(fabricApi.module("fabric-resource-loader-v0", project.fabric_version))
|
||||
modImplementation(fabricApi.module("fabric-command-api-v2", project.fabric_version))
|
||||
modImplementation("net.fabricmc.fabric-api:fabric-rendering-data-attachment-v1:0.3.38+73761d2e9a")
|
||||
|
||||
modRuntimeOnlyMsk(fabricApi.module("fabric-api-base", project.fabric_version))
|
||||
modRuntimeOnlyMsk(fabricApi.module("fabric-rendering-fluids-v1", project.fabric_version))
|
||||
modRuntimeOnlyMsk(fabricApi.module("fabric-resource-loader-v0", project.fabric_version))
|
||||
modRuntimeOnlyMsk(fabricApi.module("fabric-command-api-v2", project.fabric_version))
|
||||
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||
|
||||
//TODO: this is to eventually not need sodium installed as atm its just used for parsing shaders
|
||||
modRuntimeOnly "maven.modrinth:sodium:mc1.21-0.5.9"
|
||||
modCompileOnly "maven.modrinth:sodium:mc1.21-0.5.9"
|
||||
if (true) {
|
||||
modImplementation "maven.modrinth:sodium:mc1.21.11-0.8.2-fabric"
|
||||
} else {
|
||||
modImplementation "net.caffeinemc:sodium-fabric:0.8.2-SNAPSHOT+mc1.21.11+"
|
||||
}
|
||||
|
||||
//modRuntimeOnly "maven.modrinth:nvidium:0.2.6-beta"
|
||||
modCompileOnly "maven.modrinth:nvidium:0.2.8-beta"
|
||||
modImplementation("maven.modrinth:lithium:mc1.21.11-0.21.0-fabric")
|
||||
|
||||
//modRuntimeOnlyMsk "drouarb:nvidium:0.4.1-beta4:1.21.6@jar"
|
||||
modCompileOnly "drouarb:nvidium:0.4.1-beta4:1.21.6@jar"
|
||||
|
||||
modCompileOnly("maven.modrinth:modmenu:17.0.0-alpha.1")
|
||||
modRuntimeOnlyMsk("maven.modrinth:modmenu:17.0.0-alpha.1")
|
||||
|
||||
modCompileOnly("maven.modrinth:iris:1.10.4+1.21.11-fabric")
|
||||
modRuntimeOnlyMsk("maven.modrinth:iris:1.10.4+1.21.11-fabric")
|
||||
|
||||
//modCompileOnly("maven.modrinth:starlight:1.1.3+1.20.4")
|
||||
|
||||
modImplementation("maven.modrinth:cloth-config:13.0.121+fabric")
|
||||
modImplementation("maven.modrinth:modmenu:11.0.1")
|
||||
modCompileOnly("maven.modrinth:iris:1.7.3+1.21")
|
||||
//modRuntimeOnly("maven.modrinth:iris:1.6.17+1.20.4")
|
||||
modCompileOnly("maven.modrinth:starlight:1.1.3+1.20.4")
|
||||
//modCompileOnly("maven.modrinth:immersiveportals:v5.1.7-mc1.20.4")
|
||||
modCompileOnly("maven.modrinth:vivecraft:1.20.4-1.1.6-fabric")
|
||||
|
||||
modCompileOnly("maven.modrinth:chunky:1.4.16-fabric")
|
||||
modRuntimeOnly("maven.modrinth:chunky:1.4.16-fabric")
|
||||
modCompileOnly("maven.modrinth:chunky:1.4.54-fabric")
|
||||
//modRuntimeOnlyMsk("maven.modrinth:chunky:1.4.40-fabric")
|
||||
|
||||
modRuntimeOnly("maven.modrinth:spark:1.10.73-fabric")
|
||||
modRuntimeOnly("maven.modrinth:fabric-permissions-api:0.3.1")
|
||||
modRuntimeOnlyMsk("maven.modrinth:spark:1.10.152-fabric")
|
||||
modRuntimeOnlyMsk("maven.modrinth:fabric-permissions-api:0.3.3")
|
||||
//modRuntimeOnly("maven.modrinth:nsight-loader:1.2.0")
|
||||
|
||||
modImplementation('io.github.douira:glsl-transformer:2.0.1')
|
||||
//modImplementation('io.github.douira:glsl-transformer:2.0.1')
|
||||
|
||||
modCompileOnly("maven.modrinth:vivecraft:1.21.9-1.3.2-fabric")
|
||||
|
||||
modCompileOnly("maven.modrinth:flashback:rNCr1Rbs")
|
||||
}
|
||||
|
||||
|
||||
def targetJavaVersion = 21
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
// ensure that the encoding is set to UTF-8, no matter what the system default is
|
||||
// this fixes some edge cases with special characters not displaying correctly
|
||||
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
|
||||
// If Javadoc is generated, this must be specified in that task too.
|
||||
it.options.encoding = "UTF-8"
|
||||
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
|
||||
it.options.release = targetJavaVersion
|
||||
}
|
||||
it.options.deprecation = true
|
||||
}
|
||||
|
||||
java {
|
||||
@@ -91,16 +168,67 @@ java {
|
||||
if (JavaVersion.current() < javaVersion) {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
||||
}
|
||||
archivesBaseName = project.archives_base_name
|
||||
|
||||
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
|
||||
// if it is present.
|
||||
// If you remove this line, sources will not be generated.
|
||||
withSourcesJar()
|
||||
|
||||
|
||||
//withSourcesJar()
|
||||
}
|
||||
|
||||
jar {
|
||||
from("LICENSE") {
|
||||
rename { "${it}_${project.archivesBaseName}"}
|
||||
rename { "${it}_${project.archives_base_name}"}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
tasks.register('makeExcludedRocksDB', Zip) {
|
||||
archiveExtension.set("jar")
|
||||
entryCompression = ZipEntryCompression.STORED
|
||||
destinationDirectory.set temporaryDir
|
||||
|
||||
dependsOn configurations.includeInternal
|
||||
|
||||
def files = configurations.includeInternal.incoming.getArtifacts().getArtifactFiles().filter {
|
||||
it.name.startsWith('rocksdb')
|
||||
}
|
||||
|
||||
from {->zipTree(files.first())}
|
||||
archiveFileName.set(providers.provider{files.first().name})
|
||||
|
||||
exclude {
|
||||
def file = it.name
|
||||
if (file.endsWith(".jnilib")) {
|
||||
return true
|
||||
}
|
||||
if (!file.endsWith(".so")) {
|
||||
return false
|
||||
}
|
||||
return ["osx", "linux32", "musl", "s390x", "riscv64", "ppc64le", "aarch64"].any(file::contains)
|
||||
}
|
||||
}
|
||||
|
||||
processIncludeJars {
|
||||
outputs.cacheIf {true}
|
||||
|
||||
dependsOn makeExcludedRocksDB
|
||||
jars = jars.filter {!it.name.startsWith('rocksdb')}
|
||||
jars.from(makeExcludedRocksDB)
|
||||
}
|
||||
|
||||
remapJar {
|
||||
doFirst {
|
||||
delete fileTree(getDestinationDirectory().get())
|
||||
}
|
||||
|
||||
if (!isInGHA) {
|
||||
def hash = gitCommitHash();
|
||||
if (!hash.equals("<UnknownCommit>")) {
|
||||
archiveClassifier.set(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,22 +239,49 @@ repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
|
||||
|
||||
implementation "org.lwjgl:lwjgl"
|
||||
include(implementation "org.lwjgl:lwjgl-lmdb")
|
||||
include(implementation "org.lwjgl:lwjgl-zstd")
|
||||
//include(implementation "org.lwjgl:lwjgl-zstd")
|
||||
runtimeOnly "org.lwjgl:lwjgl:$lwjglVersion:natives-windows"
|
||||
runtimeOnly "org.lwjgl:lwjgl:$lwjglVersion:natives-linux"
|
||||
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-windows")
|
||||
include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-windows")
|
||||
//include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-windows")
|
||||
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-linux")
|
||||
include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-linux")
|
||||
//include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-linux")
|
||||
|
||||
include(implementation 'org.rocksdb:rocksdbjni:8.10.0')
|
||||
include(implementation 'redis.clients:jedis:5.1.0')
|
||||
include(implementation('org.rocksdb:rocksdbjni:10.2.1'))
|
||||
include(implementation 'org.apache.commons:commons-pool2:2.12.0')
|
||||
include(implementation 'org.lz4:lz4-java:1.8.0')
|
||||
include(implementation('org.tukaani:xz:1.10'))
|
||||
include(implementation 'com.github.luben:zstd-jni:1.5.5-1')
|
||||
|
||||
if (true) {
|
||||
if (!isInGHA) {
|
||||
minecraftRuntimeLibraries('org.xerial:sqlite-jdbc:3.49.1.0')
|
||||
}
|
||||
} else {
|
||||
include(implementation('org.xerial:sqlite-jdbc:3.49.1.0'))
|
||||
}
|
||||
//implementation 'org.rocksdb:rocksdbjni:8.10.0'
|
||||
//implementation 'redis.clients:jedis:5.1.0'
|
||||
}
|
||||
|
||||
if (!isInGHA) {
|
||||
repositories {
|
||||
maven {
|
||||
url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/*
|
||||
modRuntimeOnly('me.djtheredstoner:DevAuth-fabric:1.2.1') {
|
||||
exclude group: 'net.fabricmc', module: 'fabric-loader'
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,21 @@
|
||||
# Done to increase the memory available to gradle.
|
||||
org.gradle.jvmargs=-Xmx1G
|
||||
org.gradle.jvmargs=-Xmx2G
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.daemon = false
|
||||
|
||||
# Fabric Properties
|
||||
# check these on https://modmuss50.me/fabric.html
|
||||
minecraft_version=1.21
|
||||
yarn_mappings=1.21+build.2
|
||||
loader_version=0.15.11
|
||||
minecraft_version=1.21.11
|
||||
loader_version=0.18.2
|
||||
loom_version=1.14-SNAPSHOT
|
||||
|
||||
# Fabric API
|
||||
fabric_version=0.140.2+1.21.11
|
||||
|
||||
|
||||
# Mod Properties
|
||||
mod_version = 0.1.6-alpha
|
||||
mod_version = 0.2.9-alpha
|
||||
maven_group = me.cortex
|
||||
archives_base_name = voxy
|
||||
|
||||
fabric_version=0.100.1+1.21
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
16
init.gradle
Normal file
16
init.gradle
Normal file
@@ -0,0 +1,16 @@
|
||||
if (System.getenv("GITHUB_ACTIONS") == "true") {
|
||||
beforeSettings { settings ->
|
||||
def cleanupTime = Long.parseLong(System.getProperty("CLEANUP_TIME", Long.toString(System.currentTimeMillis()-(60_000*10))));//Remove unused entries from more then 10 min old
|
||||
|
||||
settings.caches {
|
||||
//Note: this could be Cleanup.DEFAULT
|
||||
cleanup = Cleanup.ALWAYS
|
||||
|
||||
releasedWrappers.setRemoveUnusedEntriesOlderThan(cleanupTime)
|
||||
snapshotWrappers.setRemoveUnusedEntriesOlderThan(cleanupTime)
|
||||
downloadedResources.setRemoveUnusedEntriesOlderThan(cleanupTime)
|
||||
createdResources.setRemoveUnusedEntriesOlderThan(cleanupTime)
|
||||
buildCache.setRemoveUnusedEntriesOlderThan(cleanupTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package me.cortex.voxy.api;
|
||||
|
||||
import me.cortex.voxy.common.world.WorldSection;
|
||||
|
||||
public class VoxyApi {
|
||||
private VoxyApi(String useIdentifier) {
|
||||
|
||||
}
|
||||
|
||||
public static VoxyApi makeAPI(String identifier) {
|
||||
return new VoxyApi(identifier);
|
||||
}
|
||||
|
||||
public interface ArbitraryGenerateCallback {
|
||||
int ALL_DARK = -1;
|
||||
int OK = 0;
|
||||
int ALL_SKY = 1;
|
||||
int generate(int lodLevel, int x, int y, int z, long[] outputBuffer);
|
||||
}
|
||||
|
||||
public void setArbitraryGenerator(ArbitraryGenerateCallback generator) {
|
||||
|
||||
}
|
||||
|
||||
public WorldSection acquireSection(int level, int x, int y, int z) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
57
src/main/java/me/cortex/voxy/client/ClientImportManager.java
Normal file
57
src/main/java/me/cortex/voxy/client/ClientImportManager.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.commonImpl.ImportManager;
|
||||
import me.cortex.voxy.commonImpl.importers.IDataImporter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.LerpingBossEvent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.BossEvent;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ClientImportManager extends ImportManager {
|
||||
protected class ClientImportTask extends ImportTask {
|
||||
private final UUID bossbarUUID;
|
||||
private final LerpingBossEvent bossBar;
|
||||
protected ClientImportTask(IDataImporter importer) {
|
||||
super(importer);
|
||||
|
||||
this.bossbarUUID = Mth.createInsecureUUID();
|
||||
this.bossBar = new LerpingBossEvent(this.bossbarUUID, Component.nullToEmpty("Voxy world importer"), 0.0f, BossEvent.BossBarColor.GREEN, BossEvent.BossBarOverlay.PROGRESS, false, false, false);
|
||||
Minecraft.getInstance().execute(()->{
|
||||
Minecraft.getInstance().gui.getBossOverlay().events.put(bossBar.getId(), bossBar);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onUpdate(int completed, int outOf) {
|
||||
if (!super.onUpdate(completed, outOf)) {
|
||||
return false;
|
||||
}
|
||||
Minecraft.getInstance().execute(()->{
|
||||
this.bossBar.setProgress((float) (((double)completed) / ((double) Math.max(1, outOf))));
|
||||
this.bossBar.setName(Component.nullToEmpty("Voxy import: " + completed + "/" + outOf + " chunks"));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleted(int total) {
|
||||
super.onCompleted(total);
|
||||
Minecraft.getInstance().execute(()->{
|
||||
Minecraft.getInstance().gui.getBossOverlay().events.remove(this.bossbarUUID);
|
||||
long delta = Math.max(System.currentTimeMillis() - this.startTime, 1);
|
||||
|
||||
String msg = "Voxy world import finished in " + (delta/1000) + " seconds, averaging " + (int)(total/(delta/1000f)) + " chunks per second";
|
||||
Minecraft.getInstance().gui.getChat().addMessage(Component.literal(msg));
|
||||
Logger.info(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized ImportTask createImportTask(IDataImporter importer) {
|
||||
return new ClientImportTask(importer);
|
||||
}
|
||||
}
|
||||
315
src/main/java/me/cortex/voxy/client/GPUSelectorWindows2.java
Normal file
315
src/main/java/me/cortex/voxy/client/GPUSelectorWindows2.java
Normal file
@@ -0,0 +1,315 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import org.lwjgl.system.JNI;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.system.windows.GDI32;
|
||||
import org.lwjgl.system.windows.Kernel32;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.lwjgl.system.APIUtil.apiGetFunctionAddressOptional;
|
||||
|
||||
public class GPUSelectorWindows2 {
|
||||
private static final long D3DKMTSetProperties = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTSetProperties");
|
||||
private static final long D3DKMTEnumAdapters2 = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTEnumAdapters2");
|
||||
private static final long D3DKMTCloseAdapter = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTCloseAdapter");
|
||||
private static final long D3DKMTQueryAdapterInfo = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTQueryAdapterInfo");
|
||||
|
||||
private static int setPCIProperties(int type, int vendor, int device, int subSys) {
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
var buff = stack.calloc(0x10).order(ByteOrder.nativeOrder());
|
||||
buff.putInt(0, vendor);
|
||||
buff.putInt(4, device);
|
||||
buff.putInt(8, subSys);
|
||||
buff.putInt(12, 0);
|
||||
return setProperties(type, buff);
|
||||
}
|
||||
}
|
||||
|
||||
private static int setProperties(int type, ByteBuffer payload) {
|
||||
if (D3DKMTSetProperties == 0) {
|
||||
return -1;
|
||||
}
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
var buff = stack.calloc(0x18).order(ByteOrder.nativeOrder());
|
||||
buff.putInt(0, type);
|
||||
buff.putInt(4, payload.remaining());
|
||||
buff.putLong(16, MemoryUtil.memAddress(payload));
|
||||
return JNI.callPI(MemoryUtil.memAddress(buff), D3DKMTSetProperties);
|
||||
}
|
||||
}
|
||||
|
||||
private static int query(int handle, int type, ByteBuffer payload) {
|
||||
if (D3DKMTQueryAdapterInfo == 0) {
|
||||
return -1;
|
||||
}
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
var buff = stack.calloc(0x14).order(ByteOrder.nativeOrder());
|
||||
buff.putInt(0, handle);
|
||||
buff.putInt(4, type);
|
||||
buff.putLong(8, MemoryUtil.memAddress(payload));
|
||||
buff.putInt(16, payload.remaining());
|
||||
return JNI.callPI(MemoryUtil.memAddress(buff), D3DKMTQueryAdapterInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private static int closeHandle(int handle) {
|
||||
if (D3DKMTCloseAdapter == 0) {
|
||||
return -1;
|
||||
}
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
var buff = stack.calloc(0x4).order(ByteOrder.nativeOrder());
|
||||
buff.putInt(0, handle);
|
||||
return JNI.callPI(MemoryUtil.memAddress(buff), D3DKMTCloseAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
private static int queryAdapterType(int handle, int[] out) {
|
||||
int ret;
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
var buff = stack.calloc(0x4).order(ByteOrder.nativeOrder());
|
||||
//KMTQAITYPE_ADAPTERTYPE
|
||||
if ((ret=query(handle, 15, buff))<0) {
|
||||
return ret;
|
||||
}
|
||||
out[0] = buff.getInt(0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int queryAdapterIcd(int handle, String[] out) {
|
||||
int ret;
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
var buff = stack.calloc(260*2+8).order(ByteOrder.nativeOrder());
|
||||
//KMTQAITYPE_UMOPENGLINFO
|
||||
if ((ret=query(handle, 2, buff))<0) {
|
||||
return ret;
|
||||
}
|
||||
int len = Math.min(MemoryUtil.memLengthNT2(buff), 260*2);
|
||||
out[0] = MemoryUtil.memUTF16(buff.limit(len));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private record AdapterInfo(String icdPath, int type, long luid, int vendor, int device, int subSystem) {
|
||||
@Override
|
||||
public String toString() {
|
||||
String LUID = Integer.toHexString((int) ((luid>>>32)&0xFFFFFFFFL))+"-"+Integer.toHexString((int) (luid&0xFFFFFFFFL));
|
||||
return "{type=%s, luid=%s, vendor=%s, device=%s, subSys=%s, icd=\"%s\"}".formatted(Integer.toString(type),LUID, Integer.toHexString(vendor), Integer.toHexString(device), Integer.toHexString(subSystem), icdPath);
|
||||
}
|
||||
}
|
||||
|
||||
private record PCIDeviceId(int vendor, int device, int subVendor, int subSystem, int revision, int busType) {}
|
||||
private static int queryPCIAddress(int handle, int index, PCIDeviceId[] deviceOut) {
|
||||
int ret = 0;
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
var buff = stack.calloc(4*7).order(ByteOrder.nativeOrder());
|
||||
buff.putInt(0, index);
|
||||
//KMTQAITYPE_PHYSICALADAPTERDEVICEIDS
|
||||
if ((ret = query(handle, 31, buff)) < 0) {
|
||||
return ret;
|
||||
}
|
||||
deviceOut[0] = new PCIDeviceId(buff.getInt(4),buff.getInt(8),buff.getInt(12),buff.getInt(16),buff.getInt(20),buff.getInt(24));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int enumAdapters(Consumer<AdapterInfo> consumer) {
|
||||
if (D3DKMTEnumAdapters2 == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
var query = stack.calloc(0x10).order(ByteOrder.nativeOrder());
|
||||
if ((ret = JNI.callPI(MemoryUtil.memAddress(query), D3DKMTEnumAdapters2)) < 0) {
|
||||
return ret;
|
||||
}
|
||||
int adapterCount = query.getInt(0);
|
||||
var adapterList = stack.calloc(0x14 * adapterCount).order(ByteOrder.nativeOrder());
|
||||
query.putLong(8, MemoryUtil.memAddress(adapterList));
|
||||
if ((ret = JNI.callPI(MemoryUtil.memAddress(query), D3DKMTEnumAdapters2)) < 0) {
|
||||
return ret;
|
||||
}
|
||||
adapterCount = query.getInt(0);
|
||||
for (int adapterIndex = 0; adapterIndex < adapterCount; adapterIndex++) {
|
||||
var adapter = adapterList.slice(adapterIndex*0x14, 0x14).order(ByteOrder.nativeOrder());
|
||||
//We only care about these 2
|
||||
int handle = adapter.getInt(0);
|
||||
long luid = adapter.getLong(4);
|
||||
|
||||
int[] type = new int[1];
|
||||
if ((ret = queryAdapterType(handle, type))<0) {
|
||||
Logger.error("Query type error: " + ret);
|
||||
//We errored
|
||||
if (closeHandle(handle) < 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] icd = new String[1];
|
||||
if ((ret = queryAdapterIcd(handle, icd))<0) {
|
||||
Logger.error("Query icd error: " + ret);
|
||||
//We errored
|
||||
if (closeHandle(handle) < 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
PCIDeviceId[] out = new PCIDeviceId[1];
|
||||
//Get the root adapter device
|
||||
if ((ret = queryPCIAddress(handle, 0, out)) < 0) {
|
||||
Logger.error("Query pci error: " + ret);
|
||||
//We errored
|
||||
if (closeHandle(handle) < 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int subSys = (out[0].subSystem<<16)|out[0].subVendor;//It seems the pci subsystem address is a joined integer
|
||||
consumer.accept(new AdapterInfo(icd[0], type[0], luid, out[0].vendor, out[0].device, subSys));
|
||||
|
||||
if (closeHandle(handle) < 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//=======================================================================================
|
||||
|
||||
private static final int[] HDC_STUB = { 0x48, 0x83, 0xC1, 0x0C, 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x48, 0x89, 0x01, 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2F, 0x51, 0xFF, 0xD0, 0x59, 0x8B, 0x41, 0x08, 0x89, 0x41, 0xFC, 0x48, 0x31, 0xC0, 0x89, 0x41, 0x08, 0xC3 };
|
||||
private static void insertLong(long l, byte[] out, int offset) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
out[i+offset] = (byte)(l & 0xFF);
|
||||
l >>= 8;
|
||||
}
|
||||
}
|
||||
private static byte[] createFinishedHDCStub(long luid) {
|
||||
byte[] stub = new byte[HDC_STUB.length];
|
||||
for (int i = 0; i < stub.length; i++) {
|
||||
stub[i] = (byte) HDC_STUB[i];
|
||||
}
|
||||
insertLong(luid, stub, 6);
|
||||
insertLong(D3DKMTOpenAdapterFromLuid, stub, 19);
|
||||
return stub;
|
||||
}
|
||||
|
||||
private static final long D3DKMTOpenAdapterFromLuid = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTOpenAdapterFromLuid");
|
||||
private static final long D3DKMTOpenAdapterFromHdc = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTOpenAdapterFromHdc");
|
||||
private static final long VirtualProtect = apiGetFunctionAddressOptional(Kernel32.getLibrary(), "VirtualProtect");
|
||||
|
||||
private static byte[] toByteArray(int... array) {
|
||||
byte[] res = new byte[array.length];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
res[i] = (byte) array[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
private static void VirtualProtect(long addr, long size) {
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
var oldProtection = stack.calloc(4);
|
||||
JNI.callPPPPI(addr, size, 0x40/*PAGE_EXECUTE_READWRITE*/, MemoryUtil.memAddress(oldProtection), VirtualProtect);
|
||||
}
|
||||
}
|
||||
private static void memcpy(long ptr, byte[] data) {
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
MemoryUtil.memPutByte(ptr + i, data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void installHDCStub(long adapterLuid) {
|
||||
if (D3DKMTOpenAdapterFromHdc == 0 || VirtualProtect == 0 || D3DKMTOpenAdapterFromLuid == 0) {
|
||||
return;
|
||||
}
|
||||
Logger.info("AdapterLuid callback at: " + Long.toHexString(D3DKMTOpenAdapterFromLuid));
|
||||
var stub = createFinishedHDCStub(adapterLuid);
|
||||
|
||||
VirtualProtect(D3DKMTOpenAdapterFromHdc, stub.length);
|
||||
memcpy(D3DKMTOpenAdapterFromHdc, stub);
|
||||
}
|
||||
|
||||
|
||||
private static byte[] createIntelStub(long origA, long origB, long jmpA, long jmpB) {
|
||||
byte[] stub = toByteArray(0xFE, 0x0D, 0x63, 0x00, 0x00, 0x00, 0x80, 0x3D, 0x5C, 0x00, 0x00, 0x00, 0x02, 0x75, 0x07, 0x48, 0x31, 0xC0, 0x48, 0xF7, 0xD0, 0xC3, 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x48, 0x8B, 0x0D, 0x43, 0x00, 0x00, 0x00, 0x48, 0x89, 0x08, 0x48, 0x8B, 0x0D, 0x41, 0x00, 0x00, 0x00, 0x48, 0x89, 0x48, 0x08, 0x80, 0x3D, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x74, 0x1D, 0x50, 0xFF, 0xD0, 0x58, 0x48, 0x8B, 0x0D, 0x31, 0x00, 0x00, 0x00, 0x48, 0x89, 0x08, 0x48, 0x8B, 0x0D, 0x2F, 0x00, 0x00, 0x00, 0x48, 0x89, 0x48, 0x08, 0x48, 0x31, 0xC0, 0xC3, 0x48, 0x8B, 0x41, 0x08, 0xC7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x31, 0xC0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0);
|
||||
insertLong(D3DKMTQueryAdapterInfo, stub, 24);
|
||||
stub[0x69] = 3;
|
||||
insertLong(origA, stub, 0x6a);
|
||||
insertLong(origB, stub, 0x6a+8);
|
||||
insertLong(jmpA, stub, 0x6a+16);
|
||||
insertLong(jmpB, stub, 0x6a+24);
|
||||
return stub;
|
||||
}
|
||||
|
||||
private static byte[] createSimpleStub(long origA, long origB) {
|
||||
byte[] stub = toByteArray(0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, 0x48, 0x8B, 0x0D, 0x15, 0x00, 0x00, 0x00, 0x48, 0x89, 0x08, 0x48, 0x8B, 0x0D, 0x13, 0x00, 0x00, 0x00, 0x48, 0x89, 0x48, 0x08, 0x48, 0x31, 0xC0, 0x48, 0xF7, 0xD0, 0xC3, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0);
|
||||
insertLong(D3DKMTQueryAdapterInfo, stub, 2);
|
||||
insertLong(origA, stub, 38);
|
||||
insertLong(origB, stub, 38+8);
|
||||
return stub;
|
||||
}
|
||||
|
||||
private static void installQueryStub(boolean installIntelBypass) {
|
||||
if (D3DKMTQueryAdapterInfo == 0 || VirtualProtect == 0) {
|
||||
return;
|
||||
}
|
||||
VirtualProtect(D3DKMTQueryAdapterInfo, 0x10);
|
||||
int MAX_STUB_SIZE = 1024;
|
||||
long stubPtr = MemoryUtil.nmemAlloc(MAX_STUB_SIZE);
|
||||
VirtualProtect(stubPtr, MAX_STUB_SIZE);
|
||||
Logger.info("Do stub at: " + Long.toHexString(stubPtr));
|
||||
|
||||
long origA = MemoryUtil.memGetLong(D3DKMTQueryAdapterInfo);
|
||||
long origB = MemoryUtil.memGetLong(D3DKMTQueryAdapterInfo+8);
|
||||
|
||||
var jmpStub = new byte[]{ 0x48, (byte) 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xFF, (byte) 0xE0};
|
||||
insertLong(stubPtr, jmpStub, 2);
|
||||
|
||||
memcpy(D3DKMTQueryAdapterInfo, jmpStub);
|
||||
Logger.info("D3DKMTQueryAdapterInfo at: " + Long.toHexString(D3DKMTQueryAdapterInfo));
|
||||
|
||||
long jmpA = MemoryUtil.memGetLong(D3DKMTQueryAdapterInfo);
|
||||
long jmpB = MemoryUtil.memGetLong(D3DKMTQueryAdapterInfo+8);
|
||||
|
||||
byte[] stub;
|
||||
if (installIntelBypass) {
|
||||
stub = createIntelStub(origA, origB, jmpA, jmpB);
|
||||
} else {
|
||||
stub = createSimpleStub(origA, origB);
|
||||
}
|
||||
memcpy(stubPtr, stub);
|
||||
Logger.info("QueryAdapterInfo stubs installed");
|
||||
}
|
||||
|
||||
|
||||
public static void doSelector(int index) {
|
||||
List<AdapterInfo> adapters = new ArrayList<>();
|
||||
//Must support rendering and must not be software renderer
|
||||
if (enumAdapters(adapter->{if ((adapter.type&5)==1) adapters.add(adapter); }) < 0) {
|
||||
return;
|
||||
}
|
||||
for (var adapter : adapters) {
|
||||
Logger.error(adapter.toString());
|
||||
}
|
||||
var adapter = adapters.get(index);
|
||||
|
||||
installHDCStub(adapter.luid);
|
||||
installQueryStub(adapter.icdPath.matches("\\\\ig[a-z0-9]+icd(32|64)\\.dll$"));
|
||||
|
||||
setPCIProperties(1/*USER*/, adapter.vendor, adapter.device, adapter.subSystem);
|
||||
setPCIProperties(2/*USER GLOBAL*/, adapter.vendor, adapter.device, adapter.subSystem);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
|
||||
public interface ICheekyClientChunkCache {
|
||||
LevelChunk voxy$cheekyGetChunk(int x, int z);
|
||||
}
|
||||
7
src/main/java/me/cortex/voxy/client/LoadException.java
Normal file
7
src/main/java/me/cortex/voxy/client/LoadException.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
public class LoadException extends RuntimeException {
|
||||
public LoadException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
36
src/main/java/me/cortex/voxy/client/RenderStatistics.java
Normal file
36
src/main/java/me/cortex/voxy/client/RenderStatistics.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RenderStatistics {
|
||||
public static boolean enabled = false;
|
||||
|
||||
public static final int[] hierarchicalTraversalCounts = new int[WorldEngine.MAX_LOD_LAYER+1];
|
||||
public static final int[] hierarchicalRenderSections = new int[WorldEngine.MAX_LOD_LAYER+1];
|
||||
public static final int[] visibleSections = new int[WorldEngine.MAX_LOD_LAYER+1];
|
||||
public static final int[] quadCount = new int[WorldEngine.MAX_LOD_LAYER+1];
|
||||
|
||||
|
||||
public static void addDebug(List<String> debug) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
debug.add("HTC: [" + Arrays.stream(flipCopy(RenderStatistics.hierarchicalTraversalCounts)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
|
||||
debug.add("HRS: [" + Arrays.stream(flipCopy(RenderStatistics.hierarchicalRenderSections)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
|
||||
debug.add("VS: [" + Arrays.stream(flipCopy(RenderStatistics.visibleSections)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
|
||||
debug.add("QC: [" + Arrays.stream(flipCopy(RenderStatistics.quadCount)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
|
||||
}
|
||||
|
||||
private static int[] flipCopy(int[] array) {
|
||||
int[] ret = new int[array.length];
|
||||
int i = ret.length;
|
||||
for (int j : array) {
|
||||
ret[--i] = j;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
89
src/main/java/me/cortex/voxy/client/TimingStatistics.java
Normal file
89
src/main/java/me/cortex/voxy/client/TimingStatistics.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class TimingStatistics {
|
||||
public static double ROLLING_WEIGHT = 0.96;
|
||||
private static final ArrayList<TimeSampler> allSamplers = new ArrayList<>();
|
||||
public static final class TimeSampler {
|
||||
private boolean running;
|
||||
private long timestamp;
|
||||
private long runtime;
|
||||
|
||||
private double rolling;
|
||||
|
||||
public TimeSampler() {
|
||||
TimingStatistics.allSamplers.add(this);
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
if (this.running) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.runtime = 0;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (this.running) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.running = true;
|
||||
this.timestamp = System.nanoTime();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (!this.running) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.running = false;
|
||||
this.runtime += System.nanoTime() - this.timestamp;
|
||||
}
|
||||
|
||||
public void subtract(TimeSampler sampler) {
|
||||
this.runtime -= sampler.runtime;
|
||||
}
|
||||
|
||||
private void update() {
|
||||
double time = ((double) (this.runtime / 1000)) / 1000;
|
||||
this.rolling = Math.max(this.rolling * ROLLING_WEIGHT + time * (1-ROLLING_WEIGHT), time);
|
||||
}
|
||||
|
||||
public double getRolling() {
|
||||
return this.rolling;
|
||||
}
|
||||
|
||||
public String pVal() {
|
||||
return String.format("%6.3f", this.rolling);
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetSamplers() {
|
||||
TimingStatistics.allSamplers.forEach(TimeSampler::reset);
|
||||
}
|
||||
|
||||
private static void updateSamplers() {
|
||||
TimingStatistics.allSamplers.forEach(TimeSampler::update);
|
||||
}
|
||||
|
||||
public static TimeSampler all = new TimeSampler();
|
||||
public static TimeSampler main = new TimeSampler();
|
||||
public static TimeSampler dynamic = new TimeSampler();
|
||||
public static TimeSampler postDynamic = new TimeSampler();
|
||||
|
||||
public static TimeSampler A = new TimeSampler();
|
||||
public static TimeSampler B = new TimeSampler();
|
||||
public static TimeSampler C = new TimeSampler();
|
||||
public static TimeSampler D = new TimeSampler();
|
||||
|
||||
public static TimeSampler E = new TimeSampler();
|
||||
public static TimeSampler F = new TimeSampler();
|
||||
public static TimeSampler G = new TimeSampler();
|
||||
public static TimeSampler H = new TimeSampler();
|
||||
public static TimeSampler I = new TimeSampler();
|
||||
|
||||
|
||||
public static void update() {
|
||||
updateSamplers();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import me.cortex.voxy.client.core.VoxelCore;
|
||||
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
||||
import me.cortex.voxy.client.terrain.WorldImportCommand;
|
||||
import me.cortex.voxy.common.config.Serialization;
|
||||
import me.cortex.voxy.common.storage.compressors.ZSTDCompressor;
|
||||
import me.cortex.voxy.common.storage.config.StorageConfig;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
|
||||
public class Voxy implements ClientModInitializer {
|
||||
public static final String VERSION;
|
||||
|
||||
static {
|
||||
ModContainer mod = (ModContainer) FabricLoader.getInstance().getModContainer("voxy").orElseThrow(NullPointerException::new);
|
||||
VERSION = mod.getMetadata().getVersion().getFriendlyString();
|
||||
Serialization.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
|
||||
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
|
||||
dispatcher.register(WorldImportCommand.register());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static final ContextSelectionSystem selector = new ContextSelectionSystem();
|
||||
|
||||
public static VoxelCore createVoxelCore(ClientWorld world) {
|
||||
var selection = selector.getBestSelectionOrCreate(world);
|
||||
return new VoxelCore(selection);
|
||||
}
|
||||
}
|
||||
142
src/main/java/me/cortex/voxy/client/VoxyClient.java
Normal file
142
src/main/java/me/cortex/voxy/client/VoxyClient.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||
import me.cortex.voxy.client.core.VoxyRenderSystem;
|
||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||
import me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer;
|
||||
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.debug.DebugScreenDisplayer;
|
||||
import net.minecraft.client.gui.components.debug.DebugScreenEntries;
|
||||
import net.minecraft.client.gui.components.debug.DebugScreenEntry;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import me.cortex.voxy.common.network.VoxyNetwork;
|
||||
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||
|
||||
public class VoxyClient implements ClientModInitializer {
|
||||
private static final HashSet<String> FREX = new HashSet<>();
|
||||
|
||||
public static void initVoxyClient() {
|
||||
Capabilities.init();//Ensure clinit is called
|
||||
|
||||
|
||||
if (Capabilities.INSTANCE.hasBrokenDepthSampler) {
|
||||
Logger.error("AMD broken depth sampler detected, voxy does not work correctly and has been disabled, this will hopefully be fixed in the future");
|
||||
}
|
||||
|
||||
boolean systemSupported = Capabilities.INSTANCE.compute && Capabilities.INSTANCE.indirectParameters && !Capabilities.INSTANCE.hasBrokenDepthSampler;
|
||||
if (systemSupported) {
|
||||
|
||||
SharedIndexBuffer.INSTANCE.id();
|
||||
BudgetBufferRenderer.init();
|
||||
|
||||
VoxyCommon.setInstanceFactory(VoxyClientInstance::new);
|
||||
|
||||
if (!Capabilities.INSTANCE.subgroup) {
|
||||
Logger.warn("GPU does not support subgroup operations, expect some performance degradation");
|
||||
}
|
||||
|
||||
} else {
|
||||
Logger.error("Voxy is unsupported on your system.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
DebugScreenEntries.register(Identifier.fromNamespaceAndPath("voxy", "version"), new DebugScreenEntry() {
|
||||
@Override
|
||||
public void display(DebugScreenDisplayer lines, @Nullable Level level, @Nullable LevelChunk levelChunk, @Nullable LevelChunk levelChunk2) {
|
||||
if (!VoxyCommon.isAvailable()) {
|
||||
lines.addLine(ChatFormatting.RED + "voxy-"+VoxyCommon.MOD_VERSION);//Voxy installed, not avalible
|
||||
return;
|
||||
}
|
||||
var instance = VoxyCommon.getInstance();
|
||||
if (instance == null) {
|
||||
lines.addLine(ChatFormatting.YELLOW + "voxy-" + VoxyCommon.MOD_VERSION);//Voxy avalible, no instance active
|
||||
return;
|
||||
}
|
||||
VoxyRenderSystem vrs = null;
|
||||
var wr = Minecraft.getInstance().levelRenderer;
|
||||
if (wr != null) vrs = ((IGetVoxyRenderSystem) wr).getVoxyRenderSystem();
|
||||
|
||||
//Voxy instance active
|
||||
lines.addLine((vrs==null?ChatFormatting.DARK_GREEN:ChatFormatting.GREEN)+"voxy-"+VoxyCommon.MOD_VERSION);
|
||||
}
|
||||
});
|
||||
|
||||
DebugScreenEntries.register(Identifier.fromNamespaceAndPath("voxy","debug"), new VoxyDebugScreenEntry());
|
||||
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
|
||||
if (VoxyCommon.isAvailable()) {
|
||||
dispatcher.register(VoxyCommands.register());
|
||||
}
|
||||
});
|
||||
|
||||
FabricLoader.getInstance()
|
||||
.getEntrypoints("frex_flawless_frames", Consumer.class)
|
||||
.forEach(api -> ((Consumer<Function<String,Consumer<Boolean>>>)api).accept(name->active->{if (active) {
|
||||
FREX.add(name);
|
||||
} else {
|
||||
FREX.remove(name);
|
||||
}}));
|
||||
|
||||
ClientPlayNetworking.registerGlobalReceiver(VoxyNetwork.ConfigSyncPayload.TYPE, (payload, context) -> {
|
||||
context.client().execute(() -> {
|
||||
// Update client render distance cap if needed, or store server settings
|
||||
// For now, we can perhaps log it or update a transient config
|
||||
Logger.info("Received server view distance: " + payload.viewDistance());
|
||||
// We might want to clamp the local render distance to the server's max if strictly enforced,
|
||||
// or just use it as a hint for where to expect data.
|
||||
// The user requirement says: "server sends max view distance to client... client renders up to client setting, server updates outside sim distance up to server max"
|
||||
// So we should probably store this "server max view distance" somewhere in VoxyClientInstance.
|
||||
if (VoxyCommon.getInstance() instanceof VoxyClientInstance clientInstance) {
|
||||
clientInstance.setServerViewDistance(payload.viewDistance());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ClientPlayNetworking.registerGlobalReceiver(VoxyNetwork.LodUpdatePayload.TYPE, (payload, context) -> {
|
||||
// Deserialize off-thread if possible? Packet handling is on netty thread or main thread depending on configuration.
|
||||
// But we can just schedule it.
|
||||
// Actually deserialize needs Mapper which is world specific?
|
||||
// The packet doesn't contain world ID?
|
||||
// We assume it's for the current client world.
|
||||
|
||||
// Wait, we need to know WHICH world this update is for if we have dimensions?
|
||||
// Usually packets are for the current world the player is in.
|
||||
|
||||
context.client().execute(() -> {
|
||||
// Logger.info("Received LOD update packet, size: " + payload.data().length);
|
||||
if (VoxyCommon.getInstance() instanceof VoxyClientInstance clientInstance) {
|
||||
clientInstance.handleLodUpdate(payload);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean isFrexActive() {
|
||||
return !FREX.isEmpty();
|
||||
}
|
||||
|
||||
public static int getOcclusionDebugState() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static boolean disableSodiumChunkRender() {
|
||||
return false;// getOcclusionDebugState() != 0;
|
||||
}
|
||||
}
|
||||
171
src/main/java/me/cortex/voxy/client/VoxyClientInstance.java
Normal file
171
src/main/java/me/cortex/voxy/client/VoxyClientInstance.java
Normal file
@@ -0,0 +1,171 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import me.cortex.voxy.client.compat.FlashbackCompat;
|
||||
import me.cortex.voxy.common.config.VoxyConfig;
|
||||
import me.cortex.voxy.client.mixin.sodium.AccessorSodiumWorldRenderer;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.StorageConfigUtil;
|
||||
import me.cortex.voxy.common.config.ConfigBuildCtx;
|
||||
import me.cortex.voxy.common.config.Serialization;
|
||||
import me.cortex.voxy.common.config.compressors.ZSTDCompressor;
|
||||
import me.cortex.voxy.common.config.section.SectionSerializationStorage;
|
||||
import me.cortex.voxy.common.config.section.SectionStorage;
|
||||
import me.cortex.voxy.common.config.section.SectionStorageConfig;
|
||||
import me.cortex.voxy.common.config.storage.other.CompressionStorageAdaptor;
|
||||
import me.cortex.voxy.common.config.storage.rocksdb.RocksDBStorageBackend;
|
||||
import me.cortex.voxy.commonImpl.ImportManager;
|
||||
import me.cortex.voxy.commonImpl.VoxyInstance;
|
||||
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||
import net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.level.storage.LevelResource;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class VoxyClientInstance extends VoxyInstance {
|
||||
public static boolean isInGame = false;
|
||||
|
||||
private final SectionStorageConfig storageConfig;
|
||||
private final Path basePath;
|
||||
private final boolean noIngestOverride;
|
||||
private int serverViewDistance = 32;
|
||||
|
||||
public VoxyClientInstance() {
|
||||
super();
|
||||
var path = FlashbackCompat.getReplayStoragePath();
|
||||
this.noIngestOverride = path != null;
|
||||
if (path == null) {
|
||||
path = getBasePath();
|
||||
}
|
||||
this.basePath = path;
|
||||
this.storageConfig = StorageConfigUtil.getCreateStorageConfig(Config.class, c->c.version==1&&c.sectionStorageConfig!=null, ()->DEFAULT_STORAGE_CONFIG, path).sectionStorageConfig;
|
||||
this.updateDedicatedThreads();
|
||||
}
|
||||
|
||||
public long getLastLodUpdate() {
|
||||
return lastLodUpdate;
|
||||
}
|
||||
|
||||
public int getLodUpdatesReceived() {
|
||||
return lodUpdatesReceived;
|
||||
}
|
||||
|
||||
public void setServerViewDistance(int distance) {
|
||||
this.serverViewDistance = distance;
|
||||
// Trigger a re-evaluation of render distance?
|
||||
Logger.info("Updating client view distance from server to: " + distance);
|
||||
}
|
||||
|
||||
public int getServerViewDistance() {
|
||||
return this.serverViewDistance;
|
||||
}
|
||||
|
||||
private long lastLodUpdate;
|
||||
private int lodUpdatesReceived;
|
||||
|
||||
public void handleLodUpdate(me.cortex.voxy.common.network.VoxyNetwork.LodUpdatePayload payload) {
|
||||
this.lastLodUpdate = System.currentTimeMillis();
|
||||
this.lodUpdatesReceived++;
|
||||
// 1. Get current client world
|
||||
var player = Minecraft.getInstance().player;
|
||||
if (player == null) return;
|
||||
var level = player.level();
|
||||
var wi = WorldIdentifier.of(level);
|
||||
if (wi == null) return;
|
||||
|
||||
var engine = this.getNullable(wi);
|
||||
if (engine == null) return;
|
||||
|
||||
// 2. Deserialize payload using engine's mapper
|
||||
// Note: Payload deserialization requires Mapper to resolve IDs.
|
||||
// We need to ensure the engine's mapper is up to date with the server's palette?
|
||||
// No, the payload contains palette strings/states.
|
||||
// The deserializer maps them to LOCAL IDs using the provided mapper.
|
||||
|
||||
try {
|
||||
var section = payload.deserialize(engine.getMapper());
|
||||
// 3. Insert update into engine
|
||||
me.cortex.voxy.common.world.WorldUpdater.insertUpdate(engine, section);
|
||||
} catch (Exception e) {
|
||||
Logger.error("Failed to handle LOD update", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDedicatedThreads() {
|
||||
int target = VoxyConfig.CONFIG.serviceThreads;
|
||||
if (!VoxyConfig.CONFIG.dontUseSodiumBuilderThreads) {
|
||||
var swr = SodiumWorldRenderer.instanceNullable();
|
||||
if (swr != null) {
|
||||
var rsm = ((AccessorSodiumWorldRenderer) swr).getRenderSectionManager();
|
||||
if (rsm != null) {
|
||||
this.setNumThreads(Math.max(1, target - rsm.getBuilder().getTotalThreadCount()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setNumThreads(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImportManager createImportManager() {
|
||||
return new ClientImportManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SectionStorage createStorage(WorldIdentifier identifier) {
|
||||
var ctx = new ConfigBuildCtx();
|
||||
ctx.setProperty(ConfigBuildCtx.BASE_SAVE_PATH, this.basePath.toString());
|
||||
ctx.setProperty(ConfigBuildCtx.WORLD_IDENTIFIER, identifier.getWorldId());
|
||||
ctx.pushPath(ConfigBuildCtx.DEFAULT_STORAGE_PATH);
|
||||
return this.storageConfig.build(ctx);
|
||||
}
|
||||
|
||||
public Path getStorageBasePath() {
|
||||
return this.basePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIngestEnabled(WorldIdentifier worldId) {
|
||||
return (!this.noIngestOverride) && VoxyConfig.CONFIG.ingestEnabled;
|
||||
}
|
||||
|
||||
private static class Config {
|
||||
public int version = 1;
|
||||
public SectionStorageConfig sectionStorageConfig;
|
||||
}
|
||||
|
||||
private static final Config DEFAULT_STORAGE_CONFIG;
|
||||
static {
|
||||
var config = new Config();
|
||||
config.sectionStorageConfig = StorageConfigUtil.createDefaultSerializer();
|
||||
DEFAULT_STORAGE_CONFIG = config;
|
||||
}
|
||||
|
||||
private static Path getBasePath() {
|
||||
Path basePath = Minecraft.getInstance().gameDirectory.toPath().resolve(".voxy").resolve("saves");
|
||||
var iserver = Minecraft.getInstance().getSingleplayerServer();
|
||||
if (iserver != null) {
|
||||
basePath = iserver.getWorldPath(LevelResource.ROOT).resolve("voxy");
|
||||
} else {
|
||||
var netHandle = Minecraft.getInstance().gameMode;
|
||||
if (netHandle == null) {
|
||||
Logger.error("Network handle null");
|
||||
basePath = basePath.resolve("UNKNOWN");
|
||||
} else {
|
||||
var info = netHandle.connection.getServerData();
|
||||
if (info == null) {
|
||||
Logger.error("Server info null");
|
||||
basePath = basePath.resolve("UNKNOWN");
|
||||
} else {
|
||||
if (info.isRealm()) {
|
||||
basePath = basePath.resolve("realms");
|
||||
} else {
|
||||
basePath = basePath.resolve(info.ip.replace(":", "_"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return basePath.toAbsolutePath();
|
||||
}
|
||||
}
|
||||
275
src/main/java/me/cortex/voxy/client/VoxyCommands.java
Normal file
275
src/main/java/me/cortex/voxy/client/VoxyCommands.java
Normal file
@@ -0,0 +1,275 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||
import me.cortex.voxy.commonImpl.importers.DHImporter;
|
||||
import me.cortex.voxy.commonImpl.importers.WorldImporter;
|
||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
|
||||
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.commands.SharedSuggestionProvider;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
|
||||
public class VoxyCommands {
|
||||
|
||||
public static LiteralArgumentBuilder<FabricClientCommandSource> register() {
|
||||
var imports = ClientCommandManager.literal("import")
|
||||
.then(ClientCommandManager.literal("world")
|
||||
.then(ClientCommandManager.argument("world_name", StringArgumentType.string())
|
||||
.suggests(VoxyCommands::importWorldSuggester)
|
||||
.executes(VoxyCommands::importWorld)))
|
||||
.then(ClientCommandManager.literal("bobby")
|
||||
.then(ClientCommandManager.argument("world_name", StringArgumentType.string())
|
||||
.suggests(VoxyCommands::importBobbySuggester)
|
||||
.executes(VoxyCommands::importBobby)))
|
||||
.then(ClientCommandManager.literal("raw")
|
||||
.then(ClientCommandManager.argument("path", StringArgumentType.string())
|
||||
.executes(VoxyCommands::importRaw)))
|
||||
.then(ClientCommandManager.literal("zip")
|
||||
.then(ClientCommandManager.argument("zipPath", StringArgumentType.string())
|
||||
.executes(VoxyCommands::importZip)
|
||||
.then(ClientCommandManager.argument("innerPath", StringArgumentType.string())
|
||||
.executes(VoxyCommands::importZip))))
|
||||
.then(ClientCommandManager.literal("cancel")
|
||||
.executes(VoxyCommands::cancelImport));
|
||||
|
||||
if (DHImporter.HasRequiredLibraries) {
|
||||
imports = imports
|
||||
.then(ClientCommandManager.literal("distant_horizons")
|
||||
.then(ClientCommandManager.argument("sqlDbPath", StringArgumentType.string())
|
||||
.executes(VoxyCommands::importDistantHorizons)));
|
||||
}
|
||||
|
||||
return ClientCommandManager.literal("voxy")//.requires((ctx)-> VoxyCommon.getInstance() != null)
|
||||
.then(ClientCommandManager.literal("reload")
|
||||
.executes(VoxyCommands::reloadInstance))
|
||||
.then(imports);
|
||||
}
|
||||
|
||||
private static int reloadInstance(CommandContext<FabricClientCommandSource> ctx) {
|
||||
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
||||
if (instance == null) {
|
||||
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||
return 1;
|
||||
}
|
||||
var wr = Minecraft.getInstance().levelRenderer;
|
||||
if (wr!=null) {
|
||||
((IGetVoxyRenderSystem)wr).shutdownRenderer();
|
||||
}
|
||||
|
||||
VoxyCommon.shutdownInstance();
|
||||
System.gc();
|
||||
VoxyCommon.createInstance();
|
||||
|
||||
var r = Minecraft.getInstance().levelRenderer;
|
||||
if (r != null) r.allChanged();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static int importDistantHorizons(CommandContext<FabricClientCommandSource> ctx) {
|
||||
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
||||
if (instance == null) {
|
||||
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||
return 1;
|
||||
}
|
||||
var dbFile = new File(ctx.getArgument("sqlDbPath", String.class));
|
||||
if (!dbFile.exists()) {
|
||||
return 1;
|
||||
}
|
||||
if (dbFile.isDirectory()) {
|
||||
dbFile = dbFile.toPath().resolve("DistantHorizons.sqlite").toFile();
|
||||
if (!dbFile.exists()) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
File dbFile_ = dbFile;
|
||||
var engine = WorldIdentifier.ofEngine(Minecraft.getInstance().level);
|
||||
if (engine==null)return 1;
|
||||
return instance.getImportManager().makeAndRunIfNone(engine, ()->
|
||||
new DHImporter(dbFile_, engine, Minecraft.getInstance().level, instance.getServiceManager(), instance.savingServiceRateLimiter))?0:1;
|
||||
}
|
||||
|
||||
private static boolean fileBasedImporter(File directory) {
|
||||
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
||||
if (instance == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var engine = WorldIdentifier.ofEngine(Minecraft.getInstance().level);
|
||||
if (engine==null) return false;
|
||||
return instance.getImportManager().makeAndRunIfNone(engine, ()->{
|
||||
var importer = new WorldImporter(engine, Minecraft.getInstance().level, instance.getServiceManager(), instance.savingServiceRateLimiter);
|
||||
importer.importRegionDirectoryAsync(directory);
|
||||
return importer;
|
||||
});
|
||||
}
|
||||
|
||||
private static int importRaw(CommandContext<FabricClientCommandSource> ctx) {
|
||||
if (VoxyCommon.getInstance() == null) {
|
||||
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return fileBasedImporter(new File(ctx.getArgument("path", String.class)))?0:1;
|
||||
}
|
||||
|
||||
private static int importBobby(CommandContext<FabricClientCommandSource> ctx) {
|
||||
if (VoxyCommon.getInstance() == null) {
|
||||
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
var file = new File(".bobby").toPath().resolve(ctx.getArgument("world_name", String.class)).toFile();
|
||||
return fileBasedImporter(file)?0:1;
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> importWorldSuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
|
||||
return fileDirectorySuggester(Minecraft.getInstance().gameDirectory.toPath().resolve("saves"), sb);
|
||||
}
|
||||
private static CompletableFuture<Suggestions> importBobbySuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
|
||||
return fileDirectorySuggester(Minecraft.getInstance().gameDirectory.toPath().resolve(".bobby"), sb);
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> fileDirectorySuggester(Path dir, SuggestionsBuilder sb) {
|
||||
var str = sb.getRemaining().replace("\\\\", "\\").replace("\\", "/");
|
||||
if (str.startsWith("\"")) {
|
||||
str = str.substring(1);
|
||||
}
|
||||
if (str.endsWith("\"")) {
|
||||
str = str.substring(0,str.length()-1);
|
||||
}
|
||||
var remaining = str;
|
||||
if (str.contains("/")) {
|
||||
int idx = str.lastIndexOf('/');
|
||||
remaining = str.substring(idx+1);
|
||||
try {
|
||||
dir = dir.resolve(str.substring(0, idx));
|
||||
} catch (Exception e) {
|
||||
return Suggestions.empty();
|
||||
}
|
||||
str = str.substring(0, idx+1);
|
||||
} else {
|
||||
str = "";
|
||||
}
|
||||
|
||||
try {
|
||||
var worlds = Files.list(dir).toList();
|
||||
for (var world : worlds) {
|
||||
if (!world.toFile().isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
var wn = world.getFileName().toString();
|
||||
if (wn.equals(remaining)) {
|
||||
continue;
|
||||
}
|
||||
if (SharedSuggestionProvider.matchesSubStr(remaining, wn) || SharedSuggestionProvider.matchesSubStr(remaining, '"'+wn)) {
|
||||
wn = str+wn + "/";
|
||||
sb.suggest(StringArgumentType.escapeIfRequired(wn));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {}
|
||||
|
||||
return sb.buildFuture();
|
||||
}
|
||||
|
||||
private static int importWorld(CommandContext<FabricClientCommandSource> ctx) {
|
||||
if (VoxyCommon.getInstance() == null) {
|
||||
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
var name = ctx.getArgument("world_name", String.class);
|
||||
var file = new File("saves").toPath().resolve(name);
|
||||
name = name.toLowerCase(Locale.ROOT);
|
||||
if (name.endsWith("/")) {
|
||||
name = name.substring(0, name.length()-1);
|
||||
}
|
||||
if (file.resolve("level.dat").toFile().exists()) {
|
||||
var dimFile = DimensionType.getStorageFolder(Minecraft.getInstance().level.dimension(), file)
|
||||
.resolve("region")
|
||||
.toFile();
|
||||
if (!dimFile.isDirectory()) return 1;
|
||||
return fileBasedImporter(dimFile)?0:1;
|
||||
//We are in a world directory, so import the current dimension we are in
|
||||
/*
|
||||
for (var dim : new String[]{"overworld", "the_nether", "the_end"}) {//This is so annoying that you cant loop through all the dimensions
|
||||
var id = ResourceKey.create(Registries.DIMENSION, Identifier.withDefaultNamespace(dim));
|
||||
var dimPath = DimensionType.getStorageFolder(id, file);
|
||||
dimPath = dimPath.resolve("region");
|
||||
var dimFile = dimPath.toFile();
|
||||
if (dimFile.isDirectory()) {//exists and is a directory
|
||||
if (!fileBasedImporter(dimFile)) {
|
||||
Logger.error("Failed to import dimension: " + id);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
} else {
|
||||
if (!(name.endsWith("region"))) {
|
||||
file = file.resolve("region");
|
||||
}
|
||||
return fileBasedImporter(file.toFile()) ? 0 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int importZip(CommandContext<FabricClientCommandSource> ctx) {
|
||||
var zip = new File(ctx.getArgument("zipPath", String.class));
|
||||
var innerDir = "region/";
|
||||
try {
|
||||
innerDir = ctx.getArgument("innerPath", String.class);
|
||||
} catch (Exception e) {}
|
||||
|
||||
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
||||
if (instance == null) {
|
||||
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||
return 1;
|
||||
}
|
||||
String finalInnerDir = innerDir;
|
||||
|
||||
var engine = WorldIdentifier.ofEngine(Minecraft.getInstance().level);
|
||||
if (engine != null) {
|
||||
return instance.getImportManager().makeAndRunIfNone(engine, () -> {
|
||||
var importer = new WorldImporter(engine, Minecraft.getInstance().level, instance.getServiceManager(), instance.savingServiceRateLimiter);
|
||||
importer.importZippedRegionDirectoryAsync(zip, finalInnerDir);
|
||||
return importer;
|
||||
}) ? 0 : 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int cancelImport(CommandContext<FabricClientCommandSource> ctx) {
|
||||
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
||||
if (instance == null) {
|
||||
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||
return 1;
|
||||
}
|
||||
var world = WorldIdentifier.ofEngineNullable(Minecraft.getInstance().level);
|
||||
if (world != null) {
|
||||
return instance.getImportManager().cancelImport(world)?0:1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||
import me.cortex.voxy.client.core.VoxyRenderSystem;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.debug.DebugScreenDisplayer;
|
||||
import net.minecraft.client.gui.components.debug.DebugScreenEntry;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class VoxyDebugScreenEntry implements DebugScreenEntry {
|
||||
@Override
|
||||
public void display(DebugScreenDisplayer lines, @Nullable Level world, @Nullable LevelChunk clientChunk, @Nullable LevelChunk chunk) {
|
||||
if (!VoxyCommon.isAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var instance = VoxyCommon.getInstance();
|
||||
if (instance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
VoxyRenderSystem vrs = null;
|
||||
var wr = Minecraft.getInstance().levelRenderer;
|
||||
if (wr != null) vrs = ((IGetVoxyRenderSystem) wr).getVoxyRenderSystem();
|
||||
|
||||
//lines.addLineToSection();
|
||||
List<String> instanceLines = new ArrayList<>();
|
||||
instance.addDebug(instanceLines);
|
||||
lines.addToGroup(Identifier.fromNamespaceAndPath("voxy", "instance_debug"), instanceLines);
|
||||
|
||||
if (vrs != null) {
|
||||
List<String> renderLines = new ArrayList<>();
|
||||
vrs.addDebugInfo(renderLines);
|
||||
lines.addToGroup(Identifier.fromNamespaceAndPath("voxy", "render_debug"), renderLines);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package me.cortex.voxy.client.compat;
|
||||
|
||||
import com.moulberry.flashback.Flashback;
|
||||
import com.moulberry.flashback.playback.ReplayServer;
|
||||
import com.moulberry.flashback.record.FlashbackMeta;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.config.section.SectionStorageConfig;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class FlashbackCompat {
|
||||
public static final boolean FLASHBACK_INSTALLED = FabricLoader.getInstance().isModLoaded("flashback");
|
||||
|
||||
public static Path getReplayStoragePath() {
|
||||
if (!FLASHBACK_INSTALLED) {
|
||||
return null;
|
||||
}
|
||||
return getReplayStoragePath0();
|
||||
}
|
||||
|
||||
private static Path getReplayStoragePath0() {
|
||||
ReplayServer replayServer = Flashback.getReplayServer();
|
||||
if (replayServer != null) {
|
||||
FlashbackMeta meta = replayServer.getMetadata();
|
||||
if (meta != null) {
|
||||
var path = ((IFlashbackMeta)meta).getVoxyPath();
|
||||
if (path != null) {
|
||||
Logger.info("Flashback replay server exists and meta exists");
|
||||
if (path.exists()) {
|
||||
Logger.info("Flashback voxy path exists in filesystem, using this as lod data source");
|
||||
return path.toPath();
|
||||
} else {
|
||||
Logger.warn("Flashback meta had voxy path saved but path doesnt exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package me.cortex.voxy.client.compat;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface IFlashbackMeta {
|
||||
void setVoxyPath(File path);
|
||||
File getVoxyPath();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package me.cortex.voxy.client.compat;
|
||||
|
||||
import me.cortex.voxy.common.thread.MultiThreadPrioritySemaphore;
|
||||
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
public class SemaphoreBlockImpersonator extends Semaphore {
|
||||
private final MultiThreadPrioritySemaphore.Block block;
|
||||
public SemaphoreBlockImpersonator(MultiThreadPrioritySemaphore.Block block) {
|
||||
super(0);
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release(int permits) {
|
||||
this.block.release(permits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acquire() throws InterruptedException {
|
||||
this.block.acquire();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryAcquire() {
|
||||
return this.block.tryAcquire();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int availablePermits() {
|
||||
return this.block.availablePermits();
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package me.cortex.voxy.client.config;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.Blocks;
|
||||
|
||||
public class BlockConfig {
|
||||
public Block block = Blocks.AIR;
|
||||
public boolean ignoreBlock = false;
|
||||
public final Face[] faces = new Face[6];
|
||||
public float colourMultiplier = 1.0f;
|
||||
|
||||
public BlockConfig() {
|
||||
for (int i = 0; i < this.faces.length; i++) {
|
||||
this.faces[i] = new Face();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Face {
|
||||
public BooleanChoice occludes = BooleanChoice.DEFAULT;
|
||||
public BooleanChoice canBeOccluded = BooleanChoice.DEFAULT;
|
||||
}
|
||||
|
||||
public enum BooleanChoice {
|
||||
DEFAULT,
|
||||
TRUE,
|
||||
FALSE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package me.cortex.voxy.client.config;
|
||||
|
||||
import net.caffeinemc.mods.sodium.client.config.structure.OptionPage;
|
||||
import net.caffeinemc.mods.sodium.client.config.structure.Page;
|
||||
|
||||
public interface IConfigPageSetter {
|
||||
void voxy$setPageJump(OptionPage page);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.cortex.voxy.client.config;
|
||||
|
||||
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
|
||||
import com.terraformersmc.modmenu.api.ModMenuApi;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import net.caffeinemc.mods.sodium.client.config.ConfigManager;
|
||||
import net.caffeinemc.mods.sodium.client.config.structure.OptionPage;
|
||||
import net.caffeinemc.mods.sodium.client.gui.VideoSettingsScreen;
|
||||
|
||||
public class ModMenuIntegration implements ModMenuApi {
|
||||
@Override
|
||||
public ConfigScreenFactory<?> getModConfigScreenFactory() {
|
||||
return parent -> {
|
||||
if (VoxyCommon.isAvailable()) {
|
||||
var screen = (VideoSettingsScreen)VideoSettingsScreen.createScreen(parent);
|
||||
var page = (OptionPage) ConfigManager.CONFIG.getModOptions().stream().filter(a->a.configId().equals("voxy")).findFirst().get().pages().get(0);
|
||||
((IConfigPageSetter)screen).voxy$setPageJump(page);
|
||||
return screen;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
package me.cortex.voxy.client.config;
|
||||
|
||||
import me.cortex.voxy.common.util.Pair;
|
||||
import net.caffeinemc.mods.sodium.api.config.ConfigState;
|
||||
import net.caffeinemc.mods.sodium.api.config.StorageEventHandler;
|
||||
import net.caffeinemc.mods.sodium.api.config.option.*;
|
||||
import net.caffeinemc.mods.sodium.api.config.structure.*;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.contents.TranslatableContents;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
|
||||
public class SodiumConfigBuilder {
|
||||
|
||||
private static record Enabler(Predicate<ConfigState> tester, Identifier[] dependencies) {
|
||||
public Enabler(Predicate<ConfigState> tester, String[] dependencies) {
|
||||
this(tester, mapIds(dependencies));
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class Enableable <TYPE extends Enableable<TYPE>> {
|
||||
private @Nullable Enabler prevEnabler;
|
||||
protected @Nullable Enabler enabler;
|
||||
|
||||
private TYPE setEnabler0(Enabler enabler) {
|
||||
this.prevEnabler = this.enabler;
|
||||
this.enabler = enabler;
|
||||
{
|
||||
var children = this.getEnablerChildren();
|
||||
if (children != null) {
|
||||
for (var child : children) {
|
||||
if (child.enabler == null || child.enabler == this.prevEnabler) {
|
||||
child.setEnabler0(this.enabler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (TYPE) this;
|
||||
}
|
||||
|
||||
public TYPE setEnabler(Predicate<ConfigState> enabler, String... dependencies) {
|
||||
return this.setEnabler0(new Enabler(enabler, dependencies));
|
||||
}
|
||||
|
||||
public TYPE setEnabler(String enabler) {
|
||||
if (enabler == null) {
|
||||
return this.setEnabler(s->true);
|
||||
}
|
||||
var id = Identifier.parse(enabler);
|
||||
return this.setEnabler(s->s.readBooleanOption(id), enabler);
|
||||
}
|
||||
|
||||
public TYPE setEnablerAND(String... enablers) {
|
||||
var enablersId = mapIds(enablers);
|
||||
return this.setEnabler0(new Enabler(s->{
|
||||
for (var id : enablersId) {
|
||||
if (!s.readBooleanOption(id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, enablersId));
|
||||
}
|
||||
|
||||
protected Enableable[] getEnablerChildren() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Page extends Enableable<Page> {
|
||||
protected Component name;
|
||||
protected Group[] groups;
|
||||
public Page(Component name, Group... groups) {
|
||||
this.name = name;
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
protected OptionPageBuilder create(ConfigBuilder builder, BuildCtx ctx) {
|
||||
var page = builder.createOptionPage();
|
||||
page.setName(this.name);
|
||||
for (var group : this.groups) {
|
||||
page.addOptionGroup(group.create(builder, ctx));
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Enableable[] getEnablerChildren() {
|
||||
return this.groups;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Group extends Enableable<Group> {
|
||||
protected Option[] options;
|
||||
public Group(Option... options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
protected OptionGroupBuilder create(ConfigBuilder builder, BuildCtx ctx) {
|
||||
var group = builder.createOptionGroup();
|
||||
for (var option : this.options) {
|
||||
group.addOption(option.create(builder, ctx));
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Enableable[] getEnablerChildren() {
|
||||
return this.options;
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class Option <TYPE, OPTION extends Option<TYPE,OPTION,STYPE>, STYPE extends StatefulOptionBuilder<TYPE>> extends Enableable<Option<TYPE,OPTION,STYPE>> {
|
||||
//Setter returns a post save update set
|
||||
protected String id;
|
||||
protected Component name;
|
||||
protected Component tooltip;
|
||||
protected Supplier<TYPE> getter;
|
||||
protected Consumer<TYPE> setter;
|
||||
public Option(String id, Component name, Component tooltip, Supplier<TYPE> getter, Consumer<TYPE> setter) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.tooltip = tooltip;
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
public Option(String id, Component name, Supplier<TYPE> getter, Consumer<TYPE> setter) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
if (name.getContents() instanceof TranslatableContents tc) {
|
||||
this.tooltip = Component.translatable(tc.getKey() + ".tooltip");
|
||||
} else {
|
||||
this.tooltip = name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected Consumer<TYPE> postRunner;
|
||||
protected Identifier[] postRunnerConflicts;
|
||||
protected Identifier[] postChangeFlags;
|
||||
public OPTION setPostChangeRunner(Consumer<TYPE> postRunner, String... dontRunIfChangedVars) {
|
||||
this.postRunner = postRunner;
|
||||
this.postRunnerConflicts = mapIds(dontRunIfChangedVars);
|
||||
return (OPTION) this;
|
||||
}
|
||||
|
||||
public OPTION setPostChangeFlags(String... flags) {
|
||||
this.postChangeFlags = mapIds(flags);
|
||||
return (OPTION) this;
|
||||
}
|
||||
|
||||
protected abstract STYPE createType(ConfigBuilder builder);
|
||||
|
||||
protected STYPE create(ConfigBuilder builder, BuildCtx ctx) {
|
||||
var option = this.createType(builder);
|
||||
option.setName(this.name);
|
||||
option.setTooltip(this.tooltip);
|
||||
|
||||
Set<Identifier> flags = new LinkedHashSet<>();
|
||||
if (this.postRunner != null) {
|
||||
var id = Identifier.parse(this.id);
|
||||
var runner = this.postRunner;
|
||||
var getter = this.getter;
|
||||
ctx.postRunner.register(id, ()->runner.accept(getter.get()), this.postRunnerConflicts);
|
||||
flags.add(id);
|
||||
}
|
||||
|
||||
if (this.postChangeFlags != null) {
|
||||
flags.addAll(List.of(this.postChangeFlags));
|
||||
}
|
||||
|
||||
if (!flags.isEmpty()) {
|
||||
option.setFlags(flags.toArray(Identifier[]::new));
|
||||
}
|
||||
|
||||
option.setBinding(this.setter, this.getter);
|
||||
if (this.enabler != null) {
|
||||
var pred = this.enabler.tester;
|
||||
option.setEnabledProvider(s->pred.test(s), this.enabler.dependencies);
|
||||
}
|
||||
|
||||
option.setStorageHandler(ctx.saveHandler);
|
||||
|
||||
option.setDefaultValue(this.getter.get());
|
||||
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
public static class IntOption extends Option<Integer, IntOption, IntegerOptionBuilder> {
|
||||
protected Function<ConfigState, Range> rangeProvider;
|
||||
protected String[] rangeDependencies;
|
||||
protected ControlValueFormatter formatter = v->Component.literal(Integer.toString(v));
|
||||
|
||||
public IntOption(String id, Component name, Component tooltip, Supplier<Integer> getter, Consumer<Integer> setter, Range range) {
|
||||
super(id, name, tooltip, getter, setter);
|
||||
this.rangeProvider = s->range;
|
||||
}
|
||||
|
||||
public IntOption(String id, Component name, Supplier<Integer> getter, Consumer<Integer> setter, Range range) {
|
||||
super(id, name, getter, setter);
|
||||
this.rangeProvider = s->range;
|
||||
}
|
||||
|
||||
public IntOption setFormatter(IntFunction<Component> formatter) {
|
||||
this.formatter = v->formatter.apply(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IntegerOptionBuilder createType(ConfigBuilder builder) {
|
||||
return builder.createIntegerOption(Identifier.parse(this.id));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IntegerOptionBuilder create(ConfigBuilder builder, BuildCtx ctx) {
|
||||
var option = super.create(builder, ctx);
|
||||
if (this.rangeDependencies == null || this.rangeDependencies.length == 0) {
|
||||
option.setRange(this.rangeProvider.apply(null));
|
||||
} else {
|
||||
option.setRangeProvider((Function<ConfigState, SteppedValidator>)(Object) this.rangeProvider, mapIds(this.rangeDependencies));
|
||||
}
|
||||
option.setValueFormatter(this.formatter);
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BoolOption extends Option<Boolean, BoolOption, BooleanOptionBuilder> {
|
||||
public BoolOption(String id, Component name, Component tooltip, Supplier<Boolean> getter, Consumer<Boolean> setter) {
|
||||
super(id, name, tooltip, getter, setter);
|
||||
}
|
||||
|
||||
public BoolOption(String id, Component name, Supplier<Boolean> getter, Consumer<Boolean> setter) {
|
||||
super(id, name, getter, setter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BooleanOptionBuilder createType(ConfigBuilder builder) {
|
||||
return builder.createBooleanOption(Identifier.parse(this.id));
|
||||
}
|
||||
}
|
||||
|
||||
private static <F,T> T[] map(F[] from, Function<F,T> mapper, Function<Integer,T[]> factory) {
|
||||
T[] arr = factory.apply(from.length);
|
||||
for (int i = 0; i < from.length; i++) {
|
||||
arr[i] = mapper.apply(from[i]);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static Identifier[] mapIds(String[] strings) {
|
||||
return map(strings, Identifier::parse, Identifier[]::new);
|
||||
}
|
||||
|
||||
|
||||
public static class PostApplyOps implements FlagHook {
|
||||
private record Hook(Identifier name, Runnable runnable, Set<Identifier> conflicts) {}
|
||||
private Map<Identifier, Hook> hooks = new LinkedHashMap<>();
|
||||
|
||||
public PostApplyOps register(String name, Runnable postRunner, String... conflicts) {
|
||||
return this.register(Identifier.parse(name), postRunner, mapIds(conflicts));
|
||||
}
|
||||
|
||||
public PostApplyOps register(Identifier name, Runnable postRunner, Identifier... conflicts) {
|
||||
this.hooks.put(name, new Hook(name, postRunner, new LinkedHashSet<>(List.of(conflicts))));
|
||||
return this;
|
||||
}
|
||||
|
||||
protected PostApplyOps build() {
|
||||
boolean changed = false;
|
||||
do {
|
||||
changed = false;
|
||||
for (var hook : this.hooks.values()) {
|
||||
for (var ref : new LinkedHashSet<>(hook.conflicts)) {
|
||||
var other = this.hooks.getOrDefault(ref, null);
|
||||
if (other != null) {
|
||||
changed |= hook.conflicts.addAll(other.conflicts);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (changed);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Identifier> getTriggers() {
|
||||
return this.hooks.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Collection<Identifier> identifiers, ConfigState configState) {
|
||||
for (var id : identifiers) {
|
||||
var hook = this.hooks.get(id);
|
||||
if (hook != null) {
|
||||
if (Collections.disjoint(identifiers, hook.conflicts)) {
|
||||
hook.runnable.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class BuildCtx {
|
||||
public PostApplyOps postRunner = new PostApplyOps();
|
||||
public StorageEventHandler saveHandler;
|
||||
}
|
||||
|
||||
public static void buildToSodium(ConfigBuilder builder, ModOptionsBuilder options, StorageEventHandler saveHandler, Consumer<PostApplyOps> registerOps, Page... pages) {
|
||||
var ctx = new BuildCtx();
|
||||
registerOps.accept(ctx.postRunner);
|
||||
ctx.saveHandler = saveHandler;
|
||||
for (var page : pages) {
|
||||
options.addPage(page.create(builder, ctx));
|
||||
}
|
||||
options.registerFlagHook(ctx.postRunner.build());
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package me.cortex.voxy.client.config;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import me.cortex.voxy.client.core.Capabilities;
|
||||
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import org.lwjgl.opengl.GL;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class VoxyConfig {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.setPrettyPrinting()
|
||||
.excludeFieldsWithModifiers(Modifier.PRIVATE)
|
||||
.create();
|
||||
public static VoxyConfig CONFIG = loadOrCreate();
|
||||
|
||||
public boolean enabled = true;
|
||||
public boolean ingestEnabled = true;
|
||||
public int qualityScale = 12;
|
||||
public int maxSections = 200_000;
|
||||
public int renderDistance = 128;
|
||||
public int geometryBufferSize = (1<<30)/8;
|
||||
public int ingestThreads = 2;
|
||||
public int savingThreads = 4;
|
||||
public int renderThreads = 5;
|
||||
public boolean useMeshShaderIfPossible = true;
|
||||
public String defaultSaveConfig;
|
||||
|
||||
|
||||
public static VoxyConfig loadOrCreate() {
|
||||
var path = getConfigPath();
|
||||
if (Files.exists(path)) {
|
||||
try (FileReader reader = new FileReader(path.toFile())) {
|
||||
var cfg = GSON.fromJson(reader, VoxyConfig.class);
|
||||
if (cfg.defaultSaveConfig == null) {
|
||||
//Shitty gson being a pain TODO: replace with a proper fix
|
||||
cfg.defaultSaveConfig = ContextSelectionSystem.DEFAULT_STORAGE_CONFIG;
|
||||
}
|
||||
return cfg;
|
||||
} catch (IOException e) {
|
||||
System.err.println("Could not parse config");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
var config = new VoxyConfig();
|
||||
config.defaultSaveConfig = ContextSelectionSystem.DEFAULT_STORAGE_CONFIG;
|
||||
return config;
|
||||
}
|
||||
public void save() {
|
||||
//Unsafe, todo: fixme! needs to be atomic!
|
||||
try {
|
||||
Files.writeString(getConfigPath(), GSON.toJson(this));
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to write config file");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static Path getConfigPath() {
|
||||
return FabricLoader.getInstance()
|
||||
.getConfigDir()
|
||||
.resolve("voxy-config.json");
|
||||
}
|
||||
|
||||
public boolean useMeshShaders() {
|
||||
return this.useMeshShaderIfPossible && Capabilities.INSTANCE.meshShaders;
|
||||
}
|
||||
}
|
||||
154
src/main/java/me/cortex/voxy/client/config/VoxyConfigMenu.java
Normal file
154
src/main/java/me/cortex/voxy/client/config/VoxyConfigMenu.java
Normal file
@@ -0,0 +1,154 @@
|
||||
package me.cortex.voxy.client.config;
|
||||
|
||||
import me.cortex.voxy.client.RenderStatistics;
|
||||
import me.cortex.voxy.client.config.SodiumConfigBuilder.*;
|
||||
import me.cortex.voxy.common.config.VoxyConfig;
|
||||
import me.cortex.voxy.client.VoxyClient;
|
||||
import me.cortex.voxy.client.VoxyClientInstance;
|
||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||
import me.cortex.voxy.client.core.util.IrisUtil;
|
||||
import me.cortex.voxy.common.util.cpu.CpuLayout;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import net.caffeinemc.mods.sodium.api.config.ConfigEntryPoint;
|
||||
import net.caffeinemc.mods.sodium.api.config.option.OptionFlag;
|
||||
import net.caffeinemc.mods.sodium.api.config.option.OptionImpact;
|
||||
import net.caffeinemc.mods.sodium.api.config.option.Range;
|
||||
import net.caffeinemc.mods.sodium.api.config.structure.ConfigBuilder;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
public class VoxyConfigMenu implements ConfigEntryPoint {
|
||||
@Override
|
||||
public void registerConfigLate(ConfigBuilder B) {
|
||||
if (!VoxyCommon.isAvailable()) return;//Dont even register the config if its not avalible
|
||||
|
||||
var CFG = VoxyConfig.CONFIG;
|
||||
|
||||
var cc = B.registerModOptions("voxy", "Voxy", VoxyCommon.MOD_VERSION)
|
||||
.setIcon(Identifier.parse("voxy:icon.png"));
|
||||
|
||||
final var RENDER_RELOAD = OptionFlag.REQUIRES_RENDERER_RELOAD.getId().toString();
|
||||
|
||||
SodiumConfigBuilder.buildToSodium(B, cc, CFG::save, postOp->{
|
||||
postOp.register("voxy:update_threads", ()->{
|
||||
var instance = VoxyCommon.getInstance();
|
||||
if (instance != null) {
|
||||
instance.updateDedicatedThreads();
|
||||
}
|
||||
}, "voxy:enabled").register("voxy:iris_reload", ()->IrisUtil.reload());
|
||||
},
|
||||
new Page(Component.translatable("voxy.config.general"),
|
||||
new Group(
|
||||
new BoolOption(
|
||||
"voxy:enabled",
|
||||
Component.translatable("voxy.config.general.enabled"),
|
||||
()->CFG.enabled, v->{
|
||||
CFG.enabled=v;
|
||||
//we need to special case enabled, since the render reload flag runs befor us and its quite important we get it right
|
||||
if (v&&VoxyClientInstance.isInGame) {
|
||||
VoxyCommon.createInstance();
|
||||
}
|
||||
})
|
||||
.setPostChangeRunner(c->{
|
||||
if (!c) {
|
||||
var vrsh = (IGetVoxyRenderSystem) Minecraft.getInstance().levelRenderer;
|
||||
if (vrsh != null) {
|
||||
vrsh.shutdownRenderer();
|
||||
}
|
||||
VoxyCommon.shutdownInstance();
|
||||
}
|
||||
}).setPostChangeFlags(RENDER_RELOAD, "voxy:iris_reload").setEnabler(null)
|
||||
), new Group(
|
||||
new IntOption(
|
||||
"voxy:thread_count",
|
||||
Component.translatable("voxy.config.general.serviceThreads"),
|
||||
()->CFG.serviceThreads, v->CFG.serviceThreads=v,
|
||||
new Range(1, CpuLayout.getCoreCount(), 1))
|
||||
.setPostChangeFlags("voxy:update_threads"),
|
||||
new BoolOption(
|
||||
"voxy:use_sodium_threads",
|
||||
Component.translatable("voxy.config.general.useSodiumBuilder"),
|
||||
()->!CFG.dontUseSodiumBuilderThreads, v->CFG.dontUseSodiumBuilderThreads=!v)
|
||||
.setPostChangeFlags("voxy:update_threads")
|
||||
), new Group(
|
||||
new BoolOption(
|
||||
"voxy:ingest_enabled",
|
||||
Component.translatable("voxy.config.general.ingest"),
|
||||
()->CFG.ingestEnabled, v->CFG.ingestEnabled=v)
|
||||
)
|
||||
).setEnabler("voxy:enabled"),
|
||||
new Page(Component.translatable("voxy.config.rendering"),
|
||||
new Group(
|
||||
new BoolOption(
|
||||
"voxy:rendering",
|
||||
Component.translatable("voxy.config.general.rendering"),
|
||||
()->CFG.enableRendering, v->CFG.enableRendering=v)
|
||||
.setPostChangeRunner(c->{
|
||||
var vrsh = (IGetVoxyRenderSystem)Minecraft.getInstance().levelRenderer;
|
||||
if (vrsh != null) {
|
||||
if (c) {
|
||||
vrsh.createRenderer();
|
||||
} else {
|
||||
vrsh.shutdownRenderer();
|
||||
}
|
||||
}
|
||||
},"voxy:enabled", RENDER_RELOAD)
|
||||
.setPostChangeFlags("voxy:iris_reload")
|
||||
.setEnabler("voxy:enabled")
|
||||
), new Group(
|
||||
new IntOption(
|
||||
"voxy:subdivsize",
|
||||
Component.translatable("voxy.config.general.subDivisionSize"),
|
||||
()->subDiv2ln(CFG.subDivisionSize), v->CFG.subDivisionSize=ln2subDiv(v),
|
||||
new Range(0, SUBDIV_IN_MAX, 1))
|
||||
.setFormatter(v->Component.literal(Integer.toString(Math.round(ln2subDiv(v))))),
|
||||
new IntOption(
|
||||
"voxy:render_distance",
|
||||
Component.translatable("voxy.config.general.renderDistance"),
|
||||
()->CFG.sectionRenderDistance, v->CFG.sectionRenderDistance=v,
|
||||
new Range(2, 64, 1))
|
||||
.setFormatter(v->Component.literal(Integer.toString(v*32)))//Top level rd == 32 chunks
|
||||
.setPostChangeRunner(c->{
|
||||
var vrsh = (IGetVoxyRenderSystem)Minecraft.getInstance().levelRenderer;
|
||||
if (vrsh != null) {
|
||||
var vrs = vrsh.getVoxyRenderSystem();
|
||||
if (vrs != null) {
|
||||
vrs.setRenderDistance(c);
|
||||
}
|
||||
}
|
||||
}, "voxy:rendering", RENDER_RELOAD)
|
||||
), new Group(
|
||||
new BoolOption(
|
||||
"voxy:eviromental_fog",
|
||||
Component.translatable("voxy.config.general.environmental_fog"),
|
||||
()->CFG.useEnvironmentalFog, v->CFG.useEnvironmentalFog=v)
|
||||
.setPostChangeFlags(RENDER_RELOAD)
|
||||
), new Group(
|
||||
new BoolOption(
|
||||
"voxy:render_debug",
|
||||
Component.translatable("voxy.config.general.render_statistics"),
|
||||
()-> RenderStatistics.enabled, v->RenderStatistics.enabled=v)
|
||||
.setPostChangeFlags(RENDER_RELOAD))
|
||||
).setEnablerAND("voxy:enabled", "voxy:rendering"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static final int SUBDIV_IN_MAX = 100;
|
||||
private static final double SUBDIV_MIN = 28;
|
||||
private static final double SUBDIV_MAX = 256;
|
||||
private static final double SUBDIV_CONST = Math.log(SUBDIV_MAX/SUBDIV_MIN)/Math.log(2);
|
||||
|
||||
//In range is 0->200
|
||||
//Out range is 28->256
|
||||
private static float ln2subDiv(int in) {
|
||||
return (float) (SUBDIV_MIN*Math.pow(2, SUBDIV_CONST*((double)in/SUBDIV_IN_MAX)));
|
||||
}
|
||||
|
||||
//In range is ... any?
|
||||
//Out range is 0->200
|
||||
private static int subDiv2ln(float in) {
|
||||
return (int) (((Math.log(((double)in)/SUBDIV_MIN)/Math.log(2))/SUBDIV_CONST)*SUBDIV_IN_MAX);
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
package me.cortex.voxy.client.config;
|
||||
|
||||
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
|
||||
import com.terraformersmc.modmenu.api.ModMenuApi;
|
||||
import me.cortex.voxy.client.core.IGetVoxelCore;
|
||||
import me.shedaniel.clothconfig2.ClothConfigDemo;
|
||||
import me.shedaniel.clothconfig2.api.ConfigBuilder;
|
||||
import me.shedaniel.clothconfig2.api.ConfigCategory;
|
||||
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class VoxyConfigScreenFactory implements ModMenuApi {
|
||||
private static VoxyConfig DEFAULT;
|
||||
@Override
|
||||
public ConfigScreenFactory<?> getModConfigScreenFactory() {
|
||||
return parent -> buildConfigScreen(parent, VoxyConfig.CONFIG);
|
||||
}
|
||||
|
||||
private static Screen buildConfigScreen(Screen parent, VoxyConfig config) {
|
||||
if (DEFAULT == null) {
|
||||
DEFAULT = new VoxyConfig();
|
||||
}
|
||||
ConfigBuilder builder = ConfigBuilder.create()
|
||||
.setParentScreen(parent)
|
||||
.setTitle(Text.translatable("voxy.config.title"));
|
||||
|
||||
|
||||
addGeneralCategory(builder, config);
|
||||
addThreadsCategory(builder, config);
|
||||
addStorageCategory(builder, config);
|
||||
|
||||
builder.setSavingRunnable(() -> {
|
||||
//After saving the core should be reloaded/reset
|
||||
var world = (IGetVoxelCore)MinecraftClient.getInstance().worldRenderer;
|
||||
if (world != null) {
|
||||
world.reloadVoxelCore();
|
||||
}
|
||||
VoxyConfig.CONFIG.save();
|
||||
});
|
||||
|
||||
return builder.build();//
|
||||
}
|
||||
|
||||
private static void addGeneralCategory(ConfigBuilder builder, VoxyConfig config) {
|
||||
|
||||
|
||||
ConfigCategory category = builder.getOrCreateCategory(Text.translatable("voxy.config.general"));
|
||||
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
|
||||
|
||||
/*
|
||||
category.addEntry(entryBuilder.startSubCategory(Text.translatable("aaa"), List.of(entryBuilder.startBooleanToggle(Text.translatable("voxy.config.general.enabled"), config.enabled)
|
||||
.setTooltip(Text.translatable("voxy.config.general.enabled.tooltip"))
|
||||
.setSaveConsumer(val -> config.enabled = val)
|
||||
.setDefaultValue(DEFAULT.enabled)
|
||||
.build(), entryBuilder.startSubCategory(Text.translatable("bbb"), List.of(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.geometryBuffer"), config.geometryBufferSize, (1<<27)/8, ((1<<31)-1)/8)
|
||||
.setTooltip(Text.translatable("voxy.config.general.geometryBuffer.tooltip"))
|
||||
.setSaveConsumer(val -> config.geometryBufferSize = val)
|
||||
.setDefaultValue(DEFAULT.geometryBufferSize)
|
||||
.build())).build()
|
||||
)).build());
|
||||
*/
|
||||
|
||||
category.addEntry(entryBuilder.startBooleanToggle(Text.translatable("voxy.config.general.enabled"), config.enabled)
|
||||
.setTooltip(Text.translatable("voxy.config.general.enabled.tooltip"))
|
||||
.setSaveConsumer(val -> config.enabled = val)
|
||||
.setDefaultValue(DEFAULT.enabled)
|
||||
.build());
|
||||
|
||||
category.addEntry(entryBuilder.startBooleanToggle(Text.translatable("voxy.config.general.ingest"), config.ingestEnabled)
|
||||
.setTooltip(Text.translatable("voxy.config.general.ingest.tooltip"))
|
||||
.setSaveConsumer(val -> config.ingestEnabled = val)
|
||||
.setDefaultValue(DEFAULT.ingestEnabled)
|
||||
.build());
|
||||
|
||||
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.quality"), config.qualityScale, 8, 32)
|
||||
.setTooltip(Text.translatable("voxy.config.general.quality.tooltip"))
|
||||
.setSaveConsumer(val -> config.qualityScale = val)
|
||||
.setDefaultValue(DEFAULT.qualityScale)
|
||||
.build());
|
||||
|
||||
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.geometryBuffer"), config.geometryBufferSize, (1<<27)/8, ((1<<31)-1)/8)
|
||||
.setTooltip(Text.translatable("voxy.config.general.geometryBuffer.tooltip"))
|
||||
.setSaveConsumer(val -> config.geometryBufferSize = val)
|
||||
.setDefaultValue(DEFAULT.geometryBufferSize)
|
||||
.build());
|
||||
|
||||
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.maxSections"), config.maxSections, 100_000, 400_000)
|
||||
.setTooltip(Text.translatable("voxy.config.general.maxSections.tooltip"))
|
||||
.setSaveConsumer(val -> config.maxSections = val)
|
||||
.setDefaultValue(DEFAULT.maxSections)
|
||||
.build());
|
||||
|
||||
category.addEntry(entryBuilder.startIntField(Text.translatable("voxy.config.general.renderDistance"), config.renderDistance)
|
||||
.setTooltip(Text.translatable("voxy.config.general.renderDistance.tooltip"))
|
||||
.setSaveConsumer(val -> config.renderDistance = val)
|
||||
.setDefaultValue(DEFAULT.renderDistance)
|
||||
.build());
|
||||
|
||||
category.addEntry(entryBuilder.startBooleanToggle(Text.translatable("voxy.config.general.nvmesh"), config.useMeshShaderIfPossible)
|
||||
.setTooltip(Text.translatable("voxy.config.general.nvmesh.tooltip"))
|
||||
.setSaveConsumer(val -> config.useMeshShaderIfPossible = val)
|
||||
.setDefaultValue(DEFAULT.useMeshShaderIfPossible)
|
||||
.build());
|
||||
|
||||
//category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.compression"), config.savingCompressionLevel, 1, 21)
|
||||
// .setTooltip(Text.translatable("voxy.config.general.compression.tooltip"))
|
||||
// .setSaveConsumer(val -> config.savingCompressionLevel = val)
|
||||
// .setDefaultValue(DEFAULT.savingCompressionLevel)
|
||||
// .build());
|
||||
}
|
||||
|
||||
private static void addThreadsCategory(ConfigBuilder builder, VoxyConfig config) {
|
||||
ConfigCategory category = builder.getOrCreateCategory(Text.translatable("voxy.config.threads"));
|
||||
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
|
||||
|
||||
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.threads.ingest"), config.ingestThreads, 1, Runtime.getRuntime().availableProcessors())
|
||||
.setTooltip(Text.translatable("voxy.config.threads.ingest.tooltip"))
|
||||
.setSaveConsumer(val -> config.ingestThreads = val)
|
||||
.setDefaultValue(DEFAULT.ingestThreads)
|
||||
.build());
|
||||
|
||||
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.threads.saving"), config.savingThreads, 1, Runtime.getRuntime().availableProcessors())
|
||||
.setTooltip(Text.translatable("voxy.config.threads.saving.tooltip"))
|
||||
.setSaveConsumer(val -> config.savingThreads = val)
|
||||
.setDefaultValue(DEFAULT.savingThreads)
|
||||
.build());
|
||||
|
||||
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.threads.render"), config.renderThreads, 1, Runtime.getRuntime().availableProcessors())
|
||||
.setTooltip(Text.translatable("voxy.config.threads.render.tooltip"))
|
||||
.setSaveConsumer(val -> config.renderThreads = val)
|
||||
.setDefaultValue(DEFAULT.renderThreads)
|
||||
.build());
|
||||
}
|
||||
|
||||
private static void addStorageCategory(ConfigBuilder builder, VoxyConfig config) {
|
||||
ConfigCategory category = builder.getOrCreateCategory(Text.translatable("voxy.config.storage"));
|
||||
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
|
||||
|
||||
////Temporary until i figure out how to do more complex multi layer configuration for storage
|
||||
//category.addEntry(entryBuilder.startStrField(Text.translatable("voxy.config.storage.path"), config.storagePath)
|
||||
// .setTooltip(Text.translatable("voxy.config.storage.path.tooltip"))
|
||||
// .setSaveConsumer(val -> config.storagePath = val)
|
||||
// .setDefaultValue(DEFAULT.storagePath)
|
||||
// .build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package me.cortex.voxy.client.config.screens;
|
||||
|
||||
import me.cortex.voxy.client.config.BlockConfig;
|
||||
import me.shedaniel.clothconfig2.api.AbstractConfigEntry;
|
||||
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
|
||||
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
public class BlockConfigScreen {
|
||||
private static final ConfigEntryBuilder ENTRY_BUILDER = ConfigEntryBuilder.create();
|
||||
public static AbstractConfigListEntry<BlockConfig> makeScreen(BlockConfig config) {
|
||||
var entry = ENTRY_BUILDER.startSubCategory(config.block.getName());
|
||||
entry.add(UtilityScreens.makeBlockSelectionScreen(Text.literal("a"), Blocks.AIR, null));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package me.cortex.voxy.client.config.screens;
|
||||
|
||||
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
|
||||
|
||||
public class StorageConfigScreen {
|
||||
public static AbstractConfigListEntry makeScreen() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package me.cortex.voxy.client.config.screens;
|
||||
|
||||
import me.cortex.voxy.client.config.BlockConfig;
|
||||
import me.shedaniel.clothconfig2.api.AbstractConfigEntry;
|
||||
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
|
||||
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
|
||||
import me.shedaniel.clothconfig2.impl.builders.DropdownMenuBuilder;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class UtilityScreens {
|
||||
private static final ConfigEntryBuilder ENTRY_BUILDER = ConfigEntryBuilder.create();
|
||||
public static AbstractConfigListEntry<Block> makeBlockSelectionScreen(Text name, Block selectedBlock, Consumer<Block> onSave) {
|
||||
return makeBlockSelectionScreen(name, Blocks.AIR, selectedBlock, onSave);
|
||||
}
|
||||
public static AbstractConfigListEntry<Block> makeBlockSelectionScreen(Text name, Block defaultBlock, Block selectedBlock, Consumer<Block> onSave) {
|
||||
return ENTRY_BUILDER.startDropdownMenu(name,
|
||||
DropdownMenuBuilder.TopCellElementBuilder.ofBlockObject(selectedBlock),
|
||||
DropdownMenuBuilder.CellCreatorBuilder.ofBlockObject())
|
||||
.setDefaultValue(defaultBlock)
|
||||
.setSelections(Registries.BLOCK.stream().toList())
|
||||
.setSaveConsumer(onSave)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
package me.cortex.voxy.client.core;
|
||||
|
||||
import me.cortex.voxy.client.RenderStatistics;
|
||||
import me.cortex.voxy.client.TimingStatistics;
|
||||
import me.cortex.voxy.client.VoxyClient;
|
||||
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
|
||||
import me.cortex.voxy.client.core.rendering.post.FullscreenBlit;
|
||||
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
|
||||
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
|
||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||
import me.cortex.voxy.common.util.TrackedObject;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import static org.lwjgl.opengl.GL11C.GL_ALWAYS;
|
||||
import static org.lwjgl.opengl.GL11C.GL_DEPTH_TEST;
|
||||
import static org.lwjgl.opengl.GL11C.GL_EQUAL;
|
||||
import static org.lwjgl.opengl.GL11C.GL_KEEP;
|
||||
import static org.lwjgl.opengl.GL11C.GL_REPLACE;
|
||||
import static org.lwjgl.opengl.GL11C.GL_STENCIL_TEST;
|
||||
import static org.lwjgl.opengl.GL11C.glColorMask;
|
||||
import static org.lwjgl.opengl.GL11C.glDisable;
|
||||
import static org.lwjgl.opengl.GL11C.glEnable;
|
||||
import static org.lwjgl.opengl.GL11C.glStencilFunc;
|
||||
import static org.lwjgl.opengl.GL11C.glStencilMask;
|
||||
import static org.lwjgl.opengl.GL11C.glStencilOp;
|
||||
import static org.lwjgl.opengl.GL30C.GL_DEPTH24_STENCIL8;
|
||||
import static org.lwjgl.opengl.GL30C.GL_FRAMEBUFFER;
|
||||
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
|
||||
import static org.lwjgl.opengl.GL42.GL_LEQUAL;
|
||||
import static org.lwjgl.opengl.GL42.GL_NOTEQUAL;
|
||||
import static org.lwjgl.opengl.GL42.glDepthFunc;
|
||||
import static org.lwjgl.opengl.GL42.*;
|
||||
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi;
|
||||
import static org.lwjgl.opengl.GL45.glGetNamedFramebufferAttachmentParameteri;
|
||||
import static org.lwjgl.opengl.GL45C.glBindTextureUnit;
|
||||
|
||||
public abstract class AbstractRenderPipeline extends TrackedObject {
|
||||
private final BooleanSupplier frexStillHasWork;
|
||||
|
||||
private final AsyncNodeManager nodeManager;
|
||||
private final NodeCleaner nodeCleaner;
|
||||
private final HierarchicalOcclusionTraverser traversal;
|
||||
|
||||
protected AbstractSectionRenderer<?,?> sectionRenderer;
|
||||
|
||||
private final FullscreenBlit depthMaskBlit = new FullscreenBlit("voxy:post/fullscreen2.vert", "voxy:post/noop.frag");
|
||||
private final FullscreenBlit depthSetBlit = new FullscreenBlit("voxy:post/fullscreen2.vert", "voxy:post/depth0.frag");
|
||||
private final FullscreenBlit depthCopy = new FullscreenBlit("voxy:post/fullscreen2.vert", "voxy:post/depth_copy.frag");
|
||||
|
||||
public final DepthFramebuffer fb = new DepthFramebuffer(GL_DEPTH24_STENCIL8);
|
||||
|
||||
protected final boolean deferTranslucency;
|
||||
|
||||
private static final int DEPTH_SAMPLER = glGenSamplers();
|
||||
static {
|
||||
glSamplerParameteri(DEPTH_SAMPLER, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glSamplerParameteri(DEPTH_SAMPLER, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
}
|
||||
|
||||
protected AbstractRenderPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier, boolean deferTranslucency) {
|
||||
this.frexStillHasWork = frexSupplier;
|
||||
this.nodeManager = nodeManager;
|
||||
this.nodeCleaner = nodeCleaner;
|
||||
this.traversal = traversal;
|
||||
this.deferTranslucency = deferTranslucency;
|
||||
}
|
||||
|
||||
//Allows pipelines to configure model baking system
|
||||
public void setupExtraModelBakeryData(ModelBakerySubsystem modelService) {}
|
||||
|
||||
public final void setSectionRenderer(AbstractSectionRenderer<?,?> sectionRenderer) {//Stupid java ordering not allowing something pre super
|
||||
if (this.sectionRenderer != null) throw new IllegalStateException();
|
||||
this.sectionRenderer = sectionRenderer;
|
||||
}
|
||||
|
||||
//Called before the pipeline starts running, used to update uniforms etc
|
||||
public void preSetup(Viewport<?> viewport) {
|
||||
|
||||
}
|
||||
|
||||
protected abstract int setup(Viewport<?> viewport, int sourceFramebuffer, int srcWidth, int srcHeight);
|
||||
protected abstract void postOpaquePreTranslucent(Viewport<?> viewport);
|
||||
protected void finish(Viewport<?> viewport, int sourceFrameBuffer, int srcWidth, int srcHeight) {
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, sourceFrameBuffer);
|
||||
}
|
||||
|
||||
public void runPipeline(Viewport<?> viewport, int sourceFrameBuffer, int srcWidth, int srcHeight) {
|
||||
int depthTexture = this.setup(viewport, sourceFrameBuffer, srcWidth, srcHeight);
|
||||
|
||||
var rs = ((AbstractSectionRenderer)this.sectionRenderer);
|
||||
rs.renderOpaque(viewport);
|
||||
var occlusionDebug = VoxyClient.getOcclusionDebugState();
|
||||
if (occlusionDebug==0) {
|
||||
this.innerPrimaryWork(viewport, depthTexture);
|
||||
}
|
||||
if (occlusionDebug<=1) {
|
||||
rs.buildDrawCalls(viewport);
|
||||
}
|
||||
rs.renderTemporal(viewport);
|
||||
|
||||
this.postOpaquePreTranslucent(viewport);
|
||||
|
||||
if (!this.deferTranslucency) {
|
||||
rs.renderTranslucent(viewport);
|
||||
}
|
||||
|
||||
this.finish(viewport, sourceFrameBuffer, srcWidth, srcHeight);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, sourceFrameBuffer);
|
||||
}
|
||||
|
||||
protected void initDepthStencil(int sourceFrameBuffer, int targetFb, int srcWidth, int srcHeight, int width, int height) {
|
||||
glClearNamedFramebufferfi(targetFb, GL_DEPTH_STENCIL, 0, 1.0f, 1);
|
||||
// using blit to copy depth from mismatched depth formats is not portable so instead a full screen pass is performed for a depth copy
|
||||
// the mismatched formats in this case is the d32 to d24s8
|
||||
glBindFramebuffer(GL30.GL_FRAMEBUFFER, targetFb);
|
||||
|
||||
this.depthCopy.bind();
|
||||
int depthTexture = glGetNamedFramebufferAttachmentParameteri(sourceFrameBuffer, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
|
||||
glBindTextureUnit(0, depthTexture);
|
||||
glBindSampler(0, DEPTH_SAMPLER);
|
||||
glUniform2f(1,((float)width)/srcWidth, ((float)height)/srcHeight);
|
||||
glColorMask(false,false,false,false);
|
||||
this.depthCopy.blit();
|
||||
|
||||
/*
|
||||
if (Capabilities.INSTANCE.isMesa){
|
||||
glClearStencil(1);
|
||||
glClear(GL_STENCIL_BUFFER_BIT);
|
||||
}*/
|
||||
|
||||
//This whole thing is hell, we basicly want to create a mask stenicel/depth mask specificiclly
|
||||
// in theory we could do this in a single pass by passing in the depth buffer from the sourceFrambuffer
|
||||
// but the current implmentation does a 2 pass system
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
|
||||
glStencilFunc(GL_ALWAYS, 0, 0xFF);
|
||||
glStencilMask(0xFF);
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_NOTEQUAL);//If != 1 pass
|
||||
//We do here
|
||||
this.depthMaskBlit.blit();
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
//Blit depth 0 where stencil is 0
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
||||
glStencilFunc(GL_EQUAL, 0, 0xFF);
|
||||
|
||||
this.depthSetBlit.blit();
|
||||
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
glColorMask(true,true,true,true);
|
||||
|
||||
//Make voxy terrain render only where there isnt mc terrain
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
||||
glStencilFunc(GL_EQUAL, 1, 0xFF);
|
||||
}
|
||||
|
||||
private static final long SCRATCH = MemoryUtil.nmemAlloc(4*4*4);
|
||||
protected static void transformBlitDepth(FullscreenBlit blitShader, int srcDepthTex, int dstFB, Viewport<?> viewport, Matrix4f targetTransform) {
|
||||
// at this point the dst frame buffer doesn't have a stencil attachment so we don't need to keep the stencil test on for the blit
|
||||
// in the worst case the dstFB does have a stencil attachment causing this pass to become 'corrupted'
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
glBindFramebuffer(GL30.GL_FRAMEBUFFER, dstFB);
|
||||
|
||||
blitShader.bind();
|
||||
glBindTextureUnit(0, srcDepthTex);
|
||||
new Matrix4f(viewport.MVP).invert().getToAddress(SCRATCH);
|
||||
nglUniformMatrix4fv(1, 1, false, SCRATCH);//inverse fromProjection
|
||||
targetTransform.getToAddress(SCRATCH);//new Matrix4f(tooProjection).mul(vp.modelView).get(data);
|
||||
nglUniformMatrix4fv(2, 1, false, SCRATCH);//tooProjection
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
blitShader.blit();
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
protected void innerPrimaryWork(Viewport<?> viewport, int depthBuffer) {
|
||||
|
||||
//Compute the mip chain
|
||||
viewport.hiZBuffer.buildMipChain(depthBuffer, viewport.width, viewport.height);
|
||||
|
||||
do {
|
||||
TimingStatistics.main.stop();
|
||||
TimingStatistics.dynamic.start();
|
||||
|
||||
TimingStatistics.D.start();
|
||||
//Tick download stream
|
||||
DownloadStream.INSTANCE.tick();
|
||||
TimingStatistics.D.stop();
|
||||
|
||||
this.nodeManager.tick(this.traversal.getNodeBuffer(), this.nodeCleaner);
|
||||
//glFlush();
|
||||
|
||||
this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here??
|
||||
|
||||
TimingStatistics.dynamic.stop();
|
||||
TimingStatistics.main.start();
|
||||
|
||||
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT | GL_PIXEL_BUFFER_BARRIER_BIT);
|
||||
|
||||
TimingStatistics.F.start();
|
||||
this.traversal.doTraversal(viewport);
|
||||
TimingStatistics.F.stop();
|
||||
} while (this.frexStillHasWork.getAsBoolean());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void free0() {
|
||||
this.fb.free();
|
||||
this.sectionRenderer.free();
|
||||
this.depthMaskBlit.delete();
|
||||
this.depthSetBlit.delete();
|
||||
this.depthCopy.delete();
|
||||
super.free0();
|
||||
}
|
||||
|
||||
public void addDebug(List<String> debug) {
|
||||
this.sectionRenderer.addDebug(debug);
|
||||
RenderStatistics.addDebug(debug);
|
||||
}
|
||||
|
||||
//Binds the framebuffer and any other bindings needed for rendering
|
||||
public abstract void setupAndBindOpaque(Viewport<?> viewport);
|
||||
public abstract void setupAndBindTranslucent(Viewport<?> viewport);
|
||||
|
||||
|
||||
public void bindUniforms() {
|
||||
this.bindUniforms(-1);
|
||||
}
|
||||
|
||||
public void bindUniforms(int index) {
|
||||
}
|
||||
|
||||
//null means no function, otherwise return the taa injection function
|
||||
public String taaFunction(String functionName) {
|
||||
return this.taaFunction(-1, functionName);
|
||||
}
|
||||
|
||||
public String taaFunction(int uboBindingPoint, String functionName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//null means dont transform the shader
|
||||
public String patchOpaqueShader(AbstractSectionRenderer<?,?> renderer, String input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Returning null means apply the same patch as the opaque
|
||||
public String patchTranslucentShader(AbstractSectionRenderer<?,?> renderer, String input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Null means no scaling factor
|
||||
public float[] getRenderScalingFactor() {return null;}
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package me.cortex.voxy.client.core;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GL20C;
|
||||
|
||||
public class Capabilities {
|
||||
|
||||
public static final Capabilities INSTANCE = new Capabilities();
|
||||
|
||||
public final boolean meshShaders;
|
||||
public final boolean INT64_t;
|
||||
public Capabilities() {
|
||||
var cap = GL.getCapabilities();
|
||||
this.meshShaders = cap.GL_NV_mesh_shader && cap.GL_NV_representative_fragment_test;
|
||||
//this.INT64_t = cap.GL_ARB_gpu_shader_int64 || cap.GL_AMD_gpu_shader_int64;
|
||||
//The only reliable way to test for int64 support is to try compile a shader
|
||||
this.INT64_t = testShaderCompilesOk(ShaderType.COMPUTE, """
|
||||
#version 430
|
||||
#extension GL_ARB_gpu_shader_int64 : require
|
||||
layout(local_size_x=32) in;
|
||||
void main() {
|
||||
uint64_t a = 1234;
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
}
|
||||
|
||||
private static boolean testShaderCompilesOk(ShaderType type, String src) {
|
||||
int shader = GL20C.glCreateShader(type.gl);
|
||||
GL20C.glShaderSource(shader, src);
|
||||
GL20C.glCompileShader(shader);
|
||||
int result = GL20C.glGetShaderi(shader, GL20C.GL_COMPILE_STATUS);
|
||||
GL20C.glDeleteShader(shader);
|
||||
|
||||
return result == GL20C.GL_TRUE;
|
||||
}
|
||||
}
|
||||
@@ -1,460 +0,0 @@
|
||||
package me.cortex.voxy.client.core;
|
||||
|
||||
//Contains the logic to determine what is loaded and at what LoD level, dispatches render changes
|
||||
// also determines what faces are built etc
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import me.cortex.voxy.client.core.rendering.RenderTracker;
|
||||
import me.cortex.voxy.client.core.util.RingUtil;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
|
||||
//Can use ring logic
|
||||
// i.e. when a player moves the rings of each lod change (how it was doing in the original attempt)
|
||||
// also have it do directional quad culling and rebuild the chunk if needed (this shouldent happen very often) (the reason is to significantly reduce draw calls)
|
||||
// make the rebuild range like +-5 chunks along each axis (that means at higher levels, should only need to rebuild like)
|
||||
// 4 sections or something
|
||||
public class DistanceTracker {
|
||||
private final TransitionRing2D[] loDRings;
|
||||
private final TransitionRing2D[] cacheLoadRings;
|
||||
private final TransitionRing2D[] cacheUnloadRings;
|
||||
private final TransitionRing2D mostOuterNonClampedRing;
|
||||
private final RenderTracker tracker;
|
||||
private final int minYSection;
|
||||
private final int maxYSection;
|
||||
private final int renderDistance;
|
||||
|
||||
public DistanceTracker(RenderTracker tracker, int[] lodRingScales, int renderDistance, int minY, int maxY) {
|
||||
this.loDRings = new TransitionRing2D[lodRingScales.length];
|
||||
this.cacheLoadRings = new TransitionRing2D[lodRingScales.length];
|
||||
this.cacheUnloadRings = new TransitionRing2D[lodRingScales.length];
|
||||
this.tracker = tracker;
|
||||
this.minYSection = minY;
|
||||
this.maxYSection = maxY;
|
||||
this.renderDistance = renderDistance;
|
||||
|
||||
|
||||
boolean wasRdClamped = false;
|
||||
//The rings 0+ start at 64 vanilla rd, no matter what the game is set at, that is if the game is set to 32 rd
|
||||
// there will still be 32 chunks untill the first lod drop
|
||||
// if the game is set to 16, then there will be 48 chunks until the drop
|
||||
for (int i = 0; i < this.loDRings.length; i++) {
|
||||
int scaleP = lodRingScales[i];
|
||||
boolean isTerminatingRing = ((lodRingScales[i]+2)<<(1+i) >= renderDistance)&&renderDistance>0;
|
||||
if (isTerminatingRing) {
|
||||
scaleP = Math.max(renderDistance >> (1+i), 1);
|
||||
wasRdClamped = true;
|
||||
}
|
||||
int scale = scaleP;
|
||||
|
||||
//TODO: FIXME: check that the level shift is right when inc/dec
|
||||
int capRing = i;
|
||||
this.loDRings[i] = new TransitionRing2D((isTerminatingRing?5:6)+i, isTerminatingRing?scale<<1:scale, (x, z) -> {
|
||||
if (isTerminatingRing) {
|
||||
add(capRing, x, z);
|
||||
} else
|
||||
this.dec(capRing+1, x, z);
|
||||
}, (x, z) -> {
|
||||
if (isTerminatingRing) {
|
||||
remove(capRing, x, z);
|
||||
//remove(capRing, (x<<1), (z<<1));
|
||||
} else
|
||||
this.inc(capRing+1, x, z);
|
||||
});
|
||||
|
||||
//TODO:FIXME i think the radius is wrong and (lodRingScales[i]) needs to be (lodRingScales[i]<<1) since the transition ring (the thing above)
|
||||
// acts on LoD level + 1
|
||||
|
||||
//TODO: check this is actually working lmao and make it generate parent level lods on the exit instead of entry so it looks correct when flying backwards
|
||||
if (!isTerminatingRing) {
|
||||
//TODO: COMPLETLY REDO THE CACHING SYSTEM CAUSE THE LOGIC IS COMPLETLY INCORRECT
|
||||
// we want basicly 2 rings offset by an amount such that when a position is near an lod transition point
|
||||
// it will be meshed (both the higher and lower quality lods), enabling semless loading
|
||||
// the issue is when to uncache these methods
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
//TODO: FIX AND FINISH!!!
|
||||
this.cacheLoadRings[i] = new TransitionRing2D(5 + i, (scale << 1) + 2, (x, z) -> {
|
||||
//When entering a cache ring, trigger a mesh op and inject into cache
|
||||
for (int y = this.minYSection >> capRing; y <= this.maxYSection >> capRing; y++) {
|
||||
this.tracker.addCache(capRing, x, y, z);
|
||||
}
|
||||
}, (x, z) -> {
|
||||
for (int y = this.minYSection >> capRing; y <= this.maxYSection >> capRing; y++) {
|
||||
this.tracker.removeCache(capRing, x, y, z);
|
||||
}
|
||||
});
|
||||
|
||||
this.cacheUnloadRings[i] = new TransitionRing2D(5 + i, Math.max(1, (scale << 1) - 2), (x, z) -> {
|
||||
for (int y = this.minYSection >> capRing; y <= this.maxYSection >> capRing; y++) {
|
||||
this.tracker.removeCache(capRing, x, y, z);
|
||||
}
|
||||
}, (x, z) -> {
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
if (isTerminatingRing) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!wasRdClamped) {
|
||||
this.mostOuterNonClampedRing = new TransitionRing2D(5+this.loDRings.length, Math.max(renderDistance, 2048)>>this.loDRings.length, (x,z)->
|
||||
add(this.loDRings.length, x, z), (x,z)->{
|
||||
if (renderDistance > 0) {
|
||||
remove(this.loDRings.length,x,z);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.mostOuterNonClampedRing = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void inc(int lvl, int x, int z) {
|
||||
for (int y = this.minYSection>>lvl; y <= this.maxYSection>>lvl; y++) {
|
||||
this.tracker.inc(lvl, x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
private void dec(int lvl, int x, int z) {
|
||||
for (int y = this.minYSection>>lvl; y <= this.maxYSection>>lvl; y++) {
|
||||
this.tracker.dec(lvl, x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
private void add(int lvl, int x, int z) {
|
||||
for (int y = this.minYSection>>lvl; y <= this.maxYSection>>lvl; y++) {
|
||||
this.tracker.add(lvl, x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
private void remove(int lvl, int x, int z) {
|
||||
for (int y = this.minYSection>>lvl; y <= this.maxYSection>>lvl; y++) {
|
||||
this.tracker.remove(lvl, x, y, z);
|
||||
this.tracker.removeCache(lvl, x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
//How it works is there are N ring zones (one zone for each lod boundary)
|
||||
// the transition zone is what determines what lods are rendered etc (and it biases higher lod levels cause its easier)
|
||||
// the transition zone is only ever checked when the player moves 1<<(4+lodlvl) blocks, its position is set
|
||||
|
||||
//if the center suddenly changes (say more than 1<<(7+lodlvl) block) then invalidate the entire ring and recompute
|
||||
// the lod sections
|
||||
public void setCenter(int x, int y, int z) {
|
||||
for (var ring : this.cacheLoadRings) {
|
||||
if (ring!=null)
|
||||
ring.update(x, z);
|
||||
}
|
||||
if (this.mostOuterNonClampedRing!=null)
|
||||
this.mostOuterNonClampedRing.update(x, z);
|
||||
|
||||
//Update in reverse order (biggest lod to smallest lod)
|
||||
for (int i = this.loDRings.length-1; -1<i; i-- ) {
|
||||
var ring = this.loDRings[i];
|
||||
if (ring != null)
|
||||
ring.update(x, z);
|
||||
}
|
||||
for (var ring : this.cacheUnloadRings) {
|
||||
if (ring!=null)
|
||||
ring.update(x, z);
|
||||
}
|
||||
}
|
||||
|
||||
public void init(int x, int z) {
|
||||
for (var ring : this.cacheLoadRings) {
|
||||
if (ring != null)
|
||||
ring.setCenter(x, z);
|
||||
}
|
||||
|
||||
for (var ring : this.cacheUnloadRings) {
|
||||
if (ring != null)
|
||||
ring.setCenter(x, z);
|
||||
}
|
||||
|
||||
for (var ring : this.loDRings) {
|
||||
if (ring != null)
|
||||
ring.setCenter(x, z);
|
||||
}
|
||||
if (this.mostOuterNonClampedRing!=null)
|
||||
this.mostOuterNonClampedRing.setCenter(x, z);
|
||||
|
||||
var thread = new Thread(()-> {
|
||||
for (var ring : this.cacheLoadRings) {
|
||||
if (ring != null)
|
||||
ring.fill(x, z);
|
||||
}
|
||||
|
||||
for (var ring : this.cacheUnloadRings) {
|
||||
if (ring != null)
|
||||
ring.fill(x, z);
|
||||
}
|
||||
|
||||
//This is an ungodly terrible hack to make the lods load in a semi ok order
|
||||
for (var ring : this.loDRings)
|
||||
if (ring != null)
|
||||
ring.fill(x, z);
|
||||
|
||||
if (this.mostOuterNonClampedRing!=null)
|
||||
this.mostOuterNonClampedRing.fill(x, z);
|
||||
|
||||
for (int i = this.loDRings.length - 1; 0 <= i; i--) {
|
||||
if (this.loDRings[i] != null) {
|
||||
this.loDRings[i].fill(x, z);
|
||||
}
|
||||
}
|
||||
});
|
||||
thread.setName("LoD Ring Initializer");
|
||||
thread.start();
|
||||
//TODO: FIXME: need to destory on shutdown
|
||||
}
|
||||
|
||||
|
||||
//TODO: add a new class thing that can track the central axis point so that
|
||||
// geometry can be rebuilt with new flags with correct facing geometry built
|
||||
// (could also make it so that it emits 3x the amount of draw calls, but that seems very bad idea)
|
||||
|
||||
|
||||
private interface Transition2DCallback {
|
||||
void callback(int x, int z);
|
||||
}
|
||||
private static final class TransitionRing2D {
|
||||
private final int triggerRangeSquared;
|
||||
private final int shiftSize;
|
||||
private final Transition2DCallback enter;
|
||||
private final Transition2DCallback exit;
|
||||
private final int[] cornerPoints;
|
||||
private final int radius;
|
||||
|
||||
private int lastUpdateX;
|
||||
private int lastUpdateZ;
|
||||
|
||||
private int currentX;
|
||||
private int currentZ;
|
||||
|
||||
//Note radius is in shiftScale
|
||||
private TransitionRing2D(int shiftSize, int radius, Transition2DCallback onEntry, Transition2DCallback onExit) {
|
||||
this(shiftSize, radius, onEntry, onExit, 0, 0, 0);
|
||||
}
|
||||
private TransitionRing2D(int shiftSize, int radius, Transition2DCallback onEntry, Transition2DCallback onExit, int ix, int iy, int iz) {
|
||||
//trigger just less than every shiftSize scale
|
||||
this.triggerRangeSquared = 1<<((shiftSize<<1) - 1);
|
||||
this.shiftSize = shiftSize;
|
||||
this.enter = onEntry;
|
||||
this.exit = onExit;
|
||||
this.cornerPoints = RingUtil.generatingBoundingCorner2D(radius);
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
private long Prel(int x, int z) {
|
||||
return (Integer.toUnsignedLong(this.currentZ + z)<<32)|Integer.toUnsignedLong(this.currentX + x);
|
||||
}
|
||||
|
||||
public void update(int x, int z) {
|
||||
long dx = this.lastUpdateX - x;
|
||||
long dz = this.lastUpdateZ - z;
|
||||
long distSquared = dx*dx + dz*dz;
|
||||
if (distSquared < this.triggerRangeSquared) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Update the last update position
|
||||
int maxStep = this.triggerRangeSquared/2;
|
||||
this.lastUpdateX += Math.min(maxStep,Math.max(-maxStep, x-this.lastUpdateX));
|
||||
this.lastUpdateZ += Math.min(maxStep,Math.max(-maxStep, z-this.lastUpdateZ));
|
||||
|
||||
//Compute movement if it happened
|
||||
int nx = x>>this.shiftSize;
|
||||
int nz = z>>this.shiftSize;
|
||||
|
||||
if (nx == this.currentX && nz == this.currentZ) {
|
||||
//No movement
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//FIXME: not right, needs to only call load/unload on entry and exit, cause atm its acting like a loaded circle
|
||||
|
||||
Long2IntOpenHashMap ops = new Long2IntOpenHashMap();
|
||||
while (true) {
|
||||
int dir = nz < this.currentZ ? -1 : 1;
|
||||
if (nz != this.currentZ) {
|
||||
for (int corner : this.cornerPoints) {
|
||||
int cx = corner >>> 16;
|
||||
int cz = corner & 0xFFFF;
|
||||
|
||||
ops.addTo(Prel(cx, cz + Math.max(0, dir)), dir);
|
||||
ops.addTo(Prel(cx, -cz + Math.min(0, dir)), -dir);
|
||||
if (cx != 0) {
|
||||
ops.addTo(Prel(-cx, cz + Math.max(0, dir)), dir);
|
||||
ops.addTo(Prel(-cx, -cz + Math.min(0, dir)), -dir);
|
||||
}
|
||||
}
|
||||
|
||||
this.currentZ += dir;
|
||||
}
|
||||
|
||||
dir = nx < this.currentX ? -1 : 1;
|
||||
if (nx != this.currentX) {
|
||||
for (int corner : this.cornerPoints) {
|
||||
int cx = corner & 0xFFFF;
|
||||
int cz = corner >>> 16;
|
||||
|
||||
ops.addTo(Prel(cx + Math.max(0, dir), cz), dir);
|
||||
ops.addTo(Prel(-cx + Math.min(0, dir), cz), -dir);
|
||||
if (cz != 0) {
|
||||
ops.addTo(Prel(cx + Math.max(0, dir), -cz), dir);
|
||||
ops.addTo(Prel(-cx + Math.min(0, dir), -cz), -dir);
|
||||
}
|
||||
}
|
||||
|
||||
this.currentX += dir;
|
||||
}
|
||||
|
||||
//Only break once the coords match
|
||||
if (nx == this.currentX && nz == this.currentZ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ops.forEach((pos,val)->{
|
||||
if (val > 0) {
|
||||
this.enter.callback((int) (long)pos, (int) (pos>>32));
|
||||
}
|
||||
if (val < 0) {
|
||||
this.exit.callback((int) (long)pos, (int) (pos>>32));
|
||||
}
|
||||
});
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
public void fill(int x, int z) {
|
||||
this.fill(x, z, null);
|
||||
}
|
||||
|
||||
public void fill(int x, int z, Transition2DCallback outsideCallback) {
|
||||
int cx = x>>this.shiftSize;
|
||||
int cz = z>>this.shiftSize;
|
||||
|
||||
int r2 = this.radius*this.radius;
|
||||
for (int a = -this.radius; a <= this.radius; a++) {
|
||||
//IntStream.range(-this.radius, this.radius+1).parallel().forEach(a->{
|
||||
int b = (int) Math.floor(Math.sqrt(r2-(a*a)));
|
||||
for (int c = -b; c <= b; c++) {
|
||||
this.enter.callback(a + cx, c + cz);
|
||||
}
|
||||
if (outsideCallback != null) {
|
||||
for (int c = -this.radius; c < -b; c++) {
|
||||
outsideCallback.callback(a + cx, c + cz);
|
||||
}
|
||||
|
||||
for (int c = b+1; c <= this.radius; c++) {
|
||||
outsideCallback.callback(a + cx, c + cz);
|
||||
}
|
||||
}
|
||||
}//);
|
||||
}
|
||||
|
||||
public void setCenter(int x, int z) {
|
||||
int cx = x>>this.shiftSize;
|
||||
int cz = z>>this.shiftSize;
|
||||
this.currentX = cx;
|
||||
this.currentZ = cz;
|
||||
this.lastUpdateX = x + (((int)(Math.random()*4))<<(this.shiftSize-4));
|
||||
this.lastUpdateZ = z + (((int)(Math.random()*4))<<(this.shiftSize-4));
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
public void update(int x, int z) {
|
||||
int MAX_STEPS_PER_UPDATE = 1;
|
||||
|
||||
|
||||
long dx = this.lastUpdateX - x;
|
||||
long dz = this.lastUpdateZ - z;
|
||||
long distSquared = dx*dx + dz*dz;
|
||||
if (distSquared < this.triggerRangeSquared) {
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO: fixme: this last update needs to be incremented by a delta since
|
||||
|
||||
//Update the last update position
|
||||
int maxStep = this.triggerRangeSquared/2;
|
||||
this.lastUpdateX += Math.min(maxStep,Math.max(-maxStep, x-this.lastUpdateX));
|
||||
this.lastUpdateZ += Math.min(maxStep,Math.max(-maxStep, z-this.lastUpdateZ));
|
||||
|
||||
|
||||
|
||||
//Compute movement if it happened
|
||||
int nx = x>>this.shiftSize;
|
||||
int nz = z>>this.shiftSize;
|
||||
|
||||
if (nx == this.currentX && nz == this.currentZ) {
|
||||
//No movement
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//FIXME: not right, needs to only call load/unload on entry and exit, cause atm its acting like a loaded circle
|
||||
|
||||
Long2IntOpenHashMap ops = new Long2IntOpenHashMap();
|
||||
|
||||
int zcount = MAX_STEPS_PER_UPDATE;
|
||||
int dir = nz<this.currentZ?-1:1;
|
||||
while (nz != this.currentZ) {
|
||||
for (int corner : this.cornerPoints) {
|
||||
int cx = corner>>>16;
|
||||
int cz = corner&0xFFFF;
|
||||
|
||||
ops.addTo(Prel( cx, cz+Math.max(0, dir)), dir);
|
||||
ops.addTo(Prel( cx,-cz+Math.min(0, dir)),-dir);
|
||||
if (cx != 0) {
|
||||
ops.addTo(Prel(-cx, cz+Math.max(0, dir)), dir);
|
||||
ops.addTo(Prel(-cx,-cz+Math.min(0, dir)),-dir);
|
||||
}
|
||||
}
|
||||
|
||||
//ops.addTo(Prel(0, this.radius+Math.max(0, dir)), dir);
|
||||
//ops.addTo(Prel(0, -this.radius+Math.min(0, dir)), -dir);
|
||||
|
||||
this.currentZ += dir;
|
||||
|
||||
if (--zcount == 0) break;
|
||||
}
|
||||
|
||||
int xcount = MAX_STEPS_PER_UPDATE;
|
||||
dir = nx<this.currentX?-1:1;
|
||||
while (nx != this.currentX) {
|
||||
|
||||
for (int corner : this.cornerPoints) {
|
||||
int cx = corner&0xFFFF;
|
||||
int cz = corner>>>16;
|
||||
|
||||
ops.addTo(Prel( cx+Math.max(0, dir), cz), dir);
|
||||
ops.addTo(Prel(-cx+Math.min(0, dir), cz),-dir);
|
||||
if (cz != 0) {
|
||||
ops.addTo(Prel(cx + Math.max(0, dir), -cz), dir);
|
||||
ops.addTo(Prel(-cx + Math.min(0, dir), -cz), -dir);
|
||||
}
|
||||
}
|
||||
|
||||
this.currentX += dir;
|
||||
|
||||
if (--xcount == 0) break;
|
||||
}
|
||||
|
||||
|
||||
ops.forEach((pos,val)->{
|
||||
if (val > 0) {
|
||||
this.enter.callback((int) (long)pos, (int) (pos>>32));
|
||||
}
|
||||
if (val < 0) {
|
||||
this.exit.callback((int) (long)pos, (int) (pos>>32));
|
||||
}
|
||||
});
|
||||
ops.clear();
|
||||
}*/
|
||||
@@ -1,9 +0,0 @@
|
||||
package me.cortex.voxy.client.core;
|
||||
|
||||
import me.cortex.voxy.client.core.VoxelCore;
|
||||
|
||||
public interface IGetVoxelCore {
|
||||
VoxelCore getVoxelCore();
|
||||
|
||||
void reloadVoxelCore();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package me.cortex.voxy.client.core;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
public interface IGetVoxyRenderSystem {
|
||||
VoxyRenderSystem getVoxyRenderSystem();
|
||||
void shutdownRenderer();
|
||||
void createRenderer();
|
||||
|
||||
static VoxyRenderSystem getNullable() {
|
||||
var lr = (IGetVoxyRenderSystem)Minecraft.getInstance().levelRenderer;
|
||||
if (lr == null) return null;
|
||||
return lr.getVoxyRenderSystem();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package me.cortex.voxy.client.core;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
|
||||
import me.cortex.voxy.client.core.rendering.post.FullscreenBlit;
|
||||
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
|
||||
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.client.iris.IrisVoxyRenderPipelineData;
|
||||
import net.irisshaders.iris.shaderpack.materialmap.WorldRenderingSettings;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
|
||||
import static org.lwjgl.opengl.GL30C.*;
|
||||
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
|
||||
import static org.lwjgl.opengl.GL45C.*;
|
||||
|
||||
public class IrisVoxyRenderPipeline extends AbstractRenderPipeline {
|
||||
private final IrisVoxyRenderPipelineData data;
|
||||
private final FullscreenBlit depthBlit = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag");
|
||||
public final DepthFramebuffer fbTranslucent = new DepthFramebuffer(this.fb.getFormat());
|
||||
|
||||
private final GlBuffer shaderUniforms;
|
||||
|
||||
public IrisVoxyRenderPipeline(IrisVoxyRenderPipelineData data, AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
|
||||
super(nodeManager, nodeCleaner, traversal, frexSupplier, data.shouldDeferTranslucency());
|
||||
this.data = data;
|
||||
if (this.data.thePipeline != null) {
|
||||
throw new IllegalStateException("Pipeline data already bound");
|
||||
}
|
||||
this.data.thePipeline = this;
|
||||
|
||||
//Bind the drawbuffers
|
||||
var oDT = this.data.opaqueDrawTargets;
|
||||
int[] binding = new int[oDT.length];
|
||||
for (int i = 0; i < oDT.length; i++) {
|
||||
binding[i] = GL30.GL_COLOR_ATTACHMENT0+i;
|
||||
glNamedFramebufferTexture(this.fb.framebuffer.id, GL30.GL_COLOR_ATTACHMENT0+i, oDT[i], 0);
|
||||
}
|
||||
glNamedFramebufferDrawBuffers(this.fb.framebuffer.id, binding);
|
||||
|
||||
var tDT = this.data.translucentDrawTargets;
|
||||
binding = new int[tDT.length];
|
||||
for (int i = 0; i < tDT.length; i++) {
|
||||
binding[i] = GL30.GL_COLOR_ATTACHMENT0+i;
|
||||
glNamedFramebufferTexture(this.fbTranslucent.framebuffer.id, GL30.GL_COLOR_ATTACHMENT0+i, tDT[i], 0);
|
||||
}
|
||||
glNamedFramebufferDrawBuffers(this.fbTranslucent.framebuffer.id, binding);
|
||||
|
||||
this.fb.framebuffer.verify();
|
||||
this.fbTranslucent.framebuffer.verify();
|
||||
|
||||
if (data.getUniforms() != null) {
|
||||
this.shaderUniforms = new GlBuffer(data.getUniforms().size());
|
||||
} else {
|
||||
this.shaderUniforms = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupExtraModelBakeryData(ModelBakerySubsystem modelService) {
|
||||
modelService.factory.setCustomBlockStateMapping(WorldRenderingSettings.INSTANCE.getBlockStateIds());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
if (this.data.thePipeline != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.data.thePipeline = null;
|
||||
|
||||
this.depthBlit.delete();
|
||||
this.fbTranslucent.free();
|
||||
|
||||
if (this.shaderUniforms != null) {
|
||||
this.shaderUniforms.free();
|
||||
}
|
||||
|
||||
super.free0();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preSetup(Viewport<?> viewport) {
|
||||
super.preSetup(viewport);
|
||||
if (this.shaderUniforms != null) {
|
||||
//Update the uniforms
|
||||
long ptr = UploadStream.INSTANCE.uploadTo(this.shaderUniforms);
|
||||
this.data.getUniforms().updater().accept(ptr);
|
||||
UploadStream.INSTANCE.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int setup(Viewport<?> viewport, int sourceFramebuffer, int srcWidth, int srcHeight) {
|
||||
|
||||
this.fb.resize(viewport.width, viewport.height);
|
||||
this.fbTranslucent.resize(viewport.width, viewport.height);
|
||||
|
||||
if (false) {//TODO: only do this if shader specifies
|
||||
//Clear the colour component
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this.fb.framebuffer.id);
|
||||
glClearColor(0, 0, 0, 0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
if (!this.data.useViewportDims) {
|
||||
srcWidth = viewport.width;
|
||||
srcHeight = viewport.height;
|
||||
}
|
||||
this.initDepthStencil(sourceFramebuffer, this.fb.framebuffer.id, srcWidth, srcHeight, viewport.width, viewport.height);
|
||||
return this.fb.getDepthTex().id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postOpaquePreTranslucent(Viewport<?> viewport) {
|
||||
int msk = GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT;
|
||||
if (true) {//TODO: make shader specified
|
||||
if (false) {//TODO: only do this if shader specifies
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this.fbTranslucent.framebuffer.id);
|
||||
glClearColor(0, 0, 0, 0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
} else {
|
||||
msk |= GL_COLOR_BUFFER_BIT;
|
||||
}
|
||||
glBlitNamedFramebuffer(this.fb.framebuffer.id, this.fbTranslucent.framebuffer.id, 0,0, viewport.width, viewport.height, 0,0, viewport.width, viewport.height, msk, GL_NEAREST);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finish(Viewport<?> viewport, int sourceFrameBuffer, int srcWidth, int srcHeight) {
|
||||
if (this.data.renderToVanillaDepth && srcWidth == viewport.width && srcHeight == viewport.height) {//We can only depthblit out if destination size is the same
|
||||
glColorMask(false, false, false, false);
|
||||
AbstractRenderPipeline.transformBlitDepth(this.depthBlit,
|
||||
this.fbTranslucent.getDepthTex().id, sourceFrameBuffer,
|
||||
viewport, new Matrix4f(viewport.vanillaProjection).mul(viewport.modelView));
|
||||
glColorMask(true, true, true, true);
|
||||
} else {
|
||||
// normally disabled by AbstractRenderPipeline but since we are skipping it we do it here
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void bindUniforms() {
|
||||
this.bindUniforms(UNIFORM_BINDING_POINT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindUniforms(int bindingPoint) {
|
||||
if (this.shaderUniforms != null) {
|
||||
GL30.glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, this.shaderUniforms.id);// todo: dont randomly select this to 5
|
||||
}
|
||||
}
|
||||
|
||||
private void doBindings() {
|
||||
this.bindUniforms();
|
||||
if (this.data.getSsboSet() != null) {
|
||||
this.data.getSsboSet().bindingFunction().accept(10);
|
||||
}
|
||||
if (this.data.getImageSet() != null) {
|
||||
this.data.getImageSet().bindingFunction().accept(6);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void setupAndBindOpaque(Viewport<?> viewport) {
|
||||
this.fb.bind();
|
||||
this.doBindings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupAndBindTranslucent(Viewport<?> viewport) {
|
||||
this.fbTranslucent.bind();
|
||||
this.doBindings();
|
||||
if (this.data.getBlender() != null) {
|
||||
this.data.getBlender().run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDebug(List<String> debug) {
|
||||
debug.add("Using: " + this.getClass().getSimpleName());
|
||||
super.addDebug(debug);
|
||||
}
|
||||
|
||||
private static final int UNIFORM_BINDING_POINT = 5;//TODO make ths binding point... not randomly 5
|
||||
|
||||
private StringBuilder buildGenericShaderHeader(AbstractSectionRenderer<?, ?> renderer, String input) {
|
||||
StringBuilder builder = new StringBuilder(input).append("\n\n\n");
|
||||
|
||||
if (this.data.getUniforms() != null) {
|
||||
builder.append("layout(binding = "+UNIFORM_BINDING_POINT+", std140) uniform ShaderUniformBindings ")
|
||||
.append(this.data.getUniforms().layout())
|
||||
.append(";\n\n");
|
||||
}
|
||||
|
||||
if (this.data.getSsboSet() != null) {
|
||||
builder.append("#define BUFFER_BINDING_INDEX_BASE 10\n");//TODO: DONT RANDOMLY MAKE THIS 10
|
||||
builder.append(this.data.getSsboSet().layout()).append("\n\n");
|
||||
}
|
||||
|
||||
if (this.data.getImageSet() != null) {
|
||||
builder.append("#define BASE_SAMPLER_BINDING_INDEX 6\n");//TODO: DONT RANDOMLY MAKE THIS 6
|
||||
builder.append(this.data.getImageSet().layout()).append("\n\n");
|
||||
}
|
||||
|
||||
return builder.append("\n\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String patchOpaqueShader(AbstractSectionRenderer<?, ?> renderer, String input) {
|
||||
var builder = this.buildGenericShaderHeader(renderer, input);
|
||||
|
||||
builder.append(this.data.opaqueFragPatch());
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String patchTranslucentShader(AbstractSectionRenderer<?, ?> renderer, String input) {
|
||||
if (this.data.translucentFragPatch() == null) return null;
|
||||
|
||||
var builder = this.buildGenericShaderHeader(renderer, input);
|
||||
builder.append(this.data.translucentFragPatch());
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String taaFunction(String functionName) {
|
||||
return this.taaFunction(UNIFORM_BINDING_POINT, functionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String taaFunction(int uboBindingPoint, String functionName) {
|
||||
var builder = new StringBuilder();
|
||||
|
||||
if (this.data.getUniforms() != null) {
|
||||
builder.append("layout(binding = "+uboBindingPoint+", std140) uniform ShaderUniformBindings ")
|
||||
.append(this.data.getUniforms().layout())
|
||||
.append(";\n\n");
|
||||
}
|
||||
|
||||
builder.append("vec2 ").append(functionName).append("()\n");
|
||||
builder.append(this.data.TAA);
|
||||
builder.append("\n");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getRenderScalingFactor() {
|
||||
return this.data.resolutionScale;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package me.cortex.voxy.client.core;
|
||||
|
||||
import me.cortex.voxy.common.config.VoxyConfig;
|
||||
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
|
||||
import me.cortex.voxy.client.core.rendering.post.FullscreenBlit;
|
||||
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import static org.lwjgl.opengl.ARBComputeShader.glDispatchCompute;
|
||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glBindImageTexture;
|
||||
import static org.lwjgl.opengl.GL11.GL_BLEND;
|
||||
import static org.lwjgl.opengl.GL11.GL_ONE;
|
||||
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.glEnable;
|
||||
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
|
||||
import static org.lwjgl.opengl.GL11C.GL_RGBA8;
|
||||
import static org.lwjgl.opengl.GL14.glBlendFuncSeparate;
|
||||
import static org.lwjgl.opengl.GL15.GL_READ_WRITE;
|
||||
import static org.lwjgl.opengl.GL30C.*;
|
||||
import static org.lwjgl.opengl.GL43.GL_DEPTH_STENCIL_TEXTURE_MODE;
|
||||
import static org.lwjgl.opengl.GL45C.glBindTextureUnit;
|
||||
import static org.lwjgl.opengl.GL45C.glTextureParameterf;
|
||||
|
||||
public class NormalRenderPipeline extends AbstractRenderPipeline {
|
||||
private GlTexture colourTex;
|
||||
private GlTexture colourSSAOTex;
|
||||
private final GlFramebuffer fbSSAO = new GlFramebuffer();
|
||||
|
||||
private final boolean useEnvFog;
|
||||
private final FullscreenBlit finalBlit;
|
||||
|
||||
private final Shader ssaoCompute = Shader.make()
|
||||
.add(ShaderType.COMPUTE, "voxy:post/ssao.comp")
|
||||
.compile();
|
||||
|
||||
protected NormalRenderPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
|
||||
super(nodeManager, nodeCleaner, traversal, frexSupplier, false);
|
||||
this.useEnvFog = VoxyConfig.CONFIG.useEnvironmentalFog;
|
||||
this.finalBlit = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag",
|
||||
a->a.defineIf("USE_ENV_FOG", this.useEnvFog).define("EMIT_COLOUR"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int setup(Viewport<?> viewport, int sourceFB, int srcWidth, int srcHeight) {
|
||||
if (this.colourTex == null || this.colourTex.getHeight() != viewport.height || this.colourTex.getWidth() != viewport.width) {
|
||||
if (this.colourTex != null) {
|
||||
this.colourTex.free();
|
||||
this.colourSSAOTex.free();
|
||||
}
|
||||
this.fb.resize(viewport.width, viewport.height);
|
||||
|
||||
this.colourTex = new GlTexture().store(GL_RGBA8, 1, viewport.width, viewport.height);
|
||||
this.colourSSAOTex = new GlTexture().store(GL_RGBA8, 1, viewport.width, viewport.height);
|
||||
|
||||
this.fb.framebuffer.bind(GL_COLOR_ATTACHMENT0, this.colourTex).verify();
|
||||
this.fbSSAO.bind(this.fb.getDepthAttachmentType(), this.fb.getDepthTex()).bind(GL_COLOR_ATTACHMENT0, this.colourSSAOTex).verify();
|
||||
|
||||
|
||||
glTextureParameterf(this.colourTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTextureParameterf(this.colourTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTextureParameterf(this.colourSSAOTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTextureParameterf(this.colourSSAOTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTextureParameterf(this.fb.getDepthTex().id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
|
||||
}
|
||||
|
||||
this.initDepthStencil(sourceFB, this.fb.framebuffer.id, viewport.width, viewport.height, viewport.width, viewport.height);
|
||||
|
||||
return this.fb.getDepthTex().id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postOpaquePreTranslucent(Viewport<?> viewport) {
|
||||
this.ssaoCompute.bind();
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
long ptr = stack.nmalloc(4*4*4);
|
||||
viewport.MVP.getToAddress(ptr);
|
||||
nglUniformMatrix4fv(3, 1, false, ptr);//MVP
|
||||
viewport.MVP.invert(new Matrix4f()).getToAddress(ptr);
|
||||
nglUniformMatrix4fv(4, 1, false, ptr);//invMVP
|
||||
}
|
||||
|
||||
|
||||
glBindImageTexture(0, this.colourSSAOTex.id, 0, false,0, GL_READ_WRITE, GL_RGBA8);
|
||||
glBindTextureUnit(1, this.fb.getDepthTex().id);
|
||||
glBindTextureUnit(2, this.colourTex.id);
|
||||
|
||||
glDispatchCompute((viewport.width+31)/32, (viewport.height+31)/32, 1);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this.fbSSAO.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finish(Viewport<?> viewport, int sourceFrameBuffer, int srcWidth, int srcHeight) {
|
||||
this.finalBlit.bind();
|
||||
if (this.useEnvFog) {
|
||||
float start = viewport.fogParameters.environmentalStart();
|
||||
float end = viewport.fogParameters.environmentalEnd();
|
||||
if (Math.abs(end-start)>1) {
|
||||
float invEndFogDelta = 1f / (end - start);
|
||||
float endDistance = Math.max(Minecraft.getInstance().gameRenderer.getRenderDistance(), 20*16);//TODO: make this constant a config option
|
||||
endDistance *= (float)Math.sqrt(3);
|
||||
float startDelta = -start * invEndFogDelta;
|
||||
glUniform4f(4, invEndFogDelta, startDelta, Math.clamp(endDistance*invEndFogDelta+startDelta, 0, 1),0);//
|
||||
glUniform4f(5, viewport.fogParameters.red(), viewport.fogParameters.green(), viewport.fogParameters.blue(), viewport.fogParameters.alpha());
|
||||
} else {
|
||||
glUniform4f(4, 0, 0, 0, 0);
|
||||
glUniform4f(5, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
glBindTextureUnit(3, this.colourSSAOTex.id);
|
||||
|
||||
//Do alpha blending
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
AbstractRenderPipeline.transformBlitDepth(this.finalBlit, this.fb.getDepthTex().id, sourceFrameBuffer, viewport, new Matrix4f(viewport.vanillaProjection).mul(viewport.modelView));
|
||||
glDisable(GL_BLEND);
|
||||
//glBlitNamedFramebuffer(this.fbSSAO.id, sourceFrameBuffer, 0,0, viewport.width, viewport.height, 0,0, viewport.width, viewport.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupAndBindOpaque(Viewport<?> viewport) {
|
||||
this.fb.bind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupAndBindTranslucent(Viewport<?> viewport) {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this.fbSSAO.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
this.finalBlit.delete();
|
||||
this.ssaoCompute.free();
|
||||
this.fbSSAO.free();
|
||||
if (this.colourTex != null) {
|
||||
this.colourTex.free();
|
||||
this.colourSSAOTex.free();
|
||||
}
|
||||
super.free0();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package me.cortex.voxy.client.core;
|
||||
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
|
||||
import me.cortex.voxy.client.core.util.IrisUtil;
|
||||
import me.cortex.voxy.client.iris.IGetIrisVoxyPipelineData;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import net.irisshaders.iris.Iris;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
public class RenderPipelineFactory {
|
||||
public static AbstractRenderPipeline createPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
|
||||
//Note this is where will choose/create e.g. IrisRenderPipeline or normal pipeline
|
||||
AbstractRenderPipeline pipeline = null;
|
||||
if (IrisUtil.IRIS_INSTALLED && IrisUtil.SHADER_SUPPORT) {
|
||||
pipeline = createIrisPipeline(nodeManager, nodeCleaner, traversal, frexSupplier);
|
||||
}
|
||||
if (pipeline == null) {
|
||||
pipeline = new NormalRenderPipeline(nodeManager, nodeCleaner, traversal, frexSupplier);
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
private static AbstractRenderPipeline createIrisPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
|
||||
var irisPipe = Iris.getPipelineManager().getPipelineNullable();
|
||||
if (irisPipe == null) {
|
||||
return null;
|
||||
}
|
||||
if (irisPipe instanceof IGetIrisVoxyPipelineData getVoxyPipeData) {
|
||||
var pipeData = getVoxyPipeData.voxy$getPipelineData();
|
||||
if (pipeData == null) {
|
||||
return null;
|
||||
}
|
||||
Logger.info("Creating voxy iris render pipeline");
|
||||
try {
|
||||
return new IrisVoxyRenderPipeline(pipeData, nodeManager, nodeCleaner, traversal, frexSupplier);
|
||||
} catch (Exception e) {
|
||||
Logger.error("Failed to create iris render pipeline", e);
|
||||
IrisUtil.disableIrisShaders();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package me.cortex.voxy.client.core;
|
||||
|
||||
import me.cortex.voxy.client.core.rendering.AbstractFarWorldRenderer;
|
||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import org.vivecraft.client_vr.ClientDataHolderVR;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ViewportSelector <T extends Viewport> {
|
||||
public static final boolean VIVECRAFT_INSTALLED = FabricLoader.getInstance().isModLoaded("vivecraft");
|
||||
|
||||
private final Supplier<T> creator;
|
||||
private final T defaultViewport;
|
||||
private final Map<Object, T> extraViewports = new HashMap<>();
|
||||
|
||||
public ViewportSelector(Supplier<T> viewportCreator) {
|
||||
this.creator = viewportCreator;
|
||||
this.defaultViewport = viewportCreator.get();
|
||||
}
|
||||
|
||||
private T getVivecraftViewport() {
|
||||
var cdh = ClientDataHolderVR.getInstance();
|
||||
var pass = cdh.currentPass;
|
||||
if (pass == null) {
|
||||
return this.defaultViewport;
|
||||
}
|
||||
return this.extraViewports.computeIfAbsent(pass, a->this.creator.get());
|
||||
}
|
||||
|
||||
public T getViewport() {
|
||||
if (VIVECRAFT_INSTALLED) {
|
||||
return getVivecraftViewport();
|
||||
}
|
||||
return this.defaultViewport;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.defaultViewport.delete();
|
||||
this.extraViewports.values().forEach(Viewport::delete);
|
||||
this.extraViewports.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
package me.cortex.voxy.client.core;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import me.cortex.voxy.client.Voxy;
|
||||
import me.cortex.voxy.client.config.VoxyConfig;
|
||||
import me.cortex.voxy.client.core.rendering.*;
|
||||
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
||||
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
|
||||
import me.cortex.voxy.client.core.util.IrisUtil;
|
||||
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.cortex.voxy.client.importers.WorldImporter;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.hud.ClientBossBar;
|
||||
import net.minecraft.client.render.Camera;
|
||||
import net.minecraft.client.render.Frustum;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.entity.boss.BossBar;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
|
||||
|
||||
//Core class that ingests new data from sources and updates the required systems
|
||||
|
||||
//3 primary services:
|
||||
// ingest service: this takes in unloaded chunk events from the client, processes the chunk and critically also updates the lod view of the world
|
||||
// render data builder service: this service builds the render data from build requests it also handles the collecting of build data for the selected region (only axis aligned single lod tasks)
|
||||
// serialization service: serializes changed world data and ensures that the database and any loaded data are in sync such that the database can never be more updated than loaded data, also performs compression on serialization
|
||||
|
||||
//there are multiple subsystems
|
||||
//player tracker system (determines what lods are loaded and used by the player)
|
||||
//updating system (triggers render data rebuilds when something from the ingest service causes an LOD change)
|
||||
//the render system simply renders what data it has, its responsable for gpu memory layouts in arenas and rendering in an optimal way, it makes no requests back to any of the other systems or services, it just applies render data updates
|
||||
|
||||
//There is strict forward only dataflow
|
||||
//Ingest -> world engine -> raw render data -> render data
|
||||
public class VoxelCore {
|
||||
private final WorldEngine world;
|
||||
private final DistanceTracker distanceTracker;
|
||||
private final RenderGenerationService renderGen;
|
||||
private final RenderTracker renderTracker;
|
||||
|
||||
private final AbstractFarWorldRenderer renderer;
|
||||
private final ViewportSelector viewportSelector;
|
||||
private final PostProcessing postProcessing;
|
||||
|
||||
//private final Thread shutdownThread = new Thread(this::shutdown);
|
||||
|
||||
private WorldImporter importer;
|
||||
public VoxelCore(ContextSelectionSystem.Selection worldSelection) {
|
||||
this.world = worldSelection.createEngine();
|
||||
var cfg = worldSelection.getConfig();
|
||||
System.out.println("Initializing voxy core");
|
||||
|
||||
//Trigger the shared index buffer loading
|
||||
SharedIndexBuffer.INSTANCE.id();
|
||||
Capabilities.init();//Ensure clinit is called
|
||||
this.renderer = this.createRenderBackend();
|
||||
this.viewportSelector = new ViewportSelector<>(this.renderer::createViewport);
|
||||
System.out.println("Renderer initialized");
|
||||
|
||||
this.renderTracker = new RenderTracker(this.world, this.renderer);
|
||||
this.renderGen = new RenderGenerationService(this.world, this.renderer.getModelManager(), VoxyConfig.CONFIG.renderThreads, this.renderTracker::processBuildResult, this.renderer.usesMeshlets());
|
||||
this.world.setDirtyCallback(this.renderTracker::sectionUpdated);
|
||||
this.renderTracker.setRenderGen(this.renderGen);
|
||||
System.out.println("Render tracker and generator initialized");
|
||||
|
||||
//To get to chunk scale multiply the scale by 2, the scale is after how many chunks does the lods halve
|
||||
int q = VoxyConfig.CONFIG.qualityScale;
|
||||
int minY = MinecraftClient.getInstance().world.getBottomSectionCoord()/2;
|
||||
int maxY = MinecraftClient.getInstance().world.getTopSectionCoord()/2;
|
||||
|
||||
if (cfg.minYOverride != Integer.MAX_VALUE) {
|
||||
minY = cfg.minYOverride;
|
||||
}
|
||||
|
||||
if (cfg.maxYOverride != Integer.MIN_VALUE) {
|
||||
maxY = cfg.maxYOverride;
|
||||
}
|
||||
|
||||
this.distanceTracker = new DistanceTracker(this.renderTracker, new int[]{q,q,q,q},
|
||||
(VoxyConfig.CONFIG.renderDistance<0?VoxyConfig.CONFIG.renderDistance:((VoxyConfig.CONFIG.renderDistance+1)/2)),
|
||||
minY, maxY);
|
||||
System.out.println("Distance tracker initialized");
|
||||
|
||||
this.postProcessing = new PostProcessing();
|
||||
|
||||
this.world.getMapper().setCallbacks(this.renderer::addBlockState, this.renderer::addBiome);
|
||||
|
||||
|
||||
////Resave the db incase it failed a recovery
|
||||
//this.world.getMapper().forceResaveStates();
|
||||
|
||||
var biomeRegistry = MinecraftClient.getInstance().world.getRegistryManager().get(RegistryKeys.BIOME);
|
||||
for (var biome : this.world.getMapper().getBiomeEntries()) {
|
||||
//this.renderer.getModelManager().addBiome(biome.id, biomeRegistry.get(new Identifier(biome.biome)));
|
||||
this.renderer.addBiome(biome);
|
||||
}
|
||||
|
||||
for (var state : this.world.getMapper().getStateEntries()) {
|
||||
//this.renderer.getModelManager().addEntry(state.id, state.state);
|
||||
this.renderer.addBlockState(state);
|
||||
}
|
||||
//this.renderer.getModelManager().updateEntry(0, Blocks.GRASS_BLOCK.getDefaultState());
|
||||
|
||||
System.out.println("Voxy core initialized");
|
||||
}
|
||||
|
||||
private AbstractFarWorldRenderer<?,?> createRenderBackend() {
|
||||
if (false) {
|
||||
System.out.println("Using Gl46MeshletFarWorldRendering");
|
||||
return new Gl46MeshletsFarWorldRenderer(VoxyConfig.CONFIG.geometryBufferSize, VoxyConfig.CONFIG.maxSections);
|
||||
} else {
|
||||
if (VoxyConfig.CONFIG.useMeshShaders()) {
|
||||
System.out.println("Using NvMeshFarWorldRenderer");
|
||||
return new NvMeshFarWorldRenderer(VoxyConfig.CONFIG.geometryBufferSize, VoxyConfig.CONFIG.maxSections);
|
||||
} else {
|
||||
System.out.println("Using Gl46FarWorldRenderer");
|
||||
return new Gl46FarWorldRenderer(VoxyConfig.CONFIG.geometryBufferSize, VoxyConfig.CONFIG.maxSections);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void enqueueIngest(WorldChunk worldChunk) {
|
||||
this.world.ingestService.enqueueIngest(worldChunk);
|
||||
}
|
||||
|
||||
boolean firstTime = true;
|
||||
public void renderSetup(Frustum frustum, Camera camera) {
|
||||
if (this.firstTime) {
|
||||
this.distanceTracker.init(camera.getBlockPos().getX(), camera.getBlockPos().getZ());
|
||||
this.firstTime = false;
|
||||
//this.renderTracker.addLvl0(0,6,0);
|
||||
}
|
||||
this.distanceTracker.setCenter(camera.getBlockPos().getX(), camera.getBlockPos().getY(), camera.getBlockPos().getZ());
|
||||
this.renderer.setupRender(frustum, camera);
|
||||
}
|
||||
|
||||
private static Matrix4f makeProjectionMatrix(float near, float far) {
|
||||
//TODO: use the existing projection matrix use mulLocal by the inverse of the projection and then mulLocal our projection
|
||||
|
||||
var projection = new Matrix4f();
|
||||
var client = MinecraftClient.getInstance();
|
||||
var gameRenderer = client.gameRenderer;//tickCounter.getTickDelta(true);
|
||||
|
||||
float fov = (float) gameRenderer.getFov(gameRenderer.getCamera(), client.getRenderTickCounter().getTickDelta(true), true);
|
||||
|
||||
projection.setPerspective(fov * 0.01745329238474369f,
|
||||
(float) client.getWindow().getFramebufferWidth() / (float)client.getWindow().getFramebufferHeight(),
|
||||
near, far);
|
||||
return projection;
|
||||
}
|
||||
|
||||
private static Matrix4f computeProjectionMat() {
|
||||
return new Matrix4f(RenderSystem.getProjectionMatrix()).mulLocal(
|
||||
makeProjectionMatrix(0.05f, MinecraftClient.getInstance().gameRenderer.getFarPlaneDistance()).invert()
|
||||
).mulLocal(makeProjectionMatrix(16, 16*3000));
|
||||
}
|
||||
|
||||
public void renderOpaque(MatrixStack matrices, double cameraX, double cameraY, double cameraZ) {
|
||||
if (IrisUtil.irisShadowActive()) {
|
||||
return;
|
||||
}
|
||||
matrices.push();
|
||||
matrices.translate(-cameraX, -cameraY, -cameraZ);
|
||||
matrices.pop();
|
||||
//this.renderer.getModelManager().updateEntry(0, Blocks.DIRT_PATH.getDefaultState());
|
||||
|
||||
//this.renderer.getModelManager().updateEntry(0, Blocks.COMPARATOR.getDefaultState());
|
||||
//this.renderer.getModelManager().updateEntry(0, Blocks.OAK_LEAVES.getDefaultState());
|
||||
|
||||
//var fb = Iris.getPipelineManager().getPipelineNullable().getSodiumTerrainPipeline().getTerrainSolidFramebuffer();
|
||||
//fb.bind();
|
||||
|
||||
var projection = computeProjectionMat();
|
||||
//var projection = RenderSystem.getProjectionMatrix();//computeProjectionMat();
|
||||
var viewport = this.viewportSelector.getViewport();
|
||||
viewport
|
||||
.setProjection(projection)
|
||||
.setModelView(matrices.peek().getPositionMatrix())
|
||||
.setCamera(cameraX, cameraY, cameraZ)
|
||||
.setScreenSize(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight);
|
||||
|
||||
int boundFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
|
||||
this.postProcessing.setup(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight, boundFB);
|
||||
|
||||
this.renderer.renderFarAwayOpaque(viewport);
|
||||
|
||||
//Compute the SSAO of the rendered terrain
|
||||
this.postProcessing.computeSSAO(projection, matrices);
|
||||
|
||||
//We can render the translucent directly after as it is the furthest translucent objects
|
||||
this.renderer.renderFarAwayTranslucent(viewport);
|
||||
|
||||
|
||||
this.postProcessing.renderPost(projection, RenderSystem.getProjectionMatrix(), boundFB);
|
||||
|
||||
}
|
||||
|
||||
public void addDebugInfo(List<String> debug) {
|
||||
debug.add("");
|
||||
debug.add("");
|
||||
debug.add("Voxy Core: " + Voxy.VERSION);
|
||||
/*
|
||||
debug.add("Ingest service tasks: " + this.world.ingestService.getTaskCount());
|
||||
debug.add("Saving service tasks: " + this.world.savingService.getTaskCount());
|
||||
debug.add("Render service tasks: " + this.renderGen.getTaskCount());
|
||||
*/
|
||||
debug.add("I/S/R tasks: " + this.world.ingestService.getTaskCount() + "/"+this.world.savingService.getTaskCount()+"/"+this.renderGen.getTaskCount());
|
||||
debug.add("Loaded cache sizes: " + Arrays.toString(this.world.getLoadedSectionCacheSizes()));
|
||||
debug.add("Mesh cache count: " + this.renderGen.getMeshCacheCount());
|
||||
this.renderer.addDebugData(debug);
|
||||
}
|
||||
|
||||
//Note: when doing translucent rendering, only need to sort when generating the geometry, or when crossing into the center zone
|
||||
// cause in 99.99% of cases the sections dont need to be sorted
|
||||
// since they are AABBS crossing the normal is impossible without one of the axis being equal
|
||||
|
||||
public void shutdown() {
|
||||
//if (Thread.currentThread() != this.shutdownThread) {
|
||||
// Runtime.getRuntime().removeShutdownHook(this.shutdownThread);
|
||||
//}
|
||||
|
||||
//this.world.getMapper().forceResaveStates();
|
||||
if (this.importer != null) {
|
||||
System.out.println("Shutting down importer");
|
||||
try {this.importer.shutdown();this.importer = null;} catch (Exception e) {System.err.println(e);}
|
||||
}
|
||||
System.out.println("Shutting down voxel core");
|
||||
try {this.renderGen.shutdown();} catch (Exception e) {System.err.println(e);}
|
||||
System.out.println("Render gen shut down");
|
||||
try {this.world.shutdown();} catch (Exception e) {System.err.println(e);}
|
||||
System.out.println("World engine shut down");
|
||||
try {this.renderer.shutdown(); this.viewportSelector.free();} catch (Exception e) {System.err.println(e);}
|
||||
System.out.println("Renderer shut down");
|
||||
if (this.postProcessing!=null){try {this.postProcessing.shutdown();} catch (Exception e) {System.err.println(e);}}
|
||||
System.out.println("Voxel core shut down");
|
||||
}
|
||||
|
||||
public boolean createWorldImporter(World mcWorld, File worldPath) {
|
||||
if (this.importer != null) {
|
||||
return false;
|
||||
}
|
||||
var importer = new WorldImporter(this.world, mcWorld);
|
||||
var bossBar = new ClientBossBar(MathHelper.randomUuid(), Text.of("Voxy world importer"), 0.0f, BossBar.Color.GREEN, BossBar.Style.PROGRESS, false, false, false);
|
||||
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.put(bossBar.getUuid(), bossBar);
|
||||
importer.importWorldAsyncStart(worldPath, 4, (a,b)->
|
||||
MinecraftClient.getInstance().executeSync(()-> {
|
||||
bossBar.setPercent(((float) a)/((float) b));
|
||||
bossBar.setName(Text.of("Voxy import: "+ a+"/"+b + " region files"));
|
||||
}),
|
||||
()-> {
|
||||
MinecraftClient.getInstance().executeSync(()-> {
|
||||
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.remove(bossBar.getUuid());
|
||||
String msg = "Voxy world import finished";
|
||||
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.literal(msg));
|
||||
System.err.println(msg);
|
||||
});
|
||||
this.importer = null;
|
||||
});
|
||||
this.importer = importer;
|
||||
return true;
|
||||
}
|
||||
|
||||
public WorldEngine getWorldEngine() {
|
||||
return this.world;
|
||||
}
|
||||
}
|
||||
513
src/main/java/me/cortex/voxy/client/core/VoxyRenderSystem.java
Normal file
513
src/main/java/me/cortex/voxy/client/core/VoxyRenderSystem.java
Normal file
@@ -0,0 +1,513 @@
|
||||
package me.cortex.voxy.client.core;
|
||||
|
||||
import com.mojang.blaze3d.opengl.GlConst;
|
||||
import com.mojang.blaze3d.opengl.GlStateManager;
|
||||
import me.cortex.voxy.client.TimingStatistics;
|
||||
import me.cortex.voxy.client.VoxyClient;
|
||||
import me.cortex.voxy.common.config.VoxyConfig;
|
||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||
import me.cortex.voxy.client.core.model.ModelStore;
|
||||
import me.cortex.voxy.client.core.rendering.ChunkBoundRenderer;
|
||||
import me.cortex.voxy.client.core.rendering.RenderDistanceTracker;
|
||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||
import me.cortex.voxy.client.core.rendering.ViewportSelector;
|
||||
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
|
||||
import me.cortex.voxy.client.core.rendering.section.IUsesMeshlets;
|
||||
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
|
||||
import me.cortex.voxy.client.core.rendering.section.backend.mdic.MDICSectionRenderer;
|
||||
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
|
||||
import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData;
|
||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.client.core.util.GPUTiming;
|
||||
import me.cortex.voxy.client.core.util.IrisUtil;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.thread.ServiceManager;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
|
||||
import net.caffeinemc.mods.sodium.client.util.FogParameters;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_VIEWPORT;
|
||||
import static org.lwjgl.opengl.GL11.glGetIntegerv;
|
||||
import static org.lwjgl.opengl.GL11C.*;
|
||||
import static org.lwjgl.opengl.GL30C.*;
|
||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
|
||||
import static org.lwjgl.opengl.GL43C.GL_SHADER_STORAGE_BUFFER_BINDING;
|
||||
|
||||
import me.cortex.voxy.client.VoxyClientInstance;
|
||||
import me.cortex.voxy.common.config.VoxyServerConfig;
|
||||
|
||||
public class VoxyRenderSystem {
|
||||
private final WorldEngine worldIn;
|
||||
|
||||
|
||||
private final ModelBakerySubsystem modelService;
|
||||
private final RenderGenerationService renderGen;
|
||||
private final IGeometryData geometryData;
|
||||
private final AsyncNodeManager nodeManager;
|
||||
private final NodeCleaner nodeCleaner;
|
||||
private final HierarchicalOcclusionTraverser traversal;
|
||||
|
||||
|
||||
private final RenderDistanceTracker renderDistanceTracker;
|
||||
public final ChunkBoundRenderer chunkBoundRenderer;
|
||||
|
||||
private final ViewportSelector<?> viewportSelector;
|
||||
|
||||
private final AbstractRenderPipeline pipeline;
|
||||
|
||||
private static AbstractSectionRenderer.Factory<?,? extends IGeometryData> getRenderBackendFactory() {
|
||||
//TODO: need todo a thing where selects optimal section render based on if supports the pipeline and geometry data type
|
||||
return MDICSectionRenderer.FACTORY;
|
||||
}
|
||||
|
||||
public VoxyRenderSystem(WorldEngine world, ServiceManager sm) {
|
||||
//Keep the world loaded, NOTE: this is done FIRST, to keep and ensure that even if the rest of loading takes more
|
||||
// than timeout, we keep the world acquired
|
||||
world.acquireRef();
|
||||
System.gc();
|
||||
|
||||
if (Minecraft.getInstance().options.getEffectiveRenderDistance()<3) {
|
||||
Logger.warn("Having a vanilla render distance of 2 can cause rare culling near the edge of your screen issues, please use 3 or more");
|
||||
}
|
||||
|
||||
//Fking HATE EVERYTHING AAAAAAAAAAAAAAAA
|
||||
int[] oldBufferBindings = new int[10];
|
||||
for (int i = 0; i < oldBufferBindings.length; i++) {
|
||||
oldBufferBindings[i] = glGetIntegeri(GL_SHADER_STORAGE_BUFFER_BINDING, i);
|
||||
}
|
||||
|
||||
try {
|
||||
//wait for opengl to be finished, this should hopefully ensure all memory allocations are free
|
||||
glFinish();
|
||||
glFinish();
|
||||
|
||||
this.worldIn = world;
|
||||
|
||||
long geometryCapacity = getGeometryBufferSize();
|
||||
var backendFactory = getRenderBackendFactory();
|
||||
|
||||
{
|
||||
this.modelService = new ModelBakerySubsystem(world.getMapper());
|
||||
this.renderGen = new RenderGenerationService(world, this.modelService, sm, IUsesMeshlets.class.isAssignableFrom(backendFactory.clz()));
|
||||
|
||||
this.geometryData = new BasicSectionGeometryData(1 << 20, geometryCapacity);
|
||||
|
||||
this.nodeManager = new AsyncNodeManager(1 << 21, this.geometryData, this.renderGen);
|
||||
this.nodeCleaner = new NodeCleaner(this.nodeManager);
|
||||
this.traversal = new HierarchicalOcclusionTraverser(this.nodeManager, this.nodeCleaner, this.renderGen);
|
||||
|
||||
world.setDirtyCallback(this.nodeManager::worldEvent);
|
||||
|
||||
Arrays.stream(world.getMapper().getBiomeEntries()).forEach(this.modelService::addBiome);
|
||||
world.getMapper().setBiomeCallback(this.modelService::addBiome);
|
||||
|
||||
this.nodeManager.start();
|
||||
}
|
||||
|
||||
this.pipeline = RenderPipelineFactory.createPipeline(this.nodeManager, this.nodeCleaner, this.traversal, this::frexStillHasWork);
|
||||
this.pipeline.setupExtraModelBakeryData(this.modelService);//Configure the model service
|
||||
var sectionRenderer = backendFactory.create(this.pipeline, this.modelService.getStore(), this.geometryData);
|
||||
this.pipeline.setSectionRenderer(sectionRenderer);
|
||||
this.viewportSelector = new ViewportSelector<>(sectionRenderer::createViewport);
|
||||
|
||||
{
|
||||
int minSec = Minecraft.getInstance().level.getMinSectionY() >> 5;
|
||||
int maxSec = (Minecraft.getInstance().level.getMaxSectionY() - 1) >> 5;
|
||||
|
||||
//Do some very cheeky stuff for MiB
|
||||
if (VoxyCommon.IS_MINE_IN_ABYSS) {//TODO: make this somehow configurable
|
||||
minSec = -8;
|
||||
maxSec = 7;
|
||||
}
|
||||
|
||||
this.renderDistanceTracker = new RenderDistanceTracker(20,
|
||||
minSec,
|
||||
maxSec,
|
||||
this.nodeManager::addTopLevel,
|
||||
this.nodeManager::removeTopLevel);
|
||||
|
||||
this.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
|
||||
}
|
||||
|
||||
this.chunkBoundRenderer = new ChunkBoundRenderer(this.pipeline);
|
||||
|
||||
Logger.info("Voxy render system created with " + geometryCapacity + " geometry capacity, using pipeline '" + this.pipeline.getClass().getSimpleName() + "' with renderer '" + sectionRenderer.getClass().getSimpleName() + "'");
|
||||
} catch (RuntimeException e) {
|
||||
world.releaseRef();//If something goes wrong, we must release the world first
|
||||
throw e;
|
||||
}
|
||||
|
||||
for (int i = 0; i < oldBufferBindings.length; i++) {
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, i, oldBufferBindings[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
GlStateManager._activeTexture(GlConst.GL_TEXTURE0+i);
|
||||
GlStateManager._bindTexture(0);
|
||||
glBindSampler(i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Viewport<?> setupViewport(ChunkRenderMatrices matrices, FogParameters fogParameters, double cameraX, double cameraY, double cameraZ) {
|
||||
var viewport = this.getViewport();
|
||||
if (viewport == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Do some very cheeky stuff for MiB
|
||||
if (VoxyCommon.IS_MINE_IN_ABYSS) {
|
||||
int sector = (((int)Math.floor(cameraX)>>4)+512)>>10;
|
||||
cameraX -= sector<<14;//10+4
|
||||
cameraY += (16+(256-32-sector*30))*16;
|
||||
}
|
||||
|
||||
//cameraY += 100;
|
||||
var projection = computeProjectionMat(matrices.projection());//RenderSystem.getProjectionMatrix();
|
||||
//var projection = ShadowMatrices.createOrthoMatrix(160, -16*300, 16*300);
|
||||
//var projection = new Matrix4f(matrices.projection());
|
||||
|
||||
int[] dims = new int[4];
|
||||
glGetIntegerv(GL_VIEWPORT, dims);
|
||||
|
||||
int width = dims[2];
|
||||
int height = dims[3];
|
||||
|
||||
{//Apply render scaling factor
|
||||
var factor = this.pipeline.getRenderScalingFactor();
|
||||
if (factor != null) {
|
||||
width = (int) (width*factor[0]);
|
||||
height = (int) (height*factor[1]);
|
||||
}
|
||||
}
|
||||
|
||||
viewport
|
||||
.setVanillaProjection(matrices.projection())
|
||||
.setProjection(projection)
|
||||
.setModelView(new Matrix4f(matrices.modelView()))
|
||||
.setCamera(cameraX, cameraY, cameraZ)
|
||||
.setScreenSize(width, height)
|
||||
.setFogParameters(fogParameters)
|
||||
.update();
|
||||
|
||||
if (VoxyClient.getOcclusionDebugState()==0) {
|
||||
viewport.frameId++;
|
||||
}
|
||||
|
||||
return viewport;
|
||||
}
|
||||
|
||||
public void renderOpaque(Viewport<?> viewport) {
|
||||
if (viewport == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TimingStatistics.resetSamplers();
|
||||
|
||||
long startTime = System.nanoTime();
|
||||
TimingStatistics.all.start();
|
||||
GPUTiming.INSTANCE.marker();//Start marker
|
||||
TimingStatistics.main.start();
|
||||
|
||||
//TODO: optimize
|
||||
int[] oldBufferBindings = new int[10];
|
||||
for (int i = 0; i < oldBufferBindings.length; i++) {
|
||||
oldBufferBindings[i] = glGetIntegeri(GL_SHADER_STORAGE_BUFFER_BINDING, i);
|
||||
}
|
||||
|
||||
|
||||
int oldFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
|
||||
int boundFB = oldFB;
|
||||
|
||||
int[] dims = new int[4];
|
||||
glGetIntegerv(GL_VIEWPORT, dims);
|
||||
|
||||
glViewport(0,0, viewport.width, viewport.height);
|
||||
|
||||
//var target = DefaultTerrainRenderPasses.CUTOUT.getTarget();
|
||||
//boundFB = ((net.minecraft.client.texture.GlTexture) target.getColorAttachment()).getOrCreateFramebuffer(((GlBackend) RenderSystem.getDevice()).getFramebufferManager(), target.getDepthAttachment());
|
||||
if (boundFB == 0) {
|
||||
throw new IllegalStateException("Cannot use the default framebuffer as cannot source from it");
|
||||
}
|
||||
|
||||
//this.autoBalanceSubDivSize();
|
||||
|
||||
this.pipeline.preSetup(viewport);
|
||||
|
||||
TimingStatistics.E.start();
|
||||
if ((!VoxyClient.disableSodiumChunkRender())&&!IrisUtil.irisShadowActive()) {
|
||||
this.chunkBoundRenderer.render(viewport);
|
||||
} else {
|
||||
viewport.depthBoundingBuffer.clear(0);
|
||||
}
|
||||
TimingStatistics.E.stop();
|
||||
|
||||
|
||||
GPUTiming.INSTANCE.marker();
|
||||
//The entire rendering pipeline (excluding the chunkbound thing)
|
||||
this.pipeline.runPipeline(viewport, boundFB, dims[2], dims[3]);
|
||||
GPUTiming.INSTANCE.marker();
|
||||
|
||||
|
||||
TimingStatistics.main.stop();
|
||||
TimingStatistics.postDynamic.start();
|
||||
|
||||
PrintfDebugUtil.tick();
|
||||
|
||||
//As much dynamic runtime stuff here
|
||||
{
|
||||
//Tick upload stream (this is ok to do here as upload ticking is just memory management)
|
||||
UploadStream.INSTANCE.tick();
|
||||
|
||||
while (this.renderDistanceTracker.setCenterAndProcess(viewport.cameraX, viewport.cameraZ) && VoxyClient.isFrexActive());//While FF is active, run until everything is processed
|
||||
TimingStatistics.H.start();
|
||||
//Done here as is allows less gl state resetup
|
||||
do { this.modelService.tick(900_000); } while (VoxyClient.isFrexActive() && !this.modelService.areQueuesEmpty());
|
||||
TimingStatistics.H.stop();
|
||||
}
|
||||
GPUTiming.INSTANCE.marker();
|
||||
TimingStatistics.postDynamic.stop();
|
||||
|
||||
GPUTiming.INSTANCE.tick();
|
||||
|
||||
glBindFramebuffer(GlConst.GL_FRAMEBUFFER, oldFB);
|
||||
glViewport(dims[0], dims[1], dims[2], dims[3]);
|
||||
|
||||
{//Reset state manager stuffs
|
||||
glUseProgram(0);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
GlStateManager._glBindVertexArray(0);//Clear binding
|
||||
|
||||
GlStateManager._activeTexture(GlConst.GL_TEXTURE1);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
GlStateManager._activeTexture(GlConst.GL_TEXTURE0+i);
|
||||
GlStateManager._bindTexture(0);
|
||||
glBindSampler(i, 0);
|
||||
}
|
||||
|
||||
IrisUtil.clearIrisSamplers();//Thanks iris (sigh)
|
||||
|
||||
//TODO: should/needto actually restore all of these, not just clear them
|
||||
//Clear all the bindings
|
||||
for (int i = 0; i < oldBufferBindings.length; i++) {
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, i, oldBufferBindings[i]);
|
||||
}
|
||||
|
||||
//((SodiumShader) Iris.getPipelineManager().getPipelineNullable().getSodiumPrograms().getProgram(DefaultTerrainRenderPasses.CUTOUT).getInterface()).setupState(DefaultTerrainRenderPasses.CUTOUT, fogParameters);
|
||||
}
|
||||
|
||||
TimingStatistics.all.stop();
|
||||
|
||||
//TimingStatistics.I.start();
|
||||
//glFlush();
|
||||
//TimingStatistics.I.stop();
|
||||
|
||||
/*
|
||||
TimingStatistics.F.start();
|
||||
this.postProcessing.setup(viewport.width, viewport.height, boundFB);
|
||||
TimingStatistics.F.stop();
|
||||
|
||||
this.renderer.renderFarAwayOpaque(viewport, this.chunkBoundRenderer.getDepthBoundTexture());
|
||||
|
||||
|
||||
TimingStatistics.F.start();
|
||||
//Compute the SSAO of the rendered terrain, TODO: fix it breaking depth or breaking _something_ am not sure what
|
||||
this.postProcessing.computeSSAO(viewport.MVP);
|
||||
TimingStatistics.F.stop();
|
||||
|
||||
TimingStatistics.G.start();
|
||||
//We can render the translucent directly after as it is the furthest translucent objects
|
||||
this.renderer.renderFarAwayTranslucent(viewport, this.chunkBoundRenderer.getDepthBoundTexture());
|
||||
TimingStatistics.G.stop();
|
||||
|
||||
|
||||
TimingStatistics.F.start();
|
||||
this.postProcessing.renderPost(viewport, matrices.projection(), boundFB);
|
||||
TimingStatistics.F.stop();
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void autoBalanceSubDivSize() {
|
||||
//only increase quality while there are very few mesh queues, this stops,
|
||||
// e.g. while flying and is rendering alot of low quality chunks
|
||||
boolean canDecreaseSize = this.renderGen.getTaskCount() < 300;
|
||||
int MIN_FPS = 55;
|
||||
int MAX_FPS = 65;
|
||||
float INCREASE_PER_SECOND = 60;
|
||||
float DECREASE_PER_SECOND = 30;
|
||||
//Auto fps targeting
|
||||
if (Minecraft.getInstance().getFps() < MIN_FPS) {
|
||||
VoxyConfig.CONFIG.subDivisionSize = Math.min(VoxyConfig.CONFIG.subDivisionSize + INCREASE_PER_SECOND / Math.max(1f, Minecraft.getInstance().getFps()), 256);
|
||||
}
|
||||
|
||||
if (MAX_FPS < Minecraft.getInstance().getFps() && canDecreaseSize) {
|
||||
VoxyConfig.CONFIG.subDivisionSize = Math.max(VoxyConfig.CONFIG.subDivisionSize - DECREASE_PER_SECOND / Math.max(1f, Minecraft.getInstance().getFps()), 28);
|
||||
}
|
||||
}
|
||||
|
||||
private static Matrix4f makeProjectionMatrix(float near, float far) {
|
||||
//TODO: use the existing projection matrix use mulLocal by the inverse of the projection and then mulLocal our projection
|
||||
|
||||
var projection = new Matrix4f();
|
||||
var client = Minecraft.getInstance();
|
||||
var gameRenderer = client.gameRenderer;//tickCounter.getTickDelta(true);
|
||||
|
||||
float fov = gameRenderer.getFov(gameRenderer.getMainCamera(), client.getDeltaTracker().getGameTimeDeltaPartialTick(true), true);
|
||||
|
||||
projection.setPerspective(fov * 0.01745329238474369f,
|
||||
(float) client.getWindow().getWidth() / (float)client.getWindow().getHeight(),
|
||||
near, far);
|
||||
return projection;
|
||||
}
|
||||
|
||||
//TODO: Make a reverse z buffer
|
||||
private static Matrix4f computeProjectionMat(Matrix4fc base) {
|
||||
//THis is a wild and insane problem to have
|
||||
// at short render distances the vanilla terrain doesnt end up covering the 16f near plane voxy uses
|
||||
// meaning that it explodes (due to near plane clipping).. _badly_ with the rastered culling being wrong in rare cases for the immediate
|
||||
// sections rendered after the vanilla render distance
|
||||
float nearVoxy = Minecraft.getInstance().gameRenderer.getRenderDistance()<=32.0f?8f:16f;
|
||||
nearVoxy = VoxyClient.disableSodiumChunkRender()?0.1f:nearVoxy;
|
||||
|
||||
return base.mulLocal(
|
||||
makeProjectionMatrix(0.05f, Minecraft.getInstance().gameRenderer.getDepthFar()).invert(),
|
||||
new Matrix4f()
|
||||
).mulLocal(makeProjectionMatrix(nearVoxy, 16*3000));
|
||||
}
|
||||
|
||||
private boolean frexStillHasWork() {
|
||||
if (!VoxyClient.isFrexActive()) {
|
||||
return false;
|
||||
}
|
||||
//If frex is running we must tick everything to ensure correctness
|
||||
UploadStream.INSTANCE.tick();
|
||||
//Done here as is allows less gl state resetup
|
||||
this.modelService.tick(100_000_000);
|
||||
GL11.glFinish();
|
||||
return this.nodeManager.hasWork() || this.renderGen.getTaskCount()!=0 || !this.modelService.areQueuesEmpty();
|
||||
}
|
||||
|
||||
public void setRenderDistance(int renderDistance) {
|
||||
this.renderDistanceTracker.setRenderDistance(renderDistance);
|
||||
}
|
||||
|
||||
public Viewport<?> getViewport() {
|
||||
if (IrisUtil.irisShadowActive()) {
|
||||
return null;
|
||||
}
|
||||
return this.viewportSelector.getViewport();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void addDebugInfo(List<String> debug) {
|
||||
debug.add("Buf/Tex [#/Mb]: [" + GlBuffer.getCount() + "/" + (GlBuffer.getTotalSize()/1_000_000) + "],[" + GlTexture.getCount() + "/" + (GlTexture.getEstimatedTotalSize()/1_000_000)+"]");
|
||||
{
|
||||
this.modelService.addDebugData(debug);
|
||||
this.renderGen.addDebugData(debug);
|
||||
this.nodeManager.addDebug(debug);
|
||||
this.pipeline.addDebug(debug);
|
||||
}
|
||||
{
|
||||
TimingStatistics.update();
|
||||
debug.add("Voxy frame runtime (millis): " + TimingStatistics.dynamic.pVal() + ", " + TimingStatistics.main.pVal()+ ", " + TimingStatistics.postDynamic.pVal()+ ", " + TimingStatistics.all.pVal());
|
||||
debug.add("Extra time: " + TimingStatistics.A.pVal() + ", " + TimingStatistics.B.pVal() + ", " + TimingStatistics.C.pVal() + ", " + TimingStatistics.D.pVal());
|
||||
debug.add("Extra 2 time: " + TimingStatistics.E.pVal() + ", " + TimingStatistics.F.pVal() + ", " + TimingStatistics.G.pVal() + ", " + TimingStatistics.H.pVal() + ", " + TimingStatistics.I.pVal());
|
||||
}
|
||||
debug.add(GPUTiming.INSTANCE.getDebug());
|
||||
|
||||
if (VoxyCommon.getInstance() instanceof VoxyClientInstance clientInstance) {
|
||||
long lastUpdate = clientInstance.getLastLodUpdate();
|
||||
long timeSince = System.currentTimeMillis() - lastUpdate;
|
||||
debug.add("LOD Updates: " + clientInstance.getLodUpdatesReceived() + " (Last: " + timeSince + "ms ago)");
|
||||
debug.add("Server View Dist: " + clientInstance.getServerViewDistance());
|
||||
}
|
||||
|
||||
PrintfDebugUtil.addToOut(debug);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
Logger.info("Flushing download stream");
|
||||
DownloadStream.INSTANCE.flushWaitClear();
|
||||
Logger.info("Shutting down rendering");
|
||||
try {
|
||||
//Cleanup callbacks
|
||||
this.worldIn.setDirtyCallback(null);
|
||||
this.worldIn.getMapper().setBiomeCallback(null);
|
||||
this.worldIn.getMapper().setStateCallback(null);
|
||||
|
||||
this.nodeManager.stop();
|
||||
|
||||
this.modelService.shutdown();
|
||||
this.renderGen.shutdown();
|
||||
this.traversal.free();
|
||||
this.nodeCleaner.free();
|
||||
|
||||
this.geometryData.free();
|
||||
this.chunkBoundRenderer.free();
|
||||
|
||||
this.viewportSelector.free();
|
||||
} catch (Exception e) {Logger.error("Error shutting down renderer components", e);}
|
||||
Logger.info("Shutting down render pipeline");
|
||||
try {this.pipeline.free();} catch (Exception e){Logger.error("Error releasing render pipeline", e);}
|
||||
|
||||
|
||||
|
||||
Logger.info("Flushing download stream");
|
||||
DownloadStream.INSTANCE.flushWaitClear();
|
||||
|
||||
//Release hold on the world
|
||||
this.worldIn.releaseRef();
|
||||
Logger.info("Render shutdown completed");
|
||||
}
|
||||
|
||||
private static long getGeometryBufferSize() {
|
||||
long geometryCapacity = Math.min((1L<<(64-Long.numberOfLeadingZeros(Capabilities.INSTANCE.ssboMaxSize-1)))<<1, 1L<<32)-1024/*(1L<<32)-1024*/;
|
||||
if (Capabilities.INSTANCE.isIntel) {
|
||||
geometryCapacity = Math.max(geometryCapacity, 1L<<30);//intel moment, force min 1gb
|
||||
}
|
||||
|
||||
//Limit to available dedicated memory if possible
|
||||
if (Capabilities.INSTANCE.canQueryGpuMemory) {
|
||||
//512mb less than avalible,
|
||||
long limit = Capabilities.INSTANCE.getFreeDedicatedGpuMemory() - (long)(1.5*1024*1024*1024);//1.5gb vram buffer
|
||||
// Give a minimum of 512 mb requirement
|
||||
limit = Math.max(512*1024*1024, limit);
|
||||
|
||||
geometryCapacity = Math.min(geometryCapacity, limit);
|
||||
}
|
||||
//geometryCapacity = 1<<28;
|
||||
//geometryCapacity = 1<<30;//1GB test
|
||||
var override = System.getProperty("voxy.geometryBufferSizeOverrideMB", "");
|
||||
if (!override.isEmpty()) {
|
||||
geometryCapacity = Long.parseLong(override)*1024L*1024L;
|
||||
}
|
||||
return geometryCapacity;
|
||||
}
|
||||
|
||||
public WorldEngine getEngine() {
|
||||
return this.worldIn;
|
||||
}
|
||||
}
|
||||
217
src/main/java/me/cortex/voxy/client/core/gl/Capabilities.java
Normal file
217
src/main/java/me/cortex/voxy/client/core/gl/Capabilities.java
Normal file
@@ -0,0 +1,217 @@
|
||||
package me.cortex.voxy.client.core.gl;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GL11C;
|
||||
import org.lwjgl.opengl.GL20C;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_NEAREST;
|
||||
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
|
||||
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
|
||||
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
|
||||
import static org.lwjgl.opengl.GL15.glDeleteBuffers;
|
||||
import static org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL;
|
||||
import static org.lwjgl.opengl.GL30C.GL_MAP_READ_BIT;
|
||||
import static org.lwjgl.opengl.GL32.glGetInteger64;
|
||||
import static org.lwjgl.opengl.GL43C.GL_MAX_SHADER_STORAGE_BLOCK_SIZE;
|
||||
import static org.lwjgl.opengl.GL44.GL_DYNAMIC_STORAGE_BIT;
|
||||
import static org.lwjgl.opengl.GL44.GL_MAP_COHERENT_BIT;
|
||||
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi;
|
||||
import static org.lwjgl.opengl.GL45C.*;
|
||||
import static org.lwjgl.opengl.GL45C.glCreateFramebuffers;
|
||||
import static org.lwjgl.opengl.NVXGPUMemoryInfo.*;
|
||||
|
||||
public class Capabilities {
|
||||
|
||||
public static final Capabilities INSTANCE = new Capabilities();
|
||||
|
||||
public final boolean repFragTest;
|
||||
public final boolean meshShaders;
|
||||
public final boolean INT64_t;
|
||||
public final long ssboMaxSize;
|
||||
public final boolean isMesa;
|
||||
public final boolean canQueryGpuMemory;
|
||||
public final long totalDedicatedMemory;//Bytes, dedicated memory
|
||||
public final long totalDynamicMemory;//Bytes, total allocation memory - dedicated memory
|
||||
public final boolean compute;
|
||||
public final boolean indirectParameters;
|
||||
public final boolean isIntel;
|
||||
public final boolean subgroup;
|
||||
public final boolean sparseBuffer;
|
||||
public final boolean isNvidia;
|
||||
public final boolean isAmd;
|
||||
public final boolean nvBarryCoords;
|
||||
public final boolean hasBrokenDepthSampler;
|
||||
|
||||
public Capabilities() {
|
||||
var cap = GL.getCapabilities();
|
||||
this.sparseBuffer = cap.GL_ARB_sparse_buffer;
|
||||
this.compute = cap.glDispatchComputeIndirect != 0;
|
||||
this.indirectParameters = cap.glMultiDrawElementsIndirectCountARB != 0;
|
||||
this.repFragTest = cap.GL_NV_representative_fragment_test;
|
||||
this.meshShaders = cap.GL_NV_mesh_shader;
|
||||
this.canQueryGpuMemory = cap.GL_NVX_gpu_memory_info;
|
||||
//this.INT64_t = cap.GL_ARB_gpu_shader_int64 || cap.GL_AMD_gpu_shader_int64;
|
||||
//The only reliable way to test for int64 support is to try compile a shader
|
||||
this.INT64_t = testShaderCompilesOk(ShaderType.COMPUTE, """
|
||||
#version 430
|
||||
#extension GL_ARB_gpu_shader_int64 : require
|
||||
layout(local_size_x=32) in;
|
||||
void main() {
|
||||
uint64_t a = 1234;
|
||||
}
|
||||
""");
|
||||
if (cap.GL_KHR_shader_subgroup) {
|
||||
this.subgroup = testShaderCompilesOk(ShaderType.COMPUTE, """
|
||||
#version 430
|
||||
#extension GL_KHR_shader_subgroup_basic : require
|
||||
#extension GL_KHR_shader_subgroup_arithmetic : require
|
||||
layout(local_size_x=32) in;
|
||||
void main() {
|
||||
uint a = subgroupExclusiveAdd(gl_LocalInvocationIndex);
|
||||
}
|
||||
""");
|
||||
} else {
|
||||
this.subgroup = false;
|
||||
}
|
||||
|
||||
this.ssboMaxSize = glGetInteger64(GL_MAX_SHADER_STORAGE_BLOCK_SIZE);
|
||||
|
||||
this.isMesa = glGetString(GL_VERSION).toLowerCase(Locale.ROOT).contains("mesa");
|
||||
var vendor = glGetString(GL_VENDOR).toLowerCase(Locale.ROOT);
|
||||
this.isIntel = vendor.contains("intel");
|
||||
this.isNvidia = vendor.contains("nvidia");
|
||||
this.isAmd = vendor.contains("amd")||vendor.contains("radeon");
|
||||
|
||||
if (this.canQueryGpuMemory) {
|
||||
this.totalDedicatedMemory = glGetInteger64(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX)*1024;//Since its in Kb
|
||||
this.totalDynamicMemory = (glGetInteger64(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX)*1024) - this.totalDedicatedMemory;//Since its in Kb
|
||||
} else {
|
||||
this.totalDedicatedMemory = -1;
|
||||
this.totalDynamicMemory = -1;
|
||||
}
|
||||
|
||||
this.nvBarryCoords = cap.GL_NV_fragment_shader_barycentric;
|
||||
|
||||
if (this.compute&&this.isAmd) {
|
||||
this.hasBrokenDepthSampler = testDepthSampler();
|
||||
if (this.hasBrokenDepthSampler) {
|
||||
throw new IllegalStateException("it bork, amd is bork");
|
||||
}
|
||||
} else {
|
||||
this.hasBrokenDepthSampler = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
}
|
||||
|
||||
private static boolean testDepthSampler() {
|
||||
String src = """
|
||||
#version 460 core
|
||||
layout(local_size_x=16,local_size_y=16) in;
|
||||
|
||||
layout(binding = 0) uniform sampler2D depthSampler;
|
||||
layout(binding = 1) buffer OutData {
|
||||
float[] outData;
|
||||
};
|
||||
|
||||
layout(location = 2) uniform int dynamicSampleThing;
|
||||
layout(location = 3) uniform float sampleData;
|
||||
|
||||
void main() {
|
||||
if (abs(texelFetch(depthSampler, ivec2(gl_GlobalInvocationID.xy), dynamicSampleThing).r-sampleData)>0.000001f) {
|
||||
outData[0] = 1.0;
|
||||
}
|
||||
}
|
||||
""";
|
||||
int program = GL20C.glCreateProgram();
|
||||
{
|
||||
int shader = GL20C.glCreateShader(ShaderType.COMPUTE.gl);
|
||||
GL20C.glShaderSource(shader, src);
|
||||
GL20C.glCompileShader(shader);
|
||||
if (GL20C.glGetShaderi(shader, GL20C.GL_COMPILE_STATUS) != 1) {
|
||||
GL20C.glDeleteShader(shader);
|
||||
throw new IllegalStateException("Shader compile fail");
|
||||
}
|
||||
GL20C.glAttachShader(program, shader);
|
||||
GL20C.glLinkProgram(program);
|
||||
glDeleteShader(shader);
|
||||
}
|
||||
|
||||
int buffer = glCreateBuffers();
|
||||
glNamedBufferStorage(buffer, 4096, GL_DYNAMIC_STORAGE_BIT|GL_MAP_READ_BIT);
|
||||
|
||||
int tex = glCreateTextures(GL_TEXTURE_2D);
|
||||
glTextureStorage2D(tex, 2, GL_DEPTH24_STENCIL8, 256, 256);
|
||||
glTextureParameteri(tex, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTextureParameteri(tex, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
int fb = glCreateFramebuffers();
|
||||
boolean isCorrect = true;
|
||||
for (int lvl = 0; lvl <= 1; lvl++) {
|
||||
glNamedFramebufferTexture(fb, GL_DEPTH_STENCIL_ATTACHMENT, tex, lvl);
|
||||
|
||||
for (int i = 0; i <= 10; i++) {
|
||||
float value = (float) (i / 10.0);
|
||||
|
||||
nglClearNamedBufferSubData(buffer, GL_R32F, 0, 4096, GL_RED, GL_FLOAT, 0);//Zero the buffer
|
||||
glClearNamedFramebufferfi(fb, GL_DEPTH_STENCIL, 0, value, 1);//Set the depth texture
|
||||
|
||||
glUseProgram(program);
|
||||
glUniform1i(2, lvl);
|
||||
glUniform1f(3, value);
|
||||
glBindTextureUnit(0, tex);
|
||||
GL30.glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, buffer);
|
||||
|
||||
glDispatchCompute(256>>(lvl+4), 256>>(lvl+4), 1);
|
||||
glFinish();
|
||||
|
||||
long ptr = nglMapNamedBuffer(buffer, GL_READ_ONLY);
|
||||
float gottenValue = MemoryUtil.memGetFloat(ptr);
|
||||
glUnmapNamedBuffer(buffer);
|
||||
|
||||
glUseProgram(0);
|
||||
glBindTextureUnit(0, 0);
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
|
||||
|
||||
boolean localCorrect = gottenValue==0.0f;
|
||||
if (!localCorrect) {
|
||||
Logger.error("Depth read test failed at value: " + value);
|
||||
}
|
||||
isCorrect &= localCorrect;
|
||||
}
|
||||
}
|
||||
|
||||
glDeleteFramebuffers(fb);
|
||||
glDeleteTextures(tex);
|
||||
glDeleteBuffers(buffer);
|
||||
glDeleteProgram(program);
|
||||
return !isCorrect;
|
||||
}
|
||||
|
||||
private static boolean testShaderCompilesOk(ShaderType type, String src) {
|
||||
int shader = GL20C.glCreateShader(type.gl);
|
||||
GL20C.glShaderSource(shader, src);
|
||||
GL20C.glCompileShader(shader);
|
||||
int result = GL20C.glGetShaderi(shader, GL20C.GL_COMPILE_STATUS);
|
||||
GL20C.glDeleteShader(shader);
|
||||
|
||||
return result == GL20C.GL_TRUE;
|
||||
}
|
||||
|
||||
public long getFreeDedicatedGpuMemory() {
|
||||
if (!this.canQueryGpuMemory) {
|
||||
throw new IllegalStateException("Cannot query gpu memory, missing extension");
|
||||
}
|
||||
return glGetInteger64(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX)*1024;//Since its in Kb
|
||||
}
|
||||
|
||||
//TODO: add gpu eviction tracking
|
||||
}
|
||||
@@ -1,33 +1,95 @@
|
||||
package me.cortex.voxy.client.core.gl;
|
||||
|
||||
import me.cortex.voxy.common.util.TrackedObject;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import static org.lwjgl.opengl.ARBSparseBuffer.GL_SPARSE_STORAGE_BIT_ARB;
|
||||
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
|
||||
import static org.lwjgl.opengl.GL15.glDeleteBuffers;
|
||||
import static org.lwjgl.opengl.GL44C.glBufferStorage;
|
||||
import static org.lwjgl.opengl.GL45C.glCreateBuffers;
|
||||
import static org.lwjgl.opengl.GL45C.glNamedBufferStorage;
|
||||
import static org.lwjgl.opengl.GL45C.*;
|
||||
|
||||
public class GlBuffer extends TrackedObject {
|
||||
public final int id;
|
||||
private final long size;
|
||||
private final int flags;
|
||||
|
||||
private static int COUNT;
|
||||
private static long TOTAL_SIZE;
|
||||
|
||||
public GlBuffer(long size) {
|
||||
this(size, 0);
|
||||
}
|
||||
public GlBuffer(long size, boolean zero) {
|
||||
this(size, 0, zero);
|
||||
}
|
||||
|
||||
public GlBuffer(long size, int flags) {
|
||||
this(size, flags, true);
|
||||
}
|
||||
|
||||
public GlBuffer(long size, int flags, boolean zero) {
|
||||
this.flags = flags;
|
||||
this.id = glCreateBuffers();
|
||||
this.size = size;
|
||||
glNamedBufferStorage(this.id, size, flags);
|
||||
if ((flags&GL_SPARSE_STORAGE_BIT_ARB)==0 && zero) {
|
||||
this.zero();
|
||||
}
|
||||
|
||||
COUNT++;
|
||||
TOTAL_SIZE += size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
this.free0();
|
||||
glDeleteBuffers(this.id);
|
||||
|
||||
COUNT--;
|
||||
TOTAL_SIZE -= this.size;
|
||||
}
|
||||
|
||||
public boolean isSparse() {
|
||||
return (this.flags&GL_SPARSE_STORAGE_BIT_ARB)!=0;
|
||||
}
|
||||
|
||||
public long size() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public GlBuffer zero() {
|
||||
nglClearNamedBufferData(this.id, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlBuffer zeroRange(long offset, long size) {
|
||||
nglClearNamedBufferSubData(this.id, GL_R8UI, offset, size, GL_RED_INTEGER, GL_UNSIGNED_BYTE, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlBuffer fill(int data) {
|
||||
//Clear unpack values
|
||||
//Fixed in mesa commit a5c3c452
|
||||
glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
|
||||
glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
|
||||
|
||||
MemoryUtil.memPutInt(SCRATCH, data);
|
||||
nglClearNamedBufferData(this.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, SCRATCH);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static int getCount() {
|
||||
return COUNT;
|
||||
}
|
||||
|
||||
public static long getTotalSize() {
|
||||
return TOTAL_SIZE;
|
||||
}
|
||||
|
||||
public GlBuffer name(String name) {
|
||||
return GlDebug.name(name, this);
|
||||
}
|
||||
|
||||
private static final long SCRATCH = MemoryUtil.nmemAlloc(4);
|
||||
}
|
||||
|
||||
49
src/main/java/me/cortex/voxy/client/core/gl/GlDebug.java
Normal file
49
src/main/java/me/cortex/voxy/client/core/gl/GlDebug.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package me.cortex.voxy.client.core.gl;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
|
||||
import static org.lwjgl.opengl.GL43C.*;
|
||||
|
||||
public class GlDebug {
|
||||
public static final boolean GL_DEBUG = System.getProperty("voxy.glDebug", "false").equals("true");
|
||||
|
||||
|
||||
public static void push() {
|
||||
//glPushDebugGroup()
|
||||
}
|
||||
|
||||
public static GlBuffer name(String name, GlBuffer buffer) {
|
||||
if (GL_DEBUG) {
|
||||
glObjectLabel(GL_BUFFER, buffer.id, name);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static <T extends Shader> T name(String name, T shader) {
|
||||
if (GL_DEBUG) {
|
||||
glObjectLabel(GL_PROGRAM, shader.id(), name);
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
public static GlFramebuffer name(String name, GlFramebuffer framebuffer) {
|
||||
if (GL_DEBUG) {
|
||||
glObjectLabel(GL_FRAMEBUFFER, framebuffer.id, name);
|
||||
}
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
public static GlTexture name(String name, GlTexture texture) {
|
||||
if (GL_DEBUG) {
|
||||
glObjectLabel(GL_TEXTURE, texture.id, name);
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static GlPersistentMappedBuffer name(String name, GlPersistentMappedBuffer buffer) {
|
||||
if (GL_DEBUG) {
|
||||
glObjectLabel(GL_BUFFER, buffer.id, name);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package me.cortex.voxy.client.core.gl;
|
||||
|
||||
import me.cortex.voxy.common.util.TrackedObject;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import static org.lwjgl.opengl.GL32.*;
|
||||
|
||||
@@ -12,13 +13,24 @@ public class GlFence extends TrackedObject {
|
||||
this.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
}
|
||||
|
||||
private static final long SCRATCH = MemoryUtil.nmemCalloc(1,4);
|
||||
|
||||
public boolean signaled() {
|
||||
if (!this.signaled) {
|
||||
/*
|
||||
int ret = glClientWaitSync(this.fence, 0, 0);
|
||||
if (ret == GL_ALREADY_SIGNALED || ret == GL_CONDITION_SATISFIED) {
|
||||
this.signaled = true;
|
||||
} else if (ret != GL_TIMEOUT_EXPIRED) {
|
||||
throw new IllegalStateException("Poll for fence failed, glError: " + glGetError());
|
||||
}*/
|
||||
MemoryUtil.memPutInt(SCRATCH, -1);
|
||||
nglGetSynciv(this.fence, GL_SYNC_STATUS, 1, 0, SCRATCH);
|
||||
int val = MemoryUtil.memGetInt(SCRATCH);
|
||||
if (val == GL_SIGNALED) {
|
||||
this.signaled = true;
|
||||
} else if (val != GL_UNSIGNALED) {
|
||||
throw new IllegalStateException("Unknown data from glGetSync: "+val);
|
||||
}
|
||||
}
|
||||
return this.signaled;
|
||||
|
||||
@@ -3,6 +3,7 @@ package me.cortex.voxy.client.core.gl;
|
||||
import me.cortex.voxy.common.util.TrackedObject;
|
||||
|
||||
import static org.lwjgl.opengl.GL45C.*;
|
||||
import static org.lwjgl.opengl.GL45C.glNamedFramebufferDrawBuffers;
|
||||
|
||||
public class GlFramebuffer extends TrackedObject {
|
||||
public final int id;
|
||||
@@ -19,6 +20,16 @@ public class GlFramebuffer extends TrackedObject {
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlFramebuffer bind(int attachment, GlRenderBuffer buffer) {
|
||||
glNamedFramebufferRenderbuffer(this.id, attachment, GL_RENDERBUFFER, buffer.id);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlFramebuffer setDrawBuffers(int... buffers) {
|
||||
glNamedFramebufferDrawBuffers(this.id, buffers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
super.free0();
|
||||
@@ -32,4 +43,9 @@ public class GlFramebuffer extends TrackedObject {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public GlFramebuffer name(String name) {
|
||||
return GlDebug.name(name, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ public class GlPersistentMappedBuffer extends TrackedObject {
|
||||
public GlPersistentMappedBuffer(long size, int flags) {
|
||||
this.id = glCreateBuffers();
|
||||
this.size = size;
|
||||
glNamedBufferStorage(this.id, size, GL_CLIENT_STORAGE_BIT|GL_MAP_PERSISTENT_BIT|(flags&(GL_MAP_COHERENT_BIT|GL_MAP_WRITE_BIT|GL_MAP_READ_BIT)));
|
||||
glNamedBufferStorage(this.id, size, GL_MAP_PERSISTENT_BIT|(flags&(GL_MAP_COHERENT_BIT|GL_MAP_WRITE_BIT|GL_MAP_READ_BIT|GL_CLIENT_STORAGE_BIT)));
|
||||
this.addr = nglMapNamedBufferRange(this.id, 0, size, (flags&(GL_MAP_WRITE_BIT|GL_MAP_READ_BIT|GL_MAP_UNSYNCHRONIZED_BIT|GL_MAP_FLUSH_EXPLICIT_BIT))|GL_MAP_PERSISTENT_BIT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
this.free0();
|
||||
glUnmapBuffer(this.id);
|
||||
glUnmapNamedBuffer(this.id);
|
||||
glDeleteBuffers(this.id);
|
||||
}
|
||||
|
||||
@@ -31,4 +31,8 @@ public class GlPersistentMappedBuffer extends TrackedObject {
|
||||
public long addr() {
|
||||
return this.addr;
|
||||
}
|
||||
|
||||
public GlPersistentMappedBuffer name(String name) {
|
||||
return GlDebug.name(name, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package me.cortex.voxy.client.core.gl;
|
||||
|
||||
import me.cortex.voxy.common.util.TrackedObject;
|
||||
|
||||
import static org.lwjgl.opengl.GL45C.*;
|
||||
|
||||
public class GlRenderBuffer extends TrackedObject {
|
||||
public final int id;
|
||||
|
||||
public GlRenderBuffer(int format, int width, int height) {
|
||||
this.id = glCreateRenderbuffers();
|
||||
glNamedRenderbufferStorage(this.id, format, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
super.free0();
|
||||
glDeleteRenderbuffers(this.id);
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,23 @@ package me.cortex.voxy.client.core.gl;
|
||||
|
||||
import me.cortex.voxy.common.util.TrackedObject;
|
||||
|
||||
import static org.lwjgl.opengl.ARBFramebufferObject.glDeleteFramebuffers;
|
||||
import static org.lwjgl.opengl.ARBFramebufferObject.glGenFramebuffers;
|
||||
import static org.lwjgl.opengl.GL11.GL_RGBA8;
|
||||
import static org.lwjgl.opengl.GL11C.*;
|
||||
import static org.lwjgl.opengl.GL30C.glGetIntegeri;
|
||||
import static org.lwjgl.opengl.GL30.GL_DEPTH24_STENCIL8;
|
||||
import static org.lwjgl.opengl.GL45C.*;
|
||||
|
||||
public class GlTexture extends TrackedObject {
|
||||
public final int id;
|
||||
private final int type;
|
||||
private int format;
|
||||
private int width;
|
||||
private int height;
|
||||
private int levels;
|
||||
private boolean hasAllocated;
|
||||
|
||||
private static int COUNT;
|
||||
private static long ESTIMATED_TOTAL_SIZE;
|
||||
|
||||
public GlTexture() {
|
||||
this(GL_TEXTURE_2D);
|
||||
}
|
||||
@@ -18,32 +26,111 @@ public class GlTexture extends TrackedObject {
|
||||
public GlTexture(int type) {
|
||||
this.id = glCreateTextures(type);
|
||||
this.type = type;
|
||||
COUNT++;
|
||||
}
|
||||
|
||||
private GlTexture(int type, boolean useGenTypes) {
|
||||
if (useGenTypes) {
|
||||
this.id = glGenTextures();
|
||||
} else {
|
||||
this.id = glCreateTextures(type);
|
||||
}
|
||||
this.type = type;
|
||||
COUNT++;
|
||||
}
|
||||
|
||||
public GlTexture store(int format, int levels, int width, int height) {
|
||||
if (this.hasAllocated) {
|
||||
throw new IllegalStateException("Texture already allocated");
|
||||
}
|
||||
this.hasAllocated = true;
|
||||
|
||||
this.format = format;
|
||||
if (this.type == GL_TEXTURE_2D) {
|
||||
glTextureStorage2D(this.id, levels, format, width, height);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.levels = levels;
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown texture type");
|
||||
}
|
||||
ESTIMATED_TOTAL_SIZE += this.getEstimatedSize();
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlTexture createView() {
|
||||
this.assertAllocated();
|
||||
var view = new GlTexture(this.type, true);
|
||||
glTextureView(view.id, this.type, this.id, this.format, 0, 1, 0, 1);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
if (this.hasAllocated) {
|
||||
ESTIMATED_TOTAL_SIZE -= this.getEstimatedSize();
|
||||
}
|
||||
COUNT--;
|
||||
this.hasAllocated = false;
|
||||
super.free0();
|
||||
glDeleteTextures(this.id);
|
||||
}
|
||||
|
||||
//TODO: FIXME, glGetTextureParameteri doesnt work
|
||||
public static int getRawTextureType(int texture) {
|
||||
if (!glIsTexture(texture)) {
|
||||
throw new IllegalStateException("Not texture");
|
||||
public GlTexture name(String name) {
|
||||
this.assertAllocated();
|
||||
return GlDebug.name(name, this);
|
||||
}
|
||||
int immFormat = glGetTextureParameteri(texture, GL_TEXTURE_IMMUTABLE_FORMAT);
|
||||
if (immFormat == 0) {
|
||||
throw new IllegalStateException("Texture: " + texture + " is not immutable");
|
||||
|
||||
public int getWidth() {
|
||||
this.assertAllocated();
|
||||
return this.width;
|
||||
}
|
||||
return immFormat;
|
||||
|
||||
public int getHeight() {
|
||||
this.assertAllocated();
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public int getLevels() {
|
||||
this.assertAllocated();
|
||||
return this.levels;
|
||||
}
|
||||
|
||||
public int getFormat() {
|
||||
this.assertAllocated();
|
||||
return this.format;
|
||||
}
|
||||
|
||||
private long getEstimatedSize() {
|
||||
this.assertAllocated();
|
||||
long elemSize = switch (this.format) {
|
||||
case GL_R32UI, GL_RGBA8, GL_DEPTH24_STENCIL8, GL_R32F -> 4;
|
||||
case GL_DEPTH_COMPONENT24 -> 4;//TODO: check this is right????
|
||||
case GL_DEPTH_COMPONENT32F -> 4;
|
||||
case GL_DEPTH_COMPONENT32 -> 4;
|
||||
|
||||
default -> throw new IllegalStateException("Unknown element size");
|
||||
};
|
||||
|
||||
long size = 0;
|
||||
for (int lvl = 0; lvl < this.levels; lvl++) {
|
||||
size += Math.max((((long)this.width)>>lvl), 1) * Math.max((((long)this.height)>>lvl), 1) * elemSize;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
public void assertAllocated() {
|
||||
if (!this.hasAllocated) {
|
||||
throw new IllegalStateException("Texture not yet allocated");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static int getCount() {
|
||||
return COUNT;
|
||||
}
|
||||
|
||||
public static long getEstimatedTotalSize() {
|
||||
return ESTIMATED_TOTAL_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package me.cortex.voxy.client.core.gl;
|
||||
|
||||
import me.cortex.voxy.common.util.TrackedObject;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.lwjgl.opengl.GL30.glGenVertexArrays;
|
||||
import static org.lwjgl.opengl.GL45C.*;
|
||||
|
||||
public class GlVertexArray extends TrackedObject {
|
||||
public static final int STATIC_VAO = glGenVertexArrays();
|
||||
|
||||
public final int id;
|
||||
private int[] indices = new int[0];
|
||||
private int stride;
|
||||
public GlVertexArray() {
|
||||
this.id = glCreateVertexArrays();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
this.free0();
|
||||
glDeleteVertexArrays(this.id);
|
||||
}
|
||||
|
||||
public void bind() {
|
||||
glBindVertexArray(this.id);
|
||||
}
|
||||
|
||||
public GlVertexArray bindBuffer(int buffer) {
|
||||
//TODO: optimization, use glVertexArrayVertexBuffers
|
||||
for (int index : this.indices) {
|
||||
glVertexArrayVertexBuffer(this.id, index, buffer, 0, this.stride);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlVertexArray bindElementBuffer(int buffer) {
|
||||
glVertexArrayElementBuffer(this.id, buffer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlVertexArray setStride(int stride) {
|
||||
this.stride = stride;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlVertexArray setI(int index, int type, int count, int offset) {
|
||||
this.addIndex(index);
|
||||
glEnableVertexArrayAttrib(this.id, index);
|
||||
glVertexArrayAttribIFormat(this.id, index, count, type, offset);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlVertexArray setF(int index, int type, int count, int offset) {
|
||||
return this.setF(index, type, count, false, offset);
|
||||
}
|
||||
|
||||
public GlVertexArray setF(int index, int type, int count, boolean normalize, int offset) {
|
||||
this.addIndex(index);
|
||||
glEnableVertexArrayAttrib(this.id, index);
|
||||
glVertexArrayAttribFormat(this.id, index, count, type, normalize, offset);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addIndex(int index) {
|
||||
for (int i : this.indices) {
|
||||
if (i == index) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.indices = Arrays.copyOf(this.indices, this.indices.length+1);
|
||||
this.indices[this.indices.length-1] = index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package me.cortex.voxy.client.core.gl.shader;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlDebug;
|
||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.lwjgl.opengl.ARBDirectStateAccess.glBindTextureUnit;
|
||||
import static org.lwjgl.opengl.GL30.glBindBufferBase;
|
||||
import static org.lwjgl.opengl.GL30.glBindBufferRange;
|
||||
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
|
||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
|
||||
|
||||
|
||||
//TODO: rewrite the entire shader builder system
|
||||
public class AutoBindingShader extends Shader {
|
||||
|
||||
private record BufferBinding(int target, int index, GlBuffer buffer, long offset, long size) {}
|
||||
private record TextureBinding(int unit, int sampler, GlTexture texture) {}
|
||||
|
||||
private final Map<String, String> defines;
|
||||
private final List<BufferBinding> bindings = new ArrayList<>();
|
||||
private final List<TextureBinding> textureBindings = new ArrayList<>();
|
||||
|
||||
private boolean rebuild = true;
|
||||
|
||||
AutoBindingShader(Shader.Builder<AutoBindingShader> builder, int program) {
|
||||
super(program);
|
||||
this.defines = builder.defines;
|
||||
}
|
||||
|
||||
public AutoBindingShader name(String name) {
|
||||
return GlDebug.name(name, this);
|
||||
}
|
||||
|
||||
public AutoBindingShader ssboIf(String define, GlBuffer buffer) {
|
||||
if (this.defines.containsKey(define)) {
|
||||
return this.ssbo(define, buffer);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AutoBindingShader ssbo(int index, GlBuffer binding) {
|
||||
return this.ssbo(index, binding, 0);
|
||||
}
|
||||
|
||||
public AutoBindingShader ssbo(String define, GlBuffer binding) {
|
||||
return this.ssbo(Integer.parseInt(this.defines.get(define)), binding, 0);
|
||||
}
|
||||
|
||||
public AutoBindingShader ssbo(int index, GlBuffer buffer, long offset) {
|
||||
this.insertOrReplaceBinding(new BufferBinding(GL_SHADER_STORAGE_BUFFER, index, buffer, offset, -1));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public AutoBindingShader ubo(String define, GlBuffer buffer) {
|
||||
return this.ubo(Integer.parseInt(this.defines.get(define)), buffer);
|
||||
}
|
||||
|
||||
public AutoBindingShader ubo(int index, GlBuffer buffer) {
|
||||
return this.ubo(index, buffer, 0);
|
||||
}
|
||||
|
||||
public AutoBindingShader ubo(int index, GlBuffer buffer, long offset) {
|
||||
this.insertOrReplaceBinding(new BufferBinding(GL_UNIFORM_BUFFER, index, buffer, offset, -1));
|
||||
return this;
|
||||
}
|
||||
|
||||
private void insertOrReplaceBinding(BufferBinding binding) {
|
||||
this.rebuild = true;
|
||||
|
||||
//Check if there is already a binding at the index with the target, if so, replace it
|
||||
for (int i = 0; i < this.bindings.size(); i++) {
|
||||
var entry = this.bindings.get(i);
|
||||
if (entry.target == binding.target && entry.index == binding.index) {
|
||||
this.bindings.set(i, binding);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Else add the new binding
|
||||
this.bindings.add(binding);
|
||||
}
|
||||
|
||||
public AutoBindingShader texture(String define, GlTexture texture) {
|
||||
return this.texture(define, -1, texture);
|
||||
}
|
||||
|
||||
public AutoBindingShader texture(String define, int sampler, GlTexture texture) {
|
||||
return this.texture(Integer.parseInt(this.defines.get(define)), sampler, texture);
|
||||
}
|
||||
|
||||
public AutoBindingShader texture(int unit, int sampler, GlTexture texture) {
|
||||
this.rebuild = true;
|
||||
|
||||
for (int i = 0; i < this.textureBindings.size(); i++) {
|
||||
var entry = this.textureBindings.get(i);
|
||||
if (entry.unit == unit) {
|
||||
this.textureBindings.set(i, new TextureBinding(unit, sampler, texture));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
this.textureBindings.add(new TextureBinding(unit, sampler, texture));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind() {
|
||||
super.bind();
|
||||
//TODO: replace with multibind and use the invalidate flag
|
||||
/*
|
||||
glBindSamplers();
|
||||
glBindTextures();
|
||||
glBindBuffersBase();
|
||||
glBindBuffersRange();
|
||||
*/
|
||||
if (!this.bindings.isEmpty()) {
|
||||
for (var binding : this.bindings) {
|
||||
binding.buffer.assertNotFreed();
|
||||
if (binding.offset == 0 && binding.size == -1) {
|
||||
glBindBufferBase(binding.target, binding.index, binding.buffer.id);
|
||||
} else {
|
||||
glBindBufferRange(binding.target, binding.index, binding.buffer.id, binding.offset, binding.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.textureBindings.isEmpty()) {
|
||||
for (var binding : this.textureBindings) {
|
||||
if (binding.texture != null) {
|
||||
binding.texture.assertNotFreed();
|
||||
glBindTextureUnit(binding.unit, binding.texture.id);
|
||||
}
|
||||
if (binding.sampler != -1) {
|
||||
glBindSampler(binding.unit, binding.sampler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package me.cortex.voxy.client.core.gl.shader;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class GenericsProcessor implements IShaderProcessor {
|
||||
private static final Pattern GENERIC_DEFINE = Pattern.compile("#defineGen (?<name>[A-Za-z0-9]+)<(?<generic>[A-Za-z0-9]*)>");
|
||||
private static final Pattern GENERIC_USE = Pattern.compile("(?<type>[A-Za-z0-9]+)<(?<generic>[A-Za-z0-9]*)>");
|
||||
@Override
|
||||
public String process(ShaderType type, String source) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package me.cortex.voxy.client.core.gl.shader;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.lwjgl.opengl.ARBDirectStateAccess.nglClearNamedBufferData;
|
||||
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
|
||||
import static org.lwjgl.opengl.GL30.GL_R32UI;
|
||||
import static org.lwjgl.opengl.GL30.glBindBufferBase;
|
||||
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
|
||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
|
||||
import static org.lwjgl.opengl.GL45.nglClearNamedBufferSubData;
|
||||
|
||||
public class PrintfInjector implements IShaderProcessor {
|
||||
private final GlBuffer textBuffer;
|
||||
private final HashMap<String, Integer> printfStringMap = new HashMap<>();
|
||||
private final HashMap<Integer, String> idToPrintfStringMap = new HashMap<>();
|
||||
private final int bindingIndex;
|
||||
private final Consumer<String> callback;
|
||||
private final Runnable preRun;
|
||||
public PrintfInjector(int bufferSize, int bufferBindingIndex, Consumer<String> callback) {
|
||||
this(bufferSize, bufferBindingIndex, callback, null);
|
||||
}
|
||||
|
||||
public PrintfInjector(int bufferSize, int bufferBindingIndex, Consumer<String> callback, Runnable pre) {
|
||||
this.textBuffer = new GlBuffer(bufferSize*4L+4);
|
||||
nglClearNamedBufferData(this.textBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
this.bindingIndex = bufferBindingIndex;
|
||||
this.callback = callback;
|
||||
this.preRun = pre;
|
||||
}
|
||||
|
||||
private static int findNextCall(String src, int after) {
|
||||
while (true) {
|
||||
int idx = src.indexOf("printf", after);
|
||||
if (idx == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
boolean lineComment = false;
|
||||
boolean multiLineComment = false;
|
||||
//Check for comments
|
||||
for (int i = 0; i < idx; i++) {
|
||||
if (src.charAt(i) == '/' && src.charAt(i + 1) == '/') {
|
||||
lineComment = true;
|
||||
}
|
||||
if (src.charAt(i) == '\n') {
|
||||
lineComment = false;
|
||||
}
|
||||
if ((!lineComment) && src.charAt(i) == '/' && src.charAt(i + 1) == '*') {
|
||||
multiLineComment = true;
|
||||
}
|
||||
if ((!lineComment) && src.charAt(i) == '*' && src.charAt(i + 1) == '/') {
|
||||
multiLineComment = false;
|
||||
}
|
||||
}
|
||||
if (lineComment || multiLineComment) {
|
||||
after = idx+1;
|
||||
continue;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
private static void parsePrintfTypes(String fmtStr, List<Character> types) {
|
||||
for (int i = 0; i < fmtStr.length()-1; i++) {
|
||||
if (fmtStr.charAt(i)=='%' && (i==0||fmtStr.charAt(i-1)!='%')) {
|
||||
types.add(fmtStr.charAt(i+1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String transformInject(String src) {
|
||||
String original = src;
|
||||
//Quick exit
|
||||
if (!src.contains("printf")) {
|
||||
return src;
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
StringBuilder result = new StringBuilder();
|
||||
List<String> argVals = new ArrayList<>();
|
||||
List<Character> types = new ArrayList<>();
|
||||
|
||||
{
|
||||
int bufferInjection = Math.max(src.lastIndexOf("#version"), src.lastIndexOf("#extension"));
|
||||
bufferInjection = src.indexOf("\n", bufferInjection);
|
||||
|
||||
result.append(src, 0, bufferInjection+1);
|
||||
|
||||
result.append(String.format("""
|
||||
layout(binding = %s, std430) restrict buffer PrintfOutputStream {
|
||||
uint index;
|
||||
uint stream[];
|
||||
} printfOutputStruct;
|
||||
""", this.bindingIndex));
|
||||
|
||||
src = src.substring(bufferInjection+1);
|
||||
}
|
||||
boolean usedPrintf = false;
|
||||
while (true) {
|
||||
int nextCall = findNextCall(src, pos);
|
||||
if (nextCall == -1) {
|
||||
result.append(src, pos, src.length());
|
||||
break;
|
||||
}
|
||||
result.append(src, pos, nextCall);
|
||||
|
||||
|
||||
//Parse the printf() call
|
||||
String sub = src.substring(nextCall);
|
||||
sub = sub.substring(sub.indexOf('"')+1);
|
||||
sub = sub.substring(0, sub.indexOf(';'));
|
||||
String fmtStr = sub.substring(0, sub.indexOf('"'));
|
||||
String args = sub.substring(sub.indexOf('"'));
|
||||
|
||||
//Parse the commas in the args
|
||||
int prev = 0;
|
||||
int brace = 0;
|
||||
argVals.clear();
|
||||
for (int i = 0; i < args.length(); i++) {
|
||||
if (args.charAt(i) == '(' || args.charAt(i) == '[') brace++;
|
||||
if (args.charAt(i) == ')' || args.charAt(i) == ']') brace--;
|
||||
if ((args.charAt(i) == ',' && brace==0) || brace==-1) {
|
||||
if (prev == 0) {
|
||||
prev = i;
|
||||
continue;
|
||||
}
|
||||
String arg = args.substring(prev+1, i);
|
||||
prev = i;
|
||||
argVals.add(arg);
|
||||
|
||||
if (brace==-1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Parse the format string
|
||||
types.clear();
|
||||
parsePrintfTypes(fmtStr, types);
|
||||
|
||||
if (types.size() != argVals.size()) {
|
||||
throw new IllegalStateException("Printf obj count dont match arg size");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Inject the printf code
|
||||
StringBuilder subCode = new StringBuilder();
|
||||
subCode.append(String.format("{" +
|
||||
"uint printfWriteIndex = atomicAdd(printfOutputStruct.index,%s);" +
|
||||
"printfOutputStruct.stream[printfWriteIndex]=%s;", types.size()+1,
|
||||
this.printfStringMap.computeIfAbsent(fmtStr, a->{int id = this.printfStringMap.size();
|
||||
this.idToPrintfStringMap.put(id, a);
|
||||
return id;})));
|
||||
|
||||
for (int i = 0; i < types.size(); i++) {
|
||||
subCode.append("printfOutputStruct.stream[printfWriteIndex+").append(i+1).append("]=");
|
||||
if (types.get(i) == 'd') {
|
||||
subCode.append("uint(").append(argVals.get(i)).append(")");
|
||||
} else if (types.get(i) == 'f') {
|
||||
subCode.append("floatBitsToUint(").append(argVals.get(i)).append(")");
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown type " + types.get(i));
|
||||
}
|
||||
subCode.append(";");
|
||||
}
|
||||
subCode.append("}");
|
||||
result.append(subCode);
|
||||
usedPrintf = true;
|
||||
pos = src.indexOf(';', nextCall)+1;
|
||||
}
|
||||
if (!usedPrintf) {
|
||||
return original;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public void bind() {
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, this.bindingIndex, this.textBuffer.id);
|
||||
}
|
||||
|
||||
private void processResult(long ptr, long size) {
|
||||
int total = MemoryUtil.memGetInt(ptr); ptr += 4;
|
||||
if (total == 0) {
|
||||
return;
|
||||
}
|
||||
if (this.preRun != null) {
|
||||
this.preRun.run();
|
||||
}
|
||||
|
||||
int cnt = 0;
|
||||
List<Character> types = new ArrayList<>();
|
||||
while (cnt < total) {
|
||||
int id = MemoryUtil.memGetInt(ptr);
|
||||
ptr += 4;
|
||||
cnt++;
|
||||
String fmt = this.idToPrintfStringMap.get(id);
|
||||
if (fmt == null) {
|
||||
throw new IllegalStateException("Unknown id: "+ id);
|
||||
}
|
||||
types.clear();
|
||||
parsePrintfTypes(fmt, types);
|
||||
Object[] args = new Object[types.size()];
|
||||
for (int i = 0; i < types.size(); i++) {
|
||||
if (types.get(i) == 'd') {
|
||||
args[i] = MemoryUtil.memGetInt(ptr);
|
||||
ptr += 4;
|
||||
cnt++;
|
||||
}
|
||||
if (types.get(i) == 'f') {
|
||||
args[i] = Float.intBitsToFloat(MemoryUtil.memGetInt(ptr));
|
||||
ptr += 4;
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
this.callback.accept(String.format(fmt, args));
|
||||
}
|
||||
}
|
||||
|
||||
public void download() {
|
||||
DownloadStream.INSTANCE.download(this.textBuffer, this::processResult);
|
||||
nglClearNamedBufferSubData(this.textBuffer.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.textBuffer.free();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String process(ShaderType type, String source) {
|
||||
return this.transformInject(source);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,17 @@
|
||||
package me.cortex.voxy.client.core.gl.shader;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||
import me.cortex.voxy.client.core.gl.GlDebug;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.util.ThreadUtils;
|
||||
import me.cortex.voxy.common.util.TrackedObject;
|
||||
import org.lwjgl.opengl.GL20C;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -11,25 +20,10 @@ import static org.lwjgl.opengl.GL20.glUseProgram;
|
||||
|
||||
public class Shader extends TrackedObject {
|
||||
private final int id;
|
||||
private Shader(int program) {
|
||||
Shader(int program) {
|
||||
id = program;
|
||||
}
|
||||
|
||||
public static Builder make(IShaderProcessor... processors) {
|
||||
List<IShaderProcessor> aa = new ArrayList<>(List.of(processors));
|
||||
Collections.reverse(aa);
|
||||
IShaderProcessor applicator = (type,source)->source;
|
||||
for (IShaderProcessor processor : processors) {
|
||||
IShaderProcessor finalApplicator = applicator;
|
||||
applicator = (type, source) -> finalApplicator.process(type, processor.process(type, source));
|
||||
}
|
||||
return new Builder(applicator);
|
||||
}
|
||||
|
||||
public static Builder make() {
|
||||
return new Builder((aa,source)->source);
|
||||
}
|
||||
|
||||
public int id() {
|
||||
return this.id;
|
||||
}
|
||||
@@ -43,35 +37,106 @@ public class Shader extends TrackedObject {
|
||||
glDeleteProgram(this.id);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final Map<String, String> defines = new HashMap<>();
|
||||
|
||||
public Shader name(String name) {
|
||||
return GlDebug.name(name, this);
|
||||
}
|
||||
|
||||
|
||||
public static Builder<Shader> make(IShaderProcessor... processor) {
|
||||
return makeInternal((a,b)->new Shader(b), processor);
|
||||
}
|
||||
|
||||
public static Builder<AutoBindingShader> makeAuto(IShaderProcessor... processor) {
|
||||
return makeInternal(AutoBindingShader::new, processor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static <T extends Shader> Builder<T> makeInternal(Builder.IShaderObjectConstructor<T> constructor, IShaderProcessor[] processors) {
|
||||
List<IShaderProcessor> aa = new ArrayList<>(List.of(processors));
|
||||
Collections.reverse(aa);
|
||||
IShaderProcessor applicator = (type,source)->source;
|
||||
for (IShaderProcessor processor : processors) {
|
||||
IShaderProcessor finalApplicator = applicator;
|
||||
applicator = (type, source) -> finalApplicator.process(type, processor.process(type, source));
|
||||
}
|
||||
return new Builder<>(constructor, applicator);
|
||||
}
|
||||
|
||||
public static class Builder <T extends Shader> {
|
||||
protected interface IShaderObjectConstructor <J extends Shader> {
|
||||
J make(Builder<J> builder, int program);
|
||||
}
|
||||
final Map<String, String> defines = new HashMap<>();
|
||||
final Map<String, String> replacements = new LinkedHashMap<>();
|
||||
private final Map<ShaderType, String> sources = new HashMap<>();
|
||||
private final IShaderProcessor processor;
|
||||
private Builder(IShaderProcessor processor) {
|
||||
private final IShaderObjectConstructor<T> constructor;
|
||||
private Builder(IShaderObjectConstructor<T> constructor, IShaderProcessor processor) {
|
||||
this.constructor = constructor;
|
||||
this.processor = processor;
|
||||
}
|
||||
|
||||
public Builder define(String name) {
|
||||
public Builder<T> clone() {
|
||||
var clone = new Builder<>(this.constructor, this.processor);
|
||||
clone.defines.putAll(this.defines);
|
||||
clone.sources.putAll(this.sources);
|
||||
return clone;
|
||||
}
|
||||
|
||||
public Builder<T> define(String name) {
|
||||
this.defines.put(name, "");
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder define(String name, int value) {
|
||||
//Useful for inline setting (such as debug)
|
||||
public Builder<T> defineIf(String name, boolean condition) {
|
||||
if (condition) {
|
||||
this.defines.put(name, "");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> defineIf(String name, boolean condition, int value) {
|
||||
if (condition) {
|
||||
this.defines.put(name, Integer.toString(value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> define(String name, int value) {
|
||||
this.defines.put(name, Integer.toString(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder add(ShaderType type, String id) {
|
||||
public Builder<T> define(String name, float value) {
|
||||
this.defines.put(name, Float.toString(value)+"f");
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> define(String name, String value) {
|
||||
this.defines.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> replace(String value, String replacement) {
|
||||
this.defines.put(value, replacement);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> add(ShaderType type, String id) {
|
||||
this.addSource(type, ShaderLoader.parse(id));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addSource(ShaderType type, String source) {
|
||||
public Builder<T> addSource(ShaderType type, String source) {
|
||||
this.sources.put(type, this.processor.process(type, source));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Shader compile() {
|
||||
|
||||
private int compileToProgram() {
|
||||
int program = GL20C.glCreateProgram();
|
||||
int[] shaders = new int[this.sources.size()];
|
||||
{
|
||||
@@ -85,6 +150,10 @@ public class Shader extends TrackedObject {
|
||||
defs
|
||||
+ src.substring(src.indexOf('\n')+1);
|
||||
|
||||
for (var replacement : this.replacements.entrySet()) {
|
||||
src = src.replace(replacement.getKey(), replacement.getValue());
|
||||
}
|
||||
|
||||
shaders[i++] = createShader(entry.getKey(), src);
|
||||
}
|
||||
}
|
||||
@@ -99,15 +168,20 @@ public class Shader extends TrackedObject {
|
||||
}
|
||||
printProgramLinkLog(program);
|
||||
verifyProgramLinked(program);
|
||||
return new Shader(program);
|
||||
return program;
|
||||
}
|
||||
|
||||
public T compile() {
|
||||
this.defineIf("IS_INTEL", Capabilities.INSTANCE.isIntel);
|
||||
this.defineIf("IS_WINDOWS", ThreadUtils.isWindows);
|
||||
return this.constructor.make(this, this.compileToProgram());
|
||||
}
|
||||
|
||||
private static void printProgramLinkLog(int program) {
|
||||
String log = GL20C.glGetProgramInfoLog(program);
|
||||
|
||||
if (!log.isEmpty()) {
|
||||
System.err.println(log);
|
||||
Logger.error(log);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,24 +195,33 @@ public class Shader extends TrackedObject {
|
||||
|
||||
private static int createShader(ShaderType type, String src) {
|
||||
int shader = GL20C.glCreateShader(type.gl);
|
||||
GL20C.glShaderSource(shader, src);
|
||||
{//https://github.com/CaffeineMC/sodium/blob/fc42a7b19836c98a35df46e63303608de0587ab6/src/main/java/me/jellysquid/mods/sodium/client/gl/shader/ShaderWorkarounds.java
|
||||
long ptr = MemoryUtil.memAddress(MemoryUtil.memUTF8(src, true));
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
GL20C.nglShaderSource(shader, 1, stack.pointers(ptr).address0(), 0);
|
||||
}
|
||||
MemoryUtil.nmemFree(ptr);
|
||||
}
|
||||
GL20C.glCompileShader(shader);
|
||||
String log = GL20C.glGetShaderInfoLog(shader);
|
||||
|
||||
if (!log.isEmpty()) {
|
||||
System.err.println(log);
|
||||
Logger.warn(log);
|
||||
}
|
||||
|
||||
int result = GL20C.glGetShaderi(shader, GL20C.GL_COMPILE_STATUS);
|
||||
|
||||
if (result != GL20C.GL_TRUE) {
|
||||
GL20C.glDeleteShader(shader);
|
||||
|
||||
throw new RuntimeException("Shader compilation failed of type " + type.name() + ", see log for details");
|
||||
try {
|
||||
Files.writeString(Path.of("SHADER_DUMP.txt"), src);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
throw new RuntimeException("Shader compilation failed of type " + type.name() + ", see log for details, dumped shader");
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package me.cortex.voxy.client.core.gl.shader;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.gl.shader.ShaderConstants;
|
||||
import me.jellysquid.mods.sodium.client.gl.shader.ShaderParser;
|
||||
|
||||
import net.caffeinemc.mods.sodium.client.gl.shader.ShaderConstants;
|
||||
import net.caffeinemc.mods.sodium.client.gl.shader.ShaderParser;
|
||||
|
||||
public class ShaderLoader {
|
||||
public static String parse(String id) {
|
||||
return ShaderParser.parseShader("#import <" + id + ">", ShaderConstants.builder().build());
|
||||
return "#version 460 core\n"+ShaderParser.parseShader("\n#import <" + id + ">\n//beans", ShaderConstants.builder().build()).src().replaceAll("\r\n", "\n").replaceFirst("\n#version .+\n", "\n");
|
||||
//return me.jellysquid.mods.sodium.client.gl.shader.ShaderLoader.getShaderSource(new Identifier(id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
package me.cortex.voxy.client.core.model;
|
||||
|
||||
import net.minecraft.block.BlockEntityProvider;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.*;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
|
||||
import static org.lwjgl.opengl.GL11.glBindTexture;
|
||||
|
||||
public class BakedBlockEntityModel {
|
||||
private static final class BakedVertices implements VertexConsumer {
|
||||
private boolean makingVertex = false;
|
||||
public final RenderLayer layer;
|
||||
private float cX, cY, cZ;
|
||||
private int cR, cG, cB, cA;
|
||||
private float cU, cV;
|
||||
|
||||
private final List<int[]> vertices = new ArrayList<>();
|
||||
|
||||
private BakedVertices(RenderLayer layer) {
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer vertex(float x, float y, float z) {
|
||||
this.next();
|
||||
this.cX = x;
|
||||
this.cY = y;
|
||||
this.cZ = z;
|
||||
this.makingVertex = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer color(int red, int green, int blue, int alpha) {
|
||||
this.cR = 0;//red;
|
||||
this.cG = 0;//green;
|
||||
this.cB = 0;//blue;
|
||||
this.cA = alpha;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer texture(float u, float v) {
|
||||
this.cU = u;
|
||||
this.cV = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer overlay(int u, int v) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer light(int u, int v) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer normal(float x, float y, float z) {
|
||||
return this;
|
||||
}
|
||||
|
||||
private void next() {
|
||||
if (this.makingVertex) {
|
||||
this.makingVertex = false;
|
||||
this.vertices.add(new int[]{
|
||||
Float.floatToIntBits(this.cX), Float.floatToIntBits(this.cY), Float.floatToIntBits(this.cZ),
|
||||
this.cR, this.cG, this.cB, this.cA,
|
||||
Float.floatToIntBits(this.cU), Float.floatToIntBits(this.cV)});
|
||||
}
|
||||
}
|
||||
|
||||
public void putInto(VertexConsumer vc) {
|
||||
this.next();
|
||||
for (var vert : this.vertices) {
|
||||
vc.vertex(Float.intBitsToFloat(vert[0]), Float.intBitsToFloat(vert[1]), Float.intBitsToFloat(vert[2]))
|
||||
.color(vert[3], vert[4], vert[5], vert[6])
|
||||
.texture(Float.intBitsToFloat(vert[7]), Float.intBitsToFloat(vert[8]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final List<BakedVertices> layers;
|
||||
private BakedBlockEntityModel(List<BakedVertices> layers) {
|
||||
this.layers = layers;
|
||||
}
|
||||
|
||||
public void renderOut() {
|
||||
var vc = Tessellator.getInstance();
|
||||
for (var layer : this.layers) {
|
||||
var bb = vc.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR);
|
||||
if (layer.layer instanceof RenderLayer.MultiPhase mp) {
|
||||
Identifier textureId = mp.phases.texture.getId().orElse(null);
|
||||
if (textureId == null) {
|
||||
System.err.println("ERROR: Empty texture id for layer: " + layer);
|
||||
} else {
|
||||
var texture = MinecraftClient.getInstance().getTextureManager().getTexture(textureId);
|
||||
glBindTexture(GL_TEXTURE_2D, texture.getGlId());
|
||||
}
|
||||
}
|
||||
layer.putInto(bb);
|
||||
BufferRenderer.draw(bb.end());
|
||||
}
|
||||
}
|
||||
|
||||
public static BakedBlockEntityModel bake(BlockState state) {
|
||||
Map<RenderLayer, BakedVertices> map = new HashMap<>();
|
||||
var entity = ((BlockEntityProvider)state.getBlock()).createBlockEntity(BlockPos.ORIGIN, state);
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
var renderer = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(entity);
|
||||
if (renderer != null) {
|
||||
entity.setWorld(MinecraftClient.getInstance().world);
|
||||
try {
|
||||
renderer.render(entity, 0.0f, new MatrixStack(), layer->map.computeIfAbsent(layer, BakedVertices::new), 0, 0);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Unable to bake block entity: " + entity);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
entity.markRemoved();
|
||||
if (map.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new BakedBlockEntityModel(map.values().stream().toList());
|
||||
}
|
||||
}
|
||||
@@ -2,21 +2,25 @@ package me.cortex.voxy.client.core.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public record ColourDepthTextureData(int[] colour, int[] depth, int width, int height) {
|
||||
public record ColourDepthTextureData(int[] colour, int[] depth, int width, int height, int hash) {
|
||||
public ColourDepthTextureData(int[] colour, int[] depth, int width, int height) {
|
||||
this(colour, depth, width, height, width * 312337173 * (Arrays.hashCode(colour) ^ Arrays.hashCode(depth)) ^ height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
var other = ((ColourDepthTextureData)obj);
|
||||
return Arrays.equals(other.colour, this.colour) && Arrays.equals(other.depth, this.depth);
|
||||
return this.hash == other.hash && Arrays.equals(other.colour, this.colour) && Arrays.equals(other.depth, this.depth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (this.width * 312337173 * (Arrays.hashCode(this.colour) ^ Arrays.hashCode(this.depth))) ^ this.height;
|
||||
return this.hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColourDepthTextureData clone() {
|
||||
return new ColourDepthTextureData(Arrays.copyOf(this.colour, this.colour.length), Arrays.copyOf(this.depth, this.depth.length), this.width, this.height);
|
||||
return new ColourDepthTextureData(Arrays.copyOf(this.colour, this.colour.length), Arrays.copyOf(this.depth, this.depth.length), this.width, this.height, this.hash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package me.cortex.voxy.client.core.model;
|
||||
|
||||
public class IdNotYetComputedException extends RuntimeException {
|
||||
public IdNotYetComputedException(int id) {
|
||||
super("Id not yet computed: " + id);
|
||||
public final int id;
|
||||
public final boolean isIdBlockId;
|
||||
public int auxBitMsk;
|
||||
public long[] auxData;
|
||||
public IdNotYetComputedException(int id, boolean isIdBlockId) {
|
||||
super(null, null, false, false);
|
||||
this.id = id;
|
||||
this.isIdBlockId = isIdBlockId;
|
||||
}
|
||||
}
|
||||
|
||||
128
src/main/java/me/cortex/voxy/client/core/model/MipGen.java
Normal file
128
src/main/java/me/cortex/voxy/client/core/model/MipGen.java
Normal file
@@ -0,0 +1,128 @@
|
||||
package me.cortex.voxy.client.core.model;
|
||||
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
|
||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||
import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static me.cortex.voxy.client.core.model.ModelFactory.LAYERS;
|
||||
import static me.cortex.voxy.client.core.model.ModelFactory.MODEL_TEXTURE_SIZE;
|
||||
|
||||
public class MipGen {
|
||||
static {
|
||||
if (MODEL_TEXTURE_SIZE>16) throw new IllegalStateException("TODO: THIS MUST BE UPDATED, IT CURRENTLY ASSUMES 16 OR SMALLER SIZE");
|
||||
}
|
||||
private static final short[] SCRATCH = new short[MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE];
|
||||
private static final ByteArrayFIFOQueue QUEUE = new ByteArrayFIFOQueue(MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE);
|
||||
|
||||
private static long getOffset(int bx, int by, int i) {
|
||||
bx += i&(MODEL_TEXTURE_SIZE-1);
|
||||
by += i/MODEL_TEXTURE_SIZE;
|
||||
return bx+by*MODEL_TEXTURE_SIZE*3;
|
||||
}
|
||||
|
||||
private static void solidify(long baseAddr, byte msk) {
|
||||
for (int idx = 0; idx < 6; idx++) {
|
||||
if (((msk>>idx)&1)==0) continue;
|
||||
int bx = (idx>>1)*MODEL_TEXTURE_SIZE;
|
||||
int by = (idx&1)*MODEL_TEXTURE_SIZE;
|
||||
long cAddr = baseAddr + (long)(bx+by*MODEL_TEXTURE_SIZE*3)*4;
|
||||
Arrays.fill(SCRATCH, (short) -1);
|
||||
for (int y = 0; y<MODEL_TEXTURE_SIZE;y++) {
|
||||
for (int x = 0; x<MODEL_TEXTURE_SIZE;x++) {
|
||||
int colour = MemoryUtil.memGetInt(cAddr+(x+y*MODEL_TEXTURE_SIZE*3)*4);
|
||||
if ((colour&0xFF000000)!=0) {
|
||||
int pos = x+y*MODEL_TEXTURE_SIZE;
|
||||
SCRATCH[pos] = ((short)pos);
|
||||
QUEUE.enqueue((byte) pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!QUEUE.isEmpty()) {
|
||||
int pos = Byte.toUnsignedInt(QUEUE.dequeueByte());
|
||||
int x = pos&(MODEL_TEXTURE_SIZE-1);
|
||||
int y = pos/MODEL_TEXTURE_SIZE;//this better be turned into a bitshift
|
||||
short newVal = (short) (SCRATCH[pos]+(short) 0x0100);
|
||||
for (int D = 3; D!=-1; D--) {
|
||||
int d = 2*(D&1)-1;
|
||||
int x2 = x+(((D&2)==2)?d:0);
|
||||
int y2 = y+(((D&2)==0)?d:0);
|
||||
if (x2<0||x2>=MODEL_TEXTURE_SIZE||y2<0||y2>=MODEL_TEXTURE_SIZE) continue;
|
||||
int pos2 = x2+y2*MODEL_TEXTURE_SIZE;
|
||||
if ((newVal&0xFF00)<(SCRATCH[pos2]&0xFF00)) {
|
||||
SCRATCH[pos2] = newVal;
|
||||
QUEUE.enqueue((byte) pos2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE; i++) {
|
||||
int d = Short.toUnsignedInt(SCRATCH[i]);
|
||||
if ((d&0xFF00)!=0) {
|
||||
int c = MemoryUtil.memGetInt(baseAddr+getOffset(bx, by, d&0xFF)*4)&0x00FFFFFF;
|
||||
MemoryUtil.memPutInt(baseAddr+getOffset(bx, by, i)*4, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void putTextures(boolean darkened, ColourDepthTextureData[] textures, MemoryBuffer into) {
|
||||
//if (MODEL_TEXTURE_SIZE != 16) {throw new IllegalStateException("THIS METHOD MUST BE REDONE IF THIS CONST CHANGES");}
|
||||
|
||||
//TODO: need to use a write mask to see what pixels must be used to contribute to mipping
|
||||
// as in, using the depth/stencil info, check if pixel was written to, if so, use that pixel when blending, else dont
|
||||
|
||||
final long addr = into.address;
|
||||
final int LENGTH_B = MODEL_TEXTURE_SIZE*3;
|
||||
byte solidMsk = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int x = (i>>1)*MODEL_TEXTURE_SIZE;
|
||||
int y = (i&1)*MODEL_TEXTURE_SIZE;
|
||||
int j = 0;
|
||||
boolean anyTransparent = false;
|
||||
for (int t : textures[i].colour()) {
|
||||
int o = ((y+(j>>LAYERS))*LENGTH_B + ((j&(MODEL_TEXTURE_SIZE-1))+x))*4; j++;//LAYERS here is just cause faster
|
||||
//t = ((t&0xFF000000)==0)?0x00_FF_00_FF:t;//great for testing
|
||||
MemoryUtil.memPutInt(addr+o, t);
|
||||
anyTransparent |= ((t&0xFF000000)==0);
|
||||
}
|
||||
solidMsk |= (anyTransparent?1:0)<<i;
|
||||
}
|
||||
|
||||
if (!darkened) {
|
||||
solidify(addr, solidMsk);
|
||||
}
|
||||
|
||||
|
||||
//Mip the scratch
|
||||
long dAddr = addr;
|
||||
for (int i = 0; i < LAYERS-1; i++) {
|
||||
long sAddr = dAddr;
|
||||
dAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(i<<1);//is.. i*2 because shrink both MODEL_TEXTURE_SIZE by >>i so is 2*i total shift
|
||||
int width = (MODEL_TEXTURE_SIZE*3)>>(i+1);
|
||||
int sWidth = (MODEL_TEXTURE_SIZE*3)>>i;
|
||||
int height = (MODEL_TEXTURE_SIZE*2)>>(i+1);
|
||||
//TODO: OPTIMZIE THIS
|
||||
for (int px = 0; px < width; px++) {
|
||||
for (int py = 0; py < height; py++) {
|
||||
long bp = sAddr + (px*2 + py*2*sWidth)*4;
|
||||
int C00 = MemoryUtil.memGetInt(bp);
|
||||
int C01 = MemoryUtil.memGetInt(bp+sWidth*4);
|
||||
int C10 = MemoryUtil.memGetInt(bp+4);
|
||||
int C11 = MemoryUtil.memGetInt(bp+sWidth*4+4);
|
||||
MemoryUtil.memPutInt(dAddr + (px+py*width) * 4L, TextureUtils.mipColours(darkened, C00, C01, C10, C11));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
}
|
||||
|
||||
public static void generateMipmaps(long[] textures, int size) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package me.cortex.voxy.client.core.model;
|
||||
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.world.other.Mapper;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.glGetInteger;
|
||||
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER;
|
||||
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_BINDING;
|
||||
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
|
||||
|
||||
public class ModelBakerySubsystem {
|
||||
//Redo to just make it request the block faces with the async texture download stream which
|
||||
// basicly solves all the render stutter due to the baking
|
||||
|
||||
private final ModelStore storage = new ModelStore();
|
||||
public final ModelFactory factory;
|
||||
private final Mapper mapper;
|
||||
private final AtomicInteger blockIdCount = new AtomicInteger();
|
||||
private final ConcurrentLinkedDeque<Integer> blockIdQueue = new ConcurrentLinkedDeque<>();//TODO: replace with custom DS
|
||||
|
||||
private final Thread processingThread;
|
||||
private volatile boolean isRunning = true;
|
||||
public ModelBakerySubsystem(Mapper mapper) {
|
||||
this.mapper = mapper;
|
||||
this.factory = new ModelFactory(mapper, this.storage);
|
||||
this.processingThread = new Thread(()->{//TODO replace this with something good/integrate it into the async processor so that we just have less threads overall
|
||||
while (this.isRunning) {
|
||||
this.factory.processAllThings();
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}, "Model factory processor");
|
||||
this.processingThread.start();
|
||||
}
|
||||
|
||||
public void tick(long totalBudget) {
|
||||
long start = System.nanoTime();
|
||||
this.factory.tickAndProcessUploads();
|
||||
//Always do 1 iteration minimum
|
||||
Integer i = this.blockIdQueue.poll();
|
||||
if (i != null) {
|
||||
int j = 0;
|
||||
if (i != null) {
|
||||
int fbBinding = glGetInteger(GL_FRAMEBUFFER_BINDING);
|
||||
|
||||
do {
|
||||
this.factory.addEntry(i);
|
||||
j++;
|
||||
if (4<j&&(totalBudget<(System.nanoTime() - start)+50_000))//20<j||
|
||||
break;
|
||||
i = this.blockIdQueue.poll();
|
||||
} while (i != null);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbBinding);//This is done here as stops needing to set then unset the fb in the thing 1000x
|
||||
}
|
||||
this.blockIdCount.addAndGet(-j);
|
||||
}
|
||||
|
||||
//TimingStatistics.modelProcess.stop();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.isRunning = false;
|
||||
try {
|
||||
this.processingThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
this.factory.free();
|
||||
this.storage.free();
|
||||
}
|
||||
|
||||
//This is on this side only and done like this as only worker threads call this code
|
||||
private final ReentrantLock seenIdsLock = new ReentrantLock();
|
||||
private final IntOpenHashSet seenIds = new IntOpenHashSet(6000);//TODO: move to a lock free concurrent hashmap
|
||||
public void requestBlockBake(int blockId) {
|
||||
if (this.mapper.getBlockStateCount() < blockId) {
|
||||
Logger.error("Error, got bakeing request for out of range state id. StateId: " + blockId + " max id: " + this.mapper.getBlockStateCount(), new Exception());
|
||||
return;
|
||||
}
|
||||
this.seenIdsLock.lock();
|
||||
if (!this.seenIds.add(blockId)) {
|
||||
this.seenIdsLock.unlock();
|
||||
return;
|
||||
}
|
||||
this.seenIdsLock.unlock();
|
||||
this.blockIdQueue.add(blockId);
|
||||
this.blockIdCount.incrementAndGet();
|
||||
}
|
||||
|
||||
public void addBiome(Mapper.BiomeEntry biomeEntry) {
|
||||
this.factory.addBiome(biomeEntry);
|
||||
}
|
||||
|
||||
public void addDebugData(List<String> debug) {
|
||||
debug.add(String.format("MQ/IF/MC: %04d, %03d, %04d", this.blockIdCount.get(), this.factory.getInflightCount(), this.factory.getBakedCount()));//Model bake queue/in flight/model baked count
|
||||
}
|
||||
|
||||
public ModelStore getStore() {
|
||||
return this.storage;
|
||||
}
|
||||
|
||||
public boolean areQueuesEmpty() {
|
||||
return this.blockIdCount.get()==0 && this.factory.getInflightCount() == 0;
|
||||
}
|
||||
|
||||
public int getProcessingCount() {
|
||||
return this.blockIdCount.get() + this.factory.getInflightCount();
|
||||
}
|
||||
}
|
||||
923
src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java
Normal file
923
src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java
Normal file
@@ -0,0 +1,923 @@
|
||||
package me.cortex.voxy.client.core.model;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||
import me.cortex.voxy.client.core.model.bakery.ModelTextureBakery;
|
||||
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||
import me.cortex.voxy.common.util.Pair;
|
||||
import me.cortex.voxy.common.world.other.Mapper;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.color.block.BlockColor;
|
||||
import net.minecraft.client.renderer.ItemBlockRenderTypes;
|
||||
import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
import net.minecraft.world.level.ColorResolver;
|
||||
import net.minecraft.world.level.LightLayer;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.LeavesBlock;
|
||||
import net.minecraft.world.level.block.LiquidBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.lighting.LevelLightEngine;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import static me.cortex.voxy.client.core.model.ModelStore.MODEL_SIZE;
|
||||
import static org.lwjgl.opengl.ARBDirectStateAccess.nglTextureSubImage2D;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
|
||||
//Manages the storage and updating of model states, textures and colours
|
||||
|
||||
//Also has a fast long[] based metadata lookup for when the terrain mesher needs to look up the face occlusion data
|
||||
|
||||
//TODO: support more than 65535 states, what should actually happen is a blockstate is registered, the model data is generated, then compared
|
||||
// to all other models already loaded, if it is a duplicate, create a mapping from the id to the already loaded id, this will help with meshing aswell
|
||||
// as leaves and such will be able to be merged
|
||||
|
||||
|
||||
|
||||
//TODO: NOTE!!! is it worth even uploading as a 16x16 texture, since automatic lod selection... doing 8x8 textures might be perfectly ok!!!
|
||||
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
|
||||
public class ModelFactory {
|
||||
public static final int MODEL_TEXTURE_SIZE = 16;
|
||||
public static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE);
|
||||
|
||||
//TODO: replace the fluid BlockState with a client model id integer of the fluidState, requires looking up
|
||||
// the fluid state in the mipper
|
||||
private record ModelEntry(ColourDepthTextureData down, ColourDepthTextureData up, ColourDepthTextureData north, ColourDepthTextureData south, ColourDepthTextureData west, ColourDepthTextureData east, int fluidBlockStateId, int tintingColour) {
|
||||
public ModelEntry(ColourDepthTextureData[] textures, int fluidBlockStateId, int tintingColour) {
|
||||
this(textures[0], textures[1], textures[2], textures[3], textures[4], textures[5], fluidBlockStateId, tintingColour);
|
||||
}
|
||||
}
|
||||
|
||||
private final Biome DEFAULT_BIOME = Minecraft.getInstance().level.registryAccess().lookupOrThrow(Registries.BIOME).getValue(Biomes.PLAINS);
|
||||
|
||||
public final ModelTextureBakery bakery;
|
||||
|
||||
|
||||
//Model data might also contain a constant colour if the colour resolver produces a constant colour, this saves space in the
|
||||
// section buffer reverse indexing
|
||||
|
||||
//model data also contains if a face should be randomly rotated,flipped etc to get rid of moire effect
|
||||
// this would be done in the fragment shader
|
||||
|
||||
//The Meta-cache contains critical information needed for meshing, colour provider bit, per-face = is empty, has alpha, is solid, full width, full height
|
||||
// alpha means that some pixels have alpha values and belong in the translucent rendering layer,
|
||||
// is empty means that the face is air/shouldent be rendered as there is nothing there
|
||||
// is solid means that every pixel is fully opaque
|
||||
// full width, height, is if the blockmodel dimentions occupy a full block, e.g. comparator, some faces do some dont and some only in a specific axis
|
||||
|
||||
//FIXME: the issue is e.g. leaves are translucent but the alpha value is used to colour the leaves, so a block can have alpha but still be only made up of transparent or opaque pixels
|
||||
// will need to find a way to send this info to the shader via the material, if it is in the opaque phase render as transparent with blending shiz
|
||||
|
||||
//TODO: ADD an occlusion mask that can be queried (16x16 pixels takes up 4 longs) this mask shows what pixels are exactly occluded at the edge of the block
|
||||
// so that full block occlusion can work nicely
|
||||
|
||||
|
||||
//TODO: what might work maybe, is that all the transparent pixels should be set to the average of the other pixels
|
||||
// that way the block is always "fully occluding" (if the block model doesnt cover the entire thing), maybe
|
||||
// this has some issues with quad merging
|
||||
//TODO: ACTUALLY, full out all the transparent pixels that are _within_ the bounding box of the model
|
||||
// this will mean that when quad merging and rendering, the transparent pixels of the block where there shouldent be
|
||||
// might still work???
|
||||
|
||||
// this has an issue with scaffolding i believe tho, so maybe make it a probability to render??? idk
|
||||
private final long[] metadataCache;
|
||||
private final int[] fluidStateLUT;
|
||||
|
||||
//Provides a map from id -> model id as multiple ids might have the same internal model id
|
||||
private final int[] idMappings;
|
||||
private final Object2IntOpenHashMap<ModelEntry> modelTexture2id = new Object2IntOpenHashMap<>();
|
||||
|
||||
//Contains the set of all block ids that are currently inflight/being baked
|
||||
// this is required due to "async" nature of gpu feedback
|
||||
private final IntOpenHashSet blockStatesInFlight = new IntOpenHashSet();
|
||||
private final ReentrantLock blockStatesInFlightLock = new ReentrantLock();
|
||||
|
||||
private final List<Biome> biomes = new ArrayList<>();
|
||||
private final List<Pair<Integer, BlockState>> modelsRequiringBiomeColours = new ArrayList<>();
|
||||
|
||||
private static final ObjectSet<BlockState> LOGGED_SELF_CULLING_WARNING = new ObjectOpenHashSet<>();
|
||||
|
||||
private final Mapper mapper;
|
||||
private final ModelStore storage;
|
||||
private final RawDownloadStream downstream = new RawDownloadStream(8*1024*1024);//8mb downstream
|
||||
|
||||
private final ConcurrentLinkedDeque<RawBakeResult> rawBakeResults = new ConcurrentLinkedDeque<>();
|
||||
|
||||
private final ConcurrentLinkedDeque<ResultUploader> uploadResults = new ConcurrentLinkedDeque<>();
|
||||
|
||||
private Object2IntMap<BlockState> customBlockStateIdMapping;
|
||||
|
||||
//TODO: NOTE!!! is it worth even uploading as a 16x16 texture, since automatic lod selection... doing 8x8 textures might be perfectly ok!!!
|
||||
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
|
||||
public ModelFactory(Mapper mapper, ModelStore storage) {
|
||||
this.mapper = mapper;
|
||||
this.storage = storage;
|
||||
this.bakery = new ModelTextureBakery(MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
|
||||
|
||||
this.metadataCache = new long[1<<16];
|
||||
this.fluidStateLUT = new int[1<<16];
|
||||
this.idMappings = new int[1<<20];//Max of 1 million blockstates mapping to 65k model states
|
||||
Arrays.fill(this.idMappings, -1);
|
||||
Arrays.fill(this.fluidStateLUT, -1);
|
||||
|
||||
this.modelTexture2id.defaultReturnValue(-1);
|
||||
this.addEntry(0);//Add air as the first entry
|
||||
}
|
||||
|
||||
public void setCustomBlockStateMapping(Object2IntMap<BlockState> mapping) {
|
||||
this.customBlockStateIdMapping = mapping;
|
||||
}
|
||||
|
||||
private static final class RawBakeResult {
|
||||
private final int blockId;
|
||||
private final BlockState blockState;
|
||||
private final MemoryBuffer rawData;
|
||||
|
||||
public boolean isShaded;
|
||||
public boolean hasDarkenedTextures;
|
||||
|
||||
public RawBakeResult(int blockId, BlockState blockState, MemoryBuffer rawData) {
|
||||
this.blockId = blockId;
|
||||
this.blockState = blockState;
|
||||
this.rawData = rawData;
|
||||
}
|
||||
|
||||
public RawBakeResult(int blockId, BlockState blockState) {
|
||||
this(blockId, blockState, new MemoryBuffer(MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6));
|
||||
}
|
||||
|
||||
public RawBakeResult cpyBuf(long ptr) {
|
||||
this.rawData.cpyFrom(ptr);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addEntry(int blockId) {
|
||||
if (this.idMappings[blockId] != -1) {
|
||||
return false;
|
||||
}
|
||||
//We are (probably) going to be baking the block id
|
||||
// check that it is currently not inflight, if it is, return as its already being baked
|
||||
// else add it to the flight as it is going to be baked
|
||||
this.blockStatesInFlightLock.lock();
|
||||
if (!this.blockStatesInFlight.add(blockId)) {
|
||||
this.blockStatesInFlightLock.unlock();
|
||||
//Block baking is already in-flight
|
||||
return false;
|
||||
}
|
||||
this.blockStatesInFlightLock.unlock();
|
||||
|
||||
VarHandle.loadLoadFence();
|
||||
|
||||
//We need to get it twice cause of threading
|
||||
if (this.idMappings[blockId] != -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var blockState = this.mapper.getBlockStateFromBlockId(blockId);
|
||||
|
||||
//Before we enqueue the baking of this blockstate, we must check if it has a fluid state associated with it
|
||||
// if it does, we must ensure that it is (effectivly) baked BEFORE we bake this blockstate
|
||||
boolean isFluid = blockState.getBlock() instanceof LiquidBlock;
|
||||
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
|
||||
//Insert into the fluid LUT
|
||||
var fluidState = blockState.getFluidState().createLegacyBlock();
|
||||
|
||||
int fluidStateId = this.mapper.getIdForBlockState(fluidState);
|
||||
|
||||
if (this.idMappings[fluidStateId] == -1) {
|
||||
//Dont have to check for inflight as that is done recursively :p
|
||||
|
||||
//This is a hack but does work :tm: due to how the download stream is setup
|
||||
// it should enforce that the fluid state is processed before our blockstate
|
||||
addEntry(fluidStateId);
|
||||
}
|
||||
}
|
||||
|
||||
RawBakeResult result = new RawBakeResult(blockId, blockState);
|
||||
int allocation = this.downstream.download(MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6, ptr -> this.rawBakeResults.add(result.cpyBuf(ptr)));
|
||||
int flags = this.bakery.renderToStream(blockState, this.downstream.getBufferId(), allocation);
|
||||
result.hasDarkenedTextures = (flags&2)!=0;
|
||||
result.isShaded = (flags&1)!=0;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean processModelResult() {
|
||||
var result = this.rawBakeResults.poll();
|
||||
if (result == null) return false;
|
||||
ColourDepthTextureData[] textureData = new ColourDepthTextureData[6];
|
||||
{//Create texture data
|
||||
long ptr = result.rawData.address;
|
||||
final int FACE_SIZE = MODEL_TEXTURE_SIZE * MODEL_TEXTURE_SIZE;
|
||||
for (int face = 0; face < 6; face++) {
|
||||
long faceDataPtr = ptr + (FACE_SIZE * 4) * face * 2;
|
||||
int[] colour = new int[FACE_SIZE];
|
||||
int[] depth = new int[FACE_SIZE];
|
||||
|
||||
//Copy out colour
|
||||
for (int i = 0; i < FACE_SIZE; i++) {
|
||||
//De-interpolate results
|
||||
colour[i] = MemoryUtil.memGetInt(faceDataPtr + (i * 4 * 2));
|
||||
depth[i] = MemoryUtil.memGetInt(faceDataPtr + (i * 4 * 2) + 4);
|
||||
}
|
||||
textureData[face] = new ColourDepthTextureData(colour, depth, MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
|
||||
}
|
||||
}
|
||||
result.rawData.free();
|
||||
var bakeResult = this.processTextureBakeResult(result.blockId, result.blockState, textureData, result.isShaded, result.hasDarkenedTextures);
|
||||
if (bakeResult!=null) {
|
||||
this.uploadResults.add(bakeResult);
|
||||
}
|
||||
return !this.rawBakeResults.isEmpty();
|
||||
}
|
||||
|
||||
private final ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeQueue = new ConcurrentLinkedDeque<>();
|
||||
public void addBiome(Mapper.BiomeEntry biome) {
|
||||
this.biomeQueue.add(biome);
|
||||
}
|
||||
|
||||
public void processAllThings() {
|
||||
var biomeEntry = this.biomeQueue.poll();
|
||||
while (biomeEntry != null) {
|
||||
var biomeRegistry = Minecraft.getInstance().level.registryAccess().lookupOrThrow(Registries.BIOME);
|
||||
var res = this.addBiome0(biomeEntry.id, biomeRegistry.getValue(Identifier.parse(biomeEntry.biome)));
|
||||
if (res != null) {
|
||||
this.uploadResults.add(res);
|
||||
}
|
||||
biomeEntry = this.biomeQueue.poll();
|
||||
}
|
||||
|
||||
while (this.processModelResult());
|
||||
}
|
||||
|
||||
public void tickAndProcessUploads() {
|
||||
this.downstream.tick();
|
||||
|
||||
var upload = this.uploadResults.poll();
|
||||
if (upload==null) return;
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
|
||||
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
do {
|
||||
upload.upload(this.storage);
|
||||
upload.free();
|
||||
upload = this.uploadResults.poll();
|
||||
} while (upload != null);
|
||||
UploadStream.INSTANCE.commit();
|
||||
}
|
||||
|
||||
private interface ResultUploader {
|
||||
void upload(ModelStore store);
|
||||
void free();
|
||||
}
|
||||
|
||||
private static final class ModelBakeResultUpload implements ResultUploader {
|
||||
private final MemoryBuffer model = new MemoryBuffer(MODEL_SIZE).zero();
|
||||
private final MemoryBuffer texture = new MemoryBuffer((2L*3*computeSizeWithMips(MODEL_TEXTURE_SIZE))*4);
|
||||
|
||||
public int modelId = -1;
|
||||
|
||||
public int biomeUploadIndex = -1;
|
||||
public @Nullable MemoryBuffer biomeUpload;
|
||||
|
||||
public void upload(ModelStore store) {//Uploads and resets for reuse
|
||||
this.upload(store.modelBuffer, store.modelColourBuffer, store.textures);
|
||||
}
|
||||
|
||||
public void upload(GlBuffer modelBuffer, GlBuffer colourBuffer, GlTexture atlas) {//Uploads and resets for reuse
|
||||
this.model.cpyTo(UploadStream.INSTANCE.upload(modelBuffer, (long) this.modelId * MODEL_SIZE, MODEL_SIZE));
|
||||
if (this.biomeUploadIndex != -1) {
|
||||
this.biomeUpload.cpyTo(UploadStream.INSTANCE.upload(colourBuffer, this.biomeUploadIndex * 4L, this.biomeUpload.size));
|
||||
this.biomeUploadIndex = -1;
|
||||
this.biomeUpload.free();
|
||||
this.biomeUpload = null;
|
||||
}
|
||||
|
||||
int X = (this.modelId&0xFF) * MODEL_TEXTURE_SIZE*3;
|
||||
int Y = ((this.modelId>>8)&0xFF) * MODEL_TEXTURE_SIZE*2;
|
||||
|
||||
long cAddr = this.texture.address;
|
||||
for (int lvl = 0; lvl < LAYERS; lvl++) {
|
||||
nglTextureSubImage2D(atlas.id, lvl, X >> lvl, Y >> lvl, (MODEL_TEXTURE_SIZE*3) >> lvl, (MODEL_TEXTURE_SIZE*2) >> lvl, GL_RGBA, GL_UNSIGNED_BYTE, cAddr);
|
||||
cAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(lvl<<1);
|
||||
}
|
||||
|
||||
this.modelId = -1;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.model.free();
|
||||
this.texture.free();
|
||||
if (this.biomeUpload != null) {
|
||||
this.biomeUpload.free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ModelBakeResultUpload processTextureBakeResult(int blockId, BlockState blockState, ColourDepthTextureData[] textureData, boolean isShaded, boolean darkenedTinting) {
|
||||
if (this.idMappings[blockId] != -1) {
|
||||
//This should be impossible to reach as it means that multiple bakes for the same blockId happened and where inflight at the same time!
|
||||
throw new IllegalStateException("Block id already added: " + blockId + " for state: " + blockState);
|
||||
}
|
||||
|
||||
this.blockStatesInFlightLock.lock();
|
||||
if (!this.blockStatesInFlight.contains(blockId)) {
|
||||
this.blockStatesInFlightLock.unlock();
|
||||
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
|
||||
}
|
||||
this.blockStatesInFlightLock.unlock();
|
||||
|
||||
//TODO: add thing for `blockState.hasEmissiveLighting()` and `blockState.getLuminance()`
|
||||
|
||||
boolean isFluid = blockState.getBlock() instanceof LiquidBlock;
|
||||
int modelId = -1;
|
||||
|
||||
|
||||
int clientFluidStateId = -1;
|
||||
|
||||
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
|
||||
//Insert into the fluid LUT
|
||||
var fluidState = blockState.getFluidState().createLegacyBlock();
|
||||
|
||||
int fluidStateId = this.mapper.getIdForBlockState(fluidState);
|
||||
|
||||
clientFluidStateId = this.idMappings[fluidStateId];
|
||||
if (clientFluidStateId == -1) {
|
||||
throw new IllegalStateException("Block has a fluid state but fluid state is not already baked!!!");
|
||||
}
|
||||
}
|
||||
|
||||
var colourProvider = getColourProvider(blockState.getBlock());
|
||||
|
||||
boolean isBiomeColourDependent = false;
|
||||
if (colourProvider != null) {
|
||||
isBiomeColourDependent = isBiomeDependentColour(colourProvider, blockState);
|
||||
}
|
||||
|
||||
ModelEntry entry;
|
||||
{//Deduplicate same entries
|
||||
entry = new ModelEntry(textureData, clientFluidStateId, isBiomeColourDependent||colourProvider==null?-1:captureColourConstant(colourProvider, blockState, DEFAULT_BIOME)|0xFF000000);
|
||||
int possibleDuplicate = this.modelTexture2id.getInt(entry);
|
||||
if (possibleDuplicate != -1) {//Duplicate found
|
||||
this.idMappings[blockId] = possibleDuplicate;
|
||||
modelId = possibleDuplicate;
|
||||
//Remove from flight
|
||||
this.blockStatesInFlightLock.lock();
|
||||
if (!this.blockStatesInFlight.remove(blockId)) {
|
||||
this.blockStatesInFlightLock.unlock();
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.blockStatesInFlightLock.unlock();
|
||||
return null;
|
||||
} else {//Not a duplicate so create a new entry
|
||||
modelId = this.modelTexture2id.size();
|
||||
//NOTE: we set the mapping at the very end so that race conditions with this and getMetadata dont occur
|
||||
//this.idMappings[blockId] = modelId;
|
||||
this.modelTexture2id.put(entry, modelId);
|
||||
}
|
||||
}
|
||||
|
||||
if (isFluid) {
|
||||
this.fluidStateLUT[modelId] = modelId;
|
||||
} else if (clientFluidStateId != -1) {
|
||||
this.fluidStateLUT[modelId] = clientFluidStateId;
|
||||
}
|
||||
|
||||
ChunkSectionLayer blockRenderLayer = null;
|
||||
if (blockState.getBlock() instanceof LiquidBlock) {
|
||||
blockRenderLayer = ItemBlockRenderTypes.getRenderLayer(blockState.getFluidState());
|
||||
} else {
|
||||
if (blockState.getBlock() instanceof LeavesBlock) {
|
||||
blockRenderLayer = ChunkSectionLayer.SOLID;
|
||||
} else {
|
||||
blockRenderLayer = ItemBlockRenderTypes.getChunkRenderType(blockState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int checkMode = blockRenderLayer==ChunkSectionLayer.SOLID?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
|
||||
|
||||
|
||||
|
||||
|
||||
ModelBakeResultUpload uploadResult = new ModelBakeResultUpload();
|
||||
uploadResult.modelId = modelId;
|
||||
long uploadPtr = uploadResult.model.address;
|
||||
|
||||
//TODO: implement;
|
||||
// TODO: if it has a constant colour instead... idk why (apparently for things like spruce leaves)?? but premultiply the texture data by the constant colour
|
||||
|
||||
//If it contains fluid but isnt a fluid
|
||||
if ((!isFluid) && (!blockState.getFluidState().isEmpty()) && clientFluidStateId != -1) {
|
||||
|
||||
//Or it with the fluid state biome dependency
|
||||
isBiomeColourDependent |= ModelQueries.isBiomeColoured(this.getModelMetadataFromClientId(clientFluidStateId));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO: special case stuff like vines and glow lichen, where it can be represented by a single double sided quad
|
||||
// since that would help alot with perf of lots of vines, can be done by having one of the faces just not exist and the other be in no occlusion mode
|
||||
|
||||
var depths = this.computeModelDepth(textureData, checkMode);
|
||||
|
||||
//TODO: THIS, note this can be tested for in 2 ways, re render the model with quad culling disabled and see if the result
|
||||
// is the same, (if yes then needs double sided quads)
|
||||
// another way to test it is if e.g. up and down havent got anything rendered but the sides do (e.g. all plants etc)
|
||||
boolean needsDoubleSidedQuads = (depths[0] < -0.1 && depths[1] < -0.1) || (depths[2] < -0.1 && depths[3] < -0.1) || (depths[4] < -0.1 && depths[5] < -0.1);
|
||||
|
||||
|
||||
boolean cullsSame = false;
|
||||
|
||||
{
|
||||
//TODO: Could also move this into the RenderDataFactory and do it on the actual blockstates instead of a guestimation
|
||||
boolean allTrue = true;
|
||||
boolean allFalse = true;
|
||||
//Guestimation test for if the block culls itself
|
||||
for (var dir : Direction.values()) {
|
||||
if (blockState.skipRendering(blockState, dir)) {
|
||||
allFalse = false;
|
||||
} else {
|
||||
allTrue = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allFalse == allTrue) {//If only some sides where self culled then abort
|
||||
cullsSame = false;
|
||||
//if (LOGGED_SELF_CULLING_WARNING.add(blockState))
|
||||
// Logger.info("Warning! blockstate: " + blockState + " only culled against its self some of the time");
|
||||
}
|
||||
|
||||
if (allTrue) {
|
||||
cullsSame = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Each face gets 1 byte, with the top 2 bytes being for whatever
|
||||
long metadata = 0;
|
||||
metadata |= isBiomeColourDependent?1:0;
|
||||
metadata |= blockRenderLayer == ChunkSectionLayer.TRANSLUCENT?2:0;
|
||||
metadata |= needsDoubleSidedQuads?4:0;
|
||||
metadata |= ((!isFluid) && !blockState.getFluidState().isEmpty())?8:0;//Has a fluid state accosiacted with it and is not itself a fluid
|
||||
metadata |= isFluid?16:0;//Is a fluid
|
||||
|
||||
metadata |= cullsSame?32:0;
|
||||
|
||||
boolean fullyOpaque = true;
|
||||
|
||||
//TODO: FIXME faces that have the same "alignment depth" e.g. (sizes[0]+sizes[1])~=1 can be merged into a double faced single quad
|
||||
|
||||
//TODO: add a bunch of control config options for overriding/setting options of metadata for each face of each type
|
||||
for (int face = 5; face != -1; face--) {//In reverse order to make indexing into the metadata long easier
|
||||
long faceUploadPtr = uploadPtr + 4L * face;//Each face gets 4 bytes worth of data
|
||||
metadata <<= 8;
|
||||
float offset = depths[face];
|
||||
if (offset < -0.1) {//Face is empty, so ignore
|
||||
metadata |= 0xFF;//Mark the face as non-existent
|
||||
//Set to -1 as safepoint
|
||||
MemoryUtil.memPutInt(faceUploadPtr, -1);
|
||||
|
||||
fullyOpaque = false;
|
||||
continue;
|
||||
}
|
||||
var faceSize = TextureUtils.computeBounds(textureData[face], checkMode);
|
||||
int writeCount = TextureUtils.getWrittenPixelCount(textureData[face], checkMode);
|
||||
|
||||
boolean faceCoversFullBlock = faceSize[0] == 0 && faceSize[2] == 0 &&
|
||||
faceSize[1] == (MODEL_TEXTURE_SIZE-1) && faceSize[3] == (MODEL_TEXTURE_SIZE-1);
|
||||
|
||||
//TODO: use faceSize and the depths to compute if mesh can be correctly rendered
|
||||
|
||||
metadata |= faceCoversFullBlock?2:0;
|
||||
|
||||
//TODO: add alot of config options for the following
|
||||
boolean occludesFace = true;
|
||||
occludesFace &= blockRenderLayer != ChunkSectionLayer.TRANSLUCENT;//If its translucent, it doesnt occlude
|
||||
|
||||
//TODO: make this an option, basicly if the face is really close, it occludes otherwise it doesnt
|
||||
occludesFace &= offset < 0.1;//If the face is rendered far away from the other face, then it doesnt occlude
|
||||
|
||||
if (occludesFace) {
|
||||
occludesFace &= ((float)writeCount)/(MODEL_TEXTURE_SIZE * MODEL_TEXTURE_SIZE) > 0.9;// only occlude if the face covers more than 90% of the face
|
||||
}
|
||||
metadata |= occludesFace?1:0;
|
||||
fullyOpaque &= occludesFace;
|
||||
|
||||
|
||||
|
||||
boolean canBeOccluded = true;
|
||||
//TODO: make this an option on how far/close
|
||||
canBeOccluded &= offset < 0.3;//If the face is rendered far away from the other face, then it cant be occluded
|
||||
|
||||
metadata |= canBeOccluded?4:0;
|
||||
|
||||
//Face uses its own lighting if its not flat against the adjacent block & isnt traslucent
|
||||
metadata |= (offset > 0.01 || blockRenderLayer == ChunkSectionLayer.TRANSLUCENT)?0b1000:0;
|
||||
|
||||
|
||||
|
||||
if (MODEL_TEXTURE_SIZE-1 != 15) {
|
||||
//Scale face size from 0->this.modelTextureSize-1 to 0->15
|
||||
for (int i = 0; i < 4; i++) {
|
||||
faceSize[i] = Math.round((((float) faceSize[i]) / (MODEL_TEXTURE_SIZE - 1)) * 15);
|
||||
}
|
||||
}
|
||||
|
||||
int faceModelData = 0;
|
||||
faceModelData |= faceSize[0] | (faceSize[1]<<4) | (faceSize[2]<<8) | (faceSize[3]<<12);
|
||||
//Change the scale from 0->1 (ends inclusive)
|
||||
// this is cursed also warning stuff at 63 (i.e half a pixel from the end will be clamped to the end)
|
||||
int enc = Math.round(offset*64);
|
||||
faceModelData |= Math.min(enc,62)<<16;
|
||||
//Still have 11 bits free
|
||||
|
||||
//Stuff like fences are solid, however they have extra side piece that mean it needs to have discard on
|
||||
int area = (faceSize[1]-faceSize[0]+1) * (faceSize[3]-faceSize[2]+1);
|
||||
boolean needsAlphaDiscard = ((float)writeCount)/area<0.9;//If the amount of area covered by written pixels is less than a threashold, disable discard as its not needed
|
||||
|
||||
needsAlphaDiscard |= blockRenderLayer != ChunkSectionLayer.SOLID;
|
||||
needsAlphaDiscard &= blockRenderLayer != ChunkSectionLayer.TRANSLUCENT;//Translucent doesnt have alpha discard
|
||||
faceModelData |= needsAlphaDiscard?1<<22:0;
|
||||
|
||||
faceModelData |= ((!faceCoversFullBlock)&&blockRenderLayer != ChunkSectionLayer.TRANSLUCENT)?1<<23:0;//Alpha discard override, translucency doesnt have alpha discard
|
||||
|
||||
//Bits 24,25 are tint metadata
|
||||
if (colourProvider!=null) {//We have a tint
|
||||
int tintState = TextureUtils.computeFaceTint(textureData[face], checkMode);
|
||||
if (tintState == 2) {//Partial tint
|
||||
faceModelData |= 1<<24;
|
||||
} else if (tintState == 3) {//Full tint
|
||||
faceModelData |= 2<<24;
|
||||
}
|
||||
}
|
||||
|
||||
MemoryUtil.memPutInt(faceUploadPtr, faceModelData);
|
||||
}
|
||||
|
||||
metadata |= fullyOpaque?(1L<<(48+6)):0;
|
||||
|
||||
boolean canBeCorrectlyRendered = true;//This represents if a model can be correctly (perfectly) represented
|
||||
// i.e. no gaps
|
||||
|
||||
this.metadataCache[modelId] = metadata;
|
||||
|
||||
uploadPtr += 4*6;
|
||||
//Have 40 bytes free for remaining model data
|
||||
// todo: put in like the render layer type ig? along with colour resolver info
|
||||
int modelFlags = 0;
|
||||
modelFlags |= colourProvider != null?1:0;
|
||||
modelFlags |= isBiomeColourDependent?2:0;//Basicly whether to use the next int as a colour or as a base index/id into a colour buffer for biome dependent colours
|
||||
modelFlags |= blockRenderLayer == ChunkSectionLayer.TRANSLUCENT?4:0;//Is translucent
|
||||
|
||||
|
||||
//TODO: THIS
|
||||
modelFlags |= isShaded?8:0;//model has AO and shade
|
||||
|
||||
//modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha
|
||||
MemoryUtil.memPutInt(uploadPtr, modelFlags); uploadPtr += 4;
|
||||
|
||||
|
||||
//Temporary override to always be non biome specific
|
||||
if (colourProvider == null) {
|
||||
MemoryUtil.memPutInt(uploadPtr, -1);//Set the default to nothing so that its faster on the gpu
|
||||
} else if (!isBiomeColourDependent) {
|
||||
MemoryUtil.memPutInt(uploadPtr, entry.tintingColour);
|
||||
} else if (!this.biomes.isEmpty()) {
|
||||
//Populate the list of biomes for the model state
|
||||
int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size();
|
||||
MemoryUtil.memPutInt(uploadPtr, biomeIndex);
|
||||
this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState));
|
||||
|
||||
uploadResult.biomeUploadIndex = biomeIndex;
|
||||
long clrUploadPtr = (uploadResult.biomeUpload = new MemoryBuffer(4L * this.biomes.size())).address;
|
||||
for (var biome : this.biomes) {
|
||||
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, blockState, biome)|0xFF000000); clrUploadPtr += 4;
|
||||
}
|
||||
}
|
||||
uploadPtr += 4;
|
||||
|
||||
//have 32 bytes of free space after here
|
||||
|
||||
//install the custom mapping id if it exists
|
||||
if (this.customBlockStateIdMapping != null && this.customBlockStateIdMapping.containsKey(blockState)) {
|
||||
MemoryUtil.memPutInt(uploadPtr, this.customBlockStateIdMapping.getInt(blockState));
|
||||
} else {
|
||||
MemoryUtil.memPutInt(uploadPtr, 0);
|
||||
} uploadPtr += 4;
|
||||
|
||||
|
||||
//Note: if the layer isSolid then need to fill all the points in the texture where alpha == 0 with the average colour
|
||||
// of the surrounding blocks but only within the computed face size bounds
|
||||
|
||||
//TODO callback to inject extra data into the model data
|
||||
|
||||
|
||||
MipGen.putTextures(darkenedTinting, textureData, uploadResult.texture);
|
||||
|
||||
//glGenerateTextureMipmap(this.textures.id);
|
||||
|
||||
//Set the mapping at the very end
|
||||
this.idMappings[blockId] = modelId;
|
||||
|
||||
this.blockStatesInFlightLock.lock();
|
||||
if (!this.blockStatesInFlight.remove(blockId)) {
|
||||
this.blockStatesInFlightLock.unlock();
|
||||
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
|
||||
}
|
||||
this.blockStatesInFlightLock.unlock();
|
||||
|
||||
return uploadResult;
|
||||
}
|
||||
|
||||
private static final class BiomeUploadResult implements ResultUploader {
|
||||
private final MemoryBuffer biomeColourBuffer;
|
||||
private final MemoryBuffer modelBiomeIndexPairs;
|
||||
private BiomeUploadResult(int biomes, int models) {
|
||||
this.biomeColourBuffer = new MemoryBuffer(biomes*models*4);
|
||||
this.modelBiomeIndexPairs = new MemoryBuffer(models*8);
|
||||
}
|
||||
|
||||
public void upload(ModelStore store) {
|
||||
this.upload(store.modelBuffer, store.modelColourBuffer);
|
||||
}
|
||||
|
||||
public void upload(GlBuffer modelBuffer, GlBuffer modelColourBuffer) {
|
||||
this.biomeColourBuffer.cpyTo(UploadStream.INSTANCE.upload(modelColourBuffer, 0, this.biomeColourBuffer.size));
|
||||
|
||||
//TODO: optimize this to like a compute scatter update or something
|
||||
long ptr = this.modelBiomeIndexPairs.address;
|
||||
for (long offset = 0; offset < this.modelBiomeIndexPairs.size; offset += 8) {
|
||||
long v = MemoryUtil.memGetLong(ptr);ptr += 8;
|
||||
MemoryUtil.memPutInt(UploadStream.INSTANCE.upload(modelBuffer, (MODEL_SIZE*(v&((1L<<32)-1)))+ 4*6 + 4, 4), (int) (v>>>32));
|
||||
}
|
||||
|
||||
this.biomeColourBuffer.free();
|
||||
this.modelBiomeIndexPairs.free();
|
||||
}
|
||||
|
||||
public void free() {
|
||||
if (!this.biomeColourBuffer.isFreed()) {
|
||||
this.biomeColourBuffer.free();
|
||||
this.modelBiomeIndexPairs.free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BiomeUploadResult addBiome0(int id, Biome biome) {
|
||||
for (int i = this.biomes.size(); i <= id; i++) {
|
||||
this.biomes.add(null);
|
||||
}
|
||||
var oldBiome = this.biomes.set(id, biome);
|
||||
|
||||
if (oldBiome != null && oldBiome != biome) {
|
||||
throw new IllegalStateException("Biome was put in an id that was not null");
|
||||
}
|
||||
if (oldBiome == biome) {
|
||||
Logger.error("Biome added was a duplicate");
|
||||
}
|
||||
|
||||
if (this.modelsRequiringBiomeColours.isEmpty()) return null;
|
||||
|
||||
var result = new BiomeUploadResult(this.biomes.size(), this.modelsRequiringBiomeColours.size());
|
||||
|
||||
int i = 0;
|
||||
long modelUpPtr = result.modelBiomeIndexPairs.address;
|
||||
for (var entry : this.modelsRequiringBiomeColours) {
|
||||
var colourProvider = getColourProvider(entry.right().getBlock());
|
||||
if (colourProvider == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
//Populate the list of biomes for the model state
|
||||
int biomeIndex = (i++) * this.biomes.size();
|
||||
MemoryUtil.memPutLong(modelUpPtr, Integer.toUnsignedLong(entry.left())|(Integer.toUnsignedLong(biomeIndex)<<32));modelUpPtr+=8;
|
||||
long clrUploadPtr = result.biomeColourBuffer.address + biomeIndex * 4L;
|
||||
for (var biomeE : this.biomes) {
|
||||
if (biomeE == null) {
|
||||
continue;//If null, ignore
|
||||
}
|
||||
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, entry.right(), biomeE)|0xFF000000); clrUploadPtr += 4;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static BlockColor getColourProvider(Block block) {
|
||||
return Minecraft.getInstance().getBlockColors().blockColors.byId(BuiltInRegistries.BLOCK.getId(block));
|
||||
}
|
||||
|
||||
//TODO: add a method to detect biome dependent colours (can do by detecting if getColor is ever called)
|
||||
// if it is, need to add it to a list and mark it as biome colour dependent or something then the shader
|
||||
// will either use the uint as an index or a direct colour multiplier
|
||||
private static int captureColourConstant(BlockColor colorProvider, BlockState state, Biome biome) {
|
||||
var getter = new BlockAndTintGetter() {
|
||||
@Override
|
||||
public float getShade(Direction direction, boolean shaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBrightness(LightLayer type, BlockPos pos) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LevelLightEngine getLightEngine() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockTint(BlockPos pos, ColorResolver colorResolver) {
|
||||
return colorResolver.getColor(biome, 0, 0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity getBlockEntity(BlockPos pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidState getFluidState(BlockPos pos) {
|
||||
return state.getFluidState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinY() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
//Multiple layer bs to do with flower beds
|
||||
int c = colorProvider.getColor(state, getter, BlockPos.ZERO, 0);
|
||||
if (c!=-1) return c;
|
||||
return colorProvider.getColor(state, getter, BlockPos.ZERO, 1);
|
||||
}
|
||||
|
||||
private static boolean isBiomeDependentColour(BlockColor colorProvider, BlockState state) {
|
||||
boolean[] biomeDependent = new boolean[1];
|
||||
var getter = new BlockAndTintGetter() {
|
||||
@Override
|
||||
public float getShade(Direction direction, boolean shaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBrightness(LightLayer type, BlockPos pos) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LevelLightEngine getLightEngine() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockTint(BlockPos pos, ColorResolver colorResolver) {
|
||||
biomeDependent[0] = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity getBlockEntity(BlockPos pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidState getFluidState(BlockPos pos) {
|
||||
return state.getFluidState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinY() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
colorProvider.getColor(state, getter, BlockPos.ZERO, 0);
|
||||
colorProvider.getColor(state, getter, BlockPos.ZERO, 1);
|
||||
return biomeDependent[0];
|
||||
}
|
||||
|
||||
private static float[] computeModelDepth(ColourDepthTextureData[] textures, int checkMode) {
|
||||
float[] res = new float[6];
|
||||
for (var dir : Direction.values()) {
|
||||
var data = textures[dir.get3DDataValue()];
|
||||
float fd = TextureUtils.computeDepth(data, TextureUtils.DEPTH_MODE_AVG, checkMode);//Compute the min float depth, smaller means closer to the camera, range 0-1
|
||||
//int depth = Math.round(fd * MODEL_TEXTURE_SIZE);
|
||||
//If fd is -1, it means that there was nothing rendered on that face and it should be discarded
|
||||
if (fd < -0.1) {
|
||||
res[dir.ordinal()] = -1;
|
||||
} else {
|
||||
res[dir.ordinal()] = fd;//((float) depth)/MODEL_TEXTURE_SIZE;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public int[] _unsafeRawAccess() {
|
||||
return this.idMappings;
|
||||
}
|
||||
|
||||
public int getModelId(int blockId) {
|
||||
int map = this.idMappings[blockId];
|
||||
if (map == -1) {
|
||||
throw new IdNotYetComputedException(blockId, true);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public boolean hasModelForBlockId(int blockId) {
|
||||
return this.idMappings[blockId] != -1;
|
||||
}
|
||||
|
||||
public int getFluidClientStateId(int clientBlockStateId) {
|
||||
int map = this.fluidStateLUT[clientBlockStateId];
|
||||
if (map == -1) {
|
||||
throw new IdNotYetComputedException(clientBlockStateId, false);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public long getModelMetadataFromClientId(int clientId) {
|
||||
return this.metadataCache[clientId];
|
||||
}
|
||||
|
||||
|
||||
public void free() {
|
||||
this.bakery.free();
|
||||
this.downstream.free();
|
||||
while (!this.rawBakeResults.isEmpty()) {
|
||||
this.rawBakeResults.poll().rawData.free();
|
||||
}
|
||||
while (!this.uploadResults.isEmpty()) {
|
||||
this.uploadResults.poll().free();
|
||||
}
|
||||
}
|
||||
|
||||
public int getBakedCount() {
|
||||
return this.modelTexture2id.size();
|
||||
}
|
||||
|
||||
public int getInflightCount() {
|
||||
//TODO replace all of this with an atomic?
|
||||
int size = this.blockStatesInFlight.size();
|
||||
size += this.uploadResults.size();
|
||||
size += this.biomeQueue.size();
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
private static int computeSizeWithMips(int size) {
|
||||
int total = 0;
|
||||
for (;size!=0;size>>=1) total += size*size;
|
||||
return total;
|
||||
}
|
||||
}
|
||||
@@ -1,686 +0,0 @@
|
||||
package me.cortex.voxy.client.core.model;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlConst;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||
import me.cortex.voxy.client.core.IGetVoxelCore;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.common.world.other.Mapper;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.FluidBlock;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.color.block.BlockColorProvider;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.client.render.RenderLayers;
|
||||
import net.minecraft.fluid.FluidState;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.util.Pair;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.world.BlockRenderView;
|
||||
import net.minecraft.world.LightType;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.BiomeKeys;
|
||||
import net.minecraft.world.biome.ColorResolver;
|
||||
import net.minecraft.world.chunk.light.LightingProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
|
||||
import static org.lwjgl.opengl.GL11C.GL_NEAREST_MIPMAP_LINEAR;
|
||||
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MAX_LOD;
|
||||
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MIN_LOD;
|
||||
import static org.lwjgl.opengl.GL33.glDeleteSamplers;
|
||||
import static org.lwjgl.opengl.GL33.glGenSamplers;
|
||||
import static org.lwjgl.opengl.GL33C.glSamplerParameteri;
|
||||
import static org.lwjgl.opengl.GL45C.glTextureSubImage2D;
|
||||
|
||||
//Manages the storage and updating of model states, textures and colours
|
||||
|
||||
//Also has a fast long[] based metadata lookup for when the terrain mesher needs to look up the face occlusion data
|
||||
|
||||
//TODO: support more than 65535 states, what should actually happen is a blockstate is registered, the model data is generated, then compared
|
||||
// to all other models already loaded, if it is a duplicate, create a mapping from the id to the already loaded id, this will help with meshing aswell
|
||||
// as leaves and such will be able to be merged
|
||||
public class ModelManager {
|
||||
|
||||
//TODO: replace the fluid BlockState with a client model id integer of the fluidState, requires looking up
|
||||
// the fluid state in the mipper
|
||||
private record ModelEntry(List<ColourDepthTextureData> textures, int fluidBlockStateId){
|
||||
private ModelEntry(ColourDepthTextureData[] textures, int fluidBlockStateId) {
|
||||
this(Stream.of(textures).map(ColourDepthTextureData::clone).toList(), fluidBlockStateId);
|
||||
}
|
||||
}
|
||||
|
||||
public static final int MODEL_SIZE = 64;
|
||||
public final ModelTextureBakery bakery;
|
||||
private final GlBuffer modelBuffer;
|
||||
private final GlBuffer modelColourBuffer;
|
||||
private final GlTexture textures;
|
||||
private final int blockSampler = glGenSamplers();
|
||||
|
||||
private final int modelTextureSize;
|
||||
|
||||
//Model data might also contain a constant colour if the colour resolver produces a constant colour, this saves space in the
|
||||
// section buffer reverse indexing
|
||||
|
||||
//model data also contains if a face should be randomly rotated,flipped etc to get rid of moire effect
|
||||
// this would be done in the fragment shader
|
||||
|
||||
//The Meta-cache contains critical information needed for meshing, colour provider bit, per-face = is empty, has alpha, is solid, full width, full height
|
||||
// alpha means that some pixels have alpha values and belong in the translucent rendering layer,
|
||||
// is empty means that the face is air/shouldent be rendered as there is nothing there
|
||||
// is solid means that every pixel is fully opaque
|
||||
// full width, height, is if the blockmodel dimentions occupy a full block, e.g. comparator, some faces do some dont and some only in a specific axis
|
||||
|
||||
//FIXME: the issue is e.g. leaves are translucent but the alpha value is used to colour the leaves, so a block can have alpha but still be only made up of transparent or opaque pixels
|
||||
// will need to find a way to send this info to the shader via the material, if it is in the opaque phase render as transparent with blending shiz
|
||||
|
||||
//TODO: ADD an occlusion mask that can be queried (16x16 pixels takes up 4 longs) this mask shows what pixels are exactly occluded at the edge of the block
|
||||
// so that full block occlusion can work nicely
|
||||
|
||||
|
||||
//TODO: what might work maybe, is that all the transparent pixels should be set to the average of the other pixels
|
||||
// that way the block is always "fully occluding" (if the block model doesnt cover the entire thing), maybe
|
||||
// this has some issues with quad merging
|
||||
//TODO: ACTUALLY, full out all the transparent pixels that are _within_ the bounding box of the model
|
||||
// this will mean that when quad merging and rendering, the transparent pixels of the block where there shouldent be
|
||||
// might still work???
|
||||
|
||||
// this has an issue with scaffolding i believe tho, so maybe make it a probability to render??? idk
|
||||
private final long[] metadataCache;
|
||||
private final int[] fluidStateLUT;
|
||||
|
||||
//Provides a map from id -> model id as multiple ids might have the same internal model id
|
||||
private final int[] idMappings;
|
||||
private final Object2IntOpenHashMap<ModelEntry> modelTexture2id = new Object2IntOpenHashMap<>();
|
||||
|
||||
|
||||
private final List<Biome> biomes = new ArrayList<>();
|
||||
private final List<Pair<Integer, BlockState>> modelsRequiringBiomeColours = new ArrayList<>();
|
||||
|
||||
private static final ObjectSet<BlockState> LOGGED_SELF_CULLING_WARNING = new ObjectOpenHashSet<>();
|
||||
|
||||
public ModelManager(int modelTextureSize) {
|
||||
this.modelTextureSize = modelTextureSize;
|
||||
this.bakery = new ModelTextureBakery(modelTextureSize, modelTextureSize);
|
||||
this.modelBuffer = new GlBuffer(MODEL_SIZE * (1<<16));
|
||||
|
||||
this.modelColourBuffer = new GlBuffer(4 * (1<<16));
|
||||
|
||||
//TODO: figure out how to do mipping :blobfox_pineapple:
|
||||
this.textures = new GlTexture().store(GL_RGBA8, 4, modelTextureSize*3*256,modelTextureSize*2*256);
|
||||
this.metadataCache = new long[1<<16];
|
||||
this.fluidStateLUT = new int[1<<16];
|
||||
this.idMappings = new int[1<<20];//Max of 1 million blockstates mapping to 65k model states
|
||||
Arrays.fill(this.idMappings, -1);
|
||||
Arrays.fill(this.fluidStateLUT, -1);
|
||||
|
||||
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_LOD, 0);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, 4);
|
||||
|
||||
this.modelTexture2id.defaultReturnValue(-1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//TODO: what i need to do is seperate out fluid states from blockStates
|
||||
|
||||
|
||||
//TODO: so need a few things, per face sizes and offsets, the sizes should be computed from the pixels and find the minimum bounding pixel
|
||||
// while the depth is computed from the depth buffer data
|
||||
public int addEntry(int blockId, BlockState blockState) {
|
||||
if (this.idMappings[blockId] != -1) {
|
||||
System.err.println("Block id already added: " + blockId + " for state: " + blockState);
|
||||
return this.idMappings[blockId];
|
||||
}
|
||||
|
||||
boolean isFluid = blockState.getBlock() instanceof FluidBlock;
|
||||
int modelId = -1;
|
||||
var textureData = this.bakery.renderFaces(blockState, 123456, isFluid);
|
||||
|
||||
int clientFluidStateId = -1;
|
||||
|
||||
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
|
||||
//Insert into the fluid LUT
|
||||
var fluidState = blockState.getFluidState().getBlockState();
|
||||
|
||||
//TODO:FIXME: PASS IN THE Mapper instead of grabbing it!!! THIS IS CRTICIAL TO FIX
|
||||
int fluidStateId = ((IGetVoxelCore)MinecraftClient.getInstance().worldRenderer).getVoxelCore().getWorldEngine().getMapper().getIdForBlockState(fluidState);
|
||||
|
||||
|
||||
clientFluidStateId = this.idMappings[fluidStateId];
|
||||
if (clientFluidStateId == -1) {
|
||||
clientFluidStateId = this.addEntry(fluidStateId, fluidState);
|
||||
}
|
||||
}
|
||||
|
||||
{//Deduplicate same entries
|
||||
var entry = new ModelEntry(textureData, clientFluidStateId);
|
||||
int possibleDuplicate = this.modelTexture2id.getInt(entry);
|
||||
if (possibleDuplicate != -1) {//Duplicate found
|
||||
this.idMappings[blockId] = possibleDuplicate;
|
||||
modelId = possibleDuplicate;
|
||||
return possibleDuplicate;
|
||||
} else {//Not a duplicate so create a new entry
|
||||
modelId = this.modelTexture2id.size();
|
||||
this.idMappings[blockId] = modelId;
|
||||
this.modelTexture2id.put(entry, modelId);
|
||||
}
|
||||
}
|
||||
|
||||
if (isFluid) {
|
||||
this.fluidStateLUT[modelId] = modelId;
|
||||
} else if (clientFluidStateId != -1) {
|
||||
this.fluidStateLUT[modelId] = clientFluidStateId;
|
||||
}
|
||||
|
||||
var colourProvider = MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(blockState.getBlock()));
|
||||
|
||||
|
||||
RenderLayer blockRenderLayer = null;
|
||||
if (blockState.getBlock() instanceof FluidBlock) {
|
||||
blockRenderLayer = RenderLayers.getFluidLayer(blockState.getFluidState());
|
||||
} else {
|
||||
blockRenderLayer = RenderLayers.getBlockLayer(blockState);
|
||||
}
|
||||
|
||||
|
||||
int checkMode = blockRenderLayer==RenderLayer.getSolid()?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
|
||||
|
||||
|
||||
|
||||
|
||||
long uploadPtr = UploadStream.INSTANCE.upload(this.modelBuffer, (long) modelId * MODEL_SIZE, MODEL_SIZE);
|
||||
|
||||
|
||||
//TODO: implement;
|
||||
// TODO: if it has a constant colour instead... idk why (apparently for things like spruce leaves)?? but premultiply the texture data by the constant colour
|
||||
boolean hasBiomeColourResolver = false;
|
||||
if (colourProvider != null) {
|
||||
hasBiomeColourResolver = isBiomeDependentColour(colourProvider, blockState);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO: special case stuff like vines and glow lichen, where it can be represented by a single double sided quad
|
||||
// since that would help alot with perf of lots of vines, can be done by having one of the faces just not exist and the other be in no occlusion mode
|
||||
|
||||
var sizes = this.computeModelDepth(textureData, checkMode);
|
||||
|
||||
//TODO: THIS, note this can be tested for in 2 ways, re render the model with quad culling disabled and see if the result
|
||||
// is the same, (if yes then needs double sided quads)
|
||||
// another way to test it is if e.g. up and down havent got anything rendered but the sides do (e.g. all plants etc)
|
||||
boolean needsDoubleSidedQuads = (sizes[0] < -0.1 && sizes[1] < -0.1) || (sizes[2] < -0.1 && sizes[3] < -0.1) || (sizes[4] < -0.1 && sizes[5] < -0.1);
|
||||
|
||||
|
||||
boolean cullsSame = false;
|
||||
|
||||
{
|
||||
//TODO: Could also move this into the RenderDataFactory and do it on the actual blockstates instead of a guestimation
|
||||
boolean allTrue = true;
|
||||
boolean allFalse = true;
|
||||
//Guestimation test for if the block culls itself
|
||||
for (var dir : Direction.values()) {
|
||||
if (blockState.isSideInvisible(blockState, dir)) {
|
||||
allFalse = false;
|
||||
} else {
|
||||
allTrue = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allFalse == allTrue) {//If only some sides where self culled then abort
|
||||
cullsSame = false;
|
||||
if (LOGGED_SELF_CULLING_WARNING.add(blockState)) System.err.println("Warning! blockstate: " + blockState + " only culled against its self some of the time");
|
||||
}
|
||||
|
||||
if (allTrue) {
|
||||
cullsSame = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Each face gets 1 byte, with the top 2 bytes being for whatever
|
||||
long metadata = 0;
|
||||
metadata |= hasBiomeColourResolver?1:0;
|
||||
metadata |= blockRenderLayer == RenderLayer.getTranslucent()?2:0;
|
||||
metadata |= needsDoubleSidedQuads?4:0;
|
||||
metadata |= (!blockState.getFluidState().isEmpty())?8:0;//Has a fluid state accosiacted with it
|
||||
metadata |= isFluid?16:0;//Is a fluid
|
||||
|
||||
metadata |= cullsSame?32:0;
|
||||
|
||||
//TODO: add a bunch of control config options for overriding/setting options of metadata for each face of each type
|
||||
for (int face = 5; face != -1; face--) {//In reverse order to make indexing into the metadata long easier
|
||||
long faceUploadPtr = uploadPtr + 4L * face;//Each face gets 4 bytes worth of data
|
||||
metadata <<= 8;
|
||||
float offset = sizes[face];
|
||||
if (offset < -0.1) {//Face is empty, so ignore
|
||||
metadata |= 0xFF;//Mark the face as non-existent
|
||||
//Set to -1 as safepoint
|
||||
MemoryUtil.memPutInt(faceUploadPtr, -1);
|
||||
continue;
|
||||
}
|
||||
var faceSize = TextureUtils.computeBounds(textureData[face], checkMode);
|
||||
int writeCount = TextureUtils.getWrittenPixelCount(textureData[face], checkMode);
|
||||
|
||||
boolean faceCoversFullBlock = faceSize[0] == 0 && faceSize[2] == 0 &&
|
||||
faceSize[1] == (this.modelTextureSize-1) && faceSize[3] == (this.modelTextureSize-1);
|
||||
|
||||
metadata |= faceCoversFullBlock?2:0;
|
||||
|
||||
//TODO: add alot of config options for the following
|
||||
boolean occludesFace = true;
|
||||
occludesFace &= blockRenderLayer != RenderLayer.getTranslucent();//If its translucent, it doesnt occlude
|
||||
|
||||
//TODO: make this an option, basicly if the face is really close, it occludes otherwise it doesnt
|
||||
occludesFace &= offset < 0.1;//If the face is rendered far away from the other face, then it doesnt occlude
|
||||
|
||||
if (occludesFace) {
|
||||
occludesFace &= ((float)writeCount)/(this.modelTextureSize * this.modelTextureSize) > 0.9;// only occlude if the face covers more than 90% of the face
|
||||
}
|
||||
metadata |= occludesFace?1:0;
|
||||
|
||||
|
||||
|
||||
boolean canBeOccluded = true;
|
||||
//TODO: make this an option on how far/close
|
||||
canBeOccluded &= offset < 0.3;//If the face is rendered far away from the other face, then it cant be occluded
|
||||
|
||||
metadata |= canBeOccluded?4:0;
|
||||
|
||||
//Face uses its own lighting if its not flat against the adjacent block & isnt traslucent
|
||||
metadata |= (offset != 0 || blockRenderLayer == RenderLayer.getTranslucent())?0b1000:0;
|
||||
|
||||
|
||||
|
||||
//Scale face size from 0->this.modelTextureSize-1 to 0->15
|
||||
for (int i = 0; i < 4; i++) {
|
||||
faceSize[i] = Math.round((((float)faceSize[i])/(this.modelTextureSize-1))*15);
|
||||
}
|
||||
|
||||
int faceModelData = 0;
|
||||
faceModelData |= faceSize[0] | (faceSize[1]<<4) | (faceSize[2]<<8) | (faceSize[3]<<12);
|
||||
faceModelData |= Math.round(offset*63)<<16;//Change the scale from 0->1 (ends inclusive) float to 0->63 (6 bits) NOTE! that 63 == 1.0f meaning its shifted all the way to the other side of the model
|
||||
//Still have 11 bits free
|
||||
|
||||
//Stuff like fences are solid, however they have extra side piece that mean it needs to have discard on
|
||||
int area = (faceSize[1]-faceSize[0]+1) * (faceSize[3]-faceSize[2]+1);
|
||||
boolean needsAlphaDiscard = ((float)writeCount)/area<0.9;//If the amount of area covered by written pixels is less than a threashold, disable discard as its not needed
|
||||
|
||||
needsAlphaDiscard |= blockRenderLayer != RenderLayer.getSolid();
|
||||
needsAlphaDiscard &= blockRenderLayer != RenderLayer.getTranslucent();//Translucent doesnt have alpha discard
|
||||
faceModelData |= needsAlphaDiscard?1<<22:0;
|
||||
|
||||
faceModelData |= ((!faceCoversFullBlock)&&blockRenderLayer != RenderLayer.getTranslucent())?1<<23:0;//Alpha discard override, translucency doesnt have alpha discard
|
||||
|
||||
|
||||
|
||||
MemoryUtil.memPutInt(faceUploadPtr, faceModelData);
|
||||
}
|
||||
this.metadataCache[modelId] = metadata;
|
||||
|
||||
uploadPtr += 4*6;
|
||||
//Have 40 bytes free for remaining model data
|
||||
// todo: put in like the render layer type ig? along with colour resolver info
|
||||
int modelFlags = 0;
|
||||
modelFlags |= colourProvider != null?1:0;
|
||||
modelFlags |= hasBiomeColourResolver?2:0;//Basicly whether to use the next int as a colour or as a base index/id into a colour buffer for biome dependent colours
|
||||
modelFlags |= blockRenderLayer == RenderLayer.getTranslucent()?4:0;
|
||||
modelFlags |= blockRenderLayer == RenderLayer.getCutout()?0:8;
|
||||
|
||||
//modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha
|
||||
MemoryUtil.memPutInt(uploadPtr, modelFlags);
|
||||
//Temporary override to always be non biome specific
|
||||
if (colourProvider == null) {
|
||||
MemoryUtil.memPutInt(uploadPtr + 4, -1);//Set the default to nothing so that its faster on the gpu
|
||||
} else if (!hasBiomeColourResolver) {
|
||||
Biome defaultBiome = MinecraftClient.getInstance().world.getRegistryManager().get(RegistryKeys.BIOME).get(BiomeKeys.PLAINS);
|
||||
MemoryUtil.memPutInt(uploadPtr + 4, captureColourConstant(colourProvider, blockState, defaultBiome)|0xFF000000);
|
||||
} else if (!this.biomes.isEmpty()) {
|
||||
//Populate the list of biomes for the model state
|
||||
int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size();
|
||||
MemoryUtil.memPutInt(uploadPtr + 4, biomeIndex);
|
||||
this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState));
|
||||
long clrUploadPtr = UploadStream.INSTANCE.upload(this.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size());
|
||||
for (var biome : this.biomes) {
|
||||
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, blockState, biome)|0xFF000000); clrUploadPtr += 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Note: if the layer isSolid then need to fill all the points in the texture where alpha == 0 with the average colour
|
||||
// of the surrounding blocks but only within the computed face size bounds
|
||||
//TODO
|
||||
|
||||
|
||||
this.putTextures(modelId, textureData);
|
||||
|
||||
//glGenerateTextureMipmap(this.textures.id);
|
||||
return modelId;
|
||||
}
|
||||
|
||||
public void addBiome(int id, Biome biome) {
|
||||
this.biomes.add(biome);
|
||||
if (this.biomes.size()-1 != id) {
|
||||
throw new IllegalStateException("Biome ordering not consistent with biome id for biome " + biome + " expected id: " + (this.biomes.size()-1) + " got id: " + id);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (var entry : this.modelsRequiringBiomeColours) {
|
||||
var colourProvider = MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(entry.getRight().getBlock()));
|
||||
if (colourProvider == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
//Populate the list of biomes for the model state
|
||||
int biomeIndex = (i++) * this.biomes.size();
|
||||
MemoryUtil.memPutInt( UploadStream.INSTANCE.upload(this.modelBuffer, (entry.getLeft()*MODEL_SIZE)+ 4*6 + 4, 4), biomeIndex);
|
||||
long clrUploadPtr = UploadStream.INSTANCE.upload(this.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size());
|
||||
for (var biomeE : this.biomes) {
|
||||
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, entry.getRight(), biomeE)|0xFF000000); clrUploadPtr += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//TODO: add a method to detect biome dependent colours (can do by detecting if getColor is ever called)
|
||||
// if it is, need to add it to a list and mark it as biome colour dependent or something then the shader
|
||||
// will either use the uint as an index or a direct colour multiplier
|
||||
private static int captureColourConstant(BlockColorProvider colorProvider, BlockState state, Biome biome) {
|
||||
return colorProvider.getColor(state, new BlockRenderView() {
|
||||
@Override
|
||||
public float getBrightness(Direction direction, boolean shaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLightLevel(LightType type, BlockPos pos) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightingProvider getLightingProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor(BlockPos pos, ColorResolver colorResolver) {
|
||||
return colorResolver.getColor(biome, 0, 0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity getBlockEntity(BlockPos pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidState getFluidState(BlockPos pos) {
|
||||
return state.getFluidState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBottomY() {
|
||||
return 0;
|
||||
}
|
||||
}, BlockPos.ORIGIN, 0);
|
||||
}
|
||||
|
||||
private static boolean isBiomeDependentColour(BlockColorProvider colorProvider, BlockState state) {
|
||||
boolean[] biomeDependent = new boolean[1];
|
||||
colorProvider.getColor(state, new BlockRenderView() {
|
||||
@Override
|
||||
public float getBrightness(Direction direction, boolean shaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLightLevel(LightType type, BlockPos pos) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightingProvider getLightingProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor(BlockPos pos, ColorResolver colorResolver) {
|
||||
biomeDependent[0] = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity getBlockEntity(BlockPos pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidState getFluidState(BlockPos pos) {
|
||||
return state.getFluidState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBottomY() {
|
||||
return 0;
|
||||
}
|
||||
}, BlockPos.ORIGIN, 0);
|
||||
return biomeDependent[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static boolean faceExists(long metadata, int face) {
|
||||
return ((metadata>>(8*face))&0xFF)!=0xFF;
|
||||
}
|
||||
|
||||
public static boolean faceCanBeOccluded(long metadata, int face) {
|
||||
return ((metadata>>(8*face))&0b100)==0b100;
|
||||
}
|
||||
|
||||
public static boolean faceOccludes(long metadata, int face) {
|
||||
return faceExists(metadata, face) && ((metadata>>(8*face))&0b1)==0b1;
|
||||
}
|
||||
|
||||
public static boolean faceUsesSelfLighting(long metadata, int face) {
|
||||
return ((metadata>>(8*face))&0b1000) != 0;
|
||||
}
|
||||
|
||||
public static boolean isDoubleSided(long metadata) {
|
||||
return ((metadata>>(8*6))&4) != 0;
|
||||
}
|
||||
|
||||
public static boolean isTranslucent(long metadata) {
|
||||
return ((metadata>>(8*6))&2) != 0;
|
||||
}
|
||||
|
||||
public static boolean containsFluid(long metadata) {
|
||||
return ((metadata>>(8*6))&8) != 0;
|
||||
}
|
||||
|
||||
public static boolean isFluid(long metadata) {
|
||||
return ((metadata>>(8*6))&16) != 0;
|
||||
}
|
||||
|
||||
public static boolean isBiomeColoured(long metadata) {
|
||||
return ((metadata>>(8*6))&1) != 0;
|
||||
}
|
||||
|
||||
//NOTE: this might need to be moved to per face
|
||||
public static boolean cullsSame(long metadata) {
|
||||
return ((metadata>>(8*6))&32) != 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private float[] computeModelDepth(ColourDepthTextureData[] textures, int checkMode) {
|
||||
float[] res = new float[6];
|
||||
for (var dir : Direction.values()) {
|
||||
var data = textures[dir.getId()];
|
||||
float fd = TextureUtils.computeDepth(data, TextureUtils.DEPTH_MODE_AVG, checkMode);//Compute the min float depth, smaller means closer to the camera, range 0-1
|
||||
int depth = Math.round(fd * this.modelTextureSize);
|
||||
//If fd is -1, it means that there was nothing rendered on that face and it should be discarded
|
||||
if (fd < -0.1) {
|
||||
res[dir.ordinal()] = -1;
|
||||
} else {
|
||||
res[dir.ordinal()] = ((float) depth)/this.modelTextureSize;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
//TODO:FIXME: DONT DO SPIN LOCKS :WAA:
|
||||
public long getModelMetadata(int blockId) {
|
||||
int map = this.idMappings[blockId];
|
||||
if (map == -1) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
map = this.idMappings[blockId];
|
||||
}
|
||||
if (map == -1) {
|
||||
throw new IdNotYetComputedException(blockId);
|
||||
}
|
||||
return this.metadataCache[map];
|
||||
//int map = 0;
|
||||
//int i = 10;
|
||||
//while ((map = this.idMappings[blockId]) == -1) {
|
||||
// Thread.onSpinWait();
|
||||
//}
|
||||
|
||||
//long meta = 0;
|
||||
//while ((meta = this.metadataCache[map]) == 0) {
|
||||
// Thread.onSpinWait();
|
||||
//}
|
||||
}
|
||||
|
||||
public long getModelMetadataFromClientId(int clientId) {
|
||||
return this.metadataCache[clientId];
|
||||
}
|
||||
|
||||
public int getModelId(int blockId) {
|
||||
int map = this.idMappings[blockId];
|
||||
if (map == -1) {
|
||||
throw new IdNotYetComputedException(blockId);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public int getFluidClientStateId(int clientBlockStateId) {
|
||||
int map = this.fluidStateLUT[clientBlockStateId];
|
||||
if (map == -1) {
|
||||
throw new IdNotYetComputedException(clientBlockStateId);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private void putTextures(int id, ColourDepthTextureData[] textures) {
|
||||
int X = (id&0xFF) * this.modelTextureSize*3;
|
||||
int Y = ((id>>8)&0xFF) * this.modelTextureSize*2;
|
||||
|
||||
for (int subTex = 0; subTex < 6; subTex++) {
|
||||
int x = X + (subTex>>1)*this.modelTextureSize;
|
||||
int y = Y + (subTex&1)*this.modelTextureSize;
|
||||
|
||||
GlStateManager._pixelStore(GlConst.GL_UNPACK_ROW_LENGTH, 0);
|
||||
GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_PIXELS, 0);
|
||||
GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_ROWS, 0);
|
||||
GlStateManager._pixelStore(GlConst.GL_UNPACK_ALIGNMENT, 4);
|
||||
var current = textures[subTex].colour();
|
||||
var next = new int[current.length>>1];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
glTextureSubImage2D(this.textures.id, i, x>>i, y>>i, this.modelTextureSize>>i, this.modelTextureSize>>i, GL_RGBA, GL_UNSIGNED_BYTE, current);
|
||||
|
||||
int size = this.modelTextureSize>>(i+1);
|
||||
for (int pX = 0; pX < size; pX++) {
|
||||
for (int pY = 0; pY < size; pY++) {
|
||||
int C00 = current[(pY*2)*size+pX*2];
|
||||
int C01 = current[(pY*2+1)*size+pX*2];
|
||||
int C10 = current[(pY*2)*size+pX*2+1];
|
||||
int C11 = current[(pY*2+1)*size+pX*2+1];
|
||||
next[pY*size+pX] = TextureUtils.mipColours(C00, C01, C10, C11);
|
||||
}
|
||||
}
|
||||
|
||||
current = next;
|
||||
next = new int[current.length>>1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getBufferId() {
|
||||
return this.modelBuffer.id;
|
||||
}
|
||||
|
||||
public int getTextureId() {
|
||||
return this.textures.id;
|
||||
}
|
||||
|
||||
public int getSamplerId() {
|
||||
return this.blockSampler;
|
||||
}
|
||||
|
||||
public int getColourBufferId() {
|
||||
return this.modelColourBuffer.id;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.bakery.free();
|
||||
this.modelBuffer.free();
|
||||
this.modelColourBuffer.free();
|
||||
this.textures.free();
|
||||
glDeleteSamplers(this.blockSampler);
|
||||
}
|
||||
|
||||
public void addDebugInfo(List<String> info) {
|
||||
info.add("BlockModels registered: " + this.modelTexture2id.size() + "/" + (1<<16));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package me.cortex.voxy.client.core.model;
|
||||
|
||||
public abstract class ModelQueries {
|
||||
public static boolean faceExists(long metadata, int face) {
|
||||
return ((metadata>>(8*face))&0xFF)!=0xFF;
|
||||
}
|
||||
|
||||
public static boolean faceCanBeOccluded(long metadata, int face) {
|
||||
return ((metadata>>(8*face))&0b100)==0b100;
|
||||
}
|
||||
|
||||
public static boolean faceOccludes(long metadata, int face) {
|
||||
return faceExists(metadata, face) && ((metadata>>(8*face))&0b1)==0b1;
|
||||
}
|
||||
|
||||
public static boolean faceUsesSelfLighting(long metadata, int face) {
|
||||
return ((metadata>>(8*face))&0b1000) != 0;
|
||||
}
|
||||
|
||||
public static boolean isDoubleSided(long metadata) {
|
||||
return ((metadata>>(8*6))&4) != 0;
|
||||
}
|
||||
|
||||
public static boolean isTranslucent(long metadata) {
|
||||
return ((metadata>>(8*6))&2) != 0;
|
||||
}
|
||||
|
||||
public static boolean containsFluid(long metadata) {
|
||||
return ((metadata>>(8*6))&8) != 0;
|
||||
}
|
||||
|
||||
public static boolean isFluid(long metadata) {
|
||||
return ((metadata>>(8*6))&16) != 0;
|
||||
}
|
||||
|
||||
public static boolean isBiomeColoured(long metadata) {
|
||||
return ((metadata>>(8*6))&1) != 0;
|
||||
}
|
||||
|
||||
//NOTE: this might need to be moved to per face
|
||||
public static boolean cullsSame(long metadata) {
|
||||
return ((metadata>>(8*6))&32) != 0;
|
||||
}
|
||||
|
||||
public static boolean isFullyOpaque(long metadata) {
|
||||
return ((metadata>>(8*6))&64) != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package me.cortex.voxy.client.core.model;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlas;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
|
||||
import static org.lwjgl.opengl.GL11C.GL_NEAREST_MIPMAP_LINEAR;
|
||||
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MAX_LOD;
|
||||
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MIN_LOD;
|
||||
import static org.lwjgl.opengl.GL30.glBindBufferBase;
|
||||
import static org.lwjgl.opengl.GL33.*;
|
||||
import static org.lwjgl.opengl.GL33C.glSamplerParameteri;
|
||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
|
||||
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
|
||||
|
||||
public class ModelStore {
|
||||
public static final int MODEL_SIZE = 64;
|
||||
final GlBuffer modelBuffer;
|
||||
final GlBuffer modelColourBuffer;
|
||||
final GlTexture textures;
|
||||
public final int blockSampler = glGenSamplers();
|
||||
|
||||
public ModelStore() {
|
||||
this.modelBuffer = new GlBuffer(MODEL_SIZE * (1<<16)).name("ModelData");
|
||||
this.modelColourBuffer = new GlBuffer(4 * (1<<16)).name("ModelColour");
|
||||
this.textures = new GlTexture().store(GL_RGBA8, Integer.numberOfTrailingZeros(ModelFactory.MODEL_TEXTURE_SIZE), ModelFactory.MODEL_TEXTURE_SIZE*3*256,ModelFactory.MODEL_TEXTURE_SIZE*2*256).name("ModelTextures");
|
||||
|
||||
|
||||
//Limit the mips of the texture to match that of the terrain atlas
|
||||
int mipLvl = ((TextureAtlas) Minecraft.getInstance().getTextureManager()
|
||||
.getTexture(Identifier.fromNamespaceAndPath("minecraft", "textures/atlas/blocks.png")))
|
||||
.maxMipLevel;
|
||||
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_LOD, 0);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, mipLvl);//Integer.numberOfTrailingZeros(ModelFactory.MODEL_TEXTURE_SIZE)
|
||||
}
|
||||
|
||||
|
||||
public void free() {
|
||||
this.modelBuffer.free();
|
||||
this.modelColourBuffer.free();
|
||||
this.textures.free();
|
||||
glDeleteSamplers(this.blockSampler);
|
||||
}
|
||||
|
||||
|
||||
public void bind(int modelBindingIndex, int colourBindingIndex, int textureBindingIndex) {
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, modelBindingIndex, this.modelBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, colourBindingIndex, this.modelColourBuffer.id);
|
||||
glBindTextureUnit(textureBindingIndex, this.textures.id);
|
||||
glBindSampler(textureBindingIndex, this.blockSampler);
|
||||
}
|
||||
}
|
||||
@@ -1,315 +0,0 @@
|
||||
package me.cortex.voxy.client.core.model;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlConst;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.systems.VertexSorter;
|
||||
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.voxy.client.core.rendering.util.GlStateCapture;
|
||||
import net.minecraft.block.*;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gl.GlUniform;
|
||||
import net.minecraft.client.render.*;
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.fluid.FluidState;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.RotationAxis;
|
||||
import net.minecraft.util.math.random.LocalRandom;
|
||||
import net.minecraft.world.BlockRenderView;
|
||||
import net.minecraft.world.LightType;
|
||||
import net.minecraft.world.biome.ColorResolver;
|
||||
import net.minecraft.world.chunk.light.LightingProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11C;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.lwjgl.opengl.ARBFramebufferObject.*;
|
||||
import static org.lwjgl.opengl.ARBImaging.GL_FUNC_ADD;
|
||||
import static org.lwjgl.opengl.ARBImaging.glBlendEquation;
|
||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_FRAMEBUFFER_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glMemoryBarrier;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL13.*;
|
||||
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
|
||||
import static org.lwjgl.opengl.GL20C.glUniformMatrix4fv;
|
||||
import static org.lwjgl.opengl.GL45C.glBlitNamedFramebuffer;
|
||||
import static org.lwjgl.opengl.GL45C.glGetTextureImage;
|
||||
|
||||
//Builds a texture for each face of a model
|
||||
public class ModelTextureBakery {
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final GlTexture colourTex;
|
||||
private final GlTexture depthTex;
|
||||
private final GlFramebuffer framebuffer;
|
||||
private final GlStateCapture glState = GlStateCapture.make()
|
||||
.addCapability(GL_DEPTH_TEST)
|
||||
.addCapability(GL_STENCIL_TEST)
|
||||
.addCapability(GL_BLEND)
|
||||
.addCapability(GL_CULL_FACE)
|
||||
.addTexture(GL_TEXTURE0)
|
||||
.addTexture(GL_TEXTURE1)
|
||||
.build()
|
||||
;
|
||||
private final Shader rasterShader = Shader.make()
|
||||
.add(ShaderType.VERTEX, "voxy:bakery/position_tex.vsh")
|
||||
.add(ShaderType.FRAGMENT, "voxy:bakery/position_tex.fsh")
|
||||
.compile();
|
||||
|
||||
private static final List<MatrixStack> FACE_VIEWS = new ArrayList<>();
|
||||
|
||||
|
||||
public ModelTextureBakery(int width, int height) {
|
||||
//TODO: Make this run in a seperate opengl context so that it can run in a seperate thread
|
||||
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.colourTex = new GlTexture().store(GL_RGBA8, 1, width, height);
|
||||
this.depthTex = new GlTexture().store(GL_DEPTH24_STENCIL8, 1, width, height);
|
||||
this.framebuffer = new GlFramebuffer().bind(GL_COLOR_ATTACHMENT0, this.colourTex).bind(GL_DEPTH_STENCIL_ATTACHMENT, this.depthTex).verify();
|
||||
|
||||
//This is done to help make debugging easier
|
||||
FACE_VIEWS.clear();
|
||||
AddViews();
|
||||
}
|
||||
|
||||
private static void AddViews() {
|
||||
addView(-90,0, 0, false);//Direction.DOWN
|
||||
addView(90,0, 0, false);//Direction.UP
|
||||
addView(0,180, 0, true);//Direction.NORTH
|
||||
addView(0,0, 0, false);//Direction.SOUTH
|
||||
//TODO: check these arnt the wrong way round
|
||||
addView(0,90, 270, false);//Direction.EAST
|
||||
addView(0,270, 270, false);//Direction.WEST
|
||||
}
|
||||
|
||||
private static void addView(float pitch, float yaw, float rotation, boolean flipX) {
|
||||
var stack = new MatrixStack();
|
||||
stack.translate(0.5f,0.5f,0.5f);
|
||||
stack.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotation));
|
||||
stack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(pitch));
|
||||
stack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
|
||||
stack.translate(-0.5f,-0.5f,-0.5f);
|
||||
FACE_VIEWS.add(stack);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO: For block entities, also somehow attempt to render the default block entity, e.g. chests and stuff
|
||||
// cause that will result in ok looking micro details in the terrain
|
||||
public ColourDepthTextureData[] renderFaces(BlockState state, long randomValue, boolean renderFluid) {
|
||||
this.glState.capture();
|
||||
var model = MinecraftClient.getInstance()
|
||||
.getBakedModelManager()
|
||||
.getBlockModels()
|
||||
.getModel(state);
|
||||
|
||||
var entityModel = state.hasBlockEntity()?BakedBlockEntityModel.bake(state):null;
|
||||
|
||||
int oldFB = GlStateManager.getBoundFramebuffer();
|
||||
var oldProjection = new Matrix4f(RenderSystem.getProjectionMatrix());
|
||||
GL11C.glViewport(0, 0, this.width, this.height);
|
||||
|
||||
RenderSystem.setProjectionMatrix(new Matrix4f().identity().set(new float[]{
|
||||
2,0,0,0,
|
||||
0, 2,0,0,
|
||||
0,0, -1f,0,
|
||||
-1,-1,0,1,
|
||||
}), VertexSorter.BY_Z);
|
||||
|
||||
|
||||
|
||||
RenderLayer renderLayer = null;
|
||||
if (!renderFluid) {
|
||||
renderLayer = RenderLayers.getBlockLayer(state);
|
||||
} else {
|
||||
renderLayer = RenderLayers.getFluidLayer(state.getFluidState());
|
||||
}
|
||||
|
||||
|
||||
//TODO: figure out why calling this makes minecraft render black
|
||||
//renderLayer.startDrawing();
|
||||
glClearColor(0,0,0,0);
|
||||
glClearDepth(1);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer.id);
|
||||
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glDepthRange(0, 1);
|
||||
glDepthMask(true);
|
||||
glEnable(GL_BLEND);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_CULL_FACE);
|
||||
//glDepthFunc(GL_LESS);
|
||||
|
||||
|
||||
//TODO: Find a better solution
|
||||
if (renderLayer == RenderLayer.getTranslucent()) {
|
||||
//Very hacky blend function to retain the effect of the applied alpha since we dont really want to apply alpha
|
||||
// this is because we apply the alpha again when rendering the terrain meaning the alpha is being double applied
|
||||
glBlendFuncSeparate(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
} else {
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
|
||||
|
||||
//glBlendFunc(GL_ONE, GL_ONE);
|
||||
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
|
||||
glStencilFunc(GL_ALWAYS, 1, 0xFF);
|
||||
glStencilMask(0xFF);
|
||||
|
||||
this.rasterShader.bind();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
int texId = MinecraftClient.getInstance().getTextureManager().getTexture(Identifier.of("minecraft", "textures/atlas/blocks.png")).getGlId();
|
||||
GlUniform.uniform1(0, 0);
|
||||
|
||||
var faces = new ColourDepthTextureData[FACE_VIEWS.size()];
|
||||
for (int i = 0; i < faces.length; i++) {
|
||||
faces[i] = captureView(state, model, entityModel, FACE_VIEWS.get(i), randomValue, i, renderFluid, texId);
|
||||
//glBlitNamedFramebuffer(this.framebuffer.id, oldFB, 0,0,16,16,300*(i>>1),300*(i&1),300*(i>>1)+256,300*(i&1)+256, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
|
||||
renderLayer.endDrawing();
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
RenderSystem.setProjectionMatrix(oldProjection, VertexSorter.BY_DISTANCE);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, oldFB);
|
||||
GL11C.glViewport(GlStateManager.Viewport.getX(), GlStateManager.Viewport.getY(), GlStateManager.Viewport.getWidth(), GlStateManager.Viewport.getHeight());
|
||||
|
||||
//TODO: FIXME: fully revert the state of opengl
|
||||
|
||||
this.glState.restore();
|
||||
return faces;
|
||||
}
|
||||
|
||||
|
||||
private ColourDepthTextureData captureView(BlockState state, BakedModel model, BakedBlockEntityModel blockEntityModel, MatrixStack stack, long randomValue, int face, boolean renderFluid, int textureId) {
|
||||
var vc = Tessellator.getInstance();
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
float[] mat = new float[4*4];
|
||||
new Matrix4f(RenderSystem.getProjectionMatrix()).mul(stack.peek().getPositionMatrix()).get(mat);
|
||||
glUniformMatrix4fv(1, false, mat);
|
||||
|
||||
|
||||
if (blockEntityModel != null && !renderFluid) {
|
||||
blockEntityModel.renderOut();
|
||||
}
|
||||
|
||||
var bb = vc.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR);
|
||||
if (!renderFluid) {
|
||||
renderQuads(bb, state, model, new MatrixStack(), randomValue);
|
||||
} else {
|
||||
MinecraftClient.getInstance().getBlockRenderManager().renderFluid(BlockPos.ORIGIN, new BlockRenderView() {
|
||||
@Override
|
||||
public float getBrightness(Direction direction, boolean shaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightingProvider getLightingProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLightLevel(LightType type, BlockPos pos) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor(BlockPos pos, ColorResolver colorResolver) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity getBlockEntity(BlockPos pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
if (pos.equals(Direction.byId(face).getVector())) {
|
||||
return Blocks.AIR.getDefaultState();
|
||||
}
|
||||
|
||||
//Fixme:
|
||||
// This makes it so that the top face of water is always air, if this is commented out
|
||||
// the up block will be a liquid state which makes the sides full
|
||||
// if this is uncommented, that issue is fixed but e.g. stacking water layers ontop of eachother
|
||||
// doesnt fill the side of the block
|
||||
|
||||
//if (pos.getY() == 1) {
|
||||
// return Blocks.AIR.getDefaultState();
|
||||
//}
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidState getFluidState(BlockPos pos) {
|
||||
if (pos.equals(Direction.byId(face).getVector())) {
|
||||
return Blocks.AIR.getDefaultState().getFluidState();
|
||||
}
|
||||
//if (pos.getY() == 1) {
|
||||
// return Blocks.AIR.getDefaultState().getFluidState();
|
||||
//}
|
||||
return state.getFluidState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBottomY() {
|
||||
return 0;
|
||||
}
|
||||
}, bb, state, state.getFluidState());
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
try {
|
||||
BufferRenderer.draw(bb.end());
|
||||
} catch (IllegalStateException e) {
|
||||
System.err.println("Got empty buffer builder! for block " + state);
|
||||
}
|
||||
|
||||
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
|
||||
int[] colourData = new int[this.width*this.height];
|
||||
int[] depthData = new int[this.width*this.height];
|
||||
glGetTextureImage(this.colourTex.id, 0, GL_RGBA, GL_UNSIGNED_BYTE, colourData);
|
||||
glGetTextureImage(this.depthTex.id, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, depthData);
|
||||
return new ColourDepthTextureData(colourData, depthData, this.width, this.height);
|
||||
}
|
||||
|
||||
private static void renderQuads(BufferBuilder builder, BlockState state, BakedModel model, MatrixStack stack, long randomValue) {
|
||||
for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) {
|
||||
var quads = model.getQuads(state, direction, new LocalRandom(randomValue));
|
||||
for (var quad : quads) {
|
||||
//TODO: mark pixels that have
|
||||
int meta = quad.hasColor()?1:0;
|
||||
builder.quad(stack.peek(), quad, 255f/((meta>>16)&0xff), 255f/((meta>>8)&0xff), 255f/(meta&0xff), 1.0f, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.framebuffer.free();
|
||||
this.colourTex.free();
|
||||
this.depthTex.free();
|
||||
this.rasterShader.free();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package me.cortex.voxy.client.core.model;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.util.color.ColorSRGB;
|
||||
import net.minecraft.util.math.ColorHelper;
|
||||
import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB;
|
||||
import net.minecraft.client.renderer.texture.MipmapGenerator;
|
||||
import net.minecraft.util.ARGB;
|
||||
|
||||
//Texturing utils to manipulate data from the model bakery
|
||||
public class TextureUtils {
|
||||
@@ -9,14 +10,14 @@ public class TextureUtils {
|
||||
public static int getWrittenPixelCount(ColourDepthTextureData texture, int checkMode) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < texture.colour().length; i++) {
|
||||
count += wasPixelWritten(texture, checkMode, i)?1:0;
|
||||
count += wasPixelWritten(texture, checkMode, i) ? 1 : 0;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static boolean isSolid(ColourDepthTextureData texture) {
|
||||
for (int pixel : texture.colour()) {
|
||||
if (((pixel>>24)&0xFF) != 255) {
|
||||
if (((pixel >> 24) & 0xFF) != 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -26,18 +27,50 @@ public class TextureUtils {
|
||||
public static final int WRITE_CHECK_STENCIL = 1;
|
||||
public static final int WRITE_CHECK_DEPTH = 2;
|
||||
public static final int WRITE_CHECK_ALPHA = 3;
|
||||
|
||||
private static boolean wasPixelWritten(ColourDepthTextureData data, int mode, int index) {
|
||||
if (mode == WRITE_CHECK_STENCIL) {
|
||||
return (data.depth()[index]&0xFF)!=0;
|
||||
return (data.depth()[index] & 0xFF) != 0;
|
||||
} else if (mode == WRITE_CHECK_DEPTH) {
|
||||
return (data.depth()[index]>>>8)!=((1<<24)-1);
|
||||
return (data.depth()[index] >>> 8) != ((1 << 24) - 1);
|
||||
} else if (mode == WRITE_CHECK_ALPHA) {
|
||||
//TODO:FIXME: for some reason it has an alpha of 1 even if its ment to be 0
|
||||
return ((data.colour()[index]>>>24)&0xff)>1;
|
||||
return ((data.colour()[index] >>> 24) & 0xff) > 1;
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
|
||||
//0: nothing written
|
||||
//1: none tinted
|
||||
//2: some tinted
|
||||
//3: all tinted
|
||||
public static int computeFaceTint(ColourDepthTextureData texture, int checkMode) {
|
||||
boolean allTinted = true;
|
||||
boolean someTinted = false;
|
||||
boolean wasWriten = false;
|
||||
|
||||
final var colourData = texture.colour();
|
||||
final var depthData = texture.depth();
|
||||
for (int i = 0; i < colourData.length; i++) {
|
||||
if (!wasPixelWritten(texture, checkMode, i)) {
|
||||
continue;
|
||||
}
|
||||
if ((colourData[i] & 0xFFFFFF) == 0 || (colourData[i] >>> 24) == 0) {//If the pixel is fully black (or translucent)
|
||||
continue;
|
||||
}
|
||||
boolean pixelTinited = (depthData[i] & (1 << 7)) != 0;
|
||||
wasWriten |= true;
|
||||
allTinted &= pixelTinited;
|
||||
someTinted |= pixelTinited;
|
||||
|
||||
}
|
||||
if (!wasWriten) {
|
||||
return 0;
|
||||
}
|
||||
return someTinted ? (allTinted ? 3 : 2) : 1;
|
||||
}
|
||||
|
||||
public static final int DEPTH_MODE_AVG = 1;
|
||||
public static final int DEPTH_MODE_MAX = 2;
|
||||
public static final int DEPTH_MODE_MIN = 3;
|
||||
@@ -59,7 +92,7 @@ public class TextureUtils {
|
||||
if (!wasPixelWritten(texture, checkMode, i)) {
|
||||
continue;
|
||||
}
|
||||
int depth = depthData[i]>>>8;
|
||||
int depth = depthData[i] >>> 8;
|
||||
if (mode == DEPTH_MODE_AVG) {
|
||||
a++;
|
||||
b += depth;
|
||||
@@ -74,7 +107,7 @@ public class TextureUtils {
|
||||
if (a == 0) {
|
||||
return -1;
|
||||
}
|
||||
return u2fdepth((int) (b/a));
|
||||
return u2fdepth((int) (b / a));
|
||||
} else if (mode == DEPTH_MODE_MAX) {
|
||||
if (a == Long.MIN_VALUE) {
|
||||
return -1;
|
||||
@@ -90,12 +123,14 @@ public class TextureUtils {
|
||||
}
|
||||
|
||||
private static float u2fdepth(int depth) {
|
||||
float depthF = (float) ((double)depth/((1<<24)-1));
|
||||
float depthF = (float) ((double) depth / ((1 << 24) - 1));
|
||||
//https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDepthRange.xhtml
|
||||
// due to this and the unsigned bullshit, i believe the depth value needs to get multiplied by 2
|
||||
// due to this and the unsigned bullshit, believe the depth value needs to get multiplied by 2
|
||||
|
||||
//Shouldent be needed due to the compute bake copy
|
||||
depthF *= 2;
|
||||
if (depthF > 1.00001f) {
|
||||
System.err.println("Warning: Depth greater than 1");
|
||||
if (depthF > 1.00001f) {//Basicly only happens when a model goes out of bounds (thing)
|
||||
//System.err.println("Warning: Depth greater than 1");
|
||||
depthF = 1.0f;
|
||||
}
|
||||
return depthF;
|
||||
@@ -104,7 +139,6 @@ public class TextureUtils {
|
||||
|
||||
//NOTE: data goes from bottom left to top right (x first then y)
|
||||
public static int[] computeBounds(ColourDepthTextureData data, int checkMode) {
|
||||
final var depth = data.depth();
|
||||
//Compute x bounds first
|
||||
int minX = 0;
|
||||
minXCheck:
|
||||
@@ -118,10 +152,10 @@ public class TextureUtils {
|
||||
minX++;
|
||||
} while (minX != data.width());
|
||||
|
||||
int maxX = data.width()-1;
|
||||
int maxX = data.width() - 1;
|
||||
maxXCheck:
|
||||
do {
|
||||
for (int y = data.height()-1; y!=-1; y--) {
|
||||
for (int y = data.height() - 1; y != -1; y--) {
|
||||
int idx = maxX + (y * data.width());
|
||||
if (wasPixelWritten(data, checkMode, idx)) {
|
||||
break maxXCheck;//pixel was written too so break from loop
|
||||
@@ -146,10 +180,10 @@ public class TextureUtils {
|
||||
} while (minY != data.height());
|
||||
|
||||
|
||||
int maxY = data.height()-1;
|
||||
int maxY = data.height() - 1;
|
||||
maxYCheck:
|
||||
do {
|
||||
for (int x = data.width()-1; x!=-1; x--) {
|
||||
for (int x = data.width() - 1; x != -1; x--) {
|
||||
int idx = (maxY * data.height()) + x;
|
||||
if (wasPixelWritten(data, checkMode, idx)) {
|
||||
break maxYCheck;//pixel was written too so break from loop
|
||||
@@ -163,56 +197,42 @@ public class TextureUtils {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static int mipColours(int one, int two, int three, int four) {
|
||||
return weightedAverageColor(weightedAverageColor(one, two), weightedAverageColor(three, four));
|
||||
public static int mipColours(boolean darkend, int C00, int C01, int C10, int C11) {
|
||||
darkend = !darkend;//Invert to make it easier
|
||||
float r = 0.0f;
|
||||
float g = 0.0f;
|
||||
float b = 0.0f;
|
||||
float a = 0.0f;
|
||||
if (darkend || (C00 >>> 24) != 0) {
|
||||
r += ColorSRGB.srgbToLinear((C00 >> 0) & 0xFF);
|
||||
g += ColorSRGB.srgbToLinear((C00 >> 8) & 0xFF);
|
||||
b += ColorSRGB.srgbToLinear((C00 >> 16) & 0xFF);
|
||||
a += darkend ? (C00 >>> 24) : ColorSRGB.srgbToLinear(C00 >>> 24);
|
||||
}
|
||||
if (darkend || (C01 >>> 24) != 0) {
|
||||
r += ColorSRGB.srgbToLinear((C01 >> 0) & 0xFF);
|
||||
g += ColorSRGB.srgbToLinear((C01 >> 8) & 0xFF);
|
||||
b += ColorSRGB.srgbToLinear((C01 >> 16) & 0xFF);
|
||||
a += darkend ? (C01 >>> 24) : ColorSRGB.srgbToLinear(C01 >>> 24);
|
||||
}
|
||||
if (darkend || (C10 >>> 24) != 0) {
|
||||
r += ColorSRGB.srgbToLinear((C10 >> 0) & 0xFF);
|
||||
g += ColorSRGB.srgbToLinear((C10 >> 8) & 0xFF);
|
||||
b += ColorSRGB.srgbToLinear((C10 >> 16) & 0xFF);
|
||||
a += darkend ? (C10 >>> 24) : ColorSRGB.srgbToLinear(C10 >>> 24);
|
||||
}
|
||||
if (darkend || (C11 >>> 24) != 0) {
|
||||
r += ColorSRGB.srgbToLinear((C11 >> 0) & 0xFF);
|
||||
g += ColorSRGB.srgbToLinear((C11 >> 8) & 0xFF);
|
||||
b += ColorSRGB.srgbToLinear((C11 >> 16) & 0xFF);
|
||||
a += darkend ? (C11 >>> 24) : ColorSRGB.srgbToLinear(C11 >>> 24);
|
||||
}
|
||||
|
||||
private static int weightedAverageColor(int one, int two) {
|
||||
int alphaOne = ColorHelper.Abgr.getAlpha(one);
|
||||
int alphaTwo = ColorHelper.Abgr.getAlpha(two);
|
||||
if (alphaOne == alphaTwo) {
|
||||
return averageRgb(one, two, alphaOne);
|
||||
} else if (alphaOne == 0) {
|
||||
return two & 16777215 | alphaTwo >> 2 << 24;
|
||||
} else if (alphaTwo == 0) {
|
||||
return one & 16777215 | alphaOne >> 2 << 24;
|
||||
} else {
|
||||
float scale = 1.0F / (float)(alphaOne + alphaTwo);
|
||||
float relativeWeightOne = (float)alphaOne * scale;
|
||||
float relativeWeightTwo = (float)alphaTwo * scale;
|
||||
float oneR = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getRed(one)) * relativeWeightOne;
|
||||
float oneG = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getGreen(one)) * relativeWeightOne;
|
||||
float oneB = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getBlue(one)) * relativeWeightOne;
|
||||
float twoR = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getRed(two)) * relativeWeightTwo;
|
||||
float twoG = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getGreen(two)) * relativeWeightTwo;
|
||||
float twoB = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getBlue(two)) * relativeWeightTwo;
|
||||
float linearR = oneR + twoR;
|
||||
float linearG = oneG + twoG;
|
||||
float linearB = oneB + twoB;
|
||||
int averageAlpha = alphaOne + alphaTwo >> 1;
|
||||
return ColorSRGB.linearToSrgb(linearR, linearG, linearB, averageAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
private static int averageRgb(int a, int b, int alpha) {
|
||||
float ar = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getRed(a));
|
||||
float ag = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getGreen(a));
|
||||
float ab = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getBlue(a));
|
||||
float br = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getRed(b));
|
||||
float bg = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getGreen(b));
|
||||
float bb = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getBlue(b));
|
||||
return ColorSRGB.linearToSrgb((ar + br) * 0.5F, (ag + bg) * 0.5F, (ab + bb) * 0.5F, alpha);
|
||||
return ColorSRGB.linearToSrgb(
|
||||
r / 4,
|
||||
g / 4,
|
||||
b / 4,
|
||||
darkend ? ((int) a) / 4 : ARGB.linearToSrgbChannel(a / 4)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
package me.cortex.voxy.client.core.model.bakery;
|
||||
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.model.Model;
|
||||
import net.minecraft.client.renderer.SubmitNodeStorage;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class BakedBlockEntityModel {
|
||||
|
||||
private record LayerConsumer(RenderType layer, ReuseVertexConsumer consumer) {}
|
||||
private final List<LayerConsumer> layers;
|
||||
private BakedBlockEntityModel(List<LayerConsumer> layers) {
|
||||
this.layers = layers;
|
||||
}
|
||||
|
||||
public void render(Matrix4f matrix, int texId) {
|
||||
for (var layer : this.layers) {
|
||||
if (layer.consumer.isEmpty()) continue;
|
||||
if (layer.layer instanceof RenderType.CompositeRenderType mp) {
|
||||
Identifier textureId = mp.state.textureState.cutoutTexture().orElse(null);
|
||||
if (textureId == null) {
|
||||
Logger.error("ERROR: Empty texture id for layer: " + layer);
|
||||
} else {
|
||||
texId = ((com.mojang.blaze3d.opengl.GlTexture)Minecraft.getInstance().getTextureManager().getTexture(textureId).getTexture()).glId();
|
||||
}
|
||||
}
|
||||
if (texId == 0) continue;
|
||||
BudgetBufferRenderer.setup(layer.consumer.getAddress(), layer.consumer.quadCount(), texId);
|
||||
BudgetBufferRenderer.render(matrix);
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
this.layers.forEach(layer->layer.consumer.free());
|
||||
}
|
||||
|
||||
private static int getMetaFromLayer(RenderType layer) {
|
||||
boolean hasDiscard = layer == RenderType.cutout() ||
|
||||
layer == RenderType.cutoutMipped() ||
|
||||
layer == RenderType.tripwire();
|
||||
|
||||
boolean isMipped = layer == RenderType.cutoutMipped() ||
|
||||
layer == RenderType.solid() ||
|
||||
layer.sortOnUpload() ||
|
||||
layer == RenderType.tripwire();
|
||||
|
||||
int meta = hasDiscard?1:0;
|
||||
meta |= isMipped?2:0;
|
||||
return meta;
|
||||
}
|
||||
|
||||
public static BakedBlockEntityModel bake(BlockState state) {
|
||||
Map<RenderType, LayerConsumer> map = new HashMap<>();
|
||||
var entity = ((EntityBlock)state.getBlock()).newBlockEntity(BlockPos.ZERO, state);
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
var renderer = Minecraft.getInstance().getBlockEntityRenderDispatcher().getRenderer(entity);
|
||||
entity.setLevel(Minecraft.getInstance().level);
|
||||
if (renderer != null) {
|
||||
try {
|
||||
var rt = renderer.createRenderState();
|
||||
renderer.extractRenderState(entity, rt, 0.0f, new Vec3d(0,0,0), null);
|
||||
|
||||
//TODO: FIXME: FINISH
|
||||
var cstate = new CameraRenderState();
|
||||
var queue = new SubmitNodeStorage();
|
||||
renderer.submit(rt, new MatrixStack(), queue, cstate);
|
||||
var qq = queue.order(0);
|
||||
qq.
|
||||
//renderer.render(entity, 0.0f, new MatrixStack(), layer->map.computeIfAbsent(layer, rl -> new LayerConsumer(rl, new ReuseVertexConsumer().setDefaultMeta(getMetaFromLayer(rl)))).consumer, 0, 0, new Vec3d(0,0,0));
|
||||
} catch (Exception e) {
|
||||
Logger.error("Unable to bake block entity: " + entity, e);
|
||||
}
|
||||
}
|
||||
entity.setRemoved();
|
||||
if (map.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (var i : new ArrayList<>(map.values())) {
|
||||
if (i.consumer.isEmpty()) {
|
||||
map.remove(i.layer);
|
||||
i.consumer.free();
|
||||
}
|
||||
}
|
||||
if (map.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new BakedBlockEntityModel(new ArrayList<>(map.values()));
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,96 @@
|
||||
package me.cortex.voxy.client.core.model.bakery;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.textures.GpuTexture;
|
||||
import com.mojang.blaze3d.vertex.MeshData;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlVertexArray;
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
|
||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||
import static org.lwjgl.opengl.GL45.*;
|
||||
|
||||
public class BudgetBufferRenderer {
|
||||
public static final int VERTEX_FORMAT_SIZE = 24;
|
||||
|
||||
private static final Shader bakeryShader = Shader.make()
|
||||
.add(ShaderType.VERTEX, "voxy:bakery/position_tex.vsh")
|
||||
.add(ShaderType.FRAGMENT, "voxy:bakery/position_tex.fsh")
|
||||
.compile();
|
||||
|
||||
|
||||
public static void init(){}
|
||||
private static final GlBuffer indexBuffer;
|
||||
static {
|
||||
var i = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
|
||||
int id = ((com.mojang.blaze3d.opengl.GlBuffer) i.getBuffer(4096*3*2)).handle;
|
||||
if (i.type() != VertexFormat.IndexType.SHORT) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
indexBuffer = new GlBuffer(3*2*2*4096);
|
||||
glCopyNamedBufferSubData(id, indexBuffer.id, 0, 0, 3*2*2*4096);
|
||||
}
|
||||
|
||||
private static final int STRIDE = 24;
|
||||
private static final GlVertexArray VA = new GlVertexArray()
|
||||
.setStride(STRIDE)
|
||||
.setF(0, GL_FLOAT, 4, 0)//pos, metadata
|
||||
.setF(1, GL_FLOAT, 2, 4 * 4)//UV
|
||||
.bindElementBuffer(indexBuffer.id);
|
||||
|
||||
private static GlBuffer immediateBuffer;
|
||||
private static int quadCount;
|
||||
public static void drawFast(MeshData buffer, GpuTexture tex, Matrix4f matrix) {
|
||||
if (buffer.drawState().mode() != VertexFormat.Mode.QUADS) {
|
||||
throw new IllegalStateException("Fast only supports quads");
|
||||
}
|
||||
|
||||
var buff = buffer.vertexBuffer();
|
||||
int size = buff.remaining();
|
||||
if (size%STRIDE != 0) throw new IllegalStateException();
|
||||
size /= STRIDE;
|
||||
if (size%4 != 0) throw new IllegalStateException();
|
||||
size /= 4;
|
||||
setup(MemoryUtil.memAddress(buff), size, ((com.mojang.blaze3d.opengl.GlTexture)tex).glId());
|
||||
buffer.close();
|
||||
|
||||
render(matrix);
|
||||
}
|
||||
|
||||
public static void setup(long dataPtr, int quads, int texId) {
|
||||
if (quads == 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
quadCount = quads;
|
||||
|
||||
long size = quads * 4L * STRIDE;
|
||||
if (immediateBuffer == null || immediateBuffer.size()<size) {
|
||||
if (immediateBuffer != null) {
|
||||
immediateBuffer.free();
|
||||
}
|
||||
immediateBuffer = new GlBuffer(size*2L);//This also accounts for when immediateBuffer == null
|
||||
VA.bindBuffer(immediateBuffer.id);
|
||||
}
|
||||
long ptr = UploadStream.INSTANCE.upload(immediateBuffer, 0, size);
|
||||
MemoryUtil.memCopy(dataPtr, ptr, size);
|
||||
UploadStream.INSTANCE.commit();
|
||||
|
||||
bakeryShader.bind();
|
||||
VA.bind();
|
||||
glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT);
|
||||
glBindSampler(0, 0);
|
||||
glBindTextureUnit(0, texId);
|
||||
}
|
||||
|
||||
public static void render(Matrix4f matrix) {
|
||||
glUniformMatrix4fv(1, false, matrix.get(new float[16]));
|
||||
glDrawElements(GL_TRIANGLES, quadCount * 2 * 3, GL_UNSIGNED_SHORT, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package me.cortex.voxy.client.core.model.bakery;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import static org.lwjgl.opengl.ARBDirectStateAccess.*;
|
||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_FRAMEBUFFER_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_PIXEL_BUFFER_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_TEXTURE_UPDATE_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glMemoryBarrier;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL30.*;
|
||||
import static org.lwjgl.opengl.GL43.*;
|
||||
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi;
|
||||
|
||||
public class GlViewCapture {
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final GlTexture colourTex;
|
||||
private final GlTexture depthTex;
|
||||
private final GlTexture stencilTex;
|
||||
private final GlTexture metaTex;
|
||||
final GlFramebuffer framebuffer;
|
||||
private final Shader copyOutShader;
|
||||
|
||||
public GlViewCapture(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.metaTex = new GlTexture().store(GL_R32UI, 1, width*3, height*2).name("ModelBakeryMetadata");
|
||||
this.colourTex = new GlTexture().store(GL_RGBA8, 1, width*3, height*2).name("ModelBakeryColour");
|
||||
this.depthTex = new GlTexture().store(GL_DEPTH24_STENCIL8, 1, width*3, height*2).name("ModelBakeryDepth");
|
||||
//TODO: FIXME: Mesa is broken when trying to read from a sampler of GL_STENCIL_INDEX
|
||||
// it seems to just ignore the value set in GL_DEPTH_STENCIL_TEXTURE_MODE
|
||||
glTextureParameteri(this.depthTex.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
|
||||
this.stencilTex = this.depthTex.createView();
|
||||
glTextureParameteri(this.depthTex.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
|
||||
|
||||
this.framebuffer = new GlFramebuffer().bind(GL_COLOR_ATTACHMENT0, this.colourTex).bind(GL_COLOR_ATTACHMENT1, this.metaTex).setDrawBuffers(GL_COLOR_ATTACHMENT0,GL_COLOR_ATTACHMENT1).bind(GL_DEPTH_STENCIL_ATTACHMENT, this.depthTex).verify().name("ModelFramebuffer");
|
||||
|
||||
glTextureParameteri(this.stencilTex.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
|
||||
glTextureParameteri(this.stencilTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTextureParameteri(this.stencilTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
glTextureParameteri(this.metaTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTextureParameteri(this.metaTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
glTextureParameteri(this.depthTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTextureParameteri(this.depthTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
this.copyOutShader = Shader.makeAuto()
|
||||
.define("WIDTH", width)
|
||||
.define("HEIGHT", height)
|
||||
.define("COLOUR_IN_BINDING", 0)
|
||||
.define("DEPTH_IN_BINDING", 1)
|
||||
.define("STENCIL_IN_BINDING", 2)
|
||||
.define("META_IN_BINDING", 3)
|
||||
.define("BUFFER_OUT_BINDING", 4)
|
||||
.add(ShaderType.COMPUTE, "voxy:bakery/bufferreorder.comp")
|
||||
.compile()
|
||||
.name("ModelBakeryOut")
|
||||
.texture("META_IN_BINDING", 0, this.metaTex)
|
||||
.texture("COLOUR_IN_BINDING", 0, this.colourTex)
|
||||
.texture("DEPTH_IN_BINDING", 0, this.depthTex)
|
||||
.texture("STENCIL_IN_BINDING", 0, this.stencilTex);
|
||||
}
|
||||
|
||||
public void emitToStream(int buffer, int offset) {
|
||||
this.copyOutShader.bind();
|
||||
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 4, buffer, offset, (this.width*3L)*(this.height*2L)*4L*2);//its 2*4 because colour + depth stencil
|
||||
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_TEXTURE_UPDATE_BARRIER_BIT|GL_PIXEL_BUFFER_BARRIER_BIT|GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);//Am not sure if barriers are right
|
||||
glDispatchCompute(3, 2, 1);
|
||||
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 4, 0, 0, 4);//WHY DOES THIS FIX FUCKING BINDING ISSUES HERE WHEN DOING THIS IN THE RENDER SYSTEM DOESNT
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
long ptr = stack.nmalloc(4*4);
|
||||
MemoryUtil.memPutLong(ptr, 0);
|
||||
MemoryUtil.memPutLong(ptr+8, 0);
|
||||
nglClearNamedFramebufferfv(this.framebuffer.id, GL_COLOR, 0, ptr);
|
||||
nglClearNamedFramebufferuiv(this.framebuffer.id, GL_COLOR, 1, ptr);
|
||||
//TODO: fix the draw buffer thing maybe? it might need todo multiple clears
|
||||
//nglClearNamedFramebufferfv(this.framebuffer.id, GL_COLOR, 0, ptr);
|
||||
}
|
||||
glClearNamedFramebufferfi(this.framebuffer.id, GL_DEPTH_STENCIL, 0, 1.0f, 0);
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.framebuffer.free();
|
||||
this.colourTex.free();
|
||||
this.stencilTex.free();
|
||||
this.depthTex.free();
|
||||
this.metaTex.free();
|
||||
this.copyOutShader.free();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
package me.cortex.voxy.client.core.model.bakery;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.ItemBlockRenderTypes;
|
||||
import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
import net.minecraft.world.level.ColorResolver;
|
||||
import net.minecraft.world.level.LightLayer;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.LeavesBlock;
|
||||
import net.minecraft.world.level.block.LiquidBlock;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.SingleThreadedRandomSource;
|
||||
import net.minecraft.world.level.lighting.LevelLightEngine;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.opengl.ARBDrawBuffersBlend;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
|
||||
import static org.lwjgl.opengl.GL30.*;
|
||||
import static org.lwjgl.opengl.GL40.glBlendFuncSeparatei;
|
||||
import static org.lwjgl.opengl.GL45.glTextureBarrier;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
public class ModelTextureBakery {
|
||||
//Note: the first bit of metadata is if alpha discard is enabled
|
||||
private static final Matrix4f[] VIEWS = new Matrix4f[6];
|
||||
|
||||
private final GlViewCapture capture;
|
||||
private final ReuseVertexConsumer vc = new ReuseVertexConsumer();
|
||||
|
||||
private final int width;
|
||||
private final int height;
|
||||
public ModelTextureBakery(int width, int height) {
|
||||
this.capture = new GlViewCapture(width, height);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public static int getMetaFromLayer(ChunkSectionLayer layer) {
|
||||
boolean hasDiscard = layer == ChunkSectionLayer.CUTOUT ||
|
||||
layer == ChunkSectionLayer.TRANSLUCENT||
|
||||
layer == ChunkSectionLayer.TRIPWIRE;
|
||||
|
||||
boolean isMipped = layer == ChunkSectionLayer.SOLID ||
|
||||
layer == ChunkSectionLayer.TRANSLUCENT ||
|
||||
layer == ChunkSectionLayer.TRIPWIRE;
|
||||
|
||||
int meta = hasDiscard?1:0;
|
||||
meta |= true?2:0;
|
||||
return meta;
|
||||
}
|
||||
|
||||
private void bakeBlockModel(BlockState state, ChunkSectionLayer layer) {
|
||||
if (state.getRenderShape() == RenderShape.INVISIBLE) {
|
||||
return;//Dont bake if invisible
|
||||
}
|
||||
var model = Minecraft.getInstance()
|
||||
.getModelManager()
|
||||
.getBlockModelShaper()
|
||||
.getBlockModel(state);
|
||||
|
||||
int meta = getMetaFromLayer(layer);
|
||||
|
||||
for (var part : model.collectParts(new SingleThreadedRandomSource(42L))) {
|
||||
for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) {
|
||||
var quads = part.getQuads(direction);
|
||||
for (var quad : quads) {
|
||||
this.vc.quad(quad, meta|(quad.isTinted()?4:0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void bakeFluidState(BlockState state, ChunkSectionLayer layer, int face) {
|
||||
{
|
||||
//TODO: somehow set the tint flag per quad or something?
|
||||
int metadata = getMetaFromLayer(layer);
|
||||
//Just assume all fluids are tinted, if they arnt it should be implicitly culled in the model baking phase
|
||||
// since it wont have the colour provider
|
||||
metadata |= 4;//Has tint
|
||||
this.vc.setDefaultMeta(metadata);//Set the meta while baking
|
||||
}
|
||||
Minecraft.getInstance().getBlockRenderer().renderLiquid(BlockPos.ZERO, new BlockAndTintGetter() {
|
||||
@Override
|
||||
public float getShade(Direction direction, boolean shaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LevelLightEngine getLightEngine() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBrightness(LightLayer type, BlockPos pos) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockTint(BlockPos pos, ColorResolver colorResolver) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity getBlockEntity(BlockPos pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
if (shouldReturnAirForFluid(pos, face)) {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
|
||||
//Fixme:
|
||||
// This makes it so that the top face of water is always air, if this is commented out
|
||||
// the up block will be a liquid state which makes the sides full
|
||||
// if this is uncommented, that issue is fixed but e.g. stacking water layers ontop of eachother
|
||||
// doesnt fill the side of the block
|
||||
|
||||
//if (pos.getY() == 1) {
|
||||
// return Blocks.AIR.getDefaultState();
|
||||
//}
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidState getFluidState(BlockPos pos) {
|
||||
if (shouldReturnAirForFluid(pos, face)) {
|
||||
return Blocks.AIR.defaultBlockState().getFluidState();
|
||||
}
|
||||
|
||||
return state.getFluidState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinY() {
|
||||
return 0;
|
||||
}
|
||||
}, this.vc, state, state.getFluidState());
|
||||
this.vc.setDefaultMeta(0);//Reset default meta
|
||||
}
|
||||
|
||||
private static boolean shouldReturnAirForFluid(BlockPos pos, int face) {
|
||||
var fv = Direction.from3DDataValue(face).getUnitVec3i();
|
||||
int dot = fv.getX()*pos.getX() + fv.getY()*pos.getY() + fv.getZ()*pos.getZ();
|
||||
return dot >= 1;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.capture.free();
|
||||
this.vc.free();
|
||||
}
|
||||
|
||||
|
||||
public int renderToStream(BlockState state, int streamBuffer, int streamOffset) {
|
||||
this.capture.clear();
|
||||
boolean isBlock = true;
|
||||
ChunkSectionLayer layer;
|
||||
if (state.getBlock() instanceof LiquidBlock) {
|
||||
layer = ItemBlockRenderTypes.getRenderLayer(state.getFluidState());
|
||||
isBlock = false;
|
||||
} else {
|
||||
if (state.getBlock() instanceof LeavesBlock) {
|
||||
layer = ChunkSectionLayer.SOLID;
|
||||
} else {
|
||||
layer = ItemBlockRenderTypes.getChunkRenderType(state);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: support block model entities
|
||||
//BakedBlockEntityModel bbem = null;
|
||||
if (state.hasBlockEntity()) {
|
||||
//bbem = BakedBlockEntityModel.bake(state);
|
||||
}
|
||||
|
||||
//Setup GL state
|
||||
int[] viewdat = new int[4];
|
||||
int blockTextureId;
|
||||
|
||||
{
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_CULL_FACE);
|
||||
if (layer == ChunkSectionLayer.TRANSLUCENT) {
|
||||
glEnablei(GL_BLEND, 0);
|
||||
glDisablei(GL_BLEND, 1);
|
||||
ARBDrawBuffersBlend.glBlendFuncSeparateiARB(0, GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
} else {
|
||||
glDisable(GL_BLEND);//FUCK YOU INTEL (screams), for _some reason_ discard or something... JUST DOESNT WORK??
|
||||
//glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ONE);
|
||||
}
|
||||
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
|
||||
glStencilFunc(GL_ALWAYS, 1, 0xFF);
|
||||
glStencilMask(0xFF);
|
||||
|
||||
glGetIntegerv(GL_VIEWPORT, viewdat);//TODO: faster way todo this, or just use main framebuffer resolution
|
||||
|
||||
//Bind the capture framebuffer
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
|
||||
|
||||
var tex = Minecraft.getInstance().getTextureManager().getTexture(Identifier.fromNamespaceAndPath("minecraft", "textures/atlas/blocks.png")).getTexture();
|
||||
blockTextureId = ((com.mojang.blaze3d.opengl.GlTexture)tex).glId();
|
||||
}
|
||||
|
||||
boolean isAnyShaded = false;
|
||||
boolean isAnyDarkend = false;
|
||||
if (isBlock) {
|
||||
this.vc.reset();
|
||||
this.bakeBlockModel(state, layer);
|
||||
isAnyShaded |= this.vc.anyShaded;
|
||||
isAnyDarkend |= this.vc.anyDarkendTex;
|
||||
if (!this.vc.isEmpty()) {//only render if there... is shit to render
|
||||
|
||||
//Setup for continual emission
|
||||
BudgetBufferRenderer.setup(this.vc.getAddress(), this.vc.quadCount(), blockTextureId);//note: this.vc.buffer.address NOT this.vc.ptr
|
||||
|
||||
var mat = new Matrix4f();
|
||||
for (int i = 0; i < VIEWS.length; i++) {
|
||||
if (i==1||i==2||i==4) {
|
||||
glCullFace(GL_FRONT);
|
||||
} else {
|
||||
glCullFace(GL_BACK);
|
||||
}
|
||||
|
||||
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
|
||||
|
||||
//The projection matrix
|
||||
mat.set(2, 0, 0, 0,
|
||||
0, 2, 0, 0,
|
||||
0, 0, -1f, 0,
|
||||
-1, -1, 0, 1)
|
||||
.mul(VIEWS[i]);
|
||||
|
||||
BudgetBufferRenderer.render(mat);
|
||||
}
|
||||
}
|
||||
glBindVertexArray(0);
|
||||
} else {//Is fluid, slow path :(
|
||||
|
||||
if (!(state.getBlock() instanceof LiquidBlock)) throw new IllegalStateException();
|
||||
|
||||
var mat = new Matrix4f();
|
||||
for (int i = 0; i < VIEWS.length; i++) {
|
||||
if (i==1||i==2||i==4) {
|
||||
glCullFace(GL_FRONT);
|
||||
} else {
|
||||
glCullFace(GL_BACK);
|
||||
}
|
||||
|
||||
this.vc.reset();
|
||||
this.bakeFluidState(state, layer, i);
|
||||
if (this.vc.isEmpty()) continue;
|
||||
isAnyShaded |= this.vc.anyShaded;
|
||||
isAnyDarkend |= this.vc.anyDarkendTex;
|
||||
BudgetBufferRenderer.setup(this.vc.getAddress(), this.vc.quadCount(), blockTextureId);
|
||||
|
||||
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
|
||||
|
||||
//The projection matrix
|
||||
mat.set(2, 0, 0, 0,
|
||||
0, 2, 0, 0,
|
||||
0, 0, -1f, 0,
|
||||
-1, -1, 0, 1)
|
||||
.mul(VIEWS[i]);
|
||||
|
||||
BudgetBufferRenderer.render(mat);
|
||||
}
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
//Render block model entity data if it exists
|
||||
/*
|
||||
if (bbem != null) {
|
||||
//Rerender everything again ;-; but is ok (is not)
|
||||
|
||||
var mat = new Matrix4f();
|
||||
for (int i = 0; i < VIEWS.length; i++) {
|
||||
if (i==1||i==2||i==4) {
|
||||
glCullFace(GL_FRONT);
|
||||
} else {
|
||||
glCullFace(GL_BACK);
|
||||
}
|
||||
|
||||
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
|
||||
|
||||
//The projection matrix
|
||||
mat.set(2, 0, 0, 0,
|
||||
0, 2, 0, 0,
|
||||
0, 0, -1f, 0,
|
||||
-1, -1, 0, 1)
|
||||
.mul(VIEWS[i]);
|
||||
|
||||
bbem.render(mat, blockTextureId);
|
||||
}
|
||||
glBindVertexArray(0);
|
||||
|
||||
bbem.release();
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
//"Restore" gl state
|
||||
glViewport(viewdat[0], viewdat[1], viewdat[2], viewdat[3]);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
//Finish and download
|
||||
glTextureBarrier();
|
||||
this.capture.emitToStream(streamBuffer, streamOffset);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
|
||||
glClearDepth(1);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
if (layer == ChunkSectionLayer.TRANSLUCENT) {
|
||||
//reset the blend func
|
||||
GL14.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
|
||||
return (isAnyShaded?1:0)|(isAnyDarkend?2:0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static {
|
||||
//the face/direction is the face (e.g. down is the down face)
|
||||
addView(0, -90,0, 0, 0);//Direction.DOWN
|
||||
addView(1, 90,0, 0, 0b100);//Direction.UP
|
||||
|
||||
addView(2, 0,180, 0, 0b001);//Direction.NORTH
|
||||
addView(3, 0,0, 0, 0);//Direction.SOUTH
|
||||
|
||||
addView(4, 0,90, 270, 0b100);//Direction.WEST
|
||||
addView(5, 0,270, 270, 0);//Direction.EAST
|
||||
}
|
||||
|
||||
private static void addView(int i, float pitch, float yaw, float rotation, int flip) {
|
||||
var stack = new PoseStack();
|
||||
stack.translate(0.5f,0.5f,0.5f);
|
||||
stack.mulPose(makeQuatFromAxisExact(new Vector3f(0,0,1), rotation));
|
||||
stack.mulPose(makeQuatFromAxisExact(new Vector3f(1,0,0), pitch));
|
||||
stack.mulPose(makeQuatFromAxisExact(new Vector3f(0,1,0), yaw));
|
||||
stack.mulPose(new Matrix4f().scale(1-2*(flip&1), 1-(flip&2), 1-((flip>>1)&2)));
|
||||
stack.translate(-0.5f,-0.5f,-0.5f);
|
||||
VIEWS[i] = new Matrix4f(stack.last().pose());
|
||||
}
|
||||
|
||||
private static Quaternionf makeQuatFromAxisExact(Vector3f vec, float angle) {
|
||||
angle = (float) Math.toRadians(angle);
|
||||
float hangle = angle / 2.0f;
|
||||
float sinAngle = (float) Math.sin(hangle);
|
||||
float invVLength = (float) (1/Math.sqrt(vec.lengthSquared()));
|
||||
return new Quaternionf(vec.x * invVLength * sinAngle,
|
||||
vec.y * invVLength * sinAngle,
|
||||
vec.z * invVLength * sinAngle,
|
||||
Math.cos(hangle));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package me.cortex.voxy.client.core.model.bakery;
|
||||
|
||||
|
||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||
import net.minecraft.client.model.geom.builders.UVPair;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.renderer.texture.MipmapStrategy;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import static me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer.VERTEX_FORMAT_SIZE;
|
||||
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
public final class ReuseVertexConsumer implements VertexConsumer {
|
||||
private MemoryBuffer buffer = new MemoryBuffer(8192);
|
||||
private long ptr;
|
||||
private int count;
|
||||
private int defaultMeta;
|
||||
|
||||
public boolean anyShaded;
|
||||
public boolean anyDarkendTex;
|
||||
|
||||
public ReuseVertexConsumer() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
public ReuseVertexConsumer setDefaultMeta(int meta) {
|
||||
this.defaultMeta = meta;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReuseVertexConsumer addVertex(float x, float y, float z) {
|
||||
this.ensureCanPut();
|
||||
this.ptr += VERTEX_FORMAT_SIZE; this.count++; //Goto next vertex
|
||||
this.meta(this.defaultMeta);
|
||||
MemoryUtil.memPutFloat(this.ptr, x);
|
||||
MemoryUtil.memPutFloat(this.ptr + 4, y);
|
||||
MemoryUtil.memPutFloat(this.ptr + 8, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReuseVertexConsumer meta(int metadata) {
|
||||
MemoryUtil.memPutInt(this.ptr + 12, metadata);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReuseVertexConsumer setColor(int red, int green, int blue, int alpha) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer setColor(int i) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReuseVertexConsumer setUv(float u, float v) {
|
||||
MemoryUtil.memPutFloat(this.ptr + 16, u);
|
||||
MemoryUtil.memPutFloat(this.ptr + 20, v);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReuseVertexConsumer setUv1(int u, int v) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReuseVertexConsumer setUv2(int u, int v) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReuseVertexConsumer setNormal(float x, float y, float z) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer setLineWidth(float f) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ReuseVertexConsumer quad(BakedQuad quad, int metadata) {
|
||||
this.anyShaded |= quad.shade();
|
||||
this.anyDarkendTex |= quad.sprite().contents().mipmapStrategy == MipmapStrategy.DARK_CUTOUT;
|
||||
this.ensureCanPut();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
var pos = quad.position(i);
|
||||
this.addVertex(pos.x(), pos.y(), pos.z());
|
||||
long puv = quad.packedUV(i);
|
||||
this.setUv(UVPair.unpackU(puv),UVPair.unpackV(puv));
|
||||
|
||||
this.meta(metadata);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void ensureCanPut() {
|
||||
if ((long) (this.count + 5) * VERTEX_FORMAT_SIZE < this.buffer.size) {
|
||||
return;
|
||||
}
|
||||
long offset = this.ptr-this.buffer.address;
|
||||
//1.5x the size
|
||||
var newBuffer = new MemoryBuffer((((int)(this.buffer.size*2)+VERTEX_FORMAT_SIZE-1)/VERTEX_FORMAT_SIZE)*VERTEX_FORMAT_SIZE);
|
||||
this.buffer.cpyTo(newBuffer.address);
|
||||
this.buffer.free();
|
||||
this.buffer = newBuffer;
|
||||
this.ptr = offset + newBuffer.address;
|
||||
}
|
||||
|
||||
public ReuseVertexConsumer reset() {
|
||||
this.anyShaded = false;
|
||||
this.anyDarkendTex = false;
|
||||
this.defaultMeta = 0;//RESET THE DEFAULT META
|
||||
this.count = 0;
|
||||
this.ptr = this.buffer.address - VERTEX_FORMAT_SIZE;//the thing is first time this gets incremented by FORMAT_STRIDE
|
||||
return this;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.ptr = 0;
|
||||
this.count = 0;
|
||||
this.buffer.free();
|
||||
this.buffer = null;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.count == 0;
|
||||
}
|
||||
|
||||
public int quadCount() {
|
||||
if (this.count%4 != 0) throw new IllegalStateException();
|
||||
return this.count/4;
|
||||
}
|
||||
|
||||
public long getAddress() {
|
||||
return this.buffer.address;
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
//NOTE: an idea on how to do it is so that any render section, we _keep_ aquired (yes this will be very memory intensive)
|
||||
// could maybe tosomething else
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.model.ModelManager;
|
||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.common.world.other.Mapper;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.Camera;
|
||||
import net.minecraft.client.render.Frustum;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.BiomeKeys;
|
||||
import org.joml.FrustumIntersection;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
import static org.lwjgl.opengl.ARBMultiDrawIndirect.glMultiDrawElementsIndirect;
|
||||
import static org.lwjgl.opengl.GL30.*;
|
||||
|
||||
//can make it so that register the key of the sections we have rendered, then when a section changes and is registered,
|
||||
// dispatch an update to the render section data builder which then gets consumed by the render system and updates
|
||||
// the rendered data
|
||||
|
||||
//Contains all the logic to render the world and manage gpu memory
|
||||
// processes section load,unload,update render data and renders the world each frame
|
||||
|
||||
|
||||
//Todo: tinker with having the compute shader where each thread is a position to render? maybe idk
|
||||
public abstract class AbstractFarWorldRenderer <T extends Viewport, J extends AbstractGeometryManager> {
|
||||
public static final int STATIC_VAO = glGenVertexArrays();
|
||||
|
||||
protected final GlBuffer uniformBuffer;
|
||||
protected final J geometry;
|
||||
protected final ModelManager models;
|
||||
protected final GlBuffer lightDataBuffer;
|
||||
|
||||
protected final int maxSections;
|
||||
|
||||
//Current camera base level section position
|
||||
protected int sx;
|
||||
protected int sy;
|
||||
protected int sz;
|
||||
|
||||
protected FrustumIntersection frustum;
|
||||
|
||||
private final List<T> viewports = new ArrayList<>();
|
||||
|
||||
protected IntArrayList updatedSectionIds;
|
||||
|
||||
private final ConcurrentLinkedDeque<Mapper.StateEntry> blockStateUpdates = new ConcurrentLinkedDeque<>();
|
||||
private final ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeUpdates = new ConcurrentLinkedDeque<>();
|
||||
public AbstractFarWorldRenderer(J geometry) {
|
||||
this.maxSections = geometry.getMaxSections();
|
||||
this.uniformBuffer = new GlBuffer(1024);
|
||||
this.lightDataBuffer = new GlBuffer(256*4);//256 of uint
|
||||
this.geometry = geometry;
|
||||
this.models = new ModelManager(16);
|
||||
}
|
||||
|
||||
public void setupRender(Frustum frustum, Camera camera) {
|
||||
this.frustum = frustum.frustumIntersection;
|
||||
|
||||
this.sx = camera.getBlockPos().getX() >> 5;
|
||||
this.sy = camera.getBlockPos().getY() >> 5;
|
||||
this.sz = camera.getBlockPos().getZ() >> 5;
|
||||
|
||||
//TODO: move this to a render function that is only called
|
||||
// once per frame when using multi viewport mods
|
||||
//it shouldent matter if its called multiple times a frame however, as its synced with fences
|
||||
UploadStream.INSTANCE.tick();
|
||||
DownloadStream.INSTANCE.tick();
|
||||
|
||||
//Update the lightmap
|
||||
{
|
||||
long upload = UploadStream.INSTANCE.upload(this.lightDataBuffer, 0, 256*4);
|
||||
var lmt = MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().texture.getImage();
|
||||
for (int light = 0; light < 256; light++) {
|
||||
int x = light&0xF;
|
||||
int y = ((light>>4)&0xF);
|
||||
int sample = lmt.getColor(x,y);
|
||||
sample = ((sample&0xFF0000)>>16)|(sample&0xFF00)|((sample&0xFF)<<16);
|
||||
MemoryUtil.memPutInt(upload + (((x<<4)|(15-y))*4), sample|(0xFF<<28));//Skylight is inverted
|
||||
}
|
||||
}
|
||||
|
||||
//Upload any new geometry
|
||||
this.updatedSectionIds = this.geometry.uploadResults();
|
||||
{
|
||||
boolean didHaveBiomeChange = false;
|
||||
|
||||
//Do any BiomeChanges
|
||||
while (!this.biomeUpdates.isEmpty()) {
|
||||
var update = this.biomeUpdates.pop();
|
||||
var biomeReg = MinecraftClient.getInstance().world.getRegistryManager().get(RegistryKeys.BIOME);
|
||||
this.models.addBiome(update.id, biomeReg.get(Identifier.of(update.biome)));
|
||||
didHaveBiomeChange = true;
|
||||
}
|
||||
|
||||
if (didHaveBiomeChange) {
|
||||
UploadStream.INSTANCE.commit();
|
||||
}
|
||||
|
||||
int maxUpdatesPerFrame = 40;
|
||||
|
||||
//Do any BlockChanges
|
||||
while ((!this.blockStateUpdates.isEmpty()) && (maxUpdatesPerFrame-- > 0)) {
|
||||
var update = this.blockStateUpdates.pop();
|
||||
this.models.addEntry(update.id, update.state);
|
||||
}
|
||||
//this.models.bakery.renderFaces(Blocks.ROSE_BUSH.getDefaultState(), 1234, false);
|
||||
}
|
||||
|
||||
//TODO: fix this in a better way than this ungodly hacky stuff, causes clouds to dissapear
|
||||
//RenderSystem.setShaderFogColor(1f, 1f, 1f, 0f);
|
||||
RenderSystem.setShaderFogEnd(99999999);
|
||||
RenderSystem.setShaderFogStart(9999999);
|
||||
}
|
||||
|
||||
public abstract void renderFarAwayOpaque(T viewport);
|
||||
|
||||
public abstract void renderFarAwayTranslucent(T viewport);
|
||||
|
||||
public void enqueueResult(BuiltSection result) {
|
||||
this.geometry.enqueueResult(result);
|
||||
}
|
||||
|
||||
public void addBlockState(Mapper.StateEntry entry) {
|
||||
this.blockStateUpdates.add(entry);
|
||||
}
|
||||
|
||||
public void addBiome(Mapper.BiomeEntry entry) {
|
||||
this.biomeUpdates.add(entry);
|
||||
}
|
||||
|
||||
public void addDebugData(List<String> debug) {
|
||||
this.models.addDebugInfo(debug);
|
||||
debug.add("Geometry buffer usage: " + ((float)Math.round((this.geometry.getGeometryBufferUsage()*100000))/1000) + "%");
|
||||
debug.add("Render Sections: " + this.geometry.getSectionCount());
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.models.free();
|
||||
this.geometry.free();
|
||||
this.uniformBuffer.free();
|
||||
this.lightDataBuffer.free();
|
||||
}
|
||||
|
||||
public ModelManager getModelManager() {
|
||||
return this.models;
|
||||
}
|
||||
|
||||
public final T createViewport() {
|
||||
var viewport = createViewport0();
|
||||
this.viewports.add(viewport);
|
||||
return viewport;
|
||||
}
|
||||
|
||||
final void removeViewport(T viewport) {
|
||||
this.viewports.remove(viewport);
|
||||
}
|
||||
|
||||
protected abstract T createViewport0();
|
||||
|
||||
public boolean usesMeshlets() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
public abstract class AbstractGeometryManager {
|
||||
protected int sectionCount = 0;
|
||||
protected final int maxSections;
|
||||
protected final ConcurrentLinkedDeque<BuiltSection> buildResults = new ConcurrentLinkedDeque<>();
|
||||
|
||||
protected AbstractGeometryManager(int maxSections) {
|
||||
this.maxSections = maxSections;
|
||||
}
|
||||
|
||||
abstract IntArrayList uploadResults();
|
||||
|
||||
int getMaxSections() {
|
||||
return this.maxSections;
|
||||
}
|
||||
|
||||
public void enqueueResult(BuiltSection sectionGeometry) {
|
||||
this.buildResults.add(sectionGeometry);
|
||||
}
|
||||
|
||||
public abstract float getGeometryBufferUsage();
|
||||
|
||||
public int getSectionCount() {
|
||||
return this.sectionCount;
|
||||
}
|
||||
|
||||
public abstract void free();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import me.cortex.voxy.client.core.AbstractRenderPipeline;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlVertexArray;
|
||||
import me.cortex.voxy.client.core.gl.shader.AutoBindingShader;
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderLoader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector3i;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import static org.lwjgl.opengl.ARBDirectStateAccess.glCopyNamedBufferSubData;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
|
||||
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
|
||||
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||
import static org.lwjgl.opengl.GL30C.*;
|
||||
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
|
||||
import static org.lwjgl.opengl.GL42.glDrawElementsInstancedBaseInstance;
|
||||
|
||||
//This is a render subsystem, its very simple in what it does
|
||||
// it renders an AABB around loaded chunks, thats it
|
||||
public class ChunkBoundRenderer {
|
||||
private static final int INIT_MAX_CHUNK_COUNT = 1<<12;
|
||||
private GlBuffer chunkPosBuffer = new GlBuffer(INIT_MAX_CHUNK_COUNT*8);//Stored as ivec2
|
||||
private final GlBuffer uniformBuffer = new GlBuffer(128);
|
||||
private final Long2IntOpenHashMap chunk2idx = new Long2IntOpenHashMap(INIT_MAX_CHUNK_COUNT);
|
||||
private long[] idx2chunk = new long[INIT_MAX_CHUNK_COUNT];
|
||||
private final Shader rasterShader;
|
||||
|
||||
private final LongOpenHashSet addQueue = new LongOpenHashSet();
|
||||
private final LongOpenHashSet remQueue = new LongOpenHashSet();
|
||||
|
||||
private final AbstractRenderPipeline pipeline;
|
||||
public ChunkBoundRenderer(AbstractRenderPipeline pipeline) {
|
||||
this.chunk2idx.defaultReturnValue(-1);
|
||||
this.pipeline = pipeline;
|
||||
|
||||
String vert = ShaderLoader.parse("voxy:chunkoutline/outline.vsh");
|
||||
String taa = pipeline.taaFunction("getTAA");
|
||||
if (taa != null) {
|
||||
vert = vert+"\n\n\n"+taa;
|
||||
}
|
||||
this.rasterShader = Shader.makeAuto()
|
||||
.addSource(ShaderType.VERTEX, vert)
|
||||
.defineIf("TAA", taa != null)
|
||||
.add(ShaderType.FRAGMENT, "voxy:chunkoutline/outline.fsh")
|
||||
.compile()
|
||||
.ubo(0, this.uniformBuffer)
|
||||
.ssbo(1, this.chunkPosBuffer);
|
||||
}
|
||||
|
||||
public void addSection(long pos) {
|
||||
if (!this.remQueue.remove(pos)) {
|
||||
this.addQueue.add(pos);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSection(long pos) {
|
||||
if (!this.addQueue.remove(pos)) {
|
||||
this.remQueue.add(pos);
|
||||
}
|
||||
}
|
||||
|
||||
//Bind and render, changing as little gl state as possible so that the caller may configure how it wants to render
|
||||
public void render(Viewport<?> viewport) {
|
||||
if (!this.remQueue.isEmpty()) {
|
||||
boolean wasEmpty = this.chunk2idx.isEmpty();
|
||||
this.remQueue.forEach(this::_remPos);//TODO: REPLACE WITH SCATTER COMPUTE
|
||||
this.remQueue.clear();
|
||||
if (this.chunk2idx.isEmpty()&&!wasEmpty) {//When going from stuff to nothing need to clear the depth buffer
|
||||
viewport.depthBoundingBuffer.clear(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.chunk2idx.isEmpty() && this.addQueue.isEmpty()) return;
|
||||
|
||||
viewport.depthBoundingBuffer.clear(0);
|
||||
|
||||
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 128);
|
||||
long matPtr = ptr; ptr += 4*4*4;
|
||||
|
||||
final float renderDistance = Minecraft.getInstance().options.getEffectiveRenderDistance()*16;//In blocks
|
||||
|
||||
{//This is recomputed to be in chunk section space not worldsection
|
||||
int sx = (int)(viewport.cameraX);
|
||||
int sy = (int)(viewport.cameraY);
|
||||
int sz = (int)(viewport.cameraZ);
|
||||
new Vector3i(sx, sy, sz).getToAddress(ptr); ptr += 4*4;
|
||||
|
||||
var negInnerSec = new Vector3f(
|
||||
(float) (viewport.cameraX - sx),
|
||||
(float) (viewport.cameraY - sy),
|
||||
(float) (viewport.cameraZ - sz));
|
||||
|
||||
|
||||
negInnerSec.getToAddress(ptr); ptr += 4*3;
|
||||
viewport.MVP.translate(negInnerSec.negate(), new Matrix4f()).getToAddress(matPtr);
|
||||
MemoryUtil.memPutFloat(ptr, renderDistance); ptr += 4;
|
||||
}
|
||||
UploadStream.INSTANCE.commit();
|
||||
|
||||
|
||||
{
|
||||
//need to reverse the winding order since we want the back faces of the AABB, not the front
|
||||
|
||||
glFrontFace(GL_CW);//Reverse winding order
|
||||
|
||||
//"reverse depth buffer" it goes from 0->1 where 1 is far away
|
||||
glEnable(GL_CULL_FACE);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_GREATER);
|
||||
}
|
||||
|
||||
glBindVertexArray(GlVertexArray.STATIC_VAO);
|
||||
viewport.depthBoundingBuffer.bind();
|
||||
this.rasterShader.bind();
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BB_BYTE.id());
|
||||
this.pipeline.bindUniforms();
|
||||
|
||||
//Batch the draws into groups of size 32
|
||||
int count = this.chunk2idx.size();
|
||||
if (count >= 32) {
|
||||
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3 * 32, GL_UNSIGNED_BYTE, 0, count/32);
|
||||
}
|
||||
if (count%32 != 0) {
|
||||
glDrawElementsInstancedBaseInstance(GL_TRIANGLES, 6 * 2 * 3 * (count%32), GL_UNSIGNED_BYTE, 0, 1, (count/32)*32);
|
||||
}
|
||||
|
||||
{
|
||||
glFrontFace(GL_CCW);//Restore winding order
|
||||
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
|
||||
//TODO: check this is correct
|
||||
glEnable(GL_CULL_FACE);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
|
||||
if (!this.addQueue.isEmpty()) {
|
||||
this.addQueue.forEach(this::_addPos);//TODO: REPLACE WITH SCATTER COMPUTE
|
||||
this.addQueue.clear();
|
||||
UploadStream.INSTANCE.commit();
|
||||
}
|
||||
}
|
||||
|
||||
private void _remPos(long pos) {
|
||||
int idx = this.chunk2idx.remove(pos);
|
||||
if (idx == -1) {
|
||||
Logger.warn("Chunk not in map: " + pos);
|
||||
return;
|
||||
}
|
||||
if (idx == this.chunk2idx.size()) {
|
||||
//Dont need to do anything as heap is already compact
|
||||
return;
|
||||
}
|
||||
if (this.idx2chunk[idx] != pos) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
//Move last entry on heap to this index
|
||||
long ePos = this.idx2chunk[this.chunk2idx.size()];// since is already removed size is correct end idx
|
||||
if (this.chunk2idx.put(ePos, idx) == -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.idx2chunk[idx] = ePos;
|
||||
|
||||
//Put the end pos into the new idx
|
||||
this.put(idx, ePos);
|
||||
}
|
||||
|
||||
private void _addPos(long pos) {
|
||||
if (this.chunk2idx.containsKey(pos)) {
|
||||
Logger.warn("Chunk already in map: " + pos);
|
||||
return;
|
||||
}
|
||||
this.ensureSize1();//Resize if needed
|
||||
|
||||
int idx = this.chunk2idx.size();
|
||||
this.chunk2idx.put(pos, idx);
|
||||
this.idx2chunk[idx] = pos;
|
||||
|
||||
this.put(idx, pos);
|
||||
}
|
||||
|
||||
private void ensureSize1() {
|
||||
if (this.chunk2idx.size() < this.idx2chunk.length) return;
|
||||
//Commit any copies, ensures is synced to new buffer
|
||||
UploadStream.INSTANCE.commit();
|
||||
|
||||
int size = (int) (this.idx2chunk.length*1.5);
|
||||
Logger.info("Resizing chunk position buffer to: " + size);
|
||||
//Need to resize
|
||||
var old = this.chunkPosBuffer;
|
||||
this.chunkPosBuffer = new GlBuffer(size * 8L);
|
||||
glCopyNamedBufferSubData(old.id, this.chunkPosBuffer.id, 0, 0, old.size());
|
||||
old.free();
|
||||
var old2 = this.idx2chunk;
|
||||
this.idx2chunk = new long[size];
|
||||
System.arraycopy(old2, 0, this.idx2chunk, 0, old2.length);
|
||||
//Replace the old buffer with the new one
|
||||
((AutoBindingShader)this.rasterShader).ssbo(1, this.chunkPosBuffer);
|
||||
}
|
||||
|
||||
private void put(int idx, long pos) {
|
||||
long ptr2 = UploadStream.INSTANCE.upload(this.chunkPosBuffer, 8L*idx, 8);
|
||||
//Need to do it in 2 parts because ivec2 is 2 parts
|
||||
MemoryUtil.memPutInt(ptr2, (int)(pos&0xFFFFFFFFL)); ptr2 += 4;
|
||||
MemoryUtil.memPutInt(ptr2, (int)((pos>>>32)&0xFFFFFFFFL));
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.chunk2idx.clear();
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.rasterShader.free();
|
||||
this.uniformBuffer.free();
|
||||
this.chunkPosBuffer.free();
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||
import me.cortex.voxy.client.core.rendering.util.BufferArena;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.text.Text;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
public class DefaultGeometryManager extends AbstractGeometryManager {
|
||||
private static final int SECTION_METADATA_SIZE = 32;
|
||||
private final Long2IntOpenHashMap pos2id = new Long2IntOpenHashMap();
|
||||
private final LongArrayList id2pos = new LongArrayList();
|
||||
private final ObjectArrayList<SectionMeta> sectionMetadata = new ObjectArrayList<>();
|
||||
private final IntArrayList markSectionIds = new IntArrayList();//Section ids to mark as visible (either due to being new, or swapping)
|
||||
|
||||
private final GlBuffer sectionMetaBuffer;
|
||||
private final BufferArena geometryBuffer;
|
||||
|
||||
private final int geometryElementSize;
|
||||
|
||||
public DefaultGeometryManager(long geometryBufferSize, int maxSections) {
|
||||
this(geometryBufferSize, maxSections, 8);//8 is default quad size
|
||||
}
|
||||
|
||||
public DefaultGeometryManager(long geometryBufferSize, int maxSections, int elementSize) {
|
||||
super(maxSections);
|
||||
this.sectionMetaBuffer = new GlBuffer(((long) maxSections) * SECTION_METADATA_SIZE);
|
||||
this.geometryBuffer = new BufferArena(geometryBufferSize, elementSize);
|
||||
this.pos2id.defaultReturnValue(-1);
|
||||
this.geometryElementSize = elementSize;
|
||||
}
|
||||
|
||||
IntArrayList uploadResults() {
|
||||
this.markSectionIds.clear();
|
||||
while (!this.buildResults.isEmpty()) {
|
||||
var result = this.buildResults.pop();
|
||||
boolean isDelete = result.isEmpty();
|
||||
if (isDelete) {
|
||||
int id = -1;
|
||||
if ((id = this.pos2id.remove(result.position)) != -1) {
|
||||
if (this.id2pos.getLong(id) != result.position) {
|
||||
throw new IllegalStateException("Removed position id not the same requested");
|
||||
}
|
||||
|
||||
var meta = this.sectionMetadata.get(id);
|
||||
this.freeMeta(meta);
|
||||
|
||||
|
||||
this.sectionCount--;
|
||||
if (id == this.sectionCount) {
|
||||
//if we are at the end of the array dont have to do anything (maybe just upload a blank data, just to be sure)
|
||||
|
||||
//Remove the last element
|
||||
this.sectionMetadata.remove(id);
|
||||
this.id2pos.removeLong(id);
|
||||
} else {
|
||||
long swapLodPos = this.id2pos.getLong(this.sectionCount);
|
||||
this.pos2id.put(swapLodPos, id);
|
||||
this.id2pos.set(id, swapLodPos);
|
||||
//Remove from the lists
|
||||
this.id2pos.removeLong(this.sectionCount);
|
||||
var swapMeta = this.sectionMetadata.remove(this.sectionCount);
|
||||
this.sectionMetadata.set(id, swapMeta);
|
||||
if (swapMeta.position != swapLodPos) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
long ptr = UploadStream.INSTANCE.upload(this.sectionMetaBuffer, (long) SECTION_METADATA_SIZE * id, SECTION_METADATA_SIZE);
|
||||
swapMeta.writeMetadata(ptr);
|
||||
this.markSectionIds.add(id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int id = -1;
|
||||
if ((id = this.pos2id.get(result.position)) != -1) {
|
||||
//Update the existing data
|
||||
var meta = this.sectionMetadata.get(id);
|
||||
if (meta.position != result.position) {
|
||||
throw new IllegalStateException("Meta position != result position");
|
||||
}
|
||||
//Delete the old data
|
||||
this.freeMeta(meta);
|
||||
|
||||
//Create the new meta
|
||||
meta = this.createMeta(result);
|
||||
if (meta == null) {
|
||||
continue;
|
||||
}
|
||||
this.sectionMetadata.set(id, meta);
|
||||
long ptr = UploadStream.INSTANCE.upload(this.sectionMetaBuffer, (long)SECTION_METADATA_SIZE * id, SECTION_METADATA_SIZE);
|
||||
meta.writeMetadata(ptr);
|
||||
} else {
|
||||
//Create the new meta
|
||||
var meta = this.createMeta(result);
|
||||
if (meta == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Add to the end of the array
|
||||
id = this.sectionCount++;
|
||||
this.pos2id.put(result.position, id);
|
||||
this.id2pos.add(result.position);
|
||||
|
||||
this.sectionMetadata.add(meta);
|
||||
long ptr = UploadStream.INSTANCE.upload(this.sectionMetaBuffer, (long)SECTION_METADATA_SIZE * id, SECTION_METADATA_SIZE);
|
||||
meta.writeMetadata(ptr);
|
||||
this.markSectionIds.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
//Assert some invarients
|
||||
if (this.id2pos.size() != this.sectionCount || this.sectionCount != this.pos2id.size()) {
|
||||
throw new IllegalStateException("Invariants broken");
|
||||
}
|
||||
|
||||
result.free();
|
||||
}
|
||||
return this.markSectionIds;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
while (!this.buildResults.isEmpty()) {
|
||||
this.buildResults.pop().free();
|
||||
}
|
||||
this.sectionMetaBuffer.free();
|
||||
this.geometryBuffer.free();
|
||||
}
|
||||
|
||||
public int geometryId() {
|
||||
return this.geometryBuffer.id();
|
||||
}
|
||||
|
||||
public int metaId() {
|
||||
return this.sectionMetaBuffer.id;
|
||||
}
|
||||
|
||||
public float getGeometryBufferUsage() {
|
||||
return this.geometryBuffer.usage();
|
||||
}
|
||||
|
||||
|
||||
//=========================================================================================================================================================================================
|
||||
//=========================================================================================================================================================================================
|
||||
//=========================================================================================================================================================================================
|
||||
|
||||
|
||||
//TODO: pack the offsets of each axis so that implicit face culling can work
|
||||
//Note! the opaquePreDataCount and translucentPreDataCount are never writen to the meta buffer, as they are indexed in reverse relative to the base opaque and translucent geometry
|
||||
protected record SectionMeta(long position, int aabb, int geometryPtr, int size, int[] offsets) {
|
||||
public void writeMetadata(long ptr) {
|
||||
//THIS IS DUE TO ENDIANNESS and that we are splitting a long into 2 ints
|
||||
MemoryUtil.memPutInt(ptr, (int) (this.position>>32)); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (int) this.position); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (int) this.aabb); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, this.geometryPtr + this.offsets[0]); ptr += 4;
|
||||
|
||||
MemoryUtil.memPutInt(ptr, (this.offsets[1]-this.offsets[0])|((this.offsets[2]-this.offsets[1])<<16)); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (this.offsets[3]-this.offsets[2])|((this.offsets[4]-this.offsets[3])<<16)); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (this.offsets[5]-this.offsets[4])|((this.offsets[6]-this.offsets[5])<<16)); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (this.offsets[7]-this.offsets[6])|((this.size -this.offsets[7])<<16)); ptr += 4;
|
||||
}
|
||||
}
|
||||
|
||||
protected SectionMeta createMeta(BuiltSection geometry) {
|
||||
int geometryPtr = (int) this.geometryBuffer.upload(geometry.geometryBuffer);
|
||||
if (geometryPtr == -1) {
|
||||
String msg = "Buffer arena out of memory, please increase it in settings or decrease LoD quality";
|
||||
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.literal(msg));
|
||||
System.err.println(msg);
|
||||
return null;
|
||||
}
|
||||
return new SectionMeta(geometry.position, geometry.aabb, geometryPtr, (int) (geometry.geometryBuffer.size/this.geometryElementSize), geometry.offsets);
|
||||
}
|
||||
|
||||
protected void freeMeta(SectionMeta meta) {
|
||||
if (meta.geometryPtr != -1) {
|
||||
this.geometryBuffer.free(meta.geometryPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
//CPU side cache for section geometry, not thread safe
|
||||
public class GeometryCache {
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private long maxCombinedSize;
|
||||
private long currentSize;
|
||||
private final Long2ObjectLinkedOpenHashMap<BuiltSection> cache = new Long2ObjectLinkedOpenHashMap<>();
|
||||
public GeometryCache(long maxSize) {
|
||||
this.setMaxTotalSize(maxSize);
|
||||
}
|
||||
|
||||
public void setMaxTotalSize(long size) {
|
||||
this.maxCombinedSize = size;
|
||||
}
|
||||
|
||||
//Puts the section into the cache
|
||||
public void put(BuiltSection section) {
|
||||
this.lock.lock();
|
||||
var prev = this.cache.put(section.position, section);
|
||||
this.currentSize += section.geometryBuffer.size;
|
||||
if (prev != null) {
|
||||
this.currentSize -= prev.geometryBuffer.size;
|
||||
}
|
||||
while (this.maxCombinedSize <= this.currentSize) {
|
||||
var entry = this.cache.removeFirst();
|
||||
this.currentSize -= entry.geometryBuffer.size;
|
||||
entry.free();
|
||||
}
|
||||
this.lock.unlock();
|
||||
if (prev != null) {
|
||||
prev.free();
|
||||
}
|
||||
}
|
||||
|
||||
public BuiltSection remove(long position) {
|
||||
this.lock.lock();
|
||||
var section = this.cache.remove(position);
|
||||
if (section != null) {
|
||||
this.currentSize -= section.geometryBuffer.size;
|
||||
}
|
||||
this.lock.unlock();
|
||||
return section;
|
||||
}
|
||||
|
||||
public void clear(long position) {
|
||||
var sec = this.remove(position);
|
||||
if (sec != null) {
|
||||
sec.free();
|
||||
}
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.lock.lock();
|
||||
this.cache.values().forEach(BuiltSection::free);
|
||||
this.cache.clear();
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.opengl.GL11C;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
|
||||
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
|
||||
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_SHORT;
|
||||
import static org.lwjgl.opengl.GL11.glGetInteger;
|
||||
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
|
||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||
import static org.lwjgl.opengl.GL30C.GL_R8UI;
|
||||
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
|
||||
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
|
||||
import static org.lwjgl.opengl.GL42.*;
|
||||
import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.GL43.*;
|
||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
|
||||
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
|
||||
import static org.lwjgl.opengl.GL45.glClearNamedBufferData;
|
||||
import static org.lwjgl.opengl.GL45C.nglClearNamedBufferData;
|
||||
|
||||
public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer<Gl46Viewport, DefaultGeometryManager> {
|
||||
private final Shader commandGen = Shader.make()
|
||||
.add(ShaderType.COMPUTE, "voxy:lod/gl46/cmdgen.comp")
|
||||
.compile();
|
||||
|
||||
private final Shader lodShader = Shader.make()
|
||||
.add(ShaderType.VERTEX, "voxy:lod/gl46/quads2.vert")
|
||||
.add(ShaderType.FRAGMENT, "voxy:lod/gl46/quads.frag")
|
||||
.compile();
|
||||
|
||||
|
||||
//TODO: Note the cull shader needs a different element array since its rastering cubes not quads
|
||||
private final Shader cullShader = Shader.make()
|
||||
.add(ShaderType.VERTEX, "voxy:lod/gl46/cull/raster.vert")
|
||||
.add(ShaderType.FRAGMENT, "voxy:lod/gl46/cull/raster.frag")
|
||||
.compile();
|
||||
|
||||
private final GlBuffer glCommandBuffer;
|
||||
private final GlBuffer glCommandCountBuffer;
|
||||
|
||||
public Gl46FarWorldRenderer(int geometryBuffer, int maxSections) {
|
||||
super(new DefaultGeometryManager(geometryBuffer*8L, maxSections));
|
||||
this.glCommandBuffer = new GlBuffer(maxSections*5L*4 * 6);
|
||||
this.glCommandCountBuffer = new GlBuffer(4*2);
|
||||
nglClearNamedBufferData(this.glCommandBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
}
|
||||
|
||||
protected void bindResources(Gl46Viewport viewport) {
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometry.geometryId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.glCommandBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.glCommandCountBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometry.metaId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, viewport.visibilityBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.models.getBufferId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, this.models.getColourBufferId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 8, this.lightDataBuffer.id);//Lighting LUT
|
||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.glCommandBuffer.id);
|
||||
glBindBuffer(GL_PARAMETER_BUFFER_ARB, this.glCommandCountBuffer.id);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
|
||||
|
||||
//Bind the texture atlas
|
||||
glBindSampler(0, this.models.getSamplerId());
|
||||
glBindTextureUnit(0, this.models.getTextureId());
|
||||
}
|
||||
|
||||
//FIXME: dont do something like this as it breaks multiviewport mods
|
||||
// the issue is that voxy expects the counter to be incremented by one each frame (the compute shader generating the commands)
|
||||
// checks `frameId - 1`, this means in a multiviewport, effectivly only the stuff that was marked visible by the last viewport
|
||||
// would be visible in the current viewport (if there are 2 viewports)
|
||||
|
||||
//To fix the issue, need to make a viewport api, that independently tracks the frameId and has its own glVisibilityBuffer buffer
|
||||
private void updateUniformBuffer(Gl46Viewport viewport) {
|
||||
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, this.uniformBuffer.size());
|
||||
|
||||
var mat = new Matrix4f(viewport.projection).mul(viewport.modelView);
|
||||
var innerTranslation = new Vector3f((float) (viewport.cameraX-(this.sx<<5)), (float) (viewport.cameraY-(this.sy<<5)), (float) (viewport.cameraZ-(this.sz<<5)));
|
||||
mat.translate(-innerTranslation.x, -innerTranslation.y, -innerTranslation.z);
|
||||
mat.getToAddress(ptr); ptr += 4*4*4;
|
||||
MemoryUtil.memPutInt(ptr, this.sx); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, this.sy); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, this.sz); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, this.geometry.getSectionCount()); ptr += 4;
|
||||
var planes = ((AccessFrustumIntersection)this.frustum).getPlanes();
|
||||
for (var plane : planes) {
|
||||
plane.getToAddress(ptr); ptr += 4*4;
|
||||
}
|
||||
innerTranslation.getToAddress(ptr); ptr += 4*3;
|
||||
MemoryUtil.memPutInt(ptr, viewport.frameId++); ptr += 4;
|
||||
}
|
||||
|
||||
public void renderFarAwayOpaque(Gl46Viewport viewport) {
|
||||
if (this.geometry.getSectionCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
{//Mark all of the updated sections as being visible from last frame
|
||||
for (int id : this.updatedSectionIds) {
|
||||
long ptr = UploadStream.INSTANCE.upload(viewport.visibilityBuffer, id * 4L, 4);
|
||||
MemoryUtil.memPutInt(ptr, viewport.frameId - 1);//(visible from last frame)
|
||||
}
|
||||
}
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
|
||||
//this.models.bakery.renderFaces(Blocks.WATER.getDefaultState().with(FluidBlock.LEVEL, 1), 1234, true);
|
||||
//this.models.bakery.renderFaces(Blocks.CHEST.getDefaultState(), 1234, false);
|
||||
|
||||
|
||||
RenderLayer.getCutoutMipped().startDrawing();
|
||||
//RenderSystem.enableBlend();
|
||||
//RenderSystem.defaultBlendFunc();
|
||||
|
||||
this.updateUniformBuffer(viewport);
|
||||
UploadStream.INSTANCE.commit();
|
||||
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
|
||||
|
||||
nglClearNamedBufferData(this.glCommandCountBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
this.commandGen.bind();
|
||||
this.bindResources(viewport);
|
||||
glDispatchCompute((this.geometry.getSectionCount()+127)/128, 1, 1);
|
||||
glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_UNIFORM_BARRIER_BIT);
|
||||
|
||||
this.lodShader.bind();
|
||||
this.bindResources(viewport);
|
||||
glDisable(GL_CULL_FACE);
|
||||
//glPointSize(10);
|
||||
//TODO: replace glMultiDrawElementsIndirectCountARB with glMultiDrawElementsIndirect on intel gpus, since it performs so much better
|
||||
//glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, drawCnt, 0);
|
||||
//glLineWidth(2);
|
||||
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, 0, (int) (this.geometry.getSectionCount()*4.4), 0);
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
|
||||
/*
|
||||
DownloadStream.INSTANCE.download(this.glCommandCountBuffer, 0, 4, (ptr, siz) -> {
|
||||
int cnt = MemoryUtil.memGetInt(ptr);
|
||||
drawCnt = cnt;
|
||||
});
|
||||
DownloadStream.INSTANCE.commit();
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
|
||||
|
||||
this.cullShader.bind();
|
||||
this.bindResources(viewport);
|
||||
|
||||
glColorMask(false, false, false, false);
|
||||
glDepthMask(false);
|
||||
|
||||
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3, GL_UNSIGNED_BYTE, (1 << 16) * 6 * 2, this.geometry.getSectionCount());
|
||||
|
||||
glDepthMask(true);
|
||||
glColorMask(true, true, true, true);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
|
||||
//TODO: need to do temporal rasterization here
|
||||
|
||||
glBindVertexArray(0);
|
||||
glBindSampler(0, 0);
|
||||
glBindTextureUnit(0, 0);
|
||||
RenderLayer.getCutoutMipped().endDrawing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderFarAwayTranslucent(Gl46Viewport viewport) {
|
||||
RenderLayer.getTranslucent().startDrawing();
|
||||
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
//TODO: maybe change this so the alpha isnt applied in the same way or something?? since atm the texture bakery uses a very hacky
|
||||
// blend equation to make it avoid double applying translucency
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
|
||||
glBindSampler(0, this.models.getSamplerId());
|
||||
glBindTextureUnit(0, this.models.getTextureId());
|
||||
|
||||
//RenderSystem.blendFunc(GlStateManager.SrcFactor.ONE, GlStateManager.DstFactor.ONE);
|
||||
this.lodShader.bind();
|
||||
this.bindResources(viewport);
|
||||
|
||||
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 400_000 * 4 * 5, 4, this.geometry.getSectionCount(), 0);
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
glBindVertexArray(0);
|
||||
|
||||
|
||||
glBindSampler(0, 0);
|
||||
glBindTextureUnit(0, 0);
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
RenderLayer.getTranslucent().endDrawing();
|
||||
}
|
||||
|
||||
protected Gl46Viewport createViewport0() {
|
||||
return new Gl46Viewport(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
super.shutdown();
|
||||
this.commandGen.free();
|
||||
this.lodShader.free();
|
||||
this.cullShader.free();
|
||||
this.glCommandBuffer.free();
|
||||
this.glCommandCountBuffer.free();
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
|
||||
import static org.lwjgl.opengl.GL30C.GL_R8UI;
|
||||
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
|
||||
import static org.lwjgl.opengl.GL42.GL_UNSIGNED_BYTE;
|
||||
import static org.lwjgl.opengl.GL45C.nglClearNamedBufferData;
|
||||
|
||||
public class Gl46MeshletViewport extends Viewport<Gl46MeshletViewport> {
|
||||
GlBuffer visibilityBuffer;
|
||||
public Gl46MeshletViewport(Gl46MeshletsFarWorldRenderer renderer) {
|
||||
super(renderer);
|
||||
this.visibilityBuffer = new GlBuffer(renderer.maxSections*4L);
|
||||
nglClearNamedBufferData(this.visibilityBuffer.id, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, 0);
|
||||
}
|
||||
|
||||
protected void delete0() {
|
||||
this.visibilityBuffer.free();
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.voxy.client.core.rendering.building.RenderDataFactory;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import static org.lwjgl.opengl.ARBDirectStateAccess.glGetNamedFramebufferAttachmentParameteriv;
|
||||
import static org.lwjgl.opengl.ARBDirectStateAccess.glTextureParameteri;
|
||||
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
|
||||
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL30.glBindBufferBase;
|
||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
|
||||
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
|
||||
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
|
||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||
import static org.lwjgl.opengl.GL40.glDrawElementsIndirect;
|
||||
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
|
||||
import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
|
||||
import static org.lwjgl.opengl.GL43.*;
|
||||
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
|
||||
import static org.lwjgl.opengl.GL45.nglClearNamedBufferSubData;
|
||||
import static org.lwjgl.opengl.GL45C.nglClearNamedBufferData;
|
||||
import static org.lwjgl.opengl.NVMeshShader.glDrawMeshTasksNV;
|
||||
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
|
||||
|
||||
//TODO: NOTE
|
||||
// can do "meshlet compaction"
|
||||
// that is, meshlets are refered not by the meshlet id but by an 8 byte alligned index, (the quad index)
|
||||
// this means that non full meshlets can have the tailing data be truncated and used in the next meshlet
|
||||
// as long as the number of quads in the meshlet is stored in the header
|
||||
// the shader can cull the verticies of any quad that has its index over the expected quuad count
|
||||
// this could potentially result in a fair bit of memory savings (especially if used in normal mc terrain rendering)
|
||||
public class Gl46MeshletsFarWorldRenderer extends AbstractFarWorldRenderer<Gl46MeshletViewport, DefaultGeometryManager> {
|
||||
private final Shader lodShader = Shader.make()
|
||||
.define("QUADS_PER_MESHLET", RenderDataFactory.QUADS_PER_MESHLET)
|
||||
.add(ShaderType.VERTEX, "voxy:lod/gl46mesh/quads.vert")
|
||||
.add(ShaderType.FRAGMENT, "voxy:lod/gl46mesh/quads.frag")
|
||||
.compile();
|
||||
|
||||
private final Shader cullShader = Shader.make()
|
||||
.define("QUADS_PER_MESHLET", RenderDataFactory.QUADS_PER_MESHLET)
|
||||
.add(ShaderType.VERTEX, "voxy:lod/gl46mesh/cull.vert")
|
||||
.add(ShaderType.FRAGMENT, "voxy:lod/gl46mesh/cull.frag")
|
||||
.compile();
|
||||
|
||||
private final Shader meshletGenerator = Shader.make()
|
||||
.define("QUADS_PER_MESHLET", RenderDataFactory.QUADS_PER_MESHLET)
|
||||
.add(ShaderType.COMPUTE, "voxy:lod/gl46mesh/cmdgen.comp")
|
||||
.compile();
|
||||
|
||||
private final Shader meshletCuller = Shader.make()
|
||||
.define("QUADS_PER_MESHLET", RenderDataFactory.QUADS_PER_MESHLET)
|
||||
.add(ShaderType.COMPUTE, "voxy:lod/gl46mesh/meshletculler.comp")
|
||||
.compile();
|
||||
|
||||
private final GlBuffer glDrawIndirect;
|
||||
private final GlBuffer meshletBuffer;
|
||||
private final HiZBuffer hiZBuffer = new HiZBuffer();
|
||||
private final int hizSampler = glGenSamplers();
|
||||
|
||||
public Gl46MeshletsFarWorldRenderer(int geometrySize, int maxSections) {
|
||||
super(new DefaultGeometryManager(alignUp(geometrySize*8L, 8* (RenderDataFactory.QUADS_PER_MESHLET+2)), maxSections, 8*(RenderDataFactory.QUADS_PER_MESHLET+2)));
|
||||
this.glDrawIndirect = new GlBuffer(4*(4+5));
|
||||
this.meshletBuffer = new GlBuffer(4*1000000);//TODO: Make max meshlet count configurable, not just 1 million (even tho thats a max of 126 million quads per frame)
|
||||
|
||||
glSamplerParameteri(this.hizSampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);//This is so that using the shadow sampler works correctly
|
||||
glTextureParameteri(this.hizSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTextureParameteri(this.hizSampler, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
|
||||
glTextureParameteri(this.hizSampler, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
|
||||
glTextureParameteri(this.hizSampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTextureParameteri(this.hizSampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
nglClearNamedBufferData(this.meshletBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
nglClearNamedBufferData(this.glDrawIndirect.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
}
|
||||
|
||||
protected void bindResources(Gl46MeshletViewport viewport, boolean bindToDrawIndirect, boolean bindToDispatchIndirect, boolean bindHiz) {
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometry.geometryId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, bindToDrawIndirect?0:this.glDrawIndirect.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.meshletBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometry.metaId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, viewport.visibilityBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.models.getBufferId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, this.models.getColourBufferId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 8, this.lightDataBuffer.id);//Lighting LUT
|
||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, bindToDrawIndirect?this.glDrawIndirect.id:0);
|
||||
glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, bindToDispatchIndirect?this.glDrawIndirect.id:0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BYTE.id());
|
||||
|
||||
if (!bindHiz) {
|
||||
//Bind the texture atlas
|
||||
glBindSampler(0, this.models.getSamplerId());
|
||||
glBindTextureUnit(0, this.models.getTextureId());
|
||||
} else {
|
||||
//Bind the hiz buffer
|
||||
glBindSampler(0, this.hizSampler);
|
||||
glBindTextureUnit(0, this.hiZBuffer.getHizTextureId());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUniformBuffer(Gl46MeshletViewport viewport) {
|
||||
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, this.uniformBuffer.size());
|
||||
|
||||
//TODO:FIXME remove this.sx/sy/sz and make it based on the viewport!!!
|
||||
|
||||
var mat = new Matrix4f(viewport.projection).mul(viewport.modelView);
|
||||
var innerTranslation = new Vector3f((float) (viewport.cameraX-(this.sx<<5)), (float) (viewport.cameraY-(this.sy<<5)), (float) (viewport.cameraZ-(this.sz<<5)));
|
||||
mat.translate(-innerTranslation.x, -innerTranslation.y, -innerTranslation.z);
|
||||
mat.getToAddress(ptr); ptr += 4*4*4;
|
||||
MemoryUtil.memPutInt(ptr, this.sx); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, this.sy); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, this.sz); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, this.geometry.getSectionCount()); ptr += 4;
|
||||
var planes = ((AccessFrustumIntersection)this.frustum).getPlanes();
|
||||
for (var plane : planes) {
|
||||
plane.getToAddress(ptr); ptr += 4*4;
|
||||
}
|
||||
innerTranslation.getToAddress(ptr); ptr += 4*3;
|
||||
MemoryUtil.memPutInt(ptr, viewport.frameId++); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, viewport.width); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, viewport.height); ptr += 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderFarAwayOpaque(Gl46MeshletViewport viewport) {
|
||||
if (this.geometry.getSectionCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
{//Mark all of the updated sections as being visible from last frame
|
||||
for (int id : this.updatedSectionIds) {
|
||||
long ptr = UploadStream.INSTANCE.upload(viewport.visibilityBuffer, id * 4L, 4);
|
||||
MemoryUtil.memPutInt(ptr, viewport.frameId - 1);//(visible from last frame)
|
||||
}
|
||||
}
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
RenderLayer.getCutoutMipped().startDrawing();
|
||||
|
||||
this.updateUniformBuffer(viewport);
|
||||
UploadStream.INSTANCE.commit();
|
||||
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
|
||||
|
||||
nglClearNamedBufferSubData(this.glDrawIndirect.id, GL_R32UI, 0, 4*4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
|
||||
this.lodShader.bind();
|
||||
this.bindResources(viewport, true, false, false);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_BYTE, 4*4);
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
|
||||
|
||||
this.cullShader.bind();
|
||||
this.bindResources(viewport, false, false, false);
|
||||
glDepthMask(false);
|
||||
glColorMask(false, false, false, false);
|
||||
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3, GL_UNSIGNED_BYTE, (1 << 8) * 6, this.geometry.getSectionCount());
|
||||
glColorMask(true, true, true, true);
|
||||
glDepthMask(true);
|
||||
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
this.meshletGenerator.bind();
|
||||
this.bindResources(viewport, false, false, false);
|
||||
glDispatchCompute((this.geometry.getSectionCount()+63)/64, 1, 1);
|
||||
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT);
|
||||
|
||||
var i = new int[1];
|
||||
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, i);
|
||||
this.hiZBuffer.buildMipChain(i[0], MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight);
|
||||
|
||||
this.meshletCuller.bind();
|
||||
this.bindResources(viewport, false, true, true);
|
||||
glDispatchComputeIndirect(0);
|
||||
|
||||
|
||||
|
||||
glBindVertexArray(0);
|
||||
glBindSampler(0, 0);
|
||||
glBindTextureUnit(0, 0);
|
||||
RenderLayer.getCutoutMipped().endDrawing();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void renderFarAwayTranslucent(Gl46MeshletViewport viewport) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Gl46MeshletViewport createViewport0() {
|
||||
return new Gl46MeshletViewport(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
super.shutdown();
|
||||
this.lodShader.free();
|
||||
this.cullShader.free();
|
||||
this.meshletGenerator.free();
|
||||
this.meshletCuller.free();
|
||||
|
||||
this.glDrawIndirect.free();
|
||||
this.meshletBuffer.free();
|
||||
|
||||
this.hiZBuffer.free();
|
||||
glDeleteSamplers(this.hizSampler);
|
||||
}
|
||||
|
||||
public static long alignUp(long n, long alignment) {
|
||||
return (n + alignment - 1) & -alignment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usesMeshlets() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
|
||||
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
|
||||
import static org.lwjgl.opengl.GL30C.GL_R8UI;
|
||||
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
|
||||
import static org.lwjgl.opengl.GL42.GL_UNSIGNED_BYTE;
|
||||
import static org.lwjgl.opengl.GL45C.glClearNamedBufferData;
|
||||
import static org.lwjgl.opengl.GL45C.nglClearNamedBufferData;
|
||||
|
||||
public class Gl46Viewport extends Viewport<Gl46Viewport> {
|
||||
GlBuffer visibilityBuffer;
|
||||
public Gl46Viewport(Gl46FarWorldRenderer renderer) {
|
||||
super(renderer);
|
||||
this.visibilityBuffer = new GlBuffer(renderer.maxSections*4L);
|
||||
nglClearNamedBufferData(this.visibilityBuffer.id, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, 0);
|
||||
}
|
||||
|
||||
protected void delete0() {
|
||||
this.visibilityBuffer.free();
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.voxy.client.core.rendering.hierarchical.NodeManager;
|
||||
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
||||
|
||||
import static org.lwjgl.opengl.GL42C.*;
|
||||
import static org.lwjgl.opengl.GL43C.GL_SHADER_STORAGE_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.GL43C.glDispatchCompute;
|
||||
import static org.lwjgl.opengl.GL44.GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT;
|
||||
|
||||
public class HierarchicalOcclusionRenderer {
|
||||
private final int workgroup_dispatch_size_x;//The number of workgroups required to saturate the gpu efficiently
|
||||
private final NodeManager nodeManager = new NodeManager(null);
|
||||
private final HiZBuffer hiz = new HiZBuffer();
|
||||
|
||||
|
||||
private Shader hiercarchialShader = Shader.make()
|
||||
.add(ShaderType.COMPUTE, "voxy:lod/hierarchical/selector.comp")
|
||||
.compile();
|
||||
|
||||
public HierarchicalOcclusionRenderer(int workgroup_size) {
|
||||
this.workgroup_dispatch_size_x = workgroup_size;
|
||||
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
|
||||
}
|
||||
|
||||
public void render(int depthBuffer, int width, int height) {
|
||||
//Make hiz
|
||||
this.hiz.buildMipChain(depthBuffer, width, height);
|
||||
//Node upload phase
|
||||
this.nodeManager.uploadPhase();
|
||||
//Node download phase (pulls from previous frame (should maybe result in lower latency)) also clears and resets the queues
|
||||
this.nodeManager.downloadPhase();
|
||||
//Bind all the resources
|
||||
this.bind();
|
||||
//run hierachial selection shader
|
||||
this.hiercarchialShader.bind();
|
||||
//barrier
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT|GL_UNIFORM_BARRIER_BIT|GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT|GL_FRAMEBUFFER_BARRIER_BIT);
|
||||
//Emit enough work to fully populate the gpu
|
||||
glDispatchCompute(this.workgroup_dispatch_size_x, 1, 1);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT|GL_COMMAND_BARRIER_BIT|GL_UNIFORM_BARRIER_BIT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
|
||||
public interface ISectionWatcher {
|
||||
default boolean watch(int lvl, int x, int y, int z, int types) {
|
||||
return this.watch(WorldEngine.getWorldSectionId(lvl, x, y, z), types);
|
||||
}
|
||||
|
||||
boolean watch(long position, int types);
|
||||
|
||||
default boolean unwatch(int lvl, int x, int y, int z, int types) {
|
||||
return this.unwatch(WorldEngine.getWorldSectionId(lvl, x, y, z), types);
|
||||
}
|
||||
|
||||
boolean unwatch(long position, int types);
|
||||
|
||||
default int get(int lvl, int x, int y, int z) {
|
||||
return this.get(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
||||
}
|
||||
|
||||
int get(long position);
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
|
||||
import net.minecraft.client.render.Camera;
|
||||
import net.minecraft.client.render.Frustum;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.opengl.GL11C;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
|
||||
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
|
||||
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL30.glBindBufferBase;
|
||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
|
||||
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
|
||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
|
||||
import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
|
||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
|
||||
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
|
||||
import static org.lwjgl.opengl.NVMeshShader.glDrawMeshTasksNV;
|
||||
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
|
||||
|
||||
public class NvMeshFarWorldRenderer extends AbstractFarWorldRenderer<NvMeshViewport, DefaultGeometryManager> {
|
||||
private final Shader terrain = Shader.make()
|
||||
.add(ShaderType.TASK, "voxy:lod/nvmesh/primary.task")
|
||||
.add(ShaderType.MESH, "voxy:lod/nvmesh/primary.mesh")
|
||||
.add(ShaderType.FRAGMENT, "voxy:lod/nvmesh/primary.frag")
|
||||
.compile();
|
||||
|
||||
private final Shader translucent = Shader.make()
|
||||
.add(ShaderType.TASK, "voxy:lod/nvmesh/translucent.task")
|
||||
.add(ShaderType.MESH, "voxy:lod/nvmesh/translucent.mesh")
|
||||
.add(ShaderType.FRAGMENT, "voxy:lod/nvmesh/primary.frag")
|
||||
.compile();
|
||||
|
||||
private final Shader cull = Shader.make()
|
||||
.add(ShaderType.VERTEX, "voxy:lod/nvmesh/cull.vert")
|
||||
.add(ShaderType.FRAGMENT, "voxy:lod/nvmesh/cull.frag")
|
||||
.compile();
|
||||
|
||||
public NvMeshFarWorldRenderer(int geometrySize, int maxSections) {
|
||||
super(new DefaultGeometryManager(geometrySize*8L, maxSections));
|
||||
}
|
||||
|
||||
|
||||
private void updateUniform(NvMeshViewport viewport) {
|
||||
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, this.uniformBuffer.size());
|
||||
|
||||
var mat = new Matrix4f(viewport.projection).mul(viewport.modelView);
|
||||
mat.getToAddress(ptr); ptr += 4*4*4;
|
||||
var innerTranslation = new Vector3f((float) (viewport.cameraX-(this.sx<<5)), (float) (viewport.cameraY-(this.sy<<5)), (float) (viewport.cameraZ-(this.sz<<5)));
|
||||
MemoryUtil.memPutInt(ptr, this.sx); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, this.sy); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, this.sz); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, this.geometry.getSectionCount()); ptr += 4;
|
||||
innerTranslation.getToAddress(ptr); ptr += 4*3;
|
||||
MemoryUtil.memPutInt(ptr, viewport.frameId++); ptr += 4;
|
||||
}
|
||||
|
||||
private void bindResources(NvMeshViewport viewport) {
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometry.geometryId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.geometry.metaId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.visibilityBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.models.getBufferId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, this.models.getColourBufferId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.lightDataBuffer.id);//Lighting LUT
|
||||
|
||||
//Bind the texture atlas
|
||||
glBindSampler(0, this.models.getSamplerId());
|
||||
glBindTextureUnit(0, this.models.getTextureId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderFarAwayOpaque(NvMeshViewport viewport) {
|
||||
{//TODO: move all this code into a common super method renderFarAwayTranslucent and make the current method renderFarAwayTranslucent0
|
||||
if (this.geometry.getSectionCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
{//Mark all of the updated sections as being visible from last frame
|
||||
for (int id : this.updatedSectionIds) {
|
||||
long ptr = UploadStream.INSTANCE.upload(viewport.visibilityBuffer, id * 4L, 4);
|
||||
MemoryUtil.memPutInt(ptr, viewport.frameId - 1);//(visible from last frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
//Update and upload data
|
||||
this.updateUniform(viewport);
|
||||
UploadStream.INSTANCE.commit();
|
||||
|
||||
|
||||
this.terrain.bind();
|
||||
|
||||
RenderLayer.getCutoutMipped().startDrawing();
|
||||
|
||||
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
|
||||
this.bindResources(viewport);
|
||||
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDrawMeshTasksNV(0, this.geometry.getSectionCount());
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
this.cull.bind();
|
||||
this.bindResources(viewport);
|
||||
glColorMask(false, false, false, false);
|
||||
glDepthMask(false);
|
||||
glEnable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
|
||||
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3, GL_UNSIGNED_BYTE, (1 << 16) * 6 * 2, this.geometry.getSectionCount());
|
||||
glDisable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
|
||||
glDepthMask(true);
|
||||
glColorMask(true, true, true, true);
|
||||
|
||||
glBindVertexArray(0);
|
||||
glBindSampler(0, 0);
|
||||
glBindTextureUnit(0, 0);
|
||||
RenderLayer.getCutoutMipped().endDrawing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderFarAwayTranslucent(NvMeshViewport viewport) {
|
||||
if (this.geometry.getSectionCount()==0) {
|
||||
return;
|
||||
}
|
||||
RenderLayer.getTranslucent().startDrawing();
|
||||
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
//TODO: maybe change this so the alpha isnt applied in the same way or something?? since atm the texture bakery uses a very hacky
|
||||
// blend equation to make it avoid double applying translucency
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
|
||||
glBindSampler(0, this.models.getSamplerId());
|
||||
glBindTextureUnit(0, this.models.getTextureId());
|
||||
|
||||
this.translucent.bind();
|
||||
this.bindResources(viewport);
|
||||
|
||||
glDrawMeshTasksNV(0, this.geometry.getSectionCount());
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
glBindVertexArray(0);
|
||||
|
||||
|
||||
glBindSampler(0, 0);
|
||||
glBindTextureUnit(0, 0);
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
RenderLayer.getTranslucent().endDrawing();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NvMeshViewport createViewport0() {
|
||||
return new NvMeshViewport(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
super.shutdown();
|
||||
this.terrain.free();
|
||||
this.translucent.free();
|
||||
this.cull.free();
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
|
||||
import static org.lwjgl.opengl.GL30C.GL_R8UI;
|
||||
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
|
||||
import static org.lwjgl.opengl.GL42.GL_UNSIGNED_BYTE;
|
||||
import static org.lwjgl.opengl.GL45C.glClearNamedBufferData;
|
||||
|
||||
public class NvMeshViewport extends Viewport<NvMeshViewport> {
|
||||
GlBuffer visibilityBuffer;
|
||||
public NvMeshViewport(NvMeshFarWorldRenderer renderer) {
|
||||
super(renderer);
|
||||
this.visibilityBuffer = new GlBuffer(renderer.maxSections*4L);
|
||||
glClearNamedBufferData(this.visibilityBuffer.id, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, new int[1]);
|
||||
}
|
||||
|
||||
protected void delete0() {
|
||||
this.visibilityBuffer.free();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.client.core.util.RingTracker;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
public class RenderDistanceTracker {
|
||||
private static final int CHECK_DISTANCE_BLOCKS = 128;
|
||||
private final LongConsumer addTopLevelNode;
|
||||
private final LongConsumer removeTopLevelNode;
|
||||
private final int processRate;
|
||||
private final int minSec;
|
||||
private final int maxSec;
|
||||
private RingTracker tracker;
|
||||
private int renderDistance;
|
||||
private double posX;
|
||||
private double posZ;
|
||||
public RenderDistanceTracker(int rate, int minSec, int maxSec, LongConsumer addTopLevelNode, LongConsumer removeTopLevelNode) {
|
||||
this.addTopLevelNode = addTopLevelNode;
|
||||
this.removeTopLevelNode = removeTopLevelNode;
|
||||
this.renderDistance = 2;
|
||||
this.tracker = new RingTracker(this.renderDistance, 0, 0, true);
|
||||
this.processRate = rate;
|
||||
this.minSec = minSec;
|
||||
this.maxSec = maxSec;
|
||||
}
|
||||
|
||||
public void setRenderDistance(int renderDistance) {
|
||||
if (renderDistance == this.renderDistance) {
|
||||
return;
|
||||
}
|
||||
this.renderDistance = renderDistance;
|
||||
this.tracker.unload();//Mark all as unload
|
||||
this.tracker = new RingTracker(this.tracker, renderDistance, ((int)this.posX)>>9, ((int)this.posZ)>>9, true);//Steal from previous tracker
|
||||
}
|
||||
|
||||
public boolean setCenterAndProcess(double x, double z) {
|
||||
double dx = this.posX-x;
|
||||
double dz = this.posZ-z;
|
||||
if (CHECK_DISTANCE_BLOCKS*CHECK_DISTANCE_BLOCKS<dx*dx+dz*dz) {
|
||||
this.posX = x;
|
||||
this.posZ = z;
|
||||
this.tracker.moveCenter(((int)x)>>9, ((int)z)>>9);
|
||||
}
|
||||
return this.tracker.process(this.processRate, this::add, this::rem)!=0;
|
||||
}
|
||||
|
||||
private void add(int x, int z) {
|
||||
for (int y = this.minSec; y <= this.maxSec; y++) {
|
||||
this.addTopLevelNode.accept(WorldEngine.getWorldSectionId(4, x, y, z));
|
||||
}
|
||||
}
|
||||
|
||||
private void rem(int x, int z) {
|
||||
for (int y = this.minSec; y <= this.maxSec; y++) {
|
||||
this.removeTopLevelNode.accept(WorldEngine.getWorldSectionId(4, x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.cortex.voxy.common.world.WorldSection;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.util.math.Direction;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
//Tracks active sections, dispatches updates to the build system, everything related to rendering flows through here
|
||||
public class RenderTracker {
|
||||
private final WorldEngine world;
|
||||
private RenderGenerationService renderGen;
|
||||
private final AbstractFarWorldRenderer renderer;
|
||||
private final LongSet[] sets;
|
||||
|
||||
|
||||
public RenderTracker(WorldEngine world, AbstractFarWorldRenderer renderer) {
|
||||
this.world = world;
|
||||
this.renderer = renderer;
|
||||
this.sets = new LongSet[1<<4];
|
||||
for (int i = 0; i < this.sets.length; i++) {
|
||||
this.sets[i] = new LongOpenHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
public void setRenderGen(RenderGenerationService renderGen) {
|
||||
this.renderGen = renderGen;
|
||||
}
|
||||
|
||||
public static long mixStafford13(long seed) {
|
||||
seed = (seed ^ seed >>> 30) * -4658895280553007687L;
|
||||
seed = (seed ^ seed >>> 27) * -7723592293110705685L;
|
||||
return seed ^ seed >>> 31;
|
||||
}
|
||||
|
||||
private LongSet getSet(long key) {
|
||||
return this.sets[(int) (mixStafford13(key) & (this.sets.length-1))];
|
||||
}
|
||||
|
||||
private void put(long key) {
|
||||
var set = this.getSet(key);
|
||||
synchronized (set) {
|
||||
set.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void remove(long key) {
|
||||
var set = this.getSet(key);
|
||||
synchronized (set) {
|
||||
set.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean contains(long key) {
|
||||
var set = this.getSet(key);
|
||||
synchronized (set) {
|
||||
return set.contains(key);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
|
||||
|
||||
//Adds a lvl 0 section into the world renderer
|
||||
public void addLvl0(int x, int y, int z) {
|
||||
this.put(WorldEngine.getWorldSectionId(0, x, y, z));
|
||||
this.renderGen.enqueueTask(0, x, y, z, this::shouldStillBuild);
|
||||
}
|
||||
|
||||
//Removes a lvl 0 section from the world renderer
|
||||
public void remLvl0(int x, int y, int z) {
|
||||
this.remove(WorldEngine.getWorldSectionId(0, x, y, z));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(0, x, y, z)));
|
||||
this.renderGen.removeTask(0, x, y, z);
|
||||
}
|
||||
|
||||
//Increases from lvl-1 to lvl at the coordinates (which are in lvl space)
|
||||
public void inc(int lvl, int x, int y, int z) {
|
||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)));
|
||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1));
|
||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)));
|
||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1));
|
||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)));
|
||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1));
|
||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)));
|
||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1));
|
||||
this.put(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
||||
|
||||
//TODO: make a seperate object to hold the build data and link it with the location in a
|
||||
// concurrent hashmap or something, this is so that e.g. the build data position
|
||||
// can be updated
|
||||
|
||||
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
|
||||
this.renderGen.enqueueTask(lvl, x, y, z, this::shouldStillBuild);
|
||||
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1))));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1)));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1))));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1)));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1))));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1)));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1))));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1)));
|
||||
|
||||
|
||||
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1), (z<<1));
|
||||
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1), (z<<1)+1);
|
||||
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1)+1, (z<<1));
|
||||
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1);
|
||||
this.renderGen.removeTask(lvl-1, (x<<1)+1, (y<<1), (z<<1));
|
||||
this.renderGen.removeTask(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1);
|
||||
this.renderGen.removeTask(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1));
|
||||
this.renderGen.removeTask(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1);
|
||||
}
|
||||
|
||||
//Decreases from lvl to lvl-1 at the coordinates (which are in lvl space)
|
||||
public void dec(int lvl, int x, int y, int z) {
|
||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)));
|
||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1));
|
||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)));
|
||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1));
|
||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)));
|
||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1));
|
||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)));
|
||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1));
|
||||
this.remove(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
||||
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl, x, y, z)));
|
||||
this.renderGen.removeTask(lvl, x, y, z);
|
||||
|
||||
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
|
||||
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1), (z<<1), this::shouldStillBuild);
|
||||
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1), (z<<1)+1, this::shouldStillBuild);
|
||||
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1)+1, (z<<1), this::shouldStillBuild);
|
||||
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1)+1, (z<<1)+1, this::shouldStillBuild);
|
||||
this.renderGen.enqueueTask(lvl - 1, (x<<1)+1, (y<<1), (z<<1), this::shouldStillBuild);
|
||||
this.renderGen.enqueueTask(lvl - 1, (x<<1)+1, (y<<1), (z<<1)+1, this::shouldStillBuild);
|
||||
this.renderGen.enqueueTask(lvl - 1, (x<<1)+1, (y<<1)+1, (z<<1), this::shouldStillBuild);
|
||||
this.renderGen.enqueueTask(lvl - 1, (x<<1)+1, (y<<1)+1, (z<<1)+1, this::shouldStillBuild);
|
||||
}
|
||||
|
||||
//Enqueues a renderTask for a section to cache the result
|
||||
public void addCache(int lvl, int x, int y, int z) {
|
||||
this.renderGen.markCache(lvl, x, y, z);
|
||||
this.renderGen.enqueueTask(lvl, x, y, z, ((lvl1, x1, y1, z1) -> true));//TODO: replace the true identity lambda with a callback check to the render cache
|
||||
}
|
||||
|
||||
//Removes the position from the cache
|
||||
public void removeCache(int lvl, int x, int y, int z) {
|
||||
this.renderGen.unmarkCache(lvl, x, y, z);
|
||||
}
|
||||
|
||||
public void remove(int lvl, int x, int y, int z) {
|
||||
this.remove(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl, x, y, z)));
|
||||
}
|
||||
|
||||
public void add(int lvl, int x, int y, int z) {
|
||||
this.put(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
||||
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
|
||||
this.renderGen.enqueueTask(lvl, x, y, z, this::shouldStillBuild);
|
||||
}
|
||||
|
||||
|
||||
//Called by the world engine when a section gets dirtied
|
||||
public void sectionUpdated(WorldSection section) {
|
||||
if (this.contains(section.key)) {
|
||||
//TODO:FIXME: if the section gets updated, that means that its neighbors might need to be updated aswell
|
||||
// (due to block occlusion)
|
||||
|
||||
//TODO: FIXME: REBUILDING THE ENTIRE NEIGHBORS when probably only the internal layout changed is NOT SMART
|
||||
this.renderGen.clearCache(section.lvl, section.x, section.y, section.z);
|
||||
this.renderGen.clearCache(section.lvl, section.x-1, section.y, section.z);
|
||||
this.renderGen.clearCache(section.lvl, section.x+1, section.y, section.z);
|
||||
this.renderGen.clearCache(section.lvl, section.x, section.y, section.z-1);
|
||||
this.renderGen.clearCache(section.lvl, section.x, section.y, section.z+1);
|
||||
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
|
||||
this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z, this::shouldStillBuild);
|
||||
this.renderGen.enqueueTask(section.lvl, section.x-1, section.y, section.z, this::shouldStillBuild);
|
||||
this.renderGen.enqueueTask(section.lvl, section.x+1, section.y, section.z, this::shouldStillBuild);
|
||||
this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z-1, this::shouldStillBuild);
|
||||
this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z+1, this::shouldStillBuild);
|
||||
}
|
||||
//this.renderGen.enqueueTask(section);
|
||||
}
|
||||
|
||||
//called by the RenderGenerationService about built geometry, the RenderTracker checks if it can use the result (e.g. the LoD hasnt changed/still correct etc)
|
||||
// and dispatches it to the renderer
|
||||
// it also batch collects the geometry sections until all the geometry for an operation is collected, then it executes the operation, its removes flickering
|
||||
public void processBuildResult(BuiltSection section) {
|
||||
//Check that we still want the section
|
||||
if (this.contains(section.position)) {
|
||||
this.renderer.enqueueResult(section);
|
||||
} else {
|
||||
section.free();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldStillBuild(int lvl, int x, int y, int z) {
|
||||
return this.contains(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.cortex.voxy.common.world.WorldSection;
|
||||
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
import static me.cortex.voxy.common.world.WorldEngine.UPDATE_TYPE_BLOCK_BIT;
|
||||
|
||||
public class SectionUpdateRouter implements ISectionWatcher {
|
||||
private static final int SLICES = 1<<4;
|
||||
public interface IChildUpdate {void accept(WorldSection section);}
|
||||
|
||||
private final Long2ByteOpenHashMap[] slices = new Long2ByteOpenHashMap[SLICES];
|
||||
private final StampedLock[] locks = new StampedLock[SLICES];
|
||||
{
|
||||
for (int i = 0; i < this.slices.length; i++) {
|
||||
this.slices[i] = new Long2ByteOpenHashMap();
|
||||
this.locks[i] = new StampedLock();
|
||||
}
|
||||
}
|
||||
|
||||
private LongConsumer initialRenderMeshGen;
|
||||
private LongConsumer renderMeshGen;
|
||||
private IChildUpdate childUpdateCallback;
|
||||
|
||||
public void setCallbacks(LongConsumer initialRenderMeshGen, LongConsumer renderMeshGen, IChildUpdate childUpdateCallback) {
|
||||
if (this.renderMeshGen != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.initialRenderMeshGen = initialRenderMeshGen;
|
||||
this.renderMeshGen = renderMeshGen;
|
||||
this.childUpdateCallback = childUpdateCallback;
|
||||
}
|
||||
|
||||
public boolean watch(int lvl, int x, int y, int z, int types) {
|
||||
return this.watch(WorldEngine.getWorldSectionId(lvl, x, y, z), types);
|
||||
}
|
||||
|
||||
public boolean watch(long position, int types) {
|
||||
int idx = getSliceIndex(position);
|
||||
var set = this.slices[idx];
|
||||
var lock = this.locks[idx];
|
||||
byte delta = 0;
|
||||
{
|
||||
long stamp = lock.readLock();
|
||||
byte current = set.getOrDefault(position, (byte) 0);
|
||||
delta = (byte) (current&types);
|
||||
current |= (byte) types;
|
||||
delta ^= (byte) (current&types);
|
||||
if (delta != 0) {//Was change
|
||||
long ws = lock.tryConvertToWriteLock(stamp);
|
||||
if (ws == 0) {
|
||||
lock.unlockRead(stamp);
|
||||
stamp = lock.writeLock();
|
||||
//We need to recompute as we failed to acquire an immediate write lock
|
||||
current = set.getOrDefault(position, (byte) 0);
|
||||
delta = (byte) (current&types);
|
||||
current |= (byte) types;
|
||||
delta ^= (byte) (current&types);
|
||||
if (delta != 0)
|
||||
set.put(position, current);
|
||||
} else {
|
||||
stamp = ws;
|
||||
set.put(position, current);
|
||||
}
|
||||
}
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
if (((delta&types)&UPDATE_TYPE_BLOCK_BIT)!=0) {
|
||||
//If we added it, immediately invoke for an update
|
||||
this.initialRenderMeshGen.accept(position);
|
||||
}
|
||||
return delta!=0;
|
||||
}
|
||||
|
||||
public boolean unwatch(int lvl, int x, int y, int z, int types) {
|
||||
return this.unwatch(WorldEngine.getWorldSectionId(lvl, x, y, z), types);
|
||||
}
|
||||
|
||||
public boolean unwatch(long position, int types) {//Types is types to unwatch
|
||||
int idx = getSliceIndex(position);
|
||||
var set = this.slices[idx];
|
||||
var lock = this.locks[idx];
|
||||
|
||||
long stamp = lock.readLock();
|
||||
|
||||
byte current = set.getOrDefault(position, (byte)0);
|
||||
if (current == 0) {
|
||||
throw new IllegalStateException("Section pos not in map " + WorldEngine.pprintPos(position));
|
||||
}
|
||||
boolean removed = false;
|
||||
if ((current&types) != 0) {//Was change
|
||||
long ws = lock.tryConvertToWriteLock(stamp);
|
||||
if (ws == 0) {//failed to get write lock, need to unlock, get write, then redo
|
||||
lock.unlockRead(stamp);
|
||||
stamp = lock.writeLock();
|
||||
|
||||
current = set.getOrDefault(position, (byte)0);
|
||||
if (current == 0) {
|
||||
throw new IllegalStateException("Section pos not in map " + WorldEngine.pprintPos(position));
|
||||
}
|
||||
} else {
|
||||
stamp = ws;
|
||||
}
|
||||
|
||||
if ((current&types) != 0) {
|
||||
current &= (byte) ~types;
|
||||
if (current == 0) {
|
||||
set.remove(position);
|
||||
removed = true;
|
||||
} else {
|
||||
set.put(position, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
lock.unlock(stamp);
|
||||
return removed;
|
||||
}
|
||||
|
||||
public int get(long position) {
|
||||
int idx = getSliceIndex(position);
|
||||
var set = this.slices[idx];
|
||||
var lock = this.locks[idx];
|
||||
long stamp = lock.readLock();
|
||||
int ret = set.getOrDefault(position, (byte) 0);
|
||||
lock.unlockRead(stamp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void forwardEvent(WorldSection section, int type) {
|
||||
final long position = section.key;
|
||||
|
||||
int idx = getSliceIndex(position);
|
||||
var set = this.slices[idx];
|
||||
var lock = this.locks[idx];
|
||||
|
||||
long stamp = lock.readLock();
|
||||
byte types = (byte) (set.getOrDefault(position, (byte) 0)&type);
|
||||
lock.unlockRead(stamp);
|
||||
|
||||
if (types!=0) {
|
||||
if ((types&WorldEngine.UPDATE_TYPE_CHILD_EXISTENCE_BIT)!=0) {
|
||||
this.childUpdateCallback.accept(section);
|
||||
}
|
||||
if ((types&UPDATE_TYPE_BLOCK_BIT)!=0) {
|
||||
this.renderMeshGen.accept(section.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void triggerRemesh(long position) {
|
||||
int idx = getSliceIndex(position);
|
||||
var set = this.slices[idx];
|
||||
var lock = this.locks[idx];
|
||||
|
||||
long stamp = lock.readLock();
|
||||
byte types = set.getOrDefault(position, (byte) 0);
|
||||
lock.unlockRead(stamp);
|
||||
if ((types&UPDATE_TYPE_BLOCK_BIT)!=0) {
|
||||
this.renderMeshGen.accept(position);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getSliceIndex(long value) {
|
||||
value = (value ^ value >>> 30) * -4658895280553007687L;
|
||||
value = (value ^ value >>> 27) * -7723592293110705685L;
|
||||
return (int) ((value ^ value >>> 31)&(SLICES-1));
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.client.core.util.IndexUtil;
|
||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
|
||||
//Has a base index buffer of 16380 quads, and also a 1 cube byte index buffer at the end
|
||||
public class SharedIndexBuffer {
|
||||
public static final SharedIndexBuffer INSTANCE = new SharedIndexBuffer();
|
||||
public static final SharedIndexBuffer INSTANCE_BYTE = new SharedIndexBuffer(true);
|
||||
|
||||
private final GlBuffer indexBuffer;
|
||||
|
||||
public SharedIndexBuffer() {
|
||||
this.indexBuffer = new GlBuffer((1<<16)*6*2 + 6*2*3);
|
||||
var quadIndexBuff = IndexUtil.generateQuadIndicesShort(16380);
|
||||
var cubeBuff = generateCubeIndexBuffer();
|
||||
|
||||
long ptr = UploadStream.INSTANCE.upload(this.indexBuffer, 0, this.indexBuffer.size());
|
||||
quadIndexBuff.cpyTo(ptr);
|
||||
cubeBuff.cpyTo((1<<16)*2*6 + ptr);
|
||||
|
||||
quadIndexBuff.free();
|
||||
cubeBuff.free();
|
||||
}
|
||||
|
||||
private SharedIndexBuffer(boolean type2) {
|
||||
this.indexBuffer = new GlBuffer((1<<8)*6 + 6*2*3);
|
||||
var quadIndexBuff = IndexUtil.generateQuadIndicesByte(63);
|
||||
var cubeBuff = generateCubeIndexBuffer();
|
||||
|
||||
long ptr = UploadStream.INSTANCE.upload(this.indexBuffer, 0, this.indexBuffer.size());
|
||||
quadIndexBuff.cpyTo(ptr);
|
||||
cubeBuff.cpyTo((1<<8)*6 + ptr);
|
||||
|
||||
quadIndexBuff.free();
|
||||
cubeBuff.free();
|
||||
}
|
||||
|
||||
private static MemoryBuffer generateCubeIndexBuffer() {
|
||||
var buffer = new MemoryBuffer(6*2*3);
|
||||
long ptr = buffer.address;
|
||||
MemoryUtil.memSet(ptr, 0, 6*2*3 );
|
||||
|
||||
//Bottom face
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 0);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 1);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 2);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 3);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 2);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 1);
|
||||
|
||||
//top face
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 6);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 5);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 4);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 5);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 6);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 7);
|
||||
|
||||
//north face
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 0);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 4);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 1);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 5);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 1);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 4);
|
||||
|
||||
//south face
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 3);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 6);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 2);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 6);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 3);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 7);
|
||||
|
||||
//west face
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 2);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 4);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 0);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 4);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 2);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 6);
|
||||
|
||||
//east face
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 1);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 5);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 3);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 7);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 3);
|
||||
MemoryUtil.memPutByte(ptr++, (byte) 5);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public int id() {
|
||||
return this.indexBuffer.id;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,69 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
|
||||
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
|
||||
import net.caffeinemc.mods.sodium.client.util.FogParameters;
|
||||
import net.minecraft.util.Mth;
|
||||
import org.joml.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public abstract class Viewport <A extends Viewport<A>> {
|
||||
private final AbstractFarWorldRenderer renderer;
|
||||
int width;
|
||||
int height;
|
||||
int frameId;
|
||||
Matrix4f projection;
|
||||
Matrix4f modelView;
|
||||
double cameraX;
|
||||
double cameraY;
|
||||
double cameraZ;
|
||||
//public final HiZBuffer2 hiZBuffer = new HiZBuffer2();
|
||||
public final HiZBuffer hiZBuffer = new HiZBuffer();
|
||||
public final DepthFramebuffer depthBoundingBuffer = new DepthFramebuffer();
|
||||
|
||||
protected Viewport(AbstractFarWorldRenderer renderer) {
|
||||
this.renderer = renderer;
|
||||
private static final Field planesField;
|
||||
static {
|
||||
try {
|
||||
planesField = FrustumIntersection.class.getDeclaredField("planes");
|
||||
planesField.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int width;
|
||||
public int height;
|
||||
public int frameId;
|
||||
public Matrix4f vanillaProjection = new Matrix4f();
|
||||
public Matrix4f projection = new Matrix4f();
|
||||
public Matrix4f modelView = new Matrix4f();
|
||||
public final FrustumIntersection frustum = new FrustumIntersection();
|
||||
public final Vector4f[] frustumPlanes;
|
||||
public double cameraX;
|
||||
public double cameraY;
|
||||
public double cameraZ;
|
||||
public FogParameters fogParameters;
|
||||
|
||||
public final Matrix4f MVP = new Matrix4f();
|
||||
public final Vector3i section = new Vector3i();
|
||||
public final Vector3f innerTranslation = new Vector3f();
|
||||
|
||||
protected Viewport() {
|
||||
Vector4f[] planes = null;
|
||||
try {
|
||||
planes = (Vector4f[]) planesField.get(this.frustum);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
this.frustumPlanes = planes;
|
||||
}
|
||||
|
||||
public final void delete() {
|
||||
this.delete0();
|
||||
this.renderer.removeViewport((A) this);
|
||||
}
|
||||
|
||||
protected abstract void delete0();
|
||||
protected void delete0() {
|
||||
this.hiZBuffer.free();
|
||||
this.depthBoundingBuffer.free();
|
||||
}
|
||||
|
||||
public A setVanillaProjection(Matrix4fc projection) {
|
||||
this.vanillaProjection.set(projection);
|
||||
return (A) this;
|
||||
}
|
||||
|
||||
public A setProjection(Matrix4f projection) {
|
||||
this.projection = projection;
|
||||
@@ -46,4 +87,36 @@ public abstract class Viewport <A extends Viewport<A>> {
|
||||
this.height = height;
|
||||
return (A) this;
|
||||
}
|
||||
|
||||
public A setFogParameters(FogParameters fogParameters) {
|
||||
this.fogParameters = fogParameters;
|
||||
return (A) this;
|
||||
}
|
||||
|
||||
public A update() {
|
||||
//MVP
|
||||
this.projection.mul(this.modelView, this.MVP);
|
||||
|
||||
//Update the frustum
|
||||
this.frustum.set(this.MVP, false);
|
||||
|
||||
//Translation vectors
|
||||
int sx = Mth.floor(this.cameraX)>>5;
|
||||
int sy = Mth.floor(this.cameraY)>>5;
|
||||
int sz = Mth.floor(this.cameraZ)>>5;
|
||||
this.section.set(sx, sy, sz);
|
||||
|
||||
this.innerTranslation.set(
|
||||
(float) (this.cameraX-(sx<<5)),
|
||||
(float) (this.cameraY-(sy<<5)),
|
||||
(float) (this.cameraZ-(sz<<5)));
|
||||
|
||||
if (this.depthBoundingBuffer.resize(this.width, this.height)) {
|
||||
this.depthBoundingBuffer.clear(0.0f);
|
||||
}
|
||||
|
||||
return (A) this;
|
||||
}
|
||||
|
||||
public abstract GlBuffer getRenderList();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.client.core.util.IrisUtil;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import org.vivecraft.api.client.VRRenderingAPI;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.vivecraft.api.client.data.RenderPass.VANILLA;
|
||||
|
||||
public class ViewportSelector <T extends Viewport<?>> {
|
||||
public static final boolean VIVECRAFT_INSTALLED = FabricLoader.getInstance().isModLoaded("vivecraft");
|
||||
|
||||
private final Supplier<T> creator;
|
||||
private final T defaultViewport;
|
||||
private final Map<Object, T> extraViewports = new HashMap<>();//TODO should maybe be a weak hashmap with value cleanup queue thing?
|
||||
|
||||
public ViewportSelector(Supplier<T> viewportCreator) {
|
||||
this.creator = viewportCreator;
|
||||
this.defaultViewport = viewportCreator.get();
|
||||
}
|
||||
|
||||
private T getOrCreate(Object holder) {
|
||||
return this.extraViewports.computeIfAbsent(holder, a->this.creator.get());
|
||||
}
|
||||
|
||||
private T getVivecraftViewport() {
|
||||
var pass = VRRenderingAPI.instance().getCurrentRenderPass();
|
||||
if (pass == null || pass == VANILLA) {
|
||||
return null;
|
||||
}
|
||||
return this.getOrCreate(pass);
|
||||
}
|
||||
|
||||
private static final Object IRIS_SHADOW_OBJECT = new Object();
|
||||
public T getViewport() {
|
||||
T viewport = null;
|
||||
if (viewport == null && VIVECRAFT_INSTALLED) {
|
||||
viewport = getVivecraftViewport();
|
||||
}
|
||||
|
||||
if (viewport == null && IrisUtil.irisShadowActive()) {
|
||||
viewport = this.getOrCreate(IRIS_SHADOW_OBJECT);
|
||||
}
|
||||
|
||||
if (viewport == null) {
|
||||
viewport = this.defaultViewport;
|
||||
}
|
||||
return viewport;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.defaultViewport.delete();
|
||||
this.extraViewports.values().forEach(Viewport::delete);
|
||||
this.extraViewports.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,38 @@
|
||||
package me.cortex.voxy.client.core.rendering.building;
|
||||
|
||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
//TODO: also have an AABB size stored
|
||||
public final class BuiltSection {
|
||||
public static final boolean VERIFY_BUILT_SECTION_OFFSETS = VoxyCommon.isVerificationFlagOn("verifyBuiltSectionOffsets");
|
||||
public final long position;
|
||||
public final byte childExistence;
|
||||
public final int aabb;
|
||||
public final MemoryBuffer geometryBuffer;
|
||||
public final int[] offsets;
|
||||
public final MemoryBuffer occupancy;
|
||||
|
||||
public BuiltSection(long position) {
|
||||
this(position, -1, null, null);
|
||||
private BuiltSection(long position, byte children) {
|
||||
this(position, children, -1, null, null, null);
|
||||
}
|
||||
|
||||
public BuiltSection(long position, int aabb, MemoryBuffer geometryBuffer, int[] offsets) {
|
||||
public static BuiltSection empty(long position) {
|
||||
return new BuiltSection(position, (byte) 0);
|
||||
}
|
||||
public static BuiltSection emptyWithChildren(long position, byte children) {
|
||||
return new BuiltSection(position, children);
|
||||
}
|
||||
|
||||
public BuiltSection(long position, byte childExistence, int aabb, MemoryBuffer geometryBuffer, int[] offsets, MemoryBuffer occupancy) {
|
||||
this.position = position;
|
||||
this.childExistence = childExistence;
|
||||
this.aabb = aabb;
|
||||
this.geometryBuffer = geometryBuffer;
|
||||
this.offsets = offsets;
|
||||
if (offsets != null) {
|
||||
if (offsets != null && VERIFY_BUILT_SECTION_OFFSETS) {
|
||||
for (int i = 0; i < offsets.length-1; i++) {
|
||||
int delta = offsets[i+1] - offsets[i];
|
||||
if (delta<0||delta>=(1<<16)) {
|
||||
@@ -28,16 +40,20 @@ public final class BuiltSection {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.occupancy = occupancy;
|
||||
}
|
||||
|
||||
public BuiltSection clone() {
|
||||
return new BuiltSection(this.position, this.aabb, this.geometryBuffer!=null?this.geometryBuffer.copy():null, this.offsets!=null?Arrays.copyOf(this.offsets, this.offsets.length):null);
|
||||
return new BuiltSection(this.position, this.childExistence, this.aabb, this.geometryBuffer!=null?this.geometryBuffer.copy():null, this.offsets!=null?Arrays.copyOf(this.offsets, this.offsets.length):null, this.occupancy!=null?this.occupancy.copy():null);
|
||||
}
|
||||
|
||||
public void free() {
|
||||
if (this.geometryBuffer != null) {
|
||||
this.geometryBuffer.free();
|
||||
}
|
||||
if (this.occupancy != null) {
|
||||
this.occupancy.free();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering.building;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
//TODO: Have a second level disk cache
|
||||
|
||||
//TODO: instead of storing duplicate render geometry between here and gpu memory
|
||||
// when a section is unloaded from the gpu, put it into a download stream and recover the BuiltSection
|
||||
// and put that into the cache, then remove the uploaded mesh from the cache
|
||||
public class BuiltSectionMeshCache {
|
||||
private static final BuiltSection HOLDER = new BuiltSection(-1);
|
||||
private final ConcurrentHashMap<Long, BuiltSection> renderCache = new ConcurrentHashMap<>(1000,0.75f,10);
|
||||
|
||||
public BuiltSection getMesh(long key) {
|
||||
BuiltSection[] res = new BuiltSection[1];
|
||||
this.renderCache.computeIfPresent(key, (a, value) -> {
|
||||
if (value == HOLDER) {
|
||||
return value;
|
||||
}
|
||||
res[0] = value.clone();
|
||||
return value;
|
||||
});
|
||||
return res[0];
|
||||
}
|
||||
|
||||
//Returns true if the mesh was used, (this is so the parent method can free mesh object)
|
||||
public boolean putMesh(BuiltSection mesh) {
|
||||
var mesh2 = this.renderCache.computeIfPresent(mesh.position, (id, value) -> {
|
||||
if (value != HOLDER) {
|
||||
value.free();
|
||||
}
|
||||
return mesh;
|
||||
});
|
||||
return mesh2 == mesh;
|
||||
}
|
||||
|
||||
public void clearMesh(long key) {
|
||||
this.renderCache.computeIfPresent(key, (a,val)->{
|
||||
if (val != HOLDER) {
|
||||
val.free();
|
||||
}
|
||||
return HOLDER;
|
||||
});
|
||||
}
|
||||
|
||||
public void markCache(long key) {
|
||||
this.renderCache.putIfAbsent(key, HOLDER);
|
||||
}
|
||||
|
||||
public void unmarkCache(long key) {
|
||||
var mesh = this.renderCache.remove(key);
|
||||
if (mesh != null && mesh != HOLDER) {
|
||||
mesh.free();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void free() {
|
||||
for (var mesh : this.renderCache.values()) {
|
||||
if (mesh != HOLDER) {
|
||||
mesh.free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return this.renderCache.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package me.cortex.voxy.client.core.rendering.building;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Random;
|
||||
|
||||
//Block occupancy, 2 lvl compacted bitset for occluding block existance
|
||||
// TODO: need to add neighboring chunk data aswell? or somehow do a linking thing where this is stored in a secondary storage
|
||||
// where we can link them together (or store the neighbor faces seperately or something) might be out of scope for this class
|
||||
public class OccupancySet {
|
||||
private long topLvl;//4x4x4
|
||||
private final long[] bottomLvl = new long[(4*4*4)*8];
|
||||
public void set(final int pos) {
|
||||
final long topBit = 1L<<Integer.compress(pos, 0b11000_11000_11000);
|
||||
final int botIdx = Integer.compress(pos, 0b00111_00111_00111);
|
||||
|
||||
int baseBotIdx = Long.bitCount(this.topLvl&(topBit-1))*8;
|
||||
if ((this.topLvl & topBit) == 0) {
|
||||
//we need to shuffle up all the bottomlvl
|
||||
long toMove = this.topLvl & (~((topBit << 1) - 1));
|
||||
if (toMove != 0) {
|
||||
int base = baseBotIdx+8;//+8 cause were bubbling
|
||||
int count = Long.bitCount(toMove);
|
||||
for (int i = base+count*8-1; base<=i; i--) {
|
||||
this.bottomLvl[i] = this.bottomLvl[i-8];
|
||||
}
|
||||
for (int i = baseBotIdx; i<baseBotIdx+8; i++) {
|
||||
this.bottomLvl[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.topLvl |= topBit;
|
||||
}
|
||||
|
||||
this.bottomLvl[baseBotIdx+(botIdx>>6)] |= 1L<<(botIdx&63);
|
||||
}
|
||||
|
||||
private boolean get(int pos) {
|
||||
final long topBit = 1L<<Integer.compress(pos, 0b11000_11000_11000);
|
||||
final int botIdx = Integer.compress(pos, 0b00111_00111_00111);
|
||||
if ((this.topLvl & topBit) == 0) {
|
||||
return false;
|
||||
}
|
||||
int baseBotIdx = Long.bitCount(this.topLvl&(topBit-1))*8;
|
||||
|
||||
return (this.bottomLvl[baseBotIdx+(botIdx>>6)]&(1L<<(botIdx&63)))!=0;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
if (this.topLvl != 0) {
|
||||
Arrays.fill(this.bottomLvl, 0);
|
||||
}
|
||||
this.topLvl = 0;
|
||||
}
|
||||
|
||||
public int writeSize() {
|
||||
return 8+Long.bitCount(this.topLvl)*8*8;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.topLvl == 0;
|
||||
}
|
||||
|
||||
public void write(long ptr, boolean asLongs) {
|
||||
if (asLongs) {
|
||||
MemoryUtil.memPutLong(ptr, this.topLvl); ptr += 8;
|
||||
int cnt = Long.bitCount(this.topLvl);
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
MemoryUtil.memPutLong(ptr, this.bottomLvl[i*8+j]); ptr += 8;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MemoryUtil.memPutInt(ptr, (int) (this.topLvl>>>32)); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (int) this.topLvl); ptr += 4;
|
||||
int cnt = Long.bitCount(this.topLvl);
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
long v = this.bottomLvl[i*8+j];
|
||||
MemoryUtil.memPutInt(ptr, (int) (v>>>32)); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (int) v); ptr += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
for (int q = 0; q < 1000; q++) {
|
||||
var o = new OccupancySet();
|
||||
var r = new Random(12523532643L*q);
|
||||
var bs = new BitSet(32 * 32 * 32);
|
||||
for (int i = 0; i < 5000; i++) {
|
||||
int p = r.nextInt(32 * 32 * 32);
|
||||
o.set(p);
|
||||
bs.set(p);
|
||||
|
||||
for (int j = 0; j < 32 * 32 * 32; j++) {
|
||||
if (o.get(j) != bs.get(j)) throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering.building;
|
||||
|
||||
|
||||
import me.cortex.voxy.client.core.util.Mesher2D;
|
||||
|
||||
|
||||
public class QuadEncoder {
|
||||
|
||||
public static int getX(long data) {
|
||||
return (int) ((data>>21)&0b11111);
|
||||
}
|
||||
public static int getY(long data) {
|
||||
return (int) ((data>>16)&0b11111);
|
||||
}
|
||||
public static int getZ(long data) {
|
||||
return (int) ((data>>11)&0b11111);
|
||||
}
|
||||
public static int getW(long data) {
|
||||
return (int) ((data>>3)&0b1111)+1;
|
||||
}
|
||||
public static int getH(long data) {
|
||||
return (int) ((data>>7)&0b1111)+1;
|
||||
}
|
||||
public static int getFace(long data) {
|
||||
return (int) (data&0b111);
|
||||
}
|
||||
|
||||
//Note: the encodedMeshedData is from the Mesher2D
|
||||
public static int encodePosition(int face, int otherAxis, int encodedMeshedData) {
|
||||
if (false&&(Mesher2D.getW(encodedMeshedData) > 16 || Mesher2D.getH(encodedMeshedData) > 16)) {
|
||||
throw new IllegalStateException("Width or height > 16");
|
||||
}
|
||||
int dat = face;
|
||||
dat |= ((Mesher2D.getW(encodedMeshedData) - 1) << 7) |
|
||||
((Mesher2D.getH(encodedMeshedData) - 1) << 3);
|
||||
|
||||
if (face>>1 == 0) {
|
||||
return dat |
|
||||
(Mesher2D.getX(encodedMeshedData) << 21) |
|
||||
(otherAxis << 16) |
|
||||
(Mesher2D.getZ(encodedMeshedData) << 11);
|
||||
}
|
||||
if (face>>1 == 1) {
|
||||
return dat |
|
||||
(Mesher2D.getX(encodedMeshedData) << 21) |
|
||||
(Mesher2D.getZ(encodedMeshedData) << 16) |
|
||||
(otherAxis << 11);
|
||||
}
|
||||
return dat |
|
||||
(otherAxis << 21) |
|
||||
(Mesher2D.getX(encodedMeshedData) << 16) |
|
||||
(Mesher2D.getZ(encodedMeshedData) << 11);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user