219 Commits

Author SHA1 Message Date
6e065e4df4 一些优化
Some checks failed
check-does-build / build (push) Failing after 11s
2026-01-12 00:10:10 +08:00
4c0399ca40 资源释放
Some checks failed
check-does-build / build (push) Failing after 8s
2026-01-07 12:23:29 +08:00
49e92b4190 初步多线程支持
Some checks failed
check-does-build / build (push) Failing after 7s
2026-01-06 15:38:06 +08:00
fa751fd473 LOD发送顺序调整
Some checks failed
check-does-build / build (push) Failing after 6s
2026-01-06 15:17:07 +08:00
0ccd024f37 可见性计算
Some checks failed
check-does-build / build (push) Failing after 7s
2026-01-06 15:07:22 +08:00
2236bf19a5 第一个版本
Some checks failed
check-does-build / build (push) Failing after 13m6s
2026-01-06 02:01:35 +08:00
mcrcortex
c5e447125b Slight change 2026-01-02 13:12:47 +10:00
mcrcortex
cff4866499 dont even register voxy config if its not supported 2025-12-30 00:47:41 +10:00
mcrcortex
935685ad08 comma 2025-12-29 23:19:25 +10:00
mcrcortex
848efe60a8 things 2025-12-29 13:00:04 +10:00
mcrcortex
470534831b Set the default sky light to 15, this is done in the tracker on an empty return result since it should be a free operation change 2025-12-28 23:14:59 +10:00
mcrcortex
df90323fc7 fix iris issues when enabling and disabling voxy rendering while shaders are active 2025-12-28 21:35:46 +10:00
mcrcortex
f703d83b91 update 2025-12-27 23:06:01 +10:00
mcrcortex
51f1197c9f attempt to fix blending 2025-12-27 12:52:26 +10:00
mcrcortex
1e92902724 move config stuff 2025-12-25 12:14:26 +10:00
mcrcortex
0f6a099345 note 2025-12-25 12:14:11 +10:00
mcrcortex
a7dc2112fa fix render doc, add samlper to shader, nearest depth tex in view 2025-12-24 15:54:55 +10:00
mcrcortex
837b779aa9 e 2025-12-23 15:31:20 +10:00
mcrcortex
692ac95549 0.8.2 sodium 2025-12-23 11:44:11 +10:00
mcrcortex
3f0a1466ac fix woops 2025-12-23 11:18:14 +10:00
mcrcortex
7bc2c2b960 fix build 2025-12-23 10:43:13 +10:00
mcrcortex
1e7b199660 more occupancy 2025-12-23 10:42:17 +10:00
mcrcortex
61da430895 wip occupancy 2025-12-23 10:39:22 +10:00
mcrcortex
263f93215a aa 2025-12-23 10:37:12 +10:00
mcrcortex
79890fde1e x 2025-12-22 16:32:21 +10:00
mcrcortex
3cc5afc1e1 Client store 2025-12-21 11:31:55 +10:00
mcrcortex
3bcdbbec90 gpu timings 2025-12-20 19:01:51 +10:00
mcrcortex
6212d95cdd face thing TODO: CHECK IS OPTIMAL 2025-12-20 19:01:33 +10:00
mcrcortex
b086832333 remove mip thing cause vanilla mipmaps have issues 2025-12-20 19:01:03 +10:00
mcrcortex
e86beefbc7 rename 2025-12-18 22:31:15 +10:00
mcrcortex
45ff6c4414 locale 2025-12-18 22:30:36 +10:00
mcrcortex
ee7ec50d44 always show voxy version in f3 2025-12-18 19:42:19 +10:00
mcrcortex
f272042a76 a 2025-12-18 19:13:30 +10:00
mcrcortex
b5c31478fb things 2025-12-18 17:42:16 +10:00
mcrcortex
b68d5b3c66 dis 2025-12-18 17:08:04 +10:00
mcrcortex
342043674d stupid idiot 2025-12-18 16:08:36 +10:00
mcrcortex
d30ea7ecc7 here we go again 2025-12-18 14:23:16 +10:00
mcrcortex
bca46143fb remove sodium extra 2025-12-18 14:04:35 +10:00
mcrcortex
1e4500a912 Sodium update + fog change 2025-12-18 13:41:59 +10:00
mcrcortex
bc995f9c0f wip defered translucency 2025-12-18 10:38:07 +10:00
mcrcortex
86ce0c0f98 attempted amd bug detection 2025-12-18 10:37:20 +10:00
mcrcortex
4ffb7583a7 complete and utter fukin idiot 2025-12-17 23:56:42 +10:00
mcrcortex
ef1a296998 fix small possiblity of a race condition (so small is stupid yet it happened) 2025-12-17 23:05:31 +10:00
mcrcortex
0428153cf5 small buildscript change 2025-12-17 22:08:35 +10:00
mcrcortex
fb9b7923b0 tweeked msg 2025-12-17 21:46:51 +10:00
mcrcortex
5b697ddb04 insane 2025-12-17 21:42:54 +10:00
mcrcortex
25463a4c11 for future 2025-12-17 21:32:49 +10:00
mcrcortex
8247248c62 think? this is more right? (need todo fluid thing tho) 2025-12-17 21:10:09 +10:00
mcrcortex
6beb6b2037 this was such an unbelievebly dumb and stupid mistake 2025-12-17 21:09:20 +10:00
mcrcortex
65e10c2c68 cleanup 2025-12-17 21:08:56 +10:00
mcrcortex
362998cc5f woooops 2025-12-17 12:59:56 +10:00
mcrcortex
76cfef5b3c update deps, fix unlikely race in import manager, clamp fog, fix some some incorrect meshing 2025-12-17 12:46:59 +10:00
mcrcortex
15604c16bf hopefully improved command usage 2025-12-16 13:47:55 +10:00
mcrcortex
85638fce98 readme 2025-12-15 22:49:44 +10:00
mcrcortex
2bf9af00e5 computed face tint 2025-12-15 22:35:31 +10:00
mcrcortex
6724157f9a finally fix the chunk flickering issue when zooming in 2025-12-15 12:55:18 +10:00
mcrcortex
bbf7d60abe todo 2025-12-15 12:54:59 +10:00
mcrcortex
b7f5798ecd fix iris 2025-12-15 10:47:28 +10:00
mcrcortex
c4f799ff53 things 2025-12-15 10:01:51 +10:00
mcrcortex
561337e10c hoist common FB 2025-12-15 09:12:08 +10:00
mcrcortex
4ca9cdbaa8 woops 2025-12-15 00:36:13 +10:00
mcrcortex
a7ea5b2f99 who knows if this even works (it very probably doesnt) 2025-12-14 23:45:24 +10:00
mcrcortex
c7cf4a74d5 mipping 2025-12-14 23:44:46 +10:00
mcrcortex
03d971385c private final 2025-12-14 23:43:14 +10:00
mcrcortex
afe41a10b5 perf tweek 2025-12-14 23:43:03 +10:00
mcrcortex
2458a4a3f6 fixes and work on ao, shading, tinting and lighting 2025-12-14 18:05:17 +10:00
mcrcortex
edd0ce33ef update mods 1.21.11 2025-12-14 18:03:54 +10:00
mcrcortex
f713ef2e8f inital 1.21.11 2025-12-14 18:02:29 +10:00
mcrcortex
0f9287adcb wip face tinit 2025-12-14 18:00:22 +10:00
mcrcortex
823babef81 hate java 2025-12-07 08:50:52 +10:00
mcrcortex
66a2061813 wip tinting 2025-12-07 08:47:53 +10:00
mcrcortex
26189d4739 fog 2025-12-07 08:47:37 +10:00
mcrcortex
53f857771e replacements 2025-12-07 08:47:21 +10:00
mcrcortex
aa314781d6 fog2, TODO: must fix translucency fog, fog should be applied to both opaque and translucent layers seperately (which results in the fog being 2x thicker for things with translucency etc 2025-12-06 00:42:21 +10:00
mcrcortex
7f49a84215 more fog fixes 2025-12-06 00:19:59 +10:00
mcrcortex
43ed8145ff remap false 2025-12-02 09:34:10 +10:00
mcrcortex
37b7feecd5 Rework fog 2025-12-02 08:58:36 +10:00
mcrcortex
e7deeed34e Fix _one of_ the race conditions when loading a chunk that is already loaded (this whole thing is horrifically bad and should be redone to clone active chunk sections instead) 2025-12-02 08:58:22 +10:00
mcrcortex
18b13370d8 sodium extra culling 2025-12-02 08:57:26 +10:00
mcrcortex
d371e80e3b Merge remote-tracking branch 'origin/dev' into dev 2025-12-02 08:53:48 +10:00
mcrcortex
f887b1ac60 timing 2025-12-01 23:40:31 +10:00
mcrcortex
e873ce76c6 Merge remote-tracking branch 'origin/dev' into dev 2025-12-01 23:13:35 +10:00
MCRcortex
b688dc38d6 logger fix 2025-11-20 17:34:12 +10:00
MCRcortex
9d8ade88f3 Ingest config stuff 2025-11-20 17:27:36 +10:00
MCRcortex
4d8de7eb3d remove lighting synchronizer + version bump 2025-11-10 11:27:39 +10:00
MCRcortex
560ba41fa5 attempted fix for incorrect sampling of depth buffer stuffs 2025-11-10 11:23:00 +10:00
mcrcortex
937eaa5fb1 Merge remote-tracking branch 'origin/dev' into dev 2025-11-07 09:22:12 +10:00
MCRcortex
bf5b8d76d2 Render factory + debug 2025-11-07 09:12:20 +10:00
mcrcortex
1c1d812bbf use single tri 2025-11-05 16:32:24 +10:00
mcrcortex
8426006435 barry + shift 2025-11-05 15:30:03 +10:00
mcrcortex
1c9ef1ea16 Set dedicated thread name 2025-11-04 11:11:07 +10:00
mcrcortex
4d5de7aa36 reload on reload 2025-11-01 13:09:44 +10:00
mcrcortex
8c6e6f75c6 fix shader lighting... again? hopefully correctly... 2025-11-01 02:31:45 +10:00
mcrcortex
9aebf20aad fix dynamic unifomrs not being exposed 2025-11-01 01:17:18 +10:00
mcrcortex
5baf4ce2db Shader lighting, fixes for loosing tasks in MultiThreadPrioritySemaphore, limit render generation rebuilds based on model queue and failure rate, fix native memory leak in ModelFactory (related to raw download stream) 2025-10-31 20:02:38 +10:00
mcrcortex
86882d5ac9 Fix mixin (again) 2025-10-31 11:59:57 +10:00
mcrcortex
601c2a2fea remap = false 2025-10-31 11:44:41 +10:00
mcrcortex
141c2b1f98 Add option to choose if to use sodium build threads (default true) 2025-10-31 11:43:36 +10:00
mcrcortex
96bd651f5c stupid 2025-10-31 01:14:47 +10:00
mcrcortex
f91f59f1f8 fix mixins 2025-10-30 23:03:54 +10:00
mcrcortex
e81d442b8b mojmap part 2 (rename the mixins) 2025-10-30 02:39:14 +10:00
mcrcortex
a8075158ba mojmap part 1 2025-10-30 02:30:59 +10:00
mcrcortex
36325a0946 Merge remote-tracking branch 'origin/dev' into dev 2025-10-30 01:25:39 +10:00
MCRcortex
c8f552f389 Merge pull request #158 from boredhuman/dev
fixed issues with stencil testing
2025-10-29 20:45:22 +10:00
mcrcortex
b27d6323e7 increment the ptr 2025-10-29 18:04:59 +10:00
mcrcortex
a17711429c todo 2025-10-29 01:01:06 +10:00
MCRcortex
31cbe85588 Free depthCopy shader 2025-10-28 21:25:32 +10:00
Daniel
4172ba357e fixed stencil state leaking 2025-10-27 18:57:24 -04:00
Daniel
b6741241f6 fixed issues with stencil testing 2025-10-27 16:32:54 -04:00
mcrcortex
d909d82c33 a 2025-10-27 10:16:04 +10:00
mcrcortex
8d4d823537 async model processing 2025-10-27 10:15:48 +10:00
mcrcortex
ec221ed820 decrease mesh gen weight 2025-10-27 10:15:26 +10:00
mcrcortex
72fd785855 add more sync wait conditions 2025-10-27 10:14:29 +10:00
mcrcortex
45f3397ca9 batch resize 2025-10-27 10:13:53 +10:00
mcrcortex
703bf5f1d8 time + fix crash 2025-10-27 10:13:38 +10:00
mcrcortex
2534740cda L 2025-10-26 23:48:31 +10:00
mcrcortex
85ce43be12 jank 2025-10-26 22:32:23 +10:00
mcrcortex
3df37b1102 ready for async 2025-10-26 20:05:05 +10:00
mcrcortex
4863f607b8 even more prep, is close 2025-10-26 12:59:04 +10:00
mcrcortex
7b1b08c997 prepare more stuff for off thread model factory 2025-10-26 12:39:27 +10:00
mcrcortex
e3f3a7536d move thing into new box 2025-10-25 23:13:28 +10:00
mcrcortex
210ed1f8af move package 2025-10-25 22:33:27 +10:00
mcrcortex
dd854b5478 set thread count to max(1,self-sodium) 2025-10-25 20:11:35 +10:00
mcrcortex
c739e545f2 balance lru capacity with respect to the number of currently loaded sections 2025-10-25 19:35:32 +10:00
mcrcortex
d2a5b1e607 Fix deadlock in servicemanager + use sodiums thread pool for our own use 2025-10-25 18:57:40 +10:00
mcrcortex
6ffa90f064 mark the world as active 2025-10-25 17:47:09 +10:00
mcrcortex
3de5740790 more work on threads 2025-10-25 17:24:10 +10:00
mcrcortex
146cffc8d9 Rewrite the service threading system tobe thread "pool indepenent" aswell as a system for allowing unified thread pools 2025-10-25 16:36:17 +10:00
mcrcortex
cb084e116e actually fix the lighting of fluids (kinda) (a full fix would require a lighting comparison and taking the max of self block + neighbor bloc) 2025-10-25 16:30:08 +10:00
mcrcortex
bc590c9d81 revert lighting 2025-10-25 16:14:01 +10:00
mcrcortex
842987e891 a 2025-10-25 14:57:42 +10:00
mcrcortex
c4769c4f1a todo 2025-10-23 20:21:26 +10:00
mcrcortex
4a98df4740 shader logging 2025-10-23 20:21:19 +10:00
mcrcortex
fbe001f559 work on async model factory processing 2025-10-23 20:21:10 +10:00
mcrcortex
b15b70860b add todo for blockstates with emissive lighting (like magma blocks) 2025-10-20 10:50:05 +10:00
mcrcortex
01fe58172c clown. emoji. ("fixed" lighting division (fixes water not looking correct) 2025-10-20 10:50:04 +10:00
mcrcortex
a09708bb30 begin prep for offthread work 2025-10-20 10:00:04 +10:00
mcrcortex
b465871bd7 fix deprecation 2025-10-20 09:59:35 +10:00
mcrcortex
b59cf5d700 lol random tab 2025-10-20 09:43:01 +10:00
mcrcortex
06537a8815 newline 2025-10-19 12:20:09 +10:00
mcrcortex
46a0c982e1 vivecraft fixes 2025-10-19 11:53:17 +10:00
mcrcortex
87c7f37bb1 fix dep warning 2025-10-19 11:08:19 +10:00
mcrcortex
a54b97d04b Vivecraft update 2025-10-19 11:07:45 +10:00
mcrcortex
2c62c876fa more shader logging 2025-10-19 10:57:24 +10:00
mcrcortex
a0512ef044 Ingest on chunk loaded 2025-10-18 17:30:35 +10:00
mcrcortex
ddbc1c27e4 tweeks 2025-10-12 15:38:33 +10:00
mcrcortex
2f49d03953 tweak thing (hoping compiles to mad (unsure if doing & -1 on the conditional is better)) 2025-10-12 00:52:45 +10:00
mcrcortex
3350e01eca delete old vertex shader 2025-10-12 00:52:04 +10:00
mcrcortex
eb8cd8ba62 fix thing 2025-10-12 00:52:03 +10:00
mcrcortex
dbac73048f abstract and pull out vertex shader internals 2025-10-11 23:50:45 +10:00
mcrcortex
c9eddb2cbb bean 2025-10-11 23:45:17 +10:00
mcrcortex
8f34fc24fb fix more oopsies 2025-10-11 23:45:06 +10:00
mcrcortex
f4509d6f0d update 2025-10-11 23:44:53 +10:00
mcrcortex
0d79e316e3 fix another oopsie 2025-10-11 16:57:42 +10:00
mcrcortex
12b0c29c01 1.21.10 2025-10-08 23:07:27 +10:00
mcrcortex
4dcb7cad6d version bump 2025-10-08 10:34:23 +10:00
mcrcortex
e2beabe2d3 small cleanup 2025-10-07 13:39:02 +10:00
mcrcortex
34caf07d8c Add occlusion debug util 2025-10-07 13:21:15 +10:00
mcrcortex
ab1c8b0f71 improve chunk outline matching sodium checks 2025-10-07 13:14:59 +10:00
mcrcortex
b2d62ec807 Fix crash on macos 2025-10-05 22:54:39 +10:00
mcrcortex
72d1fccacf include 1.21.10 2025-10-04 12:25:40 +10:00
mcrcortex
241edb946b build test 2025-10-02 21:40:04 +10:00
mcrcortex
e855f87011 test thing 2025-10-02 21:30:18 +10:00
mcrcortex
792f248c38 more fast 2025-10-02 20:55:30 +10:00
mcrcortex
7cf33bc0cd speed 2025-10-02 20:53:58 +10:00
mcrcortex
4d4f4abcfc test build speed 2025-10-02 20:42:10 +10:00
mcrcortex
5ca07e574a cache on commit 2025-10-02 20:38:37 +10:00
mcrcortex
02c9d0aa5e remove useless action 2025-10-02 20:33:58 +10:00
mcrcortex
69511b6d1f wrong ordering of matricies 2025-10-02 20:05:13 +10:00
mcrcortex
8e111aa7e4 wrapper validation v4 2025-10-02 19:30:44 +10:00
mcrcortex
f846ec0825 only specify neibhors if the state did change 2025-10-02 19:13:04 +10:00
mcrcortex
1d53f658a7 trigger neighbor mesh rebuild if rebuild mask says so 2025-10-02 19:10:05 +10:00
mcrcortex
303d3f0f46 &types 2025-10-02 19:09:24 +10:00
mcrcortex
35bd5fa414 forward neighbor edge msk 2025-10-02 18:40:47 +10:00
mcrcortex
c2f6fda5c8 am so stupid 2025-10-02 18:39:57 +10:00
mcrcortex
83eea856e1 Added hud debug entry, added support for tinting in dark dimensions, attempted to add/fix auto updating block data from older versions (wip) 2025-10-02 18:01:37 +10:00
mcrcortex
da5d7bc95e Initial 1.21.9 support (Issues with entity model baking and F3 debug) 2025-10-01 16:08:10 +10:00
mcrcortex
1438c16558 Sodium update 2025-10-01 16:06:41 +10:00
mcrcortex
0968b25968 things 2025-09-30 18:59:15 +10:00
mcrcortex
b364713268 if 2025-09-28 14:11:03 +10:00
mcrcortex
ff7aecadb2 compute neighbor mask 2025-09-26 10:24:35 +10:00
mcrcortex
02d0d024d6 Changes to commands 2025-09-26 10:02:11 +10:00
mcrcortex
471d00b534 TODOS and tweeks 2025-09-26 10:01:58 +10:00
mcrcortex
0cf46b3d5d dont crash on blockstate deserialization errors 2025-09-25 19:59:11 +10:00
mcrcortex
bc4bf03a64 force fluid tinting 2025-09-24 23:08:32 +10:00
mcrcortex
77d51dd27d use tinting metadata in vert/frag 2025-09-24 22:52:46 +10:00
mcrcortex
474a8a7e3c model metadata 2025-09-24 22:32:27 +10:00
mcrcortex
ae7004f5d8 reduce batching delay to 10 2025-09-24 13:27:12 +10:00
mcrcortex
944e1c3c7f tweek 2025-09-24 13:09:21 +10:00
mcrcortex
83975c8a98 fix using wrong mesher during building 2025-09-24 11:10:44 +10:00
mcrcortex
6f748cbe12 taa file override 2025-09-23 21:11:33 +10:00
mcrcortex
7ca910e35a fixes 2025-09-23 13:05:41 +10:00
mcrcortex
3f94bba49e not air return 2025-09-22 13:47:13 +10:00
mcrcortex
301d587535 ingest chunk section on block change on board 2025-09-22 11:29:36 +10:00
mcrcortex
7b15dbbea3 slight prep for sodium 0.7 2025-09-22 10:54:16 +10:00
mcrcortex
e0d125906b flag 2025-09-20 22:28:55 +10:00
mcrcortex
d5ab22c7f3 thing 2025-09-18 12:03:05 +10:00
mcrcortex
000fa24b2f so many bloody todos ;-; aaaaa 2025-09-18 12:02:56 +10:00
mcrcortex
b1582e5a1f added check against ModelQueries.cullsSame 2025-09-18 11:39:50 +10:00
mcrcortex
4ba2b1ca1f more returns 2025-09-18 10:52:59 +10:00
mcrcortex
7ec41006f7 extra return 2025-09-17 16:56:05 +10:00
mcrcortex
25ac827865 incremental sparse allocation, clear viewport on resize 2025-09-16 13:52:50 +10:00
mcrcortex
a6710c3e2e incremental sparcial buffer allocation, should prevent the huge lag spike on loading 2025-09-15 20:50:56 +10:00
mcrcortex
6539d67087 log 2025-09-15 18:26:55 +10:00
mcrcortex
5781f17858 improve parsing of blending data 2025-09-15 17:05:08 +10:00
mcrcortex
3ceba131f1 fk 2025-09-14 20:16:12 +10:00
mcrcortex
f0efd36674 try not to crash on shader load failure 2025-09-14 20:14:06 +10:00
mcrcortex
d78653a76f move to irisutil 2025-09-14 19:51:41 +10:00
mcrcortex
b86546a178 Auto disable shaders on load failure 2025-09-14 19:42:22 +10:00
mcrcortex
465a55a77e increase upsize to 1024 2025-09-14 18:51:05 +10:00
mcrcortex
1b023f859b more debug 2025-09-13 21:25:15 +10:00
mcrcortex
9edb680114 aaa 2025-09-12 14:27:21 +10:00
mcrcortex
a47876aca8 enable iris support 2025-09-12 10:12:51 +10:00
mcrcortex
ad182d4170 add taa injection into ChunkBoundRenderer, shift up uniform update 2025-09-12 10:04:56 +10:00
mcrcortex
2a4d6d085c a 2025-09-12 10:04:09 +10:00
mcrcortex
7605ebf48d pre-setup 2025-09-12 09:45:46 +10:00
mcrcortex
9f4282e37b Fixed rare race condition in ActiveSectionTracker, occured more frequently with high core systems. This is hopefully the last concurrency bug in the tracker 2025-09-11 01:32:24 +10:00
mcrcortex
81741974bb change mesh builder 2025-09-09 12:45:46 +10:00
mcrcortex
a2722b98de micro opt + gpu timer 2025-09-09 12:45:45 +10:00
184 changed files with 6731 additions and 3752 deletions

View File

@@ -9,9 +9,6 @@ jobs:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Verify wrapper
uses: gradle/actions/wrapper-validation@v3
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:

View File

@@ -9,19 +9,24 @@ jobs:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Verify wrapper
uses: gradle/actions/wrapper-validation@v3
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: temurin distribution: temurin
java-version: 21 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 - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
with: with:
cache-read-only: false cache-read-only: false
cache-cleanup: never
- name: Gradle build - name: Gradle build
run: ./gradlew build run: ./gradlew -I init.gradle build

View File

@@ -9,9 +9,6 @@ jobs:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Verify wrapper
uses: gradle/actions/wrapper-validation@v3
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
@@ -22,6 +19,7 @@ jobs:
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
with: with:
cache-read-only: false cache-read-only: false
cache-cleanup: always
- name: Gradle build - name: Gradle build
run: ./gradlew build run: ./gradlew build

1
README.md Normal file
View File

@@ -0,0 +1 @@
Voxy is an LoD rendering mod for minecraft

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id 'fabric-loom' version "1.11-SNAPSHOT" id 'fabric-loom' version "1.14-SNAPSHOT"
id 'maven-publish' id 'maven-publish'
} }
@@ -24,6 +24,21 @@ repositories {
includeGroup "maven.modrinth" includeGroup "maven.modrinth"
} }
} }
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.shedaniel.me/" }
maven { url = "https://maven.terraformersmc.com/releases/" } maven { url = "https://maven.terraformersmc.com/releases/" }
@@ -66,6 +81,7 @@ def gitCommitHash = { ->
} }
def buildtime = {System.currentTimeSeconds()} def buildtime = {System.currentTimeSeconds()}
processResources { processResources {
def time = buildtime() def time = buildtime()
def hash = gitCommitHash() def hash = gitCommitHash()
@@ -93,7 +109,7 @@ if (!isInGHA) {
dependencies { dependencies {
// To change the versions see the gradle.properties file // To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}" 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 "net.fabricmc:fabric-loader:${project.loader_version}"
@@ -104,36 +120,37 @@ dependencies {
modImplementation "net.fabricmc.fabric-api:fabric-api:${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 if (true) {
modRuntimeOnlyMsk "maven.modrinth:sodium:mc1.21.6-0.6.13-fabric" modImplementation "maven.modrinth:sodium:mc1.21.11-0.8.2-fabric"
modCompileOnly "maven.modrinth:sodium:mc1.21.6-0.6.13-fabric" } else {
modImplementation "net.caffeinemc:sodium-fabric:0.8.2-SNAPSHOT+mc1.21.11+"
}
modImplementation("maven.modrinth:lithium:mc1.21.8-0.18.0-fabric") modImplementation("maven.modrinth:lithium:mc1.21.11-0.21.0-fabric")
//modRuntimeOnlyMsk "drouarb:nvidium:0.4.1-beta4:1.21.6@jar" //modRuntimeOnlyMsk "drouarb:nvidium:0.4.1-beta4:1.21.6@jar"
modCompileOnly "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:15.0.0-beta.3") modCompileOnly("maven.modrinth:modmenu:17.0.0-alpha.1")
modRuntimeOnlyMsk("maven.modrinth:modmenu:15.0.0-beta.3") modRuntimeOnlyMsk("maven.modrinth:modmenu:17.0.0-alpha.1")
modCompileOnly("maven.modrinth:iris:1.9.1+1.21.7-fabric") modCompileOnly("maven.modrinth:iris:1.10.4+1.21.11-fabric")
modRuntimeOnlyMsk("maven.modrinth:iris:1.9.1+1.21.7-fabric") modRuntimeOnlyMsk("maven.modrinth:iris:1.10.4+1.21.11-fabric")
//modCompileOnly("maven.modrinth:starlight:1.1.3+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:immersiveportals:v5.1.7-mc1.20.4")
modCompileOnly("maven.modrinth:chunky:1.4.54-fabric")
//modRuntimeOnlyMsk("maven.modrinth:chunky:1.4.40-fabric")
modCompileOnly("maven.modrinth:chunky:1.4.40-fabric") modRuntimeOnlyMsk("maven.modrinth:spark:1.10.152-fabric")
modRuntimeOnlyMsk("maven.modrinth:chunky:1.4.40-fabric")
modRuntimeOnlyMsk("maven.modrinth:spark:1.10.139-fabric")
modRuntimeOnlyMsk("maven.modrinth:fabric-permissions-api:0.3.3") modRuntimeOnlyMsk("maven.modrinth:fabric-permissions-api:0.3.3")
//modRuntimeOnly("maven.modrinth:nsight-loader:1.2.0") //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.1-1.1.14-b2-fabric") modCompileOnly("maven.modrinth:vivecraft:1.21.9-1.3.2-fabric")
modCompileOnly("maven.modrinth:flashback:rNCr1Rbs") modCompileOnly("maven.modrinth:flashback:rNCr1Rbs")
} }
@@ -141,14 +158,9 @@ dependencies {
def targetJavaVersion = 21 def targetJavaVersion = 21
tasks.withType(JavaCompile).configureEach { 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" it.options.encoding = "UTF-8"
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { it.options.release = targetJavaVersion
it.options.release = targetJavaVersion it.options.deprecation = true
}
} }
java { java {
@@ -233,19 +245,20 @@ dependencies {
implementation "org.lwjgl:lwjgl" implementation "org.lwjgl:lwjgl"
include(implementation "org.lwjgl:lwjgl-lmdb") 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-windows"
runtimeOnly "org.lwjgl:lwjgl:$lwjglVersion:natives-linux" runtimeOnly "org.lwjgl:lwjgl:$lwjglVersion:natives-linux"
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-windows") 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-lmdb:$lwjglVersion:natives-linux")
include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-linux") //include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-linux")
include(implementation 'redis.clients:jedis:5.1.0') include(implementation 'redis.clients:jedis:5.1.0')
include(implementation('org.rocksdb:rocksdbjni:10.2.1')) include(implementation('org.rocksdb:rocksdbjni:10.2.1'))
include(implementation 'org.apache.commons:commons-pool2:2.12.0') include(implementation 'org.apache.commons:commons-pool2:2.12.0')
include(implementation 'org.lz4:lz4-java:1.8.0') include(implementation 'org.lz4:lz4-java:1.8.0')
include(implementation('org.tukaani:xz:1.10')) include(implementation('org.tukaani:xz:1.10'))
include(implementation 'com.github.luben:zstd-jni:1.5.5-1')
if (true) { if (true) {
if (!isInGHA) { if (!isInGHA) {
@@ -266,8 +279,9 @@ if (!isInGHA) {
} }
dependencies { dependencies {
modRuntimeOnly('me.djtheredstoner:DevAuth-fabric:1.1.0') { /*
modRuntimeOnly('me.djtheredstoner:DevAuth-fabric:1.2.1') {
exclude group: 'net.fabricmc', module: 'fabric-loader' exclude group: 'net.fabricmc', module: 'fabric-loader'
} }*/
} }
} }

View File

@@ -3,18 +3,19 @@ org.gradle.jvmargs=-Xmx2G
org.gradle.caching=true org.gradle.caching=true
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.daemon = false
# Fabric Properties # Fabric Properties
# check these on https://modmuss50.me/fabric.html # check these on https://modmuss50.me/fabric.html
minecraft_version=1.21.8 minecraft_version=1.21.11
yarn_mappings=1.21.8+build.1 loader_version=0.18.2
loader_version=0.16.14 loom_version=1.14-SNAPSHOT
loom_version=1.11-SNAPSHOT
# Fabric API # Fabric API
fabric_version=0.129.0+1.21.8 fabric_version=0.140.2+1.21.11
# Mod Properties # Mod Properties
mod_version = 0.2.5-alpha mod_version = 0.2.9-alpha
maven_group = me.cortex maven_group = me.cortex
archives_base_name = voxy archives_base_name = voxy

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000 networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

16
init.gradle Normal file
View 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)
}
}
}

View File

@@ -3,25 +3,24 @@ package me.cortex.voxy.client;
import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.Logger;
import me.cortex.voxy.commonImpl.ImportManager; import me.cortex.voxy.commonImpl.ImportManager;
import me.cortex.voxy.commonImpl.importers.IDataImporter; import me.cortex.voxy.commonImpl.importers.IDataImporter;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.hud.ClientBossBar; import net.minecraft.client.gui.components.LerpingBossEvent;
import net.minecraft.entity.boss.BossBar; import net.minecraft.network.chat.Component;
import net.minecraft.text.Text; import net.minecraft.util.Mth;
import net.minecraft.util.math.MathHelper; import net.minecraft.world.BossEvent;
import java.util.UUID; import java.util.UUID;
public class ClientImportManager extends ImportManager { public class ClientImportManager extends ImportManager {
protected class ClientImportTask extends ImportTask { protected class ClientImportTask extends ImportTask {
private final UUID bossbarUUID; private final UUID bossbarUUID;
private final ClientBossBar bossBar; private final LerpingBossEvent bossBar;
protected ClientImportTask(IDataImporter importer) { protected ClientImportTask(IDataImporter importer) {
super(importer); super(importer);
this.bossbarUUID = MathHelper.randomUuid(); this.bossbarUUID = Mth.createInsecureUUID();
this.bossBar = new ClientBossBar(this.bossbarUUID, Text.of("Voxy world importer"), 0.0f, BossBar.Color.GREEN, BossBar.Style.PROGRESS, false, false, false); this.bossBar = new LerpingBossEvent(this.bossbarUUID, Component.nullToEmpty("Voxy world importer"), 0.0f, BossEvent.BossBarColor.GREEN, BossEvent.BossBarOverlay.PROGRESS, false, false, false);
MinecraftClient.getInstance().execute(()->{ Minecraft.getInstance().execute(()->{
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.put(bossBar.getUuid(), bossBar); Minecraft.getInstance().gui.getBossOverlay().events.put(bossBar.getId(), bossBar);
}); });
} }
@@ -30,9 +29,9 @@ public class ClientImportManager extends ImportManager {
if (!super.onUpdate(completed, outOf)) { if (!super.onUpdate(completed, outOf)) {
return false; return false;
} }
MinecraftClient.getInstance().execute(()->{ Minecraft.getInstance().execute(()->{
this.bossBar.setPercent((float) (((double)completed) / ((double) Math.max(1, outOf)))); this.bossBar.setProgress((float) (((double)completed) / ((double) Math.max(1, outOf))));
this.bossBar.setName(Text.of("Voxy import: " + completed + "/" + outOf + " chunks")); this.bossBar.setName(Component.nullToEmpty("Voxy import: " + completed + "/" + outOf + " chunks"));
}); });
return true; return true;
} }
@@ -40,12 +39,12 @@ public class ClientImportManager extends ImportManager {
@Override @Override
protected void onCompleted(int total) { protected void onCompleted(int total) {
super.onCompleted(total); super.onCompleted(total);
MinecraftClient.getInstance().execute(()->{ Minecraft.getInstance().execute(()->{
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.remove(this.bossbarUUID); Minecraft.getInstance().gui.getBossOverlay().events.remove(this.bossbarUUID);
long delta = Math.max(System.currentTimeMillis() - this.startTime, 1); 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"; String msg = "Voxy world import finished in " + (delta/1000) + " seconds, averaging " + (int)(total/(delta/1000f)) + " chunks per second";
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.literal(msg)); Minecraft.getInstance().gui.getChat().addMessage(Component.literal(msg));
Logger.info(msg); Logger.info(msg);
}); });
} }

View File

@@ -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);
}

View File

@@ -1,7 +0,0 @@
package me.cortex.voxy.client;
import net.minecraft.world.chunk.WorldChunk;
public interface ICheekyClientChunkManager {
WorldChunk voxy$cheekyGetChunk(int x, int z);
}

View File

@@ -1,5 +1,7 @@
package me.cortex.voxy.client; 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.gl.Capabilities;
import me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer; import me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer;
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer; import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
@@ -8,19 +10,36 @@ import me.cortex.voxy.commonImpl.VoxyCommon;
import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.loader.api.FabricLoader; 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.HashSet;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; 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 { public class VoxyClient implements ClientModInitializer {
private static final HashSet<String> FREX = new HashSet<>(); private static final HashSet<String> FREX = new HashSet<>();
public static void initVoxyClient() { public static void initVoxyClient() {
Capabilities.init();//Ensure clinit is called Capabilities.init();//Ensure clinit is called
boolean systemSupported = Capabilities.INSTANCE.compute && Capabilities.INSTANCE.indirectParameters;
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) { if (systemSupported) {
SharedIndexBuffer.INSTANCE.id(); SharedIndexBuffer.INSTANCE.id();
@@ -39,6 +58,28 @@ public class VoxyClient implements ClientModInitializer {
@Override @Override
public void onInitializeClient() { 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) -> { ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
if (VoxyCommon.isAvailable()) { if (VoxyCommon.isAvailable()) {
dispatcher.register(VoxyCommands.register()); dispatcher.register(VoxyCommands.register());
@@ -52,9 +93,50 @@ public class VoxyClient implements ClientModInitializer {
} else { } else {
FREX.remove(name); 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() { public static boolean isFrexActive() {
return !FREX.isEmpty(); return !FREX.isEmpty();
} }
public static int getOcclusionDebugState() {
return 0;
}
public static boolean disableSodiumChunkRender() {
return false;// getOcclusionDebugState() != 0;
}
} }

View File

@@ -1,8 +1,10 @@
package me.cortex.voxy.client; package me.cortex.voxy.client;
import me.cortex.voxy.client.compat.FlashbackCompat; import me.cortex.voxy.client.compat.FlashbackCompat;
import me.cortex.voxy.client.config.VoxyConfig; 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.Logger;
import me.cortex.voxy.common.StorageConfigUtil;
import me.cortex.voxy.common.config.ConfigBuildCtx; import me.cortex.voxy.common.config.ConfigBuildCtx;
import me.cortex.voxy.common.config.Serialization; import me.cortex.voxy.common.config.Serialization;
import me.cortex.voxy.common.config.compressors.ZSTDCompressor; import me.cortex.voxy.common.config.compressors.ZSTDCompressor;
@@ -14,9 +16,9 @@ import me.cortex.voxy.common.config.storage.rocksdb.RocksDBStorageBackend;
import me.cortex.voxy.commonImpl.ImportManager; import me.cortex.voxy.commonImpl.ImportManager;
import me.cortex.voxy.commonImpl.VoxyInstance; import me.cortex.voxy.commonImpl.VoxyInstance;
import me.cortex.voxy.commonImpl.WorldIdentifier; import me.cortex.voxy.commonImpl.WorldIdentifier;
import net.minecraft.client.MinecraftClient; import net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer;
import net.minecraft.util.WorldSavePath; import net.minecraft.client.Minecraft;
import net.minecraft.world.level.storage.LevelResource;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -26,15 +28,83 @@ public class VoxyClientInstance extends VoxyInstance {
private final SectionStorageConfig storageConfig; private final SectionStorageConfig storageConfig;
private final Path basePath; private final Path basePath;
private final boolean noIngestOverride; private final boolean noIngestOverride;
private int serverViewDistance = 32;
public VoxyClientInstance() { public VoxyClientInstance() {
super(VoxyConfig.CONFIG.serviceThreads); super();
var path = FlashbackCompat.getReplayStoragePath(); var path = FlashbackCompat.getReplayStoragePath();
this.noIngestOverride = path != null; this.noIngestOverride = path != null;
if (path == null) { if (path == null) {
path = getBasePath(); path = getBasePath();
} }
this.basePath = path; this.basePath = path;
this.storageConfig = getCreateStorageConfig(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 @Override
@@ -51,90 +121,39 @@ public class VoxyClientInstance extends VoxyInstance {
return this.storageConfig.build(ctx); return this.storageConfig.build(ctx);
} }
public static SectionStorageConfig getCreateStorageConfig(Path path) {
try {
Files.createDirectories(path);
} catch (Exception e) {
throw new RuntimeException(e);
}
var json = path.resolve("config.json");
Config config = null;
if (Files.exists(json)) {
try {
config = Serialization.GSON.fromJson(Files.readString(json), Config.class);
if (config == null) {
Logger.error("Config deserialization null, reverting to default");
} else {
if (config.sectionStorageConfig == null) {
Logger.error("Config section storage null, reverting to default");
config = null;
}
}
} catch (Exception e) {
Logger.error("Failed to load the storage configuration file, resetting it to default, this will probably break your save if you used a custom storage config", e);
}
}
if (config == null) {
config = DEFAULT_STORAGE_CONFIG;
}
try {
Files.writeString(json, Serialization.GSON.toJson(config));
} catch (Exception e) {
throw new RuntimeException("Failed write the config, aborting!", e);
}
if (config == null) {
throw new IllegalStateException("Config is still null\n");
}
return config.sectionStorageConfig;
}
public Path getStorageBasePath() { public Path getStorageBasePath() {
return this.basePath; return this.basePath;
} }
@Override @Override
public boolean isIngestEnabled(WorldIdentifier worldId) { public boolean isIngestEnabled(WorldIdentifier worldId) {
return !this.noIngestOverride; return (!this.noIngestOverride) && VoxyConfig.CONFIG.ingestEnabled;
} }
private static class Config { private static class Config {
public int version = 1; public int version = 1;
public SectionStorageConfig sectionStorageConfig; public SectionStorageConfig sectionStorageConfig;
} }
private static final Config DEFAULT_STORAGE_CONFIG; private static final Config DEFAULT_STORAGE_CONFIG;
static { static {
var config = new Config(); var config = new Config();
config.sectionStorageConfig = StorageConfigUtil.createDefaultSerializer();
//Load the default config
var baseDB = new RocksDBStorageBackend.Config();
var compressor = new ZSTDCompressor.Config();
compressor.compressionLevel = 1;
var compression = new CompressionStorageAdaptor.Config();
compression.delegate = baseDB;
compression.compressor = compressor;
var serializer = new SectionSerializationStorage.Config();
serializer.storage = compression;
config.sectionStorageConfig = serializer;
DEFAULT_STORAGE_CONFIG = config; DEFAULT_STORAGE_CONFIG = config;
} }
private static Path getBasePath() { private static Path getBasePath() {
Path basePath = MinecraftClient.getInstance().runDirectory.toPath().resolve(".voxy").resolve("saves"); Path basePath = Minecraft.getInstance().gameDirectory.toPath().resolve(".voxy").resolve("saves");
var iserver = MinecraftClient.getInstance().getServer(); var iserver = Minecraft.getInstance().getSingleplayerServer();
if (iserver != null) { if (iserver != null) {
basePath = iserver.getSavePath(WorldSavePath.ROOT).resolve("voxy"); basePath = iserver.getWorldPath(LevelResource.ROOT).resolve("voxy");
} else { } else {
var netHandle = MinecraftClient.getInstance().interactionManager; var netHandle = Minecraft.getInstance().gameMode;
if (netHandle == null) { if (netHandle == null) {
Logger.error("Network handle null"); Logger.error("Network handle null");
basePath = basePath.resolve("UNKNOWN"); basePath = basePath.resolve("UNKNOWN");
} else { } else {
var info = netHandle.networkHandler.getServerInfo(); var info = netHandle.connection.getServerData();
if (info == null) { if (info == null) {
Logger.error("Server info null"); Logger.error("Server info null");
basePath = basePath.resolve("UNKNOWN"); basePath = basePath.resolve("UNKNOWN");
@@ -142,7 +161,7 @@ public class VoxyClientInstance extends VoxyInstance {
if (info.isRealm()) { if (info.isRealm()) {
basePath = basePath.resolve("realms"); basePath = basePath.resolve("realms");
} else { } else {
basePath = basePath.resolve(info.address.replace(":", "_")); basePath = basePath.resolve(info.ip.replace(":", "_"));
} }
} }
} }

View File

@@ -6,19 +6,27 @@ import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem; import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.commonImpl.VoxyCommon; import me.cortex.voxy.commonImpl.VoxyCommon;
import me.cortex.voxy.commonImpl.WorldIdentifier; import me.cortex.voxy.commonImpl.WorldIdentifier;
import me.cortex.voxy.commonImpl.importers.DHImporter; import me.cortex.voxy.commonImpl.importers.DHImporter;
import me.cortex.voxy.commonImpl.importers.WorldImporter; import me.cortex.voxy.commonImpl.importers.WorldImporter;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.Minecraft;
import net.minecraft.command.CommandSource; 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.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Locale;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -52,7 +60,7 @@ public class VoxyCommands {
.executes(VoxyCommands::importDistantHorizons))); .executes(VoxyCommands::importDistantHorizons)));
} }
return ClientCommandManager.literal("voxy").requires((ctx)-> VoxyCommon.getInstance() != null) return ClientCommandManager.literal("voxy")//.requires((ctx)-> VoxyCommon.getInstance() != null)
.then(ClientCommandManager.literal("reload") .then(ClientCommandManager.literal("reload")
.executes(VoxyCommands::reloadInstance)) .executes(VoxyCommands::reloadInstance))
.then(imports); .then(imports);
@@ -61,18 +69,20 @@ public class VoxyCommands {
private static int reloadInstance(CommandContext<FabricClientCommandSource> ctx) { private static int reloadInstance(CommandContext<FabricClientCommandSource> ctx) {
var instance = (VoxyClientInstance)VoxyCommon.getInstance(); var instance = (VoxyClientInstance)VoxyCommon.getInstance();
if (instance == null) { if (instance == null) {
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
return 1; return 1;
} }
var wr = MinecraftClient.getInstance().worldRenderer; var wr = Minecraft.getInstance().levelRenderer;
if (wr!=null) { if (wr!=null) {
((IGetVoxyRenderSystem)wr).shutdownRenderer(); ((IGetVoxyRenderSystem)wr).shutdownRenderer();
} }
VoxyCommon.shutdownInstance(); VoxyCommon.shutdownInstance();
System.gc();
VoxyCommon.createInstance(); VoxyCommon.createInstance();
if (wr!=null) {
((IGetVoxyRenderSystem)wr).createRenderer(); var r = Minecraft.getInstance().levelRenderer;
} if (r != null) r.allChanged();
return 0; return 0;
} }
@@ -82,6 +92,7 @@ public class VoxyCommands {
private static int importDistantHorizons(CommandContext<FabricClientCommandSource> ctx) { private static int importDistantHorizons(CommandContext<FabricClientCommandSource> ctx) {
var instance = (VoxyClientInstance)VoxyCommon.getInstance(); var instance = (VoxyClientInstance)VoxyCommon.getInstance();
if (instance == null) { if (instance == null) {
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
return 1; return 1;
} }
var dbFile = new File(ctx.getArgument("sqlDbPath", String.class)); var dbFile = new File(ctx.getArgument("sqlDbPath", String.class));
@@ -96,10 +107,10 @@ public class VoxyCommands {
} }
File dbFile_ = dbFile; File dbFile_ = dbFile;
var engine = WorldIdentifier.ofEngine(MinecraftClient.getInstance().player.clientWorld); var engine = WorldIdentifier.ofEngine(Minecraft.getInstance().level);
if (engine==null)return 1; if (engine==null)return 1;
return instance.getImportManager().makeAndRunIfNone(engine, ()-> return instance.getImportManager().makeAndRunIfNone(engine, ()->
new DHImporter(dbFile_, engine, MinecraftClient.getInstance().player.clientWorld, instance.getThreadPool(), instance.savingServiceRateLimiter))?0:1; new DHImporter(dbFile_, engine, Minecraft.getInstance().level, instance.getServiceManager(), instance.savingServiceRateLimiter))?0:1;
} }
private static boolean fileBasedImporter(File directory) { private static boolean fileBasedImporter(File directory) {
@@ -108,29 +119,39 @@ public class VoxyCommands {
return false; return false;
} }
var engine = WorldIdentifier.ofEngine(MinecraftClient.getInstance().player.clientWorld); var engine = WorldIdentifier.ofEngine(Minecraft.getInstance().level);
if (engine==null) return false; if (engine==null) return false;
return instance.getImportManager().makeAndRunIfNone(engine, ()->{ return instance.getImportManager().makeAndRunIfNone(engine, ()->{
var importer = new WorldImporter(engine, MinecraftClient.getInstance().player.clientWorld, instance.getThreadPool(), instance.savingServiceRateLimiter); var importer = new WorldImporter(engine, Minecraft.getInstance().level, instance.getServiceManager(), instance.savingServiceRateLimiter);
importer.importRegionDirectoryAsync(directory); importer.importRegionDirectoryAsync(directory);
return importer; return importer;
}); });
} }
private static int importRaw(CommandContext<FabricClientCommandSource> ctx) { 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; return fileBasedImporter(new File(ctx.getArgument("path", String.class)))?0:1;
} }
private static int importBobby(CommandContext<FabricClientCommandSource> ctx) { 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(); var file = new File(".bobby").toPath().resolve(ctx.getArgument("world_name", String.class)).toFile();
return fileBasedImporter(file)?0:1; return fileBasedImporter(file)?0:1;
} }
private static CompletableFuture<Suggestions> importWorldSuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) { private static CompletableFuture<Suggestions> importWorldSuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
return fileDirectorySuggester(MinecraftClient.getInstance().runDirectory.toPath().resolve("saves"), sb); return fileDirectorySuggester(Minecraft.getInstance().gameDirectory.toPath().resolve("saves"), sb);
} }
private static CompletableFuture<Suggestions> importBobbySuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) { private static CompletableFuture<Suggestions> importBobbySuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
return fileDirectorySuggester(MinecraftClient.getInstance().runDirectory.toPath().resolve(".bobby"), sb); return fileDirectorySuggester(Minecraft.getInstance().gameDirectory.toPath().resolve(".bobby"), sb);
} }
private static CompletableFuture<Suggestions> fileDirectorySuggester(Path dir, SuggestionsBuilder sb) { private static CompletableFuture<Suggestions> fileDirectorySuggester(Path dir, SuggestionsBuilder sb) {
@@ -165,7 +186,7 @@ public class VoxyCommands {
if (wn.equals(remaining)) { if (wn.equals(remaining)) {
continue; continue;
} }
if (CommandSource.shouldSuggest(remaining, wn) || CommandSource.shouldSuggest(remaining, '"'+wn)) { if (SharedSuggestionProvider.matchesSubStr(remaining, wn) || SharedSuggestionProvider.matchesSubStr(remaining, '"'+wn)) {
wn = str+wn + "/"; wn = str+wn + "/";
sb.suggest(StringArgumentType.escapeIfRequired(wn)); sb.suggest(StringArgumentType.escapeIfRequired(wn));
} }
@@ -176,16 +197,42 @@ public class VoxyCommands {
} }
private static int importWorld(CommandContext<FabricClientCommandSource> ctx) { 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 name = ctx.getArgument("world_name", String.class);
var file = new File("saves").toPath().resolve(name); var file = new File("saves").toPath().resolve(name);
name = name.toLowerCase(); name = name.toLowerCase(Locale.ROOT);
if (name.endsWith("/")) { if (name.endsWith("/")) {
name = name.substring(0, name.length()-1); name = name.substring(0, name.length()-1);
} }
if (!(name.endsWith("region"))) { if (file.resolve("level.dat").toFile().exists()) {
file = file.resolve("region"); 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;
} }
return fileBasedImporter(file.toFile())?0:1;
} }
private static int importZip(CommandContext<FabricClientCommandSource> ctx) { private static int importZip(CommandContext<FabricClientCommandSource> ctx) {
@@ -197,14 +244,15 @@ public class VoxyCommands {
var instance = (VoxyClientInstance)VoxyCommon.getInstance(); var instance = (VoxyClientInstance)VoxyCommon.getInstance();
if (instance == null) { if (instance == null) {
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
return 1; return 1;
} }
String finalInnerDir = innerDir; String finalInnerDir = innerDir;
var engine = WorldIdentifier.ofEngine(MinecraftClient.getInstance().player.clientWorld); var engine = WorldIdentifier.ofEngine(Minecraft.getInstance().level);
if (engine != null) { if (engine != null) {
return instance.getImportManager().makeAndRunIfNone(engine, () -> { return instance.getImportManager().makeAndRunIfNone(engine, () -> {
var importer = new WorldImporter(engine, MinecraftClient.getInstance().player.clientWorld, instance.getThreadPool(), instance.savingServiceRateLimiter); var importer = new WorldImporter(engine, Minecraft.getInstance().level, instance.getServiceManager(), instance.savingServiceRateLimiter);
importer.importZippedRegionDirectoryAsync(zip, finalInnerDir); importer.importZippedRegionDirectoryAsync(zip, finalInnerDir);
return importer; return importer;
}) ? 0 : 1; }) ? 0 : 1;
@@ -212,12 +260,13 @@ public class VoxyCommands {
return 1; return 1;
} }
private static int cancelImport(CommandContext<FabricClientCommandSource> fabricClientCommandSourceCommandContext) { private static int cancelImport(CommandContext<FabricClientCommandSource> ctx) {
var instance = (VoxyClientInstance)VoxyCommon.getInstance(); var instance = (VoxyClientInstance)VoxyCommon.getInstance();
if (instance == null) { if (instance == null) {
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
return 1; return 1;
} }
var world = WorldIdentifier.ofEngineNullable(MinecraftClient.getInstance().player.clientWorld); var world = WorldIdentifier.ofEngineNullable(Minecraft.getInstance().level);
if (world != null) { if (world != null) {
return instance.getImportManager().cancelImport(world)?0:1; return instance.getImportManager().cancelImport(world)?0:1;
} }

View File

@@ -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);
}
}
}

View File

@@ -6,7 +6,6 @@ import com.moulberry.flashback.record.FlashbackMeta;
import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.config.section.SectionStorageConfig; import me.cortex.voxy.common.config.section.SectionStorageConfig;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import org.apache.commons.logging.Log;
import java.nio.file.Path; import java.nio.file.Path;

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -4,25 +4,18 @@ import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi; import com.terraformersmc.modmenu.api.ModMenuApi;
import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.Logger;
import me.cortex.voxy.commonImpl.VoxyCommon; import me.cortex.voxy.commonImpl.VoxyCommon;
import net.caffeinemc.mods.sodium.client.gui.SodiumOptionsGUI; 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 { public class ModMenuIntegration implements ModMenuApi {
@Override @Override
public ConfigScreenFactory<?> getModConfigScreenFactory() { public ConfigScreenFactory<?> getModConfigScreenFactory() {
return parent -> { return parent -> {
if (VoxyCommon.isAvailable()) { if (VoxyCommon.isAvailable()) {
var screen = (SodiumOptionsGUI) SodiumOptionsGUI.createScreen(parent); var screen = (VideoSettingsScreen)VideoSettingsScreen.createScreen(parent);
//Sorry jelly and douira, please dont hurt me var page = (OptionPage) ConfigManager.CONFIG.getModOptions().stream().filter(a->a.configId().equals("voxy")).findFirst().get().pages().get(0);
try { ((IConfigPageSetter)screen).voxy$setPageJump(page);
//We cant use .setPage() as that invokes rebuildGui, however the screen hasnt been initalized yet
// causing things to crash
var field = SodiumOptionsGUI.class.getDeclaredField("currentPage");
field.setAccessible(true);
field.set(screen, VoxyConfigScreenPages.voxyOptionPage);
field.setAccessible(false);
} catch (Exception e) {
Logger.error("Failed to set the current page to voxy", e);
}
return screen; return screen;
} else { } else {
return null; return null;

View File

@@ -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());
}
}

View 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);
}
}

View File

@@ -1,173 +0,0 @@
package me.cortex.voxy.client.config;
import com.google.common.collect.ImmutableList;
import me.cortex.voxy.client.RenderStatistics;
import me.cortex.voxy.client.VoxyClientInstance;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import me.cortex.voxy.common.util.cpu.CpuLayout;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.caffeinemc.mods.sodium.client.gui.options.*;
import net.caffeinemc.mods.sodium.client.gui.options.control.SliderControl;
import net.caffeinemc.mods.sodium.client.gui.options.control.TickBoxControl;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
import java.util.ArrayList;
import java.util.List;
public abstract class VoxyConfigScreenPages {
private VoxyConfigScreenPages(){}
public static OptionPage voxyOptionPage = null;
public static OptionPage page() {
List<OptionGroup> groups = new ArrayList<>();
VoxyConfig storage = VoxyConfig.CONFIG;
//General
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(boolean.class, storage)
.setName(Text.translatable("voxy.config.general.enabled"))
.setTooltip(Text.translatable("voxy.config.general.enabled.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((s, v)->{
s.enabled = v;
if (v) {
if (VoxyClientInstance.isInGame) {
VoxyCommon.createInstance();
var vrsh = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer;
if (vrsh != null && s.enableRendering) {
vrsh.createRenderer();
}
}
} else {
var vrsh = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer;
if (vrsh != null) {
vrsh.shutdownRenderer();
}
VoxyCommon.shutdownInstance();
}
}, s -> s.enabled)
.build()
).add(OptionImpl.createBuilder(int.class, storage)
.setName(Text.translatable("voxy.config.general.serviceThreads"))
.setTooltip(Text.translatable("voxy.config.general.serviceThreads.tooltip"))
.setControl(opt->new SliderControl(opt, 1,
CpuLayout.CORES.length, //Just do core size as max
//Runtime.getRuntime().availableProcessors(),//Note: this is threads not cores, the default value is half the core count, is fine as this should technically be the limit but CpuLayout.CORES.length is more realistic
1, v->Text.literal(Integer.toString(v))))
.setBinding((s, v)->{
boolean wasEnabled = VoxyCommon.getInstance() != null;
var vrsh = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer;
if (wasEnabled) {
if (vrsh != null) {
vrsh.shutdownRenderer();
}
VoxyCommon.shutdownInstance();
}
s.serviceThreads = v;
if (wasEnabled) {
VoxyCommon.createInstance();
}
}, s -> s.serviceThreads)
.setImpact(OptionImpact.HIGH)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build()
).add(OptionImpl.createBuilder(boolean.class, storage)
.setName(Text.translatable("voxy.config.general.ingest"))
.setTooltip(Text.translatable("voxy.config.general.ingest.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((s, v) -> s.ingestEnabled = v, s -> s.ingestEnabled)
.setImpact(OptionImpact.MEDIUM)
.build()
).build()
);
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(boolean.class, storage)
.setName(Text.translatable("voxy.config.general.rendering"))
.setTooltip(Text.translatable("voxy.config.general.rendering.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((s, v)->{
s.enableRendering = v;
var vrsh = (IGetVoxyRenderSystem)MinecraftClient.getInstance().worldRenderer;
if (vrsh != null) {
if (v) {
vrsh.createRenderer();
} else {
vrsh.shutdownRenderer();
}
}
}, s -> s.enableRendering)
.setImpact(OptionImpact.HIGH)
.build()
).add(OptionImpl.createBuilder(int.class, storage)
.setName(Text.translatable("voxy.config.general.subDivisionSize"))
.setTooltip(Text.translatable("voxy.config.general.subDivisionSize.tooltip"))
.setControl(opt->new SliderControl(opt, 0, SUBDIV_IN_MAX, 1, v->Text.literal(Integer.toString(Math.round(ln2subDiv(v))))))
.setBinding((s, v) -> s.subDivisionSize = ln2subDiv(v), s -> subDiv2ln(s.subDivisionSize))
.setImpact(OptionImpact.HIGH)
.build()
).add(OptionImpl.createBuilder(int.class, storage)
.setName(Text.translatable("voxy.config.general.renderDistance"))
.setTooltip(Text.translatable("voxy.config.general.renderDistance.tooltip"))
.setControl(opt->new SliderControl(opt, 2, 64, 1, v->Text.literal(Integer.toString(v * 32))))//Every unit is equal to 32 vanilla chunks
.setBinding((s, v)-> {
s.sectionRenderDistance = v;
var vrsh = (IGetVoxyRenderSystem)MinecraftClient.getInstance().worldRenderer;
if (vrsh != null) {
var vrs = vrsh.getVoxyRenderSystem();
if (vrs != null) {
vrs.setRenderDistance(v);
}
}
}, s -> s.sectionRenderDistance)
.setImpact(OptionImpact.LOW)
.build()
).add(OptionImpl.createBuilder(boolean.class, storage)
.setName(Text.translatable("voxy.config.general.environmental_fog"))
.setTooltip(Text.translatable("voxy.config.general.environmental_fog.tooltip"))
.setControl(TickBoxControl::new)
.setImpact(OptionImpact.VARIES)
.setBinding((s, v)-> s.useEnvironmentalFog = v, s -> s.useEnvironmentalFog)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build()
).add(OptionImpl.createBuilder(boolean.class, storage)
.setName(Text.translatable("voxy.config.general.vanilla_fog"))
.setTooltip(Text.translatable("voxy.config.general.vanilla_fog.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((s, v)-> s.renderVanillaFog = v, s -> s.renderVanillaFog)
.build()
).add(OptionImpl.createBuilder(boolean.class, storage)
.setName(Text.translatable("voxy.config.general.render_statistics"))
.setTooltip(Text.translatable("voxy.config.general.render_statistics.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((s, v)-> RenderStatistics.enabled = v, s -> RenderStatistics.enabled)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build()
).build()
);
return new OptionPage(Text.translatable("voxy.config.title"), ImmutableList.copyOf(groups));
}
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);
}
}

View File

@@ -2,14 +2,15 @@ package me.cortex.voxy.client.core;
import me.cortex.voxy.client.RenderStatistics; import me.cortex.voxy.client.RenderStatistics;
import me.cortex.voxy.client.TimingStatistics; import me.cortex.voxy.client.TimingStatistics;
import me.cortex.voxy.client.core.gl.Capabilities; import me.cortex.voxy.client.VoxyClient;
import me.cortex.voxy.client.core.model.ModelBakerySubsystem; import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
import me.cortex.voxy.client.core.rendering.Viewport; 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.AsyncNodeManager;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser; 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.hierachical.NodeCleaner;
import me.cortex.voxy.client.core.rendering.post.FullscreenBlit; import me.cortex.voxy.client.core.rendering.post.FullscreenBlit;
import me.cortex.voxy.client.core.rendering.section.AbstractSectionRenderer; 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.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.common.util.TrackedObject; import me.cortex.voxy.common.util.TrackedObject;
import org.joml.Matrix4f; import org.joml.Matrix4f;
@@ -23,7 +24,6 @@ import static org.lwjgl.opengl.GL11C.GL_ALWAYS;
import static org.lwjgl.opengl.GL11C.GL_DEPTH_TEST; import static org.lwjgl.opengl.GL11C.GL_DEPTH_TEST;
import static org.lwjgl.opengl.GL11C.GL_EQUAL; import static org.lwjgl.opengl.GL11C.GL_EQUAL;
import static org.lwjgl.opengl.GL11C.GL_KEEP; import static org.lwjgl.opengl.GL11C.GL_KEEP;
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
import static org.lwjgl.opengl.GL11C.GL_REPLACE; import static org.lwjgl.opengl.GL11C.GL_REPLACE;
import static org.lwjgl.opengl.GL11C.GL_STENCIL_TEST; import static org.lwjgl.opengl.GL11C.GL_STENCIL_TEST;
import static org.lwjgl.opengl.GL11C.glColorMask; import static org.lwjgl.opengl.GL11C.glColorMask;
@@ -32,16 +32,16 @@ import static org.lwjgl.opengl.GL11C.glEnable;
import static org.lwjgl.opengl.GL11C.glStencilFunc; import static org.lwjgl.opengl.GL11C.glStencilFunc;
import static org.lwjgl.opengl.GL11C.glStencilMask; import static org.lwjgl.opengl.GL11C.glStencilMask;
import static org.lwjgl.opengl.GL11C.glStencilOp; 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.GL_FRAMEBUFFER;
import static org.lwjgl.opengl.GL30C.glBindFramebuffer; import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
import static org.lwjgl.opengl.GL42.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL42.GL_LEQUAL; import static org.lwjgl.opengl.GL42.GL_LEQUAL;
import static org.lwjgl.opengl.GL42.GL_NOTEQUAL; import static org.lwjgl.opengl.GL42.GL_NOTEQUAL;
import static org.lwjgl.opengl.GL42.glDepthFunc; import static org.lwjgl.opengl.GL42.glDepthFunc;
import static org.lwjgl.opengl.GL42.*; import static org.lwjgl.opengl.GL42.*;
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi; import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi;
import static org.lwjgl.opengl.GL45.glGetNamedFramebufferAttachmentParameteri;
import static org.lwjgl.opengl.GL45C.glBindTextureUnit; import static org.lwjgl.opengl.GL45C.glBindTextureUnit;
import static org.lwjgl.opengl.GL45C.glBlitNamedFramebuffer;
public abstract class AbstractRenderPipeline extends TrackedObject { public abstract class AbstractRenderPipeline extends TrackedObject {
private final BooleanSupplier frexStillHasWork; private final BooleanSupplier frexStillHasWork;
@@ -54,11 +54,24 @@ public abstract class AbstractRenderPipeline extends TrackedObject {
private final FullscreenBlit depthMaskBlit = new FullscreenBlit("voxy:post/fullscreen2.vert", "voxy:post/noop.frag"); 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 depthSetBlit = new FullscreenBlit("voxy:post/fullscreen2.vert", "voxy:post/depth0.frag");
protected AbstractRenderPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) { 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.frexStillHasWork = frexSupplier;
this.nodeManager = nodeManager; this.nodeManager = nodeManager;
this.nodeCleaner = nodeCleaner; this.nodeCleaner = nodeCleaner;
this.traversal = traversal; this.traversal = traversal;
this.deferTranslucency = deferTranslucency;
} }
//Allows pipelines to configure model baking system //Allows pipelines to configure model baking system
@@ -86,13 +99,20 @@ public abstract class AbstractRenderPipeline extends TrackedObject {
var rs = ((AbstractSectionRenderer)this.sectionRenderer); var rs = ((AbstractSectionRenderer)this.sectionRenderer);
rs.renderOpaque(viewport); rs.renderOpaque(viewport);
this.innerPrimaryWork(viewport, depthTexture); var occlusionDebug = VoxyClient.getOcclusionDebugState();
rs.buildDrawCalls(viewport); if (occlusionDebug==0) {
this.innerPrimaryWork(viewport, depthTexture);
}
if (occlusionDebug<=1) {
rs.buildDrawCalls(viewport);
}
rs.renderTemporal(viewport); rs.renderTemporal(viewport);
this.postOpaquePreTranslucent(viewport); this.postOpaquePreTranslucent(viewport);
rs.renderTranslucent(viewport); if (!this.deferTranslucency) {
rs.renderTranslucent(viewport);
}
this.finish(viewport, sourceFrameBuffer, srcWidth, srcHeight); this.finish(viewport, sourceFrameBuffer, srcWidth, srcHeight);
glBindFramebuffer(GL_FRAMEBUFFER, sourceFrameBuffer); glBindFramebuffer(GL_FRAMEBUFFER, sourceFrameBuffer);
@@ -100,10 +120,18 @@ public abstract class AbstractRenderPipeline extends TrackedObject {
protected void initDepthStencil(int sourceFrameBuffer, int targetFb, int srcWidth, int srcHeight, int width, int height) { protected void initDepthStencil(int sourceFrameBuffer, int targetFb, int srcWidth, int srcHeight, int width, int height) {
glClearNamedFramebufferfi(targetFb, GL_DEPTH_STENCIL, 0, 1.0f, 1); glClearNamedFramebufferfi(targetFb, GL_DEPTH_STENCIL, 0, 1.0f, 1);
glBlitNamedFramebuffer(sourceFrameBuffer, targetFb, 0,0, srcWidth, srcHeight, 0,0, width, height, GL_DEPTH_BUFFER_BIT, GL_NEAREST); // 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); 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){ if (Capabilities.INSTANCE.isMesa){
glClearStencil(1); glClearStencil(1);
@@ -120,7 +148,6 @@ public abstract class AbstractRenderPipeline extends TrackedObject {
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_NOTEQUAL);//If != 1 pass glDepthFunc(GL_NOTEQUAL);//If != 1 pass
glColorMask(false,false,false,false);
//We do here //We do here
this.depthMaskBlit.blit(); this.depthMaskBlit.blit();
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
@@ -141,6 +168,9 @@ public abstract class AbstractRenderPipeline extends TrackedObject {
private static final long SCRATCH = MemoryUtil.nmemAlloc(4*4*4); private static final long SCRATCH = MemoryUtil.nmemAlloc(4*4*4);
protected static void transformBlitDepth(FullscreenBlit blitShader, int srcDepthTex, int dstFB, Viewport<?> viewport, Matrix4f targetTransform) { 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); glBindFramebuffer(GL30.GL_FRAMEBUFFER, dstFB);
blitShader.bind(); blitShader.bind();
@@ -151,7 +181,6 @@ public abstract class AbstractRenderPipeline extends TrackedObject {
nglUniformMatrix4fv(2, 1, false, SCRATCH);//tooProjection nglUniformMatrix4fv(2, 1, false, SCRATCH);//tooProjection
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
//We keep the stencil test on with the emitting, only to where non terrain is rendered
blitShader.blit(); blitShader.blit();
glDisable(GL_STENCIL_TEST); glDisable(GL_STENCIL_TEST);
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
@@ -181,17 +210,19 @@ public abstract class AbstractRenderPipeline extends TrackedObject {
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT | GL_PIXEL_BUFFER_BARRIER_BIT); glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT | GL_PIXEL_BUFFER_BARRIER_BIT);
TimingStatistics.I.start(); TimingStatistics.F.start();
this.traversal.doTraversal(viewport); this.traversal.doTraversal(viewport);
TimingStatistics.I.stop(); TimingStatistics.F.stop();
} while (this.frexStillHasWork.getAsBoolean()); } while (this.frexStillHasWork.getAsBoolean());
} }
@Override @Override
protected void free0() { protected void free0() {
this.fb.free();
this.sectionRenderer.free(); this.sectionRenderer.free();
this.depthMaskBlit.delete(); this.depthMaskBlit.delete();
this.depthSetBlit.delete(); this.depthSetBlit.delete();
this.depthCopy.delete();
super.free0(); super.free0();
} }

View File

@@ -1,7 +1,15 @@
package me.cortex.voxy.client.core; package me.cortex.voxy.client.core;
import net.minecraft.client.Minecraft;
public interface IGetVoxyRenderSystem { public interface IGetVoxyRenderSystem {
VoxyRenderSystem getVoxyRenderSystem(); VoxyRenderSystem getVoxyRenderSystem();
void shutdownRenderer(); void shutdownRenderer();
void createRenderer(); void createRenderer();
static VoxyRenderSystem getNullable() {
var lr = (IGetVoxyRenderSystem)Minecraft.getInstance().levelRenderer;
if (lr == null) return null;
return lr.getVoxyRenderSystem();
}
} }

View File

@@ -7,7 +7,7 @@ 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.HierarchicalOcclusionTraverser;
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner; 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.post.FullscreenBlit;
import me.cortex.voxy.client.core.rendering.section.AbstractSectionRenderer; 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.DepthFramebuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream; import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.iris.IrisVoxyRenderPipelineData; import me.cortex.voxy.client.iris.IrisVoxyRenderPipelineData;
@@ -26,13 +26,12 @@ import static org.lwjgl.opengl.GL45C.*;
public class IrisVoxyRenderPipeline extends AbstractRenderPipeline { public class IrisVoxyRenderPipeline extends AbstractRenderPipeline {
private final IrisVoxyRenderPipelineData data; private final IrisVoxyRenderPipelineData data;
private final FullscreenBlit depthBlit = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag"); private final FullscreenBlit depthBlit = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag");
public final DepthFramebuffer fb = new DepthFramebuffer(GL_DEPTH24_STENCIL8); public final DepthFramebuffer fbTranslucent = new DepthFramebuffer(this.fb.getFormat());
public final DepthFramebuffer fbTranslucent = new DepthFramebuffer(GL_DEPTH24_STENCIL8);
private final GlBuffer shaderUniforms; private final GlBuffer shaderUniforms;
public IrisVoxyRenderPipeline(IrisVoxyRenderPipelineData data, AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) { public IrisVoxyRenderPipeline(IrisVoxyRenderPipelineData data, AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
super(nodeManager, nodeCleaner, traversal, frexSupplier); super(nodeManager, nodeCleaner, traversal, frexSupplier, data.shouldDeferTranslucency());
this.data = data; this.data = data;
if (this.data.thePipeline != null) { if (this.data.thePipeline != null) {
throw new IllegalStateException("Pipeline data already bound"); throw new IllegalStateException("Pipeline data already bound");
@@ -79,7 +78,6 @@ public class IrisVoxyRenderPipeline extends AbstractRenderPipeline {
this.data.thePipeline = null; this.data.thePipeline = null;
this.depthBlit.delete(); this.depthBlit.delete();
this.fb.free();
this.fbTranslucent.free(); this.fbTranslucent.free();
if (this.shaderUniforms != null) { if (this.shaderUniforms != null) {
@@ -113,7 +111,7 @@ public class IrisVoxyRenderPipeline extends AbstractRenderPipeline {
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
} }
if (this.data.useViewportDims) { if (!this.data.useViewportDims) {
srcWidth = viewport.width; srcWidth = viewport.width;
srcHeight = viewport.height; srcHeight = viewport.height;
} }
@@ -144,6 +142,10 @@ public class IrisVoxyRenderPipeline extends AbstractRenderPipeline {
this.fbTranslucent.getDepthTex().id, sourceFrameBuffer, this.fbTranslucent.getDepthTex().id, sourceFrameBuffer,
viewport, new Matrix4f(viewport.vanillaProjection).mul(viewport.modelView)); viewport, new Matrix4f(viewport.vanillaProjection).mul(viewport.modelView));
glColorMask(true, true, true, true); 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);
} }
} }

View File

@@ -1,6 +1,6 @@
package me.cortex.voxy.client.core; package me.cortex.voxy.client.core;
import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.client.core.gl.GlFramebuffer; import me.cortex.voxy.client.core.gl.GlFramebuffer;
import me.cortex.voxy.client.core.gl.GlTexture; 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.Shader;
@@ -11,7 +11,7 @@ import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTra
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner; 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.post.FullscreenBlit;
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer; import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.Minecraft;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
@@ -37,7 +37,6 @@ public class NormalRenderPipeline extends AbstractRenderPipeline {
private GlTexture colourTex; private GlTexture colourTex;
private GlTexture colourSSAOTex; private GlTexture colourSSAOTex;
private final GlFramebuffer fbSSAO = new GlFramebuffer(); private final GlFramebuffer fbSSAO = new GlFramebuffer();
private final DepthFramebuffer fb = new DepthFramebuffer(GL_DEPTH24_STENCIL8);
private final boolean useEnvFog; private final boolean useEnvFog;
private final FullscreenBlit finalBlit; private final FullscreenBlit finalBlit;
@@ -47,7 +46,7 @@ public class NormalRenderPipeline extends AbstractRenderPipeline {
.compile(); .compile();
protected NormalRenderPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) { protected NormalRenderPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
super(nodeManager, nodeCleaner, traversal, frexSupplier); super(nodeManager, nodeCleaner, traversal, frexSupplier, false);
this.useEnvFog = VoxyConfig.CONFIG.useEnvironmentalFog; this.useEnvFog = VoxyConfig.CONFIG.useEnvironmentalFog;
this.finalBlit = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag", this.finalBlit = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag",
a->a.defineIf("USE_ENV_FOG", this.useEnvFog).define("EMIT_COLOUR")); a->a.defineIf("USE_ENV_FOG", this.useEnvFog).define("EMIT_COLOUR"));
@@ -66,7 +65,7 @@ public class NormalRenderPipeline extends AbstractRenderPipeline {
this.colourSSAOTex = 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.fb.framebuffer.bind(GL_COLOR_ATTACHMENT0, this.colourTex).verify();
this.fbSSAO.bind(GL_DEPTH_STENCIL_ATTACHMENT, this.fb.getDepthTex()).bind(GL_COLOR_ATTACHMENT0, this.colourSSAOTex).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_MIN_FILTER, GL_NEAREST);
@@ -108,10 +107,17 @@ public class NormalRenderPipeline extends AbstractRenderPipeline {
if (this.useEnvFog) { if (this.useEnvFog) {
float start = viewport.fogParameters.environmentalStart(); float start = viewport.fogParameters.environmentalStart();
float end = viewport.fogParameters.environmentalEnd(); float end = viewport.fogParameters.environmentalEnd();
float invEndFogDelta = 1f/(end-start); if (Math.abs(end-start)>1) {
float endDistance = MinecraftClient.getInstance().gameRenderer.getViewDistanceBlocks()*1.5f; float invEndFogDelta = 1f / (end - start);
glUniform3f(4, endDistance, invEndFogDelta, Math.abs(start)*invEndFogDelta); float endDistance = Math.max(Minecraft.getInstance().gameRenderer.getRenderDistance(), 20*16);//TODO: make this constant a config option
glUniform3f(5, viewport.fogParameters.red(), viewport.fogParameters.green(), viewport.fogParameters.blue()); 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); glBindTextureUnit(3, this.colourSSAOTex.id);
@@ -139,7 +145,6 @@ public class NormalRenderPipeline extends AbstractRenderPipeline {
public void free() { public void free() {
this.finalBlit.delete(); this.finalBlit.delete();
this.ssaoCompute.free(); this.ssaoCompute.free();
this.fb.free();
this.fbSSAO.free(); this.fbSSAO.free();
if (this.colourTex != null) { if (this.colourTex != null) {
this.colourTex.free(); this.colourTex.free();

View File

@@ -4,7 +4,7 @@ import com.mojang.blaze3d.opengl.GlConst;
import com.mojang.blaze3d.opengl.GlStateManager; import com.mojang.blaze3d.opengl.GlStateManager;
import me.cortex.voxy.client.TimingStatistics; import me.cortex.voxy.client.TimingStatistics;
import me.cortex.voxy.client.VoxyClient; import me.cortex.voxy.client.VoxyClient;
import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.client.core.gl.Capabilities; import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture; import me.cortex.voxy.client.core.gl.GlTexture;
@@ -18,9 +18,9 @@ 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.AsyncNodeManager;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser; 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.hierachical.NodeCleaner;
import me.cortex.voxy.client.core.rendering.section.AbstractSectionRenderer; import me.cortex.voxy.client.core.rendering.section.IUsesMeshlets;
import me.cortex.voxy.client.core.rendering.section.MDICSectionRenderer; import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
import me.cortex.voxy.client.core.rendering.section.MeshEXTSectionRenderer; 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.BasicSectionGeometryData;
import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData; 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.DownloadStream;
@@ -29,12 +29,12 @@ import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.core.util.GPUTiming; import me.cortex.voxy.client.core.util.GPUTiming;
import me.cortex.voxy.client.core.util.IrisUtil; import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.thread.ServiceThreadPool; import me.cortex.voxy.common.thread.ServiceManager;
import me.cortex.voxy.common.world.WorldEngine; import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.commonImpl.VoxyCommon; import me.cortex.voxy.commonImpl.VoxyCommon;
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices; import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
import net.caffeinemc.mods.sodium.client.util.FogParameters; import net.caffeinemc.mods.sodium.client.util.FogParameters;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.Minecraft;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Matrix4fc; import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
@@ -50,6 +50,9 @@ import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER; import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL43C.GL_SHADER_STORAGE_BUFFER_BINDING; 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 { public class VoxyRenderSystem {
private final WorldEngine worldIn; private final WorldEngine worldIn;
@@ -69,16 +72,20 @@ public class VoxyRenderSystem {
private final AbstractRenderPipeline pipeline; private final AbstractRenderPipeline pipeline;
private static AbstractSectionRenderer<?,?> createSectionRenderer(AbstractRenderPipeline pipeline, ModelStore modelStore, IGeometryData geometryData) { 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 //TODO: need todo a thing where selects optimal section render based on if supports the pipeline and geometry data type
//return new MDICSectionRenderer(pipeline, modelStore, (BasicSectionGeometryData) geometryData);//We only have MDIC backend... for now return MDICSectionRenderer.FACTORY;
return new MeshEXTSectionRenderer(pipeline, modelStore, (BasicSectionGeometryData) geometryData);
} }
public VoxyRenderSystem(WorldEngine world, ServiceThreadPool threadPool) { 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 //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 // than timeout, we keep the world acquired
world.acquireRef(); 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 //Fking HATE EVERYTHING AAAAAAAAAAAAAAAA
int[] oldBufferBindings = new int[10]; int[] oldBufferBindings = new int[10];
@@ -94,11 +101,11 @@ public class VoxyRenderSystem {
this.worldIn = world; this.worldIn = world;
long geometryCapacity = getGeometryBufferSize(); long geometryCapacity = getGeometryBufferSize();
var backendFactory = getRenderBackendFactory();
{ {
this.modelService = new ModelBakerySubsystem(world.getMapper()); this.modelService = new ModelBakerySubsystem(world.getMapper());
this.renderGen = new RenderGenerationService(world, this.modelService, threadPool, false, () -> true); this.renderGen = new RenderGenerationService(world, this.modelService, sm, IUsesMeshlets.class.isAssignableFrom(backendFactory.clz()));
this.geometryData = new BasicSectionGeometryData(1 << 20, geometryCapacity); this.geometryData = new BasicSectionGeometryData(1 << 20, geometryCapacity);
@@ -116,13 +123,13 @@ public class VoxyRenderSystem {
this.pipeline = RenderPipelineFactory.createPipeline(this.nodeManager, this.nodeCleaner, this.traversal, this::frexStillHasWork); this.pipeline = RenderPipelineFactory.createPipeline(this.nodeManager, this.nodeCleaner, this.traversal, this::frexStillHasWork);
this.pipeline.setupExtraModelBakeryData(this.modelService);//Configure the model service this.pipeline.setupExtraModelBakeryData(this.modelService);//Configure the model service
var sectionRenderer = createSectionRenderer(this.pipeline, this.modelService.getStore(), this.geometryData); var sectionRenderer = backendFactory.create(this.pipeline, this.modelService.getStore(), this.geometryData);
this.pipeline.setSectionRenderer(sectionRenderer); this.pipeline.setSectionRenderer(sectionRenderer);
this.viewportSelector = new ViewportSelector<>(sectionRenderer::createViewport); this.viewportSelector = new ViewportSelector<>(sectionRenderer::createViewport);
{ {
int minSec = MinecraftClient.getInstance().world.getBottomSectionCoord() >> 5; int minSec = Minecraft.getInstance().level.getMinSectionY() >> 5;
int maxSec = (MinecraftClient.getInstance().world.getTopSectionCoord() - 1) >> 5; int maxSec = (Minecraft.getInstance().level.getMaxSectionY() - 1) >> 5;
//Do some very cheeky stuff for MiB //Do some very cheeky stuff for MiB
if (VoxyCommon.IS_MINE_IN_ABYSS) {//TODO: make this somehow configurable if (VoxyCommon.IS_MINE_IN_ABYSS) {//TODO: make this somehow configurable
@@ -136,7 +143,7 @@ public class VoxyRenderSystem {
this.nodeManager::addTopLevel, this.nodeManager::addTopLevel,
this.nodeManager::removeTopLevel); this.nodeManager::removeTopLevel);
this.renderDistanceTracker.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance); this.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
} }
this.chunkBoundRenderer = new ChunkBoundRenderer(this.pipeline); this.chunkBoundRenderer = new ChunkBoundRenderer(this.pipeline);
@@ -150,6 +157,12 @@ public class VoxyRenderSystem {
for (int i = 0; i < oldBufferBindings.length; i++) { for (int i = 0; i < oldBufferBindings.length; i++) {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, i, oldBufferBindings[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);
}
} }
@@ -193,7 +206,10 @@ public class VoxyRenderSystem {
.setScreenSize(width, height) .setScreenSize(width, height)
.setFogParameters(fogParameters) .setFogParameters(fogParameters)
.update(); .update();
viewport.frameId++;
if (VoxyClient.getOcclusionDebugState()==0) {
viewport.frameId++;
}
return viewport; return viewport;
} }
@@ -236,7 +252,7 @@ public class VoxyRenderSystem {
this.pipeline.preSetup(viewport); this.pipeline.preSetup(viewport);
TimingStatistics.E.start(); TimingStatistics.E.start();
if (!IrisUtil.irisShadowActive()) { if ((!VoxyClient.disableSodiumChunkRender())&&!IrisUtil.irisShadowActive()) {
this.chunkBoundRenderer.render(viewport); this.chunkBoundRenderer.render(viewport);
} else { } else {
viewport.depthBoundingBuffer.clear(0); viewport.depthBoundingBuffer.clear(0);
@@ -244,8 +260,10 @@ public class VoxyRenderSystem {
TimingStatistics.E.stop(); TimingStatistics.E.stop();
GPUTiming.INSTANCE.marker();
//The entire rendering pipeline (excluding the chunkbound thing) //The entire rendering pipeline (excluding the chunkbound thing)
this.pipeline.runPipeline(viewport, boundFB, dims[2], dims[3]); this.pipeline.runPipeline(viewport, boundFB, dims[2], dims[3]);
GPUTiming.INSTANCE.marker();
TimingStatistics.main.stop(); TimingStatistics.main.stop();
@@ -259,9 +277,10 @@ public class VoxyRenderSystem {
UploadStream.INSTANCE.tick(); UploadStream.INSTANCE.tick();
while (this.renderDistanceTracker.setCenterAndProcess(viewport.cameraX, viewport.cameraZ) && VoxyClient.isFrexActive());//While FF is active, run until everything is processed 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 //Done here as is allows less gl state resetup
this.modelService.tick(Math.max(3_000_000-(System.nanoTime()-startTime), 500_000)); do { this.modelService.tick(900_000); } while (VoxyClient.isFrexActive() && !this.modelService.areQueuesEmpty());
TimingStatistics.H.stop();
} }
GPUTiming.INSTANCE.marker(); GPUTiming.INSTANCE.marker();
TimingStatistics.postDynamic.stop(); TimingStatistics.postDynamic.stop();
@@ -297,6 +316,10 @@ public class VoxyRenderSystem {
TimingStatistics.all.stop(); TimingStatistics.all.stop();
//TimingStatistics.I.start();
//glFlush();
//TimingStatistics.I.stop();
/* /*
TimingStatistics.F.start(); TimingStatistics.F.start();
this.postProcessing.setup(viewport.width, viewport.height, boundFB); this.postProcessing.setup(viewport.width, viewport.height, boundFB);
@@ -333,12 +356,12 @@ public class VoxyRenderSystem {
float INCREASE_PER_SECOND = 60; float INCREASE_PER_SECOND = 60;
float DECREASE_PER_SECOND = 30; float DECREASE_PER_SECOND = 30;
//Auto fps targeting //Auto fps targeting
if (MinecraftClient.getInstance().getCurrentFps() < MIN_FPS) { if (Minecraft.getInstance().getFps() < MIN_FPS) {
VoxyConfig.CONFIG.subDivisionSize = Math.min(VoxyConfig.CONFIG.subDivisionSize + INCREASE_PER_SECOND / Math.max(1f, MinecraftClient.getInstance().getCurrentFps()), 256); VoxyConfig.CONFIG.subDivisionSize = Math.min(VoxyConfig.CONFIG.subDivisionSize + INCREASE_PER_SECOND / Math.max(1f, Minecraft.getInstance().getFps()), 256);
} }
if (MAX_FPS < MinecraftClient.getInstance().getCurrentFps() && canDecreaseSize) { if (MAX_FPS < Minecraft.getInstance().getFps() && canDecreaseSize) {
VoxyConfig.CONFIG.subDivisionSize = Math.max(VoxyConfig.CONFIG.subDivisionSize - DECREASE_PER_SECOND / Math.max(1f, MinecraftClient.getInstance().getCurrentFps()), 28); VoxyConfig.CONFIG.subDivisionSize = Math.max(VoxyConfig.CONFIG.subDivisionSize - DECREASE_PER_SECOND / Math.max(1f, Minecraft.getInstance().getFps()), 28);
} }
} }
@@ -346,23 +369,30 @@ public class VoxyRenderSystem {
//TODO: use the existing projection matrix use mulLocal by the inverse of the projection and then mulLocal our projection //TODO: use the existing projection matrix use mulLocal by the inverse of the projection and then mulLocal our projection
var projection = new Matrix4f(); var projection = new Matrix4f();
var client = MinecraftClient.getInstance(); var client = Minecraft.getInstance();
var gameRenderer = client.gameRenderer;//tickCounter.getTickDelta(true); var gameRenderer = client.gameRenderer;//tickCounter.getTickDelta(true);
float fov = gameRenderer.getFov(gameRenderer.getCamera(), client.getRenderTickCounter().getTickProgress(true), true); float fov = gameRenderer.getFov(gameRenderer.getMainCamera(), client.getDeltaTracker().getGameTimeDeltaPartialTick(true), true);
projection.setPerspective(fov * 0.01745329238474369f, projection.setPerspective(fov * 0.01745329238474369f,
(float) client.getWindow().getFramebufferWidth() / (float)client.getWindow().getFramebufferHeight(), (float) client.getWindow().getWidth() / (float)client.getWindow().getHeight(),
near, far); near, far);
return projection; return projection;
} }
//TODO: Make a reverse z buffer //TODO: Make a reverse z buffer
private static Matrix4f computeProjectionMat(Matrix4fc base) { 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( return base.mulLocal(
makeProjectionMatrix(0.05f, MinecraftClient.getInstance().gameRenderer.getFarPlaneDistance()).invert(), makeProjectionMatrix(0.05f, Minecraft.getInstance().gameRenderer.getDepthFar()).invert(),
new Matrix4f() new Matrix4f()
).mulLocal(makeProjectionMatrix(16, 16*3000)); ).mulLocal(makeProjectionMatrix(nearVoxy, 16*3000));
} }
private boolean frexStillHasWork() { private boolean frexStillHasWork() {
@@ -406,6 +436,15 @@ public class VoxyRenderSystem {
debug.add("Extra time: " + TimingStatistics.A.pVal() + ", " + TimingStatistics.B.pVal() + ", " + TimingStatistics.C.pVal() + ", " + TimingStatistics.D.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("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); PrintfDebugUtil.addToOut(debug);
} }

View File

@@ -1,12 +1,30 @@
package me.cortex.voxy.client.core.gl; package me.cortex.voxy.client.core.gl;
import me.cortex.voxy.client.core.gl.shader.ShaderType; import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.common.Logger;
import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.opengl.GL20C; import org.lwjgl.opengl.GL20C;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.GL11.*; 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.GL32.glGetInteger64;
import static org.lwjgl.opengl.GL43C.GL_MAX_SHADER_STORAGE_BLOCK_SIZE; 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.*; import static org.lwjgl.opengl.NVXGPUMemoryInfo.*;
public class Capabilities { public class Capabilities {
@@ -27,6 +45,9 @@ public class Capabilities {
public final boolean subgroup; public final boolean subgroup;
public final boolean sparseBuffer; public final boolean sparseBuffer;
public final boolean isNvidia; public final boolean isNvidia;
public final boolean isAmd;
public final boolean nvBarryCoords;
public final boolean hasBrokenDepthSampler;
public Capabilities() { public Capabilities() {
var cap = GL.getCapabilities(); var cap = GL.getCapabilities();
@@ -62,9 +83,11 @@ public class Capabilities {
this.ssboMaxSize = glGetInteger64(GL_MAX_SHADER_STORAGE_BLOCK_SIZE); this.ssboMaxSize = glGetInteger64(GL_MAX_SHADER_STORAGE_BLOCK_SIZE);
this.isMesa = glGetString(GL_VERSION).toLowerCase().contains("mesa"); this.isMesa = glGetString(GL_VERSION).toLowerCase(Locale.ROOT).contains("mesa");
this.isIntel = glGetString(GL_VENDOR).toLowerCase().contains("intel"); var vendor = glGetString(GL_VENDOR).toLowerCase(Locale.ROOT);
this.isNvidia = glGetString(GL_VENDOR).toLowerCase().contains("nvidia"); this.isIntel = vendor.contains("intel");
this.isNvidia = vendor.contains("nvidia");
this.isAmd = vendor.contains("amd")||vendor.contains("radeon");
if (this.canQueryGpuMemory) { if (this.canQueryGpuMemory) {
this.totalDedicatedMemory = glGetInteger64(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX)*1024;//Since its in Kb this.totalDedicatedMemory = glGetInteger64(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX)*1024;//Since its in Kb
@@ -73,11 +96,106 @@ public class Capabilities {
this.totalDedicatedMemory = -1; this.totalDedicatedMemory = -1;
this.totalDynamicMemory = -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() { 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) { private static boolean testShaderCompilesOk(ShaderType type, String src) {
int shader = GL20C.glCreateShader(type.gl); int shader = GL20C.glCreateShader(type.gl);
GL20C.glShaderSource(shader, src); GL20C.glShaderSource(shader, src);

View File

@@ -1,25 +0,0 @@
package me.cortex.voxy.client.core.gl;
import org.lwjgl.opengl.GL;
import org.lwjgl.system.JNI;
public class EXTMeshShader {
public static final int
GL_MESH_SHADER_EXT = 0x9559,
GL_TASK_SHADER_EXT = 0x955A;
private static final long glDrawMeshTasksIndirectEXT_ptr;
static {
if (GL.getFunctionProvider() == null) {
throw new IllegalStateException("Class must be initalized after gl context has been created");
}
glDrawMeshTasksIndirectEXT_ptr = GL.getFunctionProvider().getFunctionAddress("glDrawMeshTasksIndirectEXT");
}
public static void glDrawMeshTasksIndirectEXT(long indirect) {
if (glDrawMeshTasksIndirectEXT_ptr == 0) {
throw new IllegalStateException("glDrawMeshTasksIndirectEXT not supported");
}
JNI.callPV(indirect, glDrawMeshTasksIndirectEXT_ptr);
}
}

View File

@@ -12,6 +12,7 @@ import static org.lwjgl.opengl.GL45C.*;
public class GlBuffer extends TrackedObject { public class GlBuffer extends TrackedObject {
public final int id; public final int id;
private final long size; private final long size;
private final int flags;
private static int COUNT; private static int COUNT;
private static long TOTAL_SIZE; private static long TOTAL_SIZE;
@@ -28,6 +29,7 @@ public class GlBuffer extends TrackedObject {
} }
public GlBuffer(long size, int flags, boolean zero) { public GlBuffer(long size, int flags, boolean zero) {
this.flags = flags;
this.id = glCreateBuffers(); this.id = glCreateBuffers();
this.size = size; this.size = size;
glNamedBufferStorage(this.id, size, flags); glNamedBufferStorage(this.id, size, flags);
@@ -48,6 +50,10 @@ public class GlBuffer extends TrackedObject {
TOTAL_SIZE -= this.size; TOTAL_SIZE -= this.size;
} }
public boolean isSparse() {
return (this.flags&GL_SPARSE_STORAGE_BIT_ARB)!=0;
}
public long size() { public long size() {
return this.size; return this.size;
} }

View File

@@ -3,6 +3,7 @@ package me.cortex.voxy.client.core.gl;
import me.cortex.voxy.common.util.TrackedObject; import me.cortex.voxy.common.util.TrackedObject;
import static org.lwjgl.opengl.GL45C.*; import static org.lwjgl.opengl.GL45C.*;
import static org.lwjgl.opengl.GL45C.glNamedFramebufferDrawBuffers;
public class GlFramebuffer extends TrackedObject { public class GlFramebuffer extends TrackedObject {
public final int id; public final int id;
@@ -24,6 +25,11 @@ public class GlFramebuffer extends TrackedObject {
return this; return this;
} }
public GlFramebuffer setDrawBuffers(int... buffers) {
glNamedFramebufferDrawBuffers(this.id, buffers);
return this;
}
@Override @Override
public void free() { public void free() {
super.free0(); super.free0();

View File

@@ -96,10 +96,15 @@ public class GlTexture extends TrackedObject {
return this.levels; return this.levels;
} }
public int getFormat() {
this.assertAllocated();
return this.format;
}
private long getEstimatedSize() { private long getEstimatedSize() {
this.assertAllocated(); this.assertAllocated();
long elemSize = switch (this.format) { long elemSize = switch (this.format) {
case GL_RGBA8, GL_DEPTH24_STENCIL8, GL_R32F -> 4; case GL_R32UI, GL_RGBA8, GL_DEPTH24_STENCIL8, GL_R32F -> 4;
case GL_DEPTH_COMPONENT24 -> 4;//TODO: check this is right???? case GL_DEPTH_COMPONENT24 -> 4;//TODO: check this is right????
case GL_DEPTH_COMPONENT32F -> 4; case GL_DEPTH_COMPONENT32F -> 4;
case GL_DEPTH_COMPONENT32 -> 4; case GL_DEPTH_COMPONENT32 -> 4;

View File

@@ -69,6 +69,7 @@ public class Shader extends TrackedObject {
J make(Builder<J> builder, int program); J make(Builder<J> builder, int program);
} }
final Map<String, String> defines = new HashMap<>(); final Map<String, String> defines = new HashMap<>();
final Map<String, String> replacements = new LinkedHashMap<>();
private final Map<ShaderType, String> sources = new HashMap<>(); private final Map<ShaderType, String> sources = new HashMap<>();
private final IShaderProcessor processor; private final IShaderProcessor processor;
private final IShaderObjectConstructor<T> constructor; private final IShaderObjectConstructor<T> constructor;
@@ -109,11 +110,21 @@ public class Shader extends TrackedObject {
return this; return this;
} }
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) { public Builder<T> define(String name, String value) {
this.defines.put(name, value); this.defines.put(name, value);
return this; 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) { public Builder<T> add(ShaderType type, String id) {
this.addSource(type, ShaderLoader.parse(id)); this.addSource(type, ShaderLoader.parse(id));
return this; return this;
@@ -139,6 +150,10 @@ public class Shader extends TrackedObject {
defs defs
+ src.substring(src.indexOf('\n')+1); + 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); shaders[i++] = createShader(entry.getKey(), src);
} }
} }

View File

@@ -6,7 +6,7 @@ import net.caffeinemc.mods.sodium.client.gl.shader.ShaderParser;
public class ShaderLoader { public class ShaderLoader {
public static String parse(String id) { 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)); //return me.jellysquid.mods.sodium.client.gl.shader.ShaderLoader.getShaderSource(new Identifier(id));
} }
} }

View 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) {
}
}

View File

@@ -4,10 +4,6 @@ package me.cortex.voxy.client.core.model;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.world.other.Mapper; import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.client.MinecraftClient;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.util.Identifier;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -27,49 +23,31 @@ public class ModelBakerySubsystem {
private final Mapper mapper; private final Mapper mapper;
private final AtomicInteger blockIdCount = new AtomicInteger(); private final AtomicInteger blockIdCount = new AtomicInteger();
private final ConcurrentLinkedDeque<Integer> blockIdQueue = new ConcurrentLinkedDeque<>();//TODO: replace with custom DS private final ConcurrentLinkedDeque<Integer> blockIdQueue = new ConcurrentLinkedDeque<>();//TODO: replace with custom DS
private final ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeQueue = new ConcurrentLinkedDeque<>();
private final Thread processingThread;
private volatile boolean isRunning = true;
public ModelBakerySubsystem(Mapper mapper) { public ModelBakerySubsystem(Mapper mapper) {
this.mapper = mapper; this.mapper = mapper;
this.factory = new ModelFactory(mapper, this.storage); 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) { public void tick(long totalBudget) {
//Upload all biomes long start = System.nanoTime();
while (!this.biomeQueue.isEmpty()) { this.factory.tickAndProcessUploads();
var biome = this.biomeQueue.poll(); //Always do 1 iteration minimum
var biomeReg = MinecraftClient.getInstance().world.getRegistryManager().getOrThrow(RegistryKeys.BIOME); Integer i = this.blockIdQueue.poll();
this.factory.addBiome(biome.id, biomeReg.get(Identifier.of(biome.biome))); if (i != null) {
}
/*
//There should be a method to access the frame time IIRC, if the user framecap is unlimited lock it to like 60 fps for computation
int BUDGET = 16;//TODO: make this computed based on the remaining free time in a frame (and like div by 2 to reduce overhead) (with a min of 1)
if (!this.blockIdQueue.isEmpty()) {
int[] est = new int[Math.min(this.blockIdQueue.size(), BUDGET)];
int i = 0;
synchronized (this.blockIdQueue) {
for (;i < est.length && !this.blockIdQueue.isEmpty(); i++) {
int blockId = this.blockIdQueue.removeFirstInt();
if (blockId == -1) {
i--;
continue;
}
est[i] = blockId;
}
}
for (int j = 0; j < i; j++) {
this.factory.addEntry(est[j]);
}
}*/
//TimingStatistics.modelProcess.start();
if (this.blockIdCount.get() != 0) {
long budget = Math.min(totalBudget-150_000, totalBudget-(this.factory.resultJobs.size()*10_000L))-150_000;
//Always do 1 iteration minimum
Integer i = this.blockIdQueue.poll();
int j = 0; int j = 0;
if (i != null) { if (i != null) {
int fbBinding = glGetInteger(GL_FRAMEBUFFER_BINDING); int fbBinding = glGetInteger(GL_FRAMEBUFFER_BINDING);
@@ -77,7 +55,7 @@ public class ModelBakerySubsystem {
do { do {
this.factory.addEntry(i); this.factory.addEntry(i);
j++; j++;
if (24<j)//budget<(System.nanoTime() - start)+1000 if (4<j&&(totalBudget<(System.nanoTime() - start)+50_000))//20<j||
break; break;
i = this.blockIdQueue.poll(); i = this.blockIdQueue.poll();
} while (i != null); } while (i != null);
@@ -87,18 +65,17 @@ public class ModelBakerySubsystem {
this.blockIdCount.addAndGet(-j); this.blockIdCount.addAndGet(-j);
} }
this.factory.tick();
long start = System.nanoTime();
while (!this.factory.resultJobs.isEmpty()) {
this.factory.resultJobs.poll().run();
if (totalBudget<(System.nanoTime()-start))
break;
}
//TimingStatistics.modelProcess.stop(); //TimingStatistics.modelProcess.stop();
} }
public void shutdown() { public void shutdown() {
this.isRunning = false;
try {
this.processingThread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.factory.free(); this.factory.free();
this.storage.free(); this.storage.free();
} }
@@ -122,7 +99,7 @@ public class ModelBakerySubsystem {
} }
public void addBiome(Mapper.BiomeEntry biomeEntry) { public void addBiome(Mapper.BiomeEntry biomeEntry) {
this.biomeQueue.add(biomeEntry); this.factory.addBiome(biomeEntry);
} }
public void addDebugData(List<String> debug) { public void addDebugData(List<String> debug) {
@@ -134,7 +111,7 @@ public class ModelBakerySubsystem {
} }
public boolean areQueuesEmpty() { public boolean areQueuesEmpty() {
return this.blockIdCount.get()==0 && this.factory.getInflightCount() == 0 && this.biomeQueue.isEmpty(); return this.blockIdCount.get()==0 && this.factory.getInflightCount() == 0;
} }
public int getProcessingCount() { public int getProcessingCount() {

View File

@@ -6,37 +6,44 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectSet; import it.unimi.dsi.fastutil.objects.ObjectSet;
import me.cortex.voxy.client.core.gl.Capabilities; 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.model.bakery.ModelTextureBakery;
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream; import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream; import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.MemoryBuffer; import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.util.Pair;
import me.cortex.voxy.common.world.other.Mapper; import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.block.Block; import net.minecraft.client.Minecraft;
import net.minecraft.block.BlockState; import net.minecraft.client.color.block.BlockColor;
import net.minecraft.block.FluidBlock; import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.block.LeavesBlock; import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.core.BlockPos;
import net.minecraft.client.MinecraftClient; import net.minecraft.core.Direction;
import net.minecraft.client.color.block.BlockColorProvider; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.client.render.BlockRenderLayer; import net.minecraft.core.registries.Registries;
import net.minecraft.client.render.RenderLayers; import net.minecraft.resources.Identifier;
import net.minecraft.fluid.FluidState; import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.registry.Registries; import net.minecraft.world.level.ColorResolver;
import net.minecraft.registry.RegistryKeys; import net.minecraft.world.level.LightLayer;
import net.minecraft.util.Pair; import net.minecraft.world.level.biome.Biome;
import net.minecraft.util.math.BlockPos; import net.minecraft.world.level.biome.Biomes;
import net.minecraft.util.math.Direction; import net.minecraft.world.level.block.Block;
import net.minecraft.world.BlockRenderView; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.LightType; import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.biome.Biome; import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.biome.BiomeKeys; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.biome.ColorResolver; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.chunk.light.LightingProvider; import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.FluidState;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import java.lang.invoke.VarHandle;
import java.util.*; 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 me.cortex.voxy.client.core.model.ModelStore.MODEL_SIZE;
import static org.lwjgl.opengl.ARBDirectStateAccess.nglTextureSubImage2D; import static org.lwjgl.opengl.ARBDirectStateAccess.nglTextureSubImage2D;
@@ -56,16 +63,17 @@ import static org.lwjgl.opengl.GL11.*;
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving // this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
public class ModelFactory { public class ModelFactory {
public static final int MODEL_TEXTURE_SIZE = 16; 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 //TODO: replace the fluid BlockState with a client model id integer of the fluidState, requires looking up
// the fluid state in the mipper // the fluid state in the mipper
private record ModelEntry(ColourDepthTextureData down, ColourDepthTextureData up, ColourDepthTextureData north, ColourDepthTextureData south, ColourDepthTextureData west, ColourDepthTextureData east, int fluidBlockStateId) { 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) { public ModelEntry(ColourDepthTextureData[] textures, int fluidBlockStateId, int tintingColour) {
this(textures[0], textures[1], textures[2], textures[3], textures[4], textures[5], fluidBlockStateId); this(textures[0], textures[1], textures[2], textures[3], textures[4], textures[5], fluidBlockStateId, tintingColour);
} }
} }
private final Biome DEFAULT_BIOME = MinecraftClient.getInstance().world.getRegistryManager().getOrThrow(RegistryKeys.BIOME).get(BiomeKeys.PLAINS); private final Biome DEFAULT_BIOME = Minecraft.getInstance().level.registryAccess().lookupOrThrow(Registries.BIOME).getValue(Biomes.PLAINS);
public final ModelTextureBakery bakery; public final ModelTextureBakery bakery;
@@ -107,6 +115,7 @@ public class ModelFactory {
//Contains the set of all block ids that are currently inflight/being baked //Contains the set of all block ids that are currently inflight/being baked
// this is required due to "async" nature of gpu feedback // this is required due to "async" nature of gpu feedback
private final IntOpenHashSet blockStatesInFlight = new IntOpenHashSet(); private final IntOpenHashSet blockStatesInFlight = new IntOpenHashSet();
private final ReentrantLock blockStatesInFlightLock = new ReentrantLock();
private final List<Biome> biomes = new ArrayList<>(); private final List<Biome> biomes = new ArrayList<>();
private final List<Pair<Integer, BlockState>> modelsRequiringBiomeColours = new ArrayList<>(); private final List<Pair<Integer, BlockState>> modelsRequiringBiomeColours = new ArrayList<>();
@@ -117,7 +126,9 @@ public class ModelFactory {
private final ModelStore storage; private final ModelStore storage;
private final RawDownloadStream downstream = new RawDownloadStream(8*1024*1024);//8mb downstream private final RawDownloadStream downstream = new RawDownloadStream(8*1024*1024);//8mb downstream
public final Deque<Runnable> resultJobs = new ArrayDeque<>(); private final ConcurrentLinkedDeque<RawBakeResult> rawBakeResults = new ConcurrentLinkedDeque<>();
private final ConcurrentLinkedDeque<ResultUploader> uploadResults = new ConcurrentLinkedDeque<>();
private Object2IntMap<BlockState> customBlockStateIdMapping; private Object2IntMap<BlockState> customBlockStateIdMapping;
@@ -138,15 +149,34 @@ public class ModelFactory {
this.addEntry(0);//Add air as the first entry this.addEntry(0);//Add air as the first entry
} }
public void tick() {
this.downstream.tick();
}
public void setCustomBlockStateMapping(Object2IntMap<BlockState> mapping) { public void setCustomBlockStateMapping(Object2IntMap<BlockState> mapping) {
this.customBlockStateIdMapping = 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) { public boolean addEntry(int blockId) {
if (this.idMappings[blockId] != -1) { if (this.idMappings[blockId] != -1) {
return false; return false;
@@ -154,19 +184,29 @@ public class ModelFactory {
//We are (probably) going to be baking the block id //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 // 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 // else add it to the flight as it is going to be baked
this.blockStatesInFlightLock.lock();
if (!this.blockStatesInFlight.add(blockId)) { if (!this.blockStatesInFlight.add(blockId)) {
this.blockStatesInFlightLock.unlock();
//Block baking is already in-flight //Block baking is already in-flight
return false; 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); 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 //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 // if it does, we must ensure that it is (effectivly) baked BEFORE we bake this blockstate
boolean isFluid = blockState.getBlock() instanceof FluidBlock; boolean isFluid = blockState.getBlock() instanceof LiquidBlock;
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) { if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
//Insert into the fluid LUT //Insert into the fluid LUT
var fluidState = blockState.getFluidState().getBlockState(); var fluidState = blockState.getFluidState().createLegacyBlock();
int fluidStateId = this.mapper.getIdForBlockState(fluidState); int fluidStateId = this.mapper.getIdForBlockState(fluidState);
@@ -179,44 +219,144 @@ public class ModelFactory {
} }
} }
int TOTAL_FACES_TEXTURE_SIZE = MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6;// since both depth and colour are packed together, 6 faces, 4 bytes per pixel RawBakeResult result = new RawBakeResult(blockId, blockState);
int allocation = this.downstream.download(TOTAL_FACES_TEXTURE_SIZE, ptr -> { int allocation = this.downstream.download(MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6, ptr -> this.rawBakeResults.add(result.cpyBuf(ptr)));
ColourDepthTextureData[] textureData = new ColourDepthTextureData[6]; int flags = this.bakery.renderToStream(blockState, this.downstream.getBufferId(), allocation);
final int FACE_SIZE = MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE; 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++) { for (int face = 0; face < 6; face++) {
long faceDataPtr = ptr + (FACE_SIZE*4)*face*2; long faceDataPtr = ptr + (FACE_SIZE * 4) * face * 2;
int[] colour = new int[FACE_SIZE]; int[] colour = new int[FACE_SIZE];
int[] depth = new int[FACE_SIZE]; int[] depth = new int[FACE_SIZE];
//Copy out colour //Copy out colour
for (int i = 0; i < FACE_SIZE; i++) { for (int i = 0; i < FACE_SIZE; i++) {
//De-interpolate results //De-interpolate results
colour[i] = MemoryUtil.memGetInt(faceDataPtr+ (i*4*2)); colour[i] = MemoryUtil.memGetInt(faceDataPtr + (i * 4 * 2));
depth[i] = MemoryUtil.memGetInt(faceDataPtr+ (i*4*2)+4); depth[i] = MemoryUtil.memGetInt(faceDataPtr + (i * 4 * 2) + 4);
} }
textureData[face] = new ColourDepthTextureData(colour, depth, MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE); textureData[face] = new ColourDepthTextureData(colour, depth, MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
} }
this.resultJobs.add(()->processTextureBakeResult(blockId, blockState, textureData)); }
}); result.rawData.free();
this.bakery.renderToStream(blockState, this.downstream.getBufferId(), allocation); var bakeResult = this.processTextureBakeResult(result.blockId, result.blockState, textureData, result.isShaded, result.hasDarkenedTextures);
return true; 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();
}
//This is while (this.processModelResult());
private void processTextureBakeResult(int blockId, BlockState blockState, ColourDepthTextureData[] textureData) { }
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) { 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! //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); throw new IllegalStateException("Block id already added: " + blockId + " for state: " + blockState);
} }
this.blockStatesInFlightLock.lock();
if (!this.blockStatesInFlight.contains(blockId)) { if (!this.blockStatesInFlight.contains(blockId)) {
this.blockStatesInFlightLock.unlock();
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!"); throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
} }
this.blockStatesInFlightLock.unlock();
boolean isFluid = blockState.getBlock() instanceof FluidBlock; //TODO: add thing for `blockState.hasEmissiveLighting()` and `blockState.getLuminance()`
boolean isFluid = blockState.getBlock() instanceof LiquidBlock;
int modelId = -1; int modelId = -1;
@@ -224,7 +364,7 @@ public class ModelFactory {
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) { if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
//Insert into the fluid LUT //Insert into the fluid LUT
var fluidState = blockState.getFluidState().getBlockState(); var fluidState = blockState.getFluidState().createLegacyBlock();
int fluidStateId = this.mapper.getIdForBlockState(fluidState); int fluidStateId = this.mapper.getIdForBlockState(fluidState);
@@ -234,17 +374,28 @@ public class ModelFactory {
} }
} }
var colourProvider = getColourProvider(blockState.getBlock());
boolean isBiomeColourDependent = false;
if (colourProvider != null) {
isBiomeColourDependent = isBiomeDependentColour(colourProvider, blockState);
}
ModelEntry entry;
{//Deduplicate same entries {//Deduplicate same entries
var entry = new ModelEntry(textureData, clientFluidStateId); entry = new ModelEntry(textureData, clientFluidStateId, isBiomeColourDependent||colourProvider==null?-1:captureColourConstant(colourProvider, blockState, DEFAULT_BIOME)|0xFF000000);
int possibleDuplicate = this.modelTexture2id.getInt(entry); int possibleDuplicate = this.modelTexture2id.getInt(entry);
if (possibleDuplicate != -1) {//Duplicate found if (possibleDuplicate != -1) {//Duplicate found
this.idMappings[blockId] = possibleDuplicate; this.idMappings[blockId] = possibleDuplicate;
modelId = possibleDuplicate; modelId = possibleDuplicate;
//Remove from flight //Remove from flight
this.blockStatesInFlightLock.lock();
if (!this.blockStatesInFlight.remove(blockId)) { if (!this.blockStatesInFlight.remove(blockId)) {
this.blockStatesInFlightLock.unlock();
throw new IllegalStateException(); throw new IllegalStateException();
} }
return; this.blockStatesInFlightLock.unlock();
return null;
} else {//Not a duplicate so create a new entry } else {//Not a duplicate so create a new entry
modelId = this.modelTexture2id.size(); modelId = this.modelTexture2id.size();
//NOTE: we set the mapping at the very end so that race conditions with this and getMetadata dont occur //NOTE: we set the mapping at the very end so that race conditions with this and getMetadata dont occur
@@ -259,33 +410,30 @@ public class ModelFactory {
this.fluidStateLUT[modelId] = clientFluidStateId; this.fluidStateLUT[modelId] = clientFluidStateId;
} }
BlockRenderLayer blockRenderLayer = null; ChunkSectionLayer blockRenderLayer = null;
if (blockState.getBlock() instanceof FluidBlock) { if (blockState.getBlock() instanceof LiquidBlock) {
blockRenderLayer = RenderLayers.getFluidLayer(blockState.getFluidState()); blockRenderLayer = ItemBlockRenderTypes.getRenderLayer(blockState.getFluidState());
} else { } else {
if (blockState.getBlock() instanceof LeavesBlock) { if (blockState.getBlock() instanceof LeavesBlock) {
blockRenderLayer = BlockRenderLayer.SOLID; blockRenderLayer = ChunkSectionLayer.SOLID;
} else { } else {
blockRenderLayer = RenderLayers.getBlockLayer(blockState); blockRenderLayer = ItemBlockRenderTypes.getChunkRenderType(blockState);
} }
} }
int checkMode = blockRenderLayer==BlockRenderLayer.SOLID?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA; int checkMode = blockRenderLayer==ChunkSectionLayer.SOLID?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
var colourProvider = getColourProvider(blockState.getBlock());
long uploadPtr = UploadStream.INSTANCE.upload(this.storage.modelBuffer, (long) modelId * MODEL_SIZE, MODEL_SIZE); ModelBakeResultUpload uploadResult = new ModelBakeResultUpload();
uploadResult.modelId = modelId;
long uploadPtr = uploadResult.model.address;
//TODO: implement; //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 // 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 isBiomeColourDependent = false;
if (colourProvider != null) {
isBiomeColourDependent = isBiomeDependentColour(colourProvider, blockState);
}
//If it contains fluid but isnt a fluid //If it contains fluid but isnt a fluid
if ((!isFluid) && (!blockState.getFluidState().isEmpty()) && clientFluidStateId != -1) { if ((!isFluid) && (!blockState.getFluidState().isEmpty()) && clientFluidStateId != -1) {
@@ -298,12 +446,12 @@ public class ModelFactory {
//TODO: special case stuff like vines and glow lichen, where it can be represented by a single double sided quad //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 // 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); 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 //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) // 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) // 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 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; boolean cullsSame = false;
@@ -314,7 +462,7 @@ public class ModelFactory {
boolean allFalse = true; boolean allFalse = true;
//Guestimation test for if the block culls itself //Guestimation test for if the block culls itself
for (var dir : Direction.values()) { for (var dir : Direction.values()) {
if (blockState.isSideInvisible(blockState, dir)) { if (blockState.skipRendering(blockState, dir)) {
allFalse = false; allFalse = false;
} else { } else {
allTrue = false; allTrue = false;
@@ -336,7 +484,7 @@ public class ModelFactory {
//Each face gets 1 byte, with the top 2 bytes being for whatever //Each face gets 1 byte, with the top 2 bytes being for whatever
long metadata = 0; long metadata = 0;
metadata |= isBiomeColourDependent?1:0; metadata |= isBiomeColourDependent?1:0;
metadata |= blockRenderLayer == BlockRenderLayer.TRANSLUCENT?2:0; metadata |= blockRenderLayer == ChunkSectionLayer.TRANSLUCENT?2:0;
metadata |= needsDoubleSidedQuads?4: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) && !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 |= isFluid?16:0;//Is a fluid
@@ -345,11 +493,13 @@ public class ModelFactory {
boolean fullyOpaque = true; 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 //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 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 long faceUploadPtr = uploadPtr + 4L * face;//Each face gets 4 bytes worth of data
metadata <<= 8; metadata <<= 8;
float offset = sizes[face]; float offset = depths[face];
if (offset < -0.1) {//Face is empty, so ignore if (offset < -0.1) {//Face is empty, so ignore
metadata |= 0xFF;//Mark the face as non-existent metadata |= 0xFF;//Mark the face as non-existent
//Set to -1 as safepoint //Set to -1 as safepoint
@@ -370,7 +520,7 @@ public class ModelFactory {
//TODO: add alot of config options for the following //TODO: add alot of config options for the following
boolean occludesFace = true; boolean occludesFace = true;
occludesFace &= blockRenderLayer != BlockRenderLayer.TRANSLUCENT;//If its translucent, it doesnt occlude 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 //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 occludesFace &= offset < 0.1;//If the face is rendered far away from the other face, then it doesnt occlude
@@ -390,31 +540,44 @@ public class ModelFactory {
metadata |= canBeOccluded?4:0; metadata |= canBeOccluded?4:0;
//Face uses its own lighting if its not flat against the adjacent block & isnt traslucent //Face uses its own lighting if its not flat against the adjacent block & isnt traslucent
metadata |= (offset > 0.01 || blockRenderLayer == BlockRenderLayer.TRANSLUCENT)?0b1000:0; metadata |= (offset > 0.01 || blockRenderLayer == ChunkSectionLayer.TRANSLUCENT)?0b1000:0;
//Scale face size from 0->this.modelTextureSize-1 to 0->15 if (MODEL_TEXTURE_SIZE-1 != 15) {
for (int i = 0; i < 4; i++) { //Scale face size from 0->this.modelTextureSize-1 to 0->15
faceSize[i] = Math.round((((float)faceSize[i])/(MODEL_TEXTURE_SIZE-1))*15); for (int i = 0; i < 4; i++) {
faceSize[i] = Math.round((((float) faceSize[i]) / (MODEL_TEXTURE_SIZE - 1)) * 15);
}
} }
int faceModelData = 0; int faceModelData = 0;
faceModelData |= faceSize[0] | (faceSize[1]<<4) | (faceSize[2]<<8) | (faceSize[3]<<12); 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 //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 //Still have 11 bits free
//Stuff like fences are solid, however they have extra side piece that mean it needs to have discard on //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); 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 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 != BlockRenderLayer.SOLID; needsAlphaDiscard |= blockRenderLayer != ChunkSectionLayer.SOLID;
needsAlphaDiscard &= blockRenderLayer != BlockRenderLayer.TRANSLUCENT;//Translucent doesnt have alpha discard needsAlphaDiscard &= blockRenderLayer != ChunkSectionLayer.TRANSLUCENT;//Translucent doesnt have alpha discard
faceModelData |= needsAlphaDiscard?1<<22:0; faceModelData |= needsAlphaDiscard?1<<22:0;
faceModelData |= ((!faceCoversFullBlock)&&blockRenderLayer != BlockRenderLayer.TRANSLUCENT)?1<<23:0;//Alpha discard override, translucency doesnt have alpha discard 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); MemoryUtil.memPutInt(faceUploadPtr, faceModelData);
} }
@@ -432,8 +595,11 @@ public class ModelFactory {
int modelFlags = 0; int modelFlags = 0;
modelFlags |= colourProvider != null?1: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 |= 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 == BlockRenderLayer.TRANSLUCENT?4:0;//Is translucent modelFlags |= blockRenderLayer == ChunkSectionLayer.TRANSLUCENT?4:0;//Is translucent
modelFlags |= blockRenderLayer == BlockRenderLayer.CUTOUT?0:8;//Dont use mipmaps (AND ALSO FKING SPECIFIES IF IT HAS AO, WHY??? GREAT QUESTION, TODO FIXE THIS)
//TODO: THIS
modelFlags |= isShaded?8:0;//model has AO and shade
//modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha //modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha
MemoryUtil.memPutInt(uploadPtr, modelFlags); uploadPtr += 4; MemoryUtil.memPutInt(uploadPtr, modelFlags); uploadPtr += 4;
@@ -443,15 +609,15 @@ public class ModelFactory {
if (colourProvider == null) { if (colourProvider == null) {
MemoryUtil.memPutInt(uploadPtr, -1);//Set the default to nothing so that its faster on the gpu MemoryUtil.memPutInt(uploadPtr, -1);//Set the default to nothing so that its faster on the gpu
} else if (!isBiomeColourDependent) { } else if (!isBiomeColourDependent) {
MemoryUtil.memPutInt(uploadPtr, captureColourConstant(colourProvider, blockState, DEFAULT_BIOME)|0xFF000000); MemoryUtil.memPutInt(uploadPtr, entry.tintingColour);
} else if (!this.biomes.isEmpty()) { } else if (!this.biomes.isEmpty()) {
//Populate the list of biomes for the model state //Populate the list of biomes for the model state
int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size(); int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size();
MemoryUtil.memPutInt(uploadPtr, biomeIndex); MemoryUtil.memPutInt(uploadPtr, biomeIndex);
this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState)); this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState));
//NOTE: UploadStream.INSTANCE is called _after_ uploadPtr is finished being used, this is cause the upload pointer
// may be invalidated as soon as another upload stream is invoked uploadResult.biomeUploadIndex = biomeIndex;
long clrUploadPtr = UploadStream.INSTANCE.upload(this.storage.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size()); long clrUploadPtr = (uploadResult.biomeUpload = new MemoryBuffer(4L * this.biomes.size())).address;
for (var biome : this.biomes) { for (var biome : this.biomes) {
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, blockState, biome)|0xFF000000); clrUploadPtr += 4; MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, blockState, biome)|0xFF000000); clrUploadPtr += 4;
} }
@@ -474,23 +640,58 @@ public class ModelFactory {
//TODO callback to inject extra data into the model data //TODO callback to inject extra data into the model data
this.putTextures(modelId, textureData); MipGen.putTextures(darkenedTinting, textureData, uploadResult.texture);
//glGenerateTextureMipmap(this.textures.id); //glGenerateTextureMipmap(this.textures.id);
//Set the mapping at the very end //Set the mapping at the very end
this.idMappings[blockId] = modelId; this.idMappings[blockId] = modelId;
this.blockStatesInFlightLock.lock();
if (!this.blockStatesInFlight.remove(blockId)) { if (!this.blockStatesInFlight.remove(blockId)) {
this.blockStatesInFlightLock.unlock();
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!"); throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
} }
this.blockStatesInFlightLock.unlock();
//Upload/commit stream return uploadResult;
//TODO maybe dont do it for every uploaded block?? try to batch it
UploadStream.INSTANCE.commit();
} }
public void addBiome(int id, Biome biome) { 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++) { for (int i = this.biomes.size(); i <= id; i++) {
this.biomes.add(null); this.biomes.add(null);
} }
@@ -503,53 +704,58 @@ public class ModelFactory {
Logger.error("Biome added was a duplicate"); 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; int i = 0;
long modelUpPtr = result.modelBiomeIndexPairs.address;
for (var entry : this.modelsRequiringBiomeColours) { for (var entry : this.modelsRequiringBiomeColours) {
var colourProvider = getColourProvider(entry.getRight().getBlock()); var colourProvider = getColourProvider(entry.right().getBlock());
if (colourProvider == null) { if (colourProvider == null) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
//Populate the list of biomes for the model state //Populate the list of biomes for the model state
int biomeIndex = (i++) * this.biomes.size(); int biomeIndex = (i++) * this.biomes.size();
MemoryUtil.memPutInt(UploadStream.INSTANCE.upload(this.storage.modelBuffer, (entry.getLeft()* MODEL_SIZE)+ 4*6 + 4, 4), biomeIndex); MemoryUtil.memPutLong(modelUpPtr, Integer.toUnsignedLong(entry.left())|(Integer.toUnsignedLong(biomeIndex)<<32));modelUpPtr+=8;
long clrUploadPtr = UploadStream.INSTANCE.upload(this.storage.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size()); long clrUploadPtr = result.biomeColourBuffer.address + biomeIndex * 4L;
for (var biomeE : this.biomes) { for (var biomeE : this.biomes) {
if (biomeE == null) { if (biomeE == null) {
continue;//If null, ignore continue;//If null, ignore
} }
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, entry.getRight(), biomeE)|0xFF000000); clrUploadPtr += 4; MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, entry.right(), biomeE)|0xFF000000); clrUploadPtr += 4;
} }
} }
UploadStream.INSTANCE.commit(); return result;
} }
private static BlockColorProvider getColourProvider(Block block) { private static BlockColor getColourProvider(Block block) {
return MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(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) //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 // 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 // will either use the uint as an index or a direct colour multiplier
private static int captureColourConstant(BlockColorProvider colorProvider, BlockState state, Biome biome) { private static int captureColourConstant(BlockColor colorProvider, BlockState state, Biome biome) {
return colorProvider.getColor(state, new BlockRenderView() { var getter = new BlockAndTintGetter() {
@Override @Override
public float getBrightness(Direction direction, boolean shaded) { public float getShade(Direction direction, boolean shaded) {
return 0; return 0;
} }
@Override @Override
public int getLightLevel(LightType type, BlockPos pos) { public int getBrightness(LightLayer type, BlockPos pos) {
return 0; return 0;
} }
@Override @Override
public LightingProvider getLightingProvider() { public LevelLightEngine getLightEngine() {
return null; return null;
} }
@Override @Override
public int getColor(BlockPos pos, ColorResolver colorResolver) { public int getBlockTint(BlockPos pos, ColorResolver colorResolver) {
return colorResolver.getColor(biome, 0, 0); return colorResolver.getColor(biome, 0, 0);
} }
@@ -575,32 +781,36 @@ public class ModelFactory {
} }
@Override @Override
public int getBottomY() { public int getMinY() {
return 0; return 0;
} }
}, BlockPos.ORIGIN, 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(BlockColorProvider colorProvider, BlockState state) { private static boolean isBiomeDependentColour(BlockColor colorProvider, BlockState state) {
boolean[] biomeDependent = new boolean[1]; boolean[] biomeDependent = new boolean[1];
colorProvider.getColor(state, new BlockRenderView() { var getter = new BlockAndTintGetter() {
@Override @Override
public float getBrightness(Direction direction, boolean shaded) { public float getShade(Direction direction, boolean shaded) {
return 0; return 0;
} }
@Override @Override
public int getLightLevel(LightType type, BlockPos pos) { public int getBrightness(LightLayer type, BlockPos pos) {
return 0; return 0;
} }
@Override @Override
public LightingProvider getLightingProvider() { public LevelLightEngine getLightEngine() {
return null; return null;
} }
@Override @Override
public int getColor(BlockPos pos, ColorResolver colorResolver) { public int getBlockTint(BlockPos pos, ColorResolver colorResolver) {
biomeDependent[0] = true; biomeDependent[0] = true;
return 0; return 0;
} }
@@ -627,17 +837,19 @@ public class ModelFactory {
} }
@Override @Override
public int getBottomY() { public int getMinY() {
return 0; return 0;
} }
}, BlockPos.ORIGIN, 0); };
colorProvider.getColor(state, getter, BlockPos.ZERO, 0);
colorProvider.getColor(state, getter, BlockPos.ZERO, 1);
return biomeDependent[0]; return biomeDependent[0];
} }
private float[] computeModelDepth(ColourDepthTextureData[] textures, int checkMode) { private static float[] computeModelDepth(ColourDepthTextureData[] textures, int checkMode) {
float[] res = new float[6]; float[] res = new float[6];
for (var dir : Direction.values()) { for (var dir : Direction.values()) {
var data = textures[dir.getIndex()]; 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 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); //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 is -1, it means that there was nothing rendered on that face and it should be discarded
@@ -679,73 +891,15 @@ public class ModelFactory {
} }
private static int computeSizeWithMips(int size) {
int total = 0;
for (;size!=0;size>>=1) total += size*size;
return total;
}
private static final MemoryBuffer SCRATCH_TEX = new MemoryBuffer((2L*3*computeSizeWithMips(MODEL_TEXTURE_SIZE))*4);
private static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE);
//TODO: redo to batch blit, instead of 6 seperate blits, and also fix mipping
private void putTextures(int id, ColourDepthTextureData[] textures) {
//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
//Copy all textures into scratch
final long addr = SCRATCH_TEX.address;
final int LENGTH_B = MODEL_TEXTURE_SIZE*3;
for (int i = 0; i < 6; i++) {
int x = (i>>1)*MODEL_TEXTURE_SIZE;
int y = (i&1)*MODEL_TEXTURE_SIZE;
int j = 0;
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
MemoryUtil.memPutInt(addr+o, t);
}
}
//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(C00, C01, C10, C11));
}
}
}
int X = (id&0xFF) * MODEL_TEXTURE_SIZE*3;
int Y = ((id>>8)&0xFF) * MODEL_TEXTURE_SIZE*2;
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
long cAddr = addr;
for (int lvl = 0; lvl < LAYERS; lvl++) {
nglTextureSubImage2D(this.storage.textures.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);
}
}
public void free() { public void free() {
this.downstream.free();
this.bakery.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() { public int getBakedCount() {
@@ -753,6 +907,17 @@ public class ModelFactory {
} }
public int getInflightCount() { public int getInflightCount() {
return this.blockStatesInFlight.size(); //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;
} }
} }

View File

@@ -2,9 +2,9 @@ package me.cortex.voxy.client.core.model;
import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture; import me.cortex.voxy.client.core.gl.GlTexture;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.Minecraft;
import net.minecraft.client.texture.SpriteAtlasTexture; import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.util.Identifier; import net.minecraft.resources.Identifier;
import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL11C.GL_NEAREST; import static org.lwjgl.opengl.GL11C.GL_NEAREST;
@@ -31,9 +31,9 @@ public class ModelStore {
//Limit the mips of the texture to match that of the terrain atlas //Limit the mips of the texture to match that of the terrain atlas
int mipLvl = ((SpriteAtlasTexture) MinecraftClient.getInstance().getTextureManager() int mipLvl = ((TextureAtlas) Minecraft.getInstance().getTextureManager()
.getTexture(Identifier.of("minecraft", "textures/atlas/blocks.png"))) .getTexture(Identifier.fromNamespaceAndPath("minecraft", "textures/atlas/blocks.png")))
.mipLevel; .maxMipLevel;
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); 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_MAG_FILTER, GL_NEAREST);

View File

@@ -1,7 +1,8 @@
package me.cortex.voxy.client.core.model; package me.cortex.voxy.client.core.model;
import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB; import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB;
import net.minecraft.client.texture.MipmapHelper; import net.minecraft.client.renderer.texture.MipmapGenerator;
import net.minecraft.util.ARGB;
//Texturing utils to manipulate data from the model bakery //Texturing utils to manipulate data from the model bakery
public class TextureUtils { public class TextureUtils {
@@ -9,14 +10,14 @@ public class TextureUtils {
public static int getWrittenPixelCount(ColourDepthTextureData texture, int checkMode) { public static int getWrittenPixelCount(ColourDepthTextureData texture, int checkMode) {
int count = 0; int count = 0;
for (int i = 0; i < texture.colour().length; i++) { 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; return count;
} }
public static boolean isSolid(ColourDepthTextureData texture) { public static boolean isSolid(ColourDepthTextureData texture) {
for (int pixel : texture.colour()) { for (int pixel : texture.colour()) {
if (((pixel>>24)&0xFF) != 255) { if (((pixel >> 24) & 0xFF) != 255) {
return false; return false;
} }
} }
@@ -26,18 +27,50 @@ public class TextureUtils {
public static final int WRITE_CHECK_STENCIL = 1; public static final int WRITE_CHECK_STENCIL = 1;
public static final int WRITE_CHECK_DEPTH = 2; public static final int WRITE_CHECK_DEPTH = 2;
public static final int WRITE_CHECK_ALPHA = 3; public static final int WRITE_CHECK_ALPHA = 3;
private static boolean wasPixelWritten(ColourDepthTextureData data, int mode, int index) { private static boolean wasPixelWritten(ColourDepthTextureData data, int mode, int index) {
if (mode == WRITE_CHECK_STENCIL) { if (mode == WRITE_CHECK_STENCIL) {
return (data.depth()[index]&0xFF)!=0; return (data.depth()[index] & 0xFF) != 0;
} else if (mode == WRITE_CHECK_DEPTH) { } 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) { } else if (mode == WRITE_CHECK_ALPHA) {
//TODO:FIXME: for some reason it has an alpha of 1 even if its ment to be 0 //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(); 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_AVG = 1;
public static final int DEPTH_MODE_MAX = 2; public static final int DEPTH_MODE_MAX = 2;
public static final int DEPTH_MODE_MIN = 3; public static final int DEPTH_MODE_MIN = 3;
@@ -59,7 +92,7 @@ public class TextureUtils {
if (!wasPixelWritten(texture, checkMode, i)) { if (!wasPixelWritten(texture, checkMode, i)) {
continue; continue;
} }
int depth = depthData[i]>>>8; int depth = depthData[i] >>> 8;
if (mode == DEPTH_MODE_AVG) { if (mode == DEPTH_MODE_AVG) {
a++; a++;
b += depth; b += depth;
@@ -74,7 +107,7 @@ public class TextureUtils {
if (a == 0) { if (a == 0) {
return -1; return -1;
} }
return u2fdepth((int) (b/a)); return u2fdepth((int) (b / a));
} else if (mode == DEPTH_MODE_MAX) { } else if (mode == DEPTH_MODE_MAX) {
if (a == Long.MIN_VALUE) { if (a == Long.MIN_VALUE) {
return -1; return -1;
@@ -90,7 +123,7 @@ public class TextureUtils {
} }
private static float u2fdepth(int depth) { 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 //https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDepthRange.xhtml
// due to this and the unsigned bullshit, 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
@@ -106,7 +139,6 @@ public class TextureUtils {
//NOTE: data goes from bottom left to top right (x first then y) //NOTE: data goes from bottom left to top right (x first then y)
public static int[] computeBounds(ColourDepthTextureData data, int checkMode) { public static int[] computeBounds(ColourDepthTextureData data, int checkMode) {
final var depth = data.depth();
//Compute x bounds first //Compute x bounds first
int minX = 0; int minX = 0;
minXCheck: minXCheck:
@@ -120,10 +152,10 @@ public class TextureUtils {
minX++; minX++;
} while (minX != data.width()); } while (minX != data.width());
int maxX = data.width()-1; int maxX = data.width() - 1;
maxXCheck: maxXCheck:
do { 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()); int idx = maxX + (y * data.width());
if (wasPixelWritten(data, checkMode, idx)) { if (wasPixelWritten(data, checkMode, idx)) {
break maxXCheck;//pixel was written too so break from loop break maxXCheck;//pixel was written too so break from loop
@@ -148,10 +180,10 @@ public class TextureUtils {
} while (minY != data.height()); } while (minY != data.height());
int maxY = data.height()-1; int maxY = data.height() - 1;
maxYCheck: maxYCheck:
do { 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; int idx = (maxY * data.height()) + x;
if (wasPixelWritten(data, checkMode, idx)) { if (wasPixelWritten(data, checkMode, idx)) {
break maxYCheck;//pixel was written too so break from loop break maxYCheck;//pixel was written too so break from loop
@@ -165,63 +197,42 @@ public class TextureUtils {
} }
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);
public static int mipColours(int one, int two, int three, int four) {
if (true) {
return MipmapHelper.blend(one, two, three, four, false);
} else {
return weightedAverageColor(weightedAverageColor(one, two), weightedAverageColor(three, four));
} }
} if (darkend || (C01 >>> 24) != 0) {
r += ColorSRGB.srgbToLinear((C01 >> 0) & 0xFF);
//TODO: FIXME!!! ITS READING IT AS ABGR??? isnt the format RGBA?? g += ColorSRGB.srgbToLinear((C01 >> 8) & 0xFF);
private static int weightedAverageColor(int a, int b) { b += ColorSRGB.srgbToLinear((C01 >> 16) & 0xFF);
//We specifically want the entire other component if the alpha is zero a += darkend ? (C01 >>> 24) : ColorSRGB.srgbToLinear(C01 >>> 24);
// this prevents black mips from generating due to A) non filled colours, and B) when the sampler samples everything it doesnt detonate
if ((a&0xFF000000) == 0) {
return b;
} }
if ((b&0xFF000000) == 0) { if (darkend || (C10 >>> 24) != 0) {
return a; 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);
} }
if (((a^b)&0xFF000000)==0) { return ColorSRGB.linearToSrgb(
return ColorSRGB.linearToSrgb( r / 4,
addHalfLinear(0, a,b), g / 4,
addHalfLinear(8, a,b), b / 4,
addHalfLinear(16, a,b), darkend ? ((int) a) / 4 : ARGB.linearToSrgbChannel(a / 4)
a>>>24); );
}
{
int A = (a>>>24);
int B = (a>>>24);
float mul = 1.0F / (float)(A+B);
float wA = A * mul;
float wB = B * mul;
return ColorSRGB.linearToSrgb(
addMulLinear(0, a,b,wA,wB),
addMulLinear(8, a,b,wA,wB),
addMulLinear(16, a,b,wA,wB)
, (A + B)/2);
}
}
private static float addHalfLinear(int shift, int a, int b) {
return addMulLinear(shift, a, b, 0.5f, 0.5f);
}
private static float addMulLinear(int shift, int a, int b, float mulA, float mulB) {
return Math.fma(ColorSRGB.srgbToLinear((a>>shift)&0xFF),mulA, ColorSRGB.srgbToLinear((b>>shift)&0xFF)*mulB);
} }
} }

View File

@@ -1,15 +1,17 @@
/*
package me.cortex.voxy.client.core.model.bakery; package me.cortex.voxy.client.core.model.bakery;
import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.Logger;
import net.minecraft.block.BlockEntityProvider; import net.minecraft.client.Minecraft;
import net.minecraft.block.BlockState; import net.minecraft.client.model.Model;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.renderer.SubmitNodeStorage;
import net.minecraft.client.render.RenderLayer; import net.minecraft.core.BlockPos;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.resources.Identifier;
import net.minecraft.util.Identifier; import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.util.math.BlockPos; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.util.math.Vec3d; import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Quaternionf;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -17,7 +19,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
public class BakedBlockEntityModel { public class BakedBlockEntityModel {
private record LayerConsumer(RenderLayer layer, ReuseVertexConsumer consumer) {}
private record LayerConsumer(RenderType layer, ReuseVertexConsumer consumer) {}
private final List<LayerConsumer> layers; private final List<LayerConsumer> layers;
private BakedBlockEntityModel(List<LayerConsumer> layers) { private BakedBlockEntityModel(List<LayerConsumer> layers) {
this.layers = layers; this.layers = layers;
@@ -26,12 +29,12 @@ public class BakedBlockEntityModel {
public void render(Matrix4f matrix, int texId) { public void render(Matrix4f matrix, int texId) {
for (var layer : this.layers) { for (var layer : this.layers) {
if (layer.consumer.isEmpty()) continue; if (layer.consumer.isEmpty()) continue;
if (layer.layer instanceof RenderLayer.MultiPhase mp) { if (layer.layer instanceof RenderType.CompositeRenderType mp) {
Identifier textureId = mp.phases.texture.getId().orElse(null); Identifier textureId = mp.state.textureState.cutoutTexture().orElse(null);
if (textureId == null) { if (textureId == null) {
Logger.error("ERROR: Empty texture id for layer: " + layer); Logger.error("ERROR: Empty texture id for layer: " + layer);
} else { } else {
texId = ((net.minecraft.client.texture.GlTexture)MinecraftClient.getInstance().getTextureManager().getTexture(textureId).getGlTexture()).getGlId(); texId = ((com.mojang.blaze3d.opengl.GlTexture)Minecraft.getInstance().getTextureManager().getTexture(textureId).getTexture()).glId();
} }
} }
if (texId == 0) continue; if (texId == 0) continue;
@@ -44,15 +47,15 @@ public class BakedBlockEntityModel {
this.layers.forEach(layer->layer.consumer.free()); this.layers.forEach(layer->layer.consumer.free());
} }
private static int getMetaFromLayer(RenderLayer layer) { private static int getMetaFromLayer(RenderType layer) {
boolean hasDiscard = layer == RenderLayer.getCutout() || boolean hasDiscard = layer == RenderType.cutout() ||
layer == RenderLayer.getCutoutMipped() || layer == RenderType.cutoutMipped() ||
layer == RenderLayer.getTripwire(); layer == RenderType.tripwire();
boolean isMipped = layer == RenderLayer.getCutoutMipped() || boolean isMipped = layer == RenderType.cutoutMipped() ||
layer == RenderLayer.getSolid() || layer == RenderType.solid() ||
layer.isTranslucent() || layer.sortOnUpload() ||
layer == RenderLayer.getTripwire(); layer == RenderType.tripwire();
int meta = hasDiscard?1:0; int meta = hasDiscard?1:0;
meta |= isMipped?2:0; meta |= isMipped?2:0;
@@ -60,21 +63,30 @@ public class BakedBlockEntityModel {
} }
public static BakedBlockEntityModel bake(BlockState state) { public static BakedBlockEntityModel bake(BlockState state) {
Map<RenderLayer, LayerConsumer> map = new HashMap<>(); Map<RenderType, LayerConsumer> map = new HashMap<>();
var entity = ((BlockEntityProvider)state.getBlock()).createBlockEntity(BlockPos.ORIGIN, state); var entity = ((EntityBlock)state.getBlock()).newBlockEntity(BlockPos.ZERO, state);
if (entity == null) { if (entity == null) {
return null; return null;
} }
var renderer = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(entity); var renderer = Minecraft.getInstance().getBlockEntityRenderDispatcher().getRenderer(entity);
entity.setWorld(MinecraftClient.getInstance().world); entity.setLevel(Minecraft.getInstance().level);
if (renderer != null) { if (renderer != null) {
try { try {
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)); 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) { } catch (Exception e) {
Logger.error("Unable to bake block entity: " + entity, e); Logger.error("Unable to bake block entity: " + entity, e);
} }
} }
entity.markRemoved(); entity.setRemoved();
if (map.isEmpty()) { if (map.isEmpty()) {
return null; return null;
} }
@@ -90,3 +102,4 @@ public class BakedBlockEntityModel {
return new BakedBlockEntityModel(new ArrayList<>(map.values())); return new BakedBlockEntityModel(new ArrayList<>(map.values()));
} }
} }
*/

View File

@@ -2,14 +2,13 @@ package me.cortex.voxy.client.core.model.bakery;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.GpuTexture; import com.mojang.blaze3d.textures.GpuTexture;
import com.mojang.blaze3d.vertex.MeshData;
import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.blaze3d.vertex.VertexFormat;
import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlVertexArray; 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.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType; import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.util.UploadStream; import me.cortex.voxy.client.core.rendering.util.UploadStream;
import net.minecraft.client.gl.GlGpuBuffer;
import net.minecraft.client.render.BuiltBuffer;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
@@ -29,9 +28,9 @@ public class BudgetBufferRenderer {
public static void init(){} public static void init(){}
private static final GlBuffer indexBuffer; private static final GlBuffer indexBuffer;
static { static {
var i = RenderSystem.getSequentialBuffer(VertexFormat.DrawMode.QUADS); var i = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
int id = ((GlGpuBuffer) i.getIndexBuffer(4096*3*2)).id; int id = ((com.mojang.blaze3d.opengl.GlBuffer) i.getBuffer(4096*3*2)).handle;
if (i.getIndexType() != VertexFormat.IndexType.SHORT) { if (i.type() != VertexFormat.IndexType.SHORT) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
indexBuffer = new GlBuffer(3*2*2*4096); indexBuffer = new GlBuffer(3*2*2*4096);
@@ -47,18 +46,18 @@ public class BudgetBufferRenderer {
private static GlBuffer immediateBuffer; private static GlBuffer immediateBuffer;
private static int quadCount; private static int quadCount;
public static void drawFast(BuiltBuffer buffer, GpuTexture tex, Matrix4f matrix) { public static void drawFast(MeshData buffer, GpuTexture tex, Matrix4f matrix) {
if (buffer.getDrawParameters().mode() != VertexFormat.DrawMode.QUADS) { if (buffer.drawState().mode() != VertexFormat.Mode.QUADS) {
throw new IllegalStateException("Fast only supports quads"); throw new IllegalStateException("Fast only supports quads");
} }
var buff = buffer.getBuffer(); var buff = buffer.vertexBuffer();
int size = buff.remaining(); int size = buff.remaining();
if (size%STRIDE != 0) throw new IllegalStateException(); if (size%STRIDE != 0) throw new IllegalStateException();
size /= STRIDE; size /= STRIDE;
if (size%4 != 0) throw new IllegalStateException(); if (size%4 != 0) throw new IllegalStateException();
size /= 4; size /= 4;
setup(MemoryUtil.memAddress(buff), size, ((net.minecraft.client.texture.GlTexture)tex).getGlId()); setup(MemoryUtil.memAddress(buff), size, ((com.mojang.blaze3d.opengl.GlTexture)tex).glId());
buffer.close(); buffer.close();
render(matrix); render(matrix);

View File

@@ -7,8 +7,7 @@ import me.cortex.voxy.client.core.gl.shader.ShaderType;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.ARBDirectStateAccess.glTextureParameteri; import static org.lwjgl.opengl.ARBDirectStateAccess.*;
import static org.lwjgl.opengl.ARBDirectStateAccess.nglClearNamedFramebufferfv;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_FRAMEBUFFER_BARRIER_BIT; 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_PIXEL_BUFFER_BARRIER_BIT;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT;
@@ -25,12 +24,14 @@ public class GlViewCapture {
private final GlTexture colourTex; private final GlTexture colourTex;
private final GlTexture depthTex; private final GlTexture depthTex;
private final GlTexture stencilTex; private final GlTexture stencilTex;
private final GlTexture metaTex;
final GlFramebuffer framebuffer; final GlFramebuffer framebuffer;
private final Shader copyOutShader; private final Shader copyOutShader;
public GlViewCapture(int width, int height) { public GlViewCapture(int width, int height) {
this.width = width; this.width = width;
this.height = height; 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.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"); 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 //TODO: FIXME: Mesa is broken when trying to read from a sampler of GL_STENCIL_INDEX
@@ -39,22 +40,30 @@ public class GlViewCapture {
this.stencilTex = this.depthTex.createView(); this.stencilTex = this.depthTex.createView();
glTextureParameteri(this.depthTex.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT); glTextureParameteri(this.depthTex.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
this.framebuffer = new GlFramebuffer().bind(GL_COLOR_ATTACHMENT0, this.colourTex).bind(GL_DEPTH_STENCIL_ATTACHMENT, this.depthTex).verify().name("ModelFramebuffer"); 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_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
glTextureParameteri(this.stencilTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTextureParameteri(this.stencilTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(this.stencilTex.id, GL_TEXTURE_MIN_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() this.copyOutShader = Shader.makeAuto()
.define("WIDTH", width) .define("WIDTH", width)
.define("HEIGHT", height) .define("HEIGHT", height)
.define("COLOUR_IN_BINDING", 0) .define("COLOUR_IN_BINDING", 0)
.define("DEPTH_IN_BINDING", 1) .define("DEPTH_IN_BINDING", 1)
.define("STENCIL_IN_BINDING", 2) .define("STENCIL_IN_BINDING", 2)
.define("BUFFER_OUT_BINDING", 3) .define("META_IN_BINDING", 3)
.define("BUFFER_OUT_BINDING", 4)
.add(ShaderType.COMPUTE, "voxy:bakery/bufferreorder.comp") .add(ShaderType.COMPUTE, "voxy:bakery/bufferreorder.comp")
.compile() .compile()
.name("ModelBakeryOut") .name("ModelBakeryOut")
.texture("META_IN_BINDING", 0, this.metaTex)
.texture("COLOUR_IN_BINDING", 0, this.colourTex) .texture("COLOUR_IN_BINDING", 0, this.colourTex)
.texture("DEPTH_IN_BINDING", 0, this.depthTex) .texture("DEPTH_IN_BINDING", 0, this.depthTex)
.texture("STENCIL_IN_BINDING", 0, this.stencilTex); .texture("STENCIL_IN_BINDING", 0, this.stencilTex);
@@ -62,10 +71,10 @@ public class GlViewCapture {
public void emitToStream(int buffer, int offset) { public void emitToStream(int buffer, int offset) {
this.copyOutShader.bind(); this.copyOutShader.bind();
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 3, buffer, offset, (this.width*3L)*(this.height*2L)*4L*2);//its 2*4 because colour + depth stencil 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 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); glDispatchCompute(3, 2, 1);
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 3, 0, 0, 4);//WHY DOES THIS FIX FUCKING BINDING ISSUES HERE WHEN DOING THIS IN THE RENDER SYSTEM DOESNT 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() { public void clear() {
@@ -74,6 +83,9 @@ public class GlViewCapture {
MemoryUtil.memPutLong(ptr, 0); MemoryUtil.memPutLong(ptr, 0);
MemoryUtil.memPutLong(ptr+8, 0); MemoryUtil.memPutLong(ptr+8, 0);
nglClearNamedFramebufferfv(this.framebuffer.id, GL_COLOR, 0, ptr); 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); glClearNamedFramebufferfi(this.framebuffer.id, GL_DEPTH_STENCIL, 0, 1.0f, 0);
} }
@@ -83,6 +95,7 @@ public class GlViewCapture {
this.colourTex.free(); this.colourTex.free();
this.stencilTex.free(); this.stencilTex.free();
this.depthTex.free(); this.depthTex.free();
this.metaTex.free();
this.copyOutShader.free(); this.copyOutShader.free();
} }
} }

View File

@@ -1,31 +1,38 @@
package me.cortex.voxy.client.core.model.bakery; package me.cortex.voxy.client.core.model.bakery;
import net.minecraft.block.*; import net.minecraft.client.Minecraft;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
import net.minecraft.client.render.BlockRenderLayer; import net.minecraft.core.BlockPos;
import net.minecraft.client.render.RenderLayers; import net.minecraft.core.Direction;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.resources.Identifier;
import net.minecraft.fluid.FluidState; import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.util.Identifier; import net.minecraft.world.level.ColorResolver;
import net.minecraft.util.math.BlockPos; import net.minecraft.world.level.LightLayer;
import net.minecraft.util.math.Direction; import net.minecraft.world.level.block.Blocks;
import net.minecraft.util.math.random.LocalRandom; import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.BlockRenderView; import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.LightType; import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.biome.ColorResolver; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.chunk.light.LightingProvider; 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.jetbrains.annotations.Nullable;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Quaternionf; import org.joml.Quaternionf;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.lwjgl.opengl.ARBDrawBuffersBlend;
import org.lwjgl.opengl.GL14; import org.lwjgl.opengl.GL14;
import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate; import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
import static org.lwjgl.opengl.GL30.*; import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL40.glBlendFuncSeparatei;
import static org.lwjgl.opengl.GL45.glTextureBarrier; import static org.lwjgl.opengl.GL45.glTextureBarrier;
import com.mojang.blaze3d.vertex.PoseStack;
public class ModelTextureBakery { public class ModelTextureBakery {
//Note: the first bit of metadata is if alpha discard is enabled //Note: the first bit of metadata is if alpha discard is enabled
private static final Matrix4f[] VIEWS = new Matrix4f[6]; private static final Matrix4f[] VIEWS = new Matrix4f[6];
@@ -41,65 +48,69 @@ public class ModelTextureBakery {
this.height = height; this.height = height;
} }
public static int getMetaFromLayer(BlockRenderLayer layer) { public static int getMetaFromLayer(ChunkSectionLayer layer) {
boolean hasDiscard = layer == BlockRenderLayer.CUTOUT || boolean hasDiscard = layer == ChunkSectionLayer.CUTOUT ||
layer == BlockRenderLayer.CUTOUT_MIPPED || layer == ChunkSectionLayer.TRANSLUCENT||
layer == BlockRenderLayer.TRIPWIRE; layer == ChunkSectionLayer.TRIPWIRE;
boolean isMipped = layer == BlockRenderLayer.CUTOUT_MIPPED || boolean isMipped = layer == ChunkSectionLayer.SOLID ||
layer == BlockRenderLayer.SOLID || layer == ChunkSectionLayer.TRANSLUCENT ||
layer == BlockRenderLayer.TRANSLUCENT || layer == ChunkSectionLayer.TRIPWIRE;
layer == BlockRenderLayer.TRIPWIRE;
int meta = hasDiscard?1:0; int meta = hasDiscard?1:0;
meta |= isMipped?2:0; meta |= true?2:0;
return meta; return meta;
} }
private void bakeBlockModel(BlockState state, BlockRenderLayer layer) { private void bakeBlockModel(BlockState state, ChunkSectionLayer layer) {
if (state.getRenderType() == BlockRenderType.INVISIBLE) { if (state.getRenderShape() == RenderShape.INVISIBLE) {
return;//Dont bake if invisible return;//Dont bake if invisible
} }
var model = MinecraftClient.getInstance() var model = Minecraft.getInstance()
.getBakedModelManager() .getModelManager()
.getBlockModels() .getBlockModelShaper()
.getModel(state); .getBlockModel(state);
int meta = getMetaFromLayer(layer); int meta = getMetaFromLayer(layer);
for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) { for (var part : model.collectParts(new SingleThreadedRandomSource(42L))) {
for (var part : model.getParts(new LocalRandom(42L))) { for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) {
var quads = part.getQuads(direction); var quads = part.getQuads(direction);
for (var quad : quads) { for (var quad : quads) {
//TODO: add meta specifiying quad has a tint this.vc.quad(quad, meta|(quad.isTinted()?4:0));
//quad.hasTint()
this.vc.quad(quad, meta);
} }
} }
} }
} }
private void bakeFluidState(BlockState state, BlockRenderLayer layer, int face) { private void bakeFluidState(BlockState state, ChunkSectionLayer layer, int face) {
this.vc.setDefaultMeta(getMetaFromLayer(layer));//Set the meta while baking {
MinecraftClient.getInstance().getBlockRenderManager().renderFluid(BlockPos.ORIGIN, new BlockRenderView() { //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 @Override
public float getBrightness(Direction direction, boolean shaded) { public float getShade(Direction direction, boolean shaded) {
return 0; return 0;
} }
@Override @Override
public LightingProvider getLightingProvider() { public LevelLightEngine getLightEngine() {
return null; return null;
} }
@Override @Override
public int getLightLevel(LightType type, BlockPos pos) { public int getBrightness(LightLayer type, BlockPos pos) {
return 0; return 0;
} }
@Override @Override
public int getColor(BlockPos pos, ColorResolver colorResolver) { public int getBlockTint(BlockPos pos, ColorResolver colorResolver) {
return 0; return 0;
} }
@@ -112,7 +123,7 @@ public class ModelTextureBakery {
@Override @Override
public BlockState getBlockState(BlockPos pos) { public BlockState getBlockState(BlockPos pos) {
if (shouldReturnAirForFluid(pos, face)) { if (shouldReturnAirForFluid(pos, face)) {
return Blocks.AIR.getDefaultState(); return Blocks.AIR.defaultBlockState();
} }
//Fixme: //Fixme:
@@ -130,7 +141,7 @@ public class ModelTextureBakery {
@Override @Override
public FluidState getFluidState(BlockPos pos) { public FluidState getFluidState(BlockPos pos) {
if (shouldReturnAirForFluid(pos, face)) { if (shouldReturnAirForFluid(pos, face)) {
return Blocks.AIR.getDefaultState().getFluidState(); return Blocks.AIR.defaultBlockState().getFluidState();
} }
return state.getFluidState(); return state.getFluidState();
@@ -142,15 +153,15 @@ public class ModelTextureBakery {
} }
@Override @Override
public int getBottomY() { public int getMinY() {
return 0; return 0;
} }
}, this. vc, state, state.getFluidState()); }, this.vc, state, state.getFluidState());
this.vc.setDefaultMeta(0);//Reset default meta this.vc.setDefaultMeta(0);//Reset default meta
} }
private static boolean shouldReturnAirForFluid(BlockPos pos, int face) { private static boolean shouldReturnAirForFluid(BlockPos pos, int face) {
var fv = Direction.byIndex(face).getVector(); var fv = Direction.from3DDataValue(face).getUnitVec3i();
int dot = fv.getX()*pos.getX() + fv.getY()*pos.getY() + fv.getZ()*pos.getZ(); int dot = fv.getX()*pos.getX() + fv.getY()*pos.getY() + fv.getZ()*pos.getZ();
return dot >= 1; return dot >= 1;
} }
@@ -161,25 +172,25 @@ public class ModelTextureBakery {
} }
public void renderToStream(BlockState state, int streamBuffer, int streamOffset) { public int renderToStream(BlockState state, int streamBuffer, int streamOffset) {
this.capture.clear(); this.capture.clear();
boolean isBlock = true; boolean isBlock = true;
BlockRenderLayer layer; ChunkSectionLayer layer;
if (state.getBlock() instanceof FluidBlock) { if (state.getBlock() instanceof LiquidBlock) {
layer = RenderLayers.getFluidLayer(state.getFluidState()); layer = ItemBlockRenderTypes.getRenderLayer(state.getFluidState());
isBlock = false; isBlock = false;
} else { } else {
if (state.getBlock() instanceof LeavesBlock) { if (state.getBlock() instanceof LeavesBlock) {
layer = BlockRenderLayer.SOLID; layer = ChunkSectionLayer.SOLID;
} else { } else {
layer = RenderLayers.getBlockLayer(state); layer = ItemBlockRenderTypes.getChunkRenderType(state);
} }
} }
//TODO: support block model entities //TODO: support block model entities
BakedBlockEntityModel bbem = null; //BakedBlockEntityModel bbem = null;
if (state.hasBlockEntity()) { if (state.hasBlockEntity()) {
bbem = BakedBlockEntityModel.bake(state); //bbem = BakedBlockEntityModel.bake(state);
} }
//Setup GL state //Setup GL state
@@ -190,9 +201,10 @@ public class ModelTextureBakery {
glEnable(GL_STENCIL_TEST); glEnable(GL_STENCIL_TEST);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
if (layer == BlockRenderLayer.TRANSLUCENT) { if (layer == ChunkSectionLayer.TRANSLUCENT) {
glEnable(GL_BLEND); glEnablei(GL_BLEND, 0);
glBlendFuncSeparate(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glDisablei(GL_BLEND, 1);
ARBDrawBuffersBlend.glBlendFuncSeparateiARB(0, GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
} else { } else {
glDisable(GL_BLEND);//FUCK YOU INTEL (screams), for _some reason_ discard or something... JUST DOESNT WORK?? 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); //glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ONE);
@@ -207,14 +219,17 @@ public class ModelTextureBakery {
//Bind the capture framebuffer //Bind the capture framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id); glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
var tex = MinecraftClient.getInstance().getTextureManager().getTexture(Identifier.of("minecraft", "textures/atlas/blocks.png")).getGlTexture(); var tex = Minecraft.getInstance().getTextureManager().getTexture(Identifier.fromNamespaceAndPath("minecraft", "textures/atlas/blocks.png")).getTexture();
blockTextureId = ((net.minecraft.client.texture.GlTexture)tex).getGlId(); blockTextureId = ((com.mojang.blaze3d.opengl.GlTexture)tex).glId();
} }
//TODO: fastpath for blocks boolean isAnyShaded = false;
boolean isAnyDarkend = false;
if (isBlock) { if (isBlock) {
this.vc.reset(); this.vc.reset();
this.bakeBlockModel(state, layer); this.bakeBlockModel(state, layer);
isAnyShaded |= this.vc.anyShaded;
isAnyDarkend |= this.vc.anyDarkendTex;
if (!this.vc.isEmpty()) {//only render if there... is shit to render if (!this.vc.isEmpty()) {//only render if there... is shit to render
//Setup for continual emission //Setup for continual emission
@@ -243,7 +258,7 @@ public class ModelTextureBakery {
glBindVertexArray(0); glBindVertexArray(0);
} else {//Is fluid, slow path :( } else {//Is fluid, slow path :(
if (!(state.getBlock() instanceof FluidBlock)) throw new IllegalStateException(); if (!(state.getBlock() instanceof LiquidBlock)) throw new IllegalStateException();
var mat = new Matrix4f(); var mat = new Matrix4f();
for (int i = 0; i < VIEWS.length; i++) { for (int i = 0; i < VIEWS.length; i++) {
@@ -256,6 +271,8 @@ public class ModelTextureBakery {
this.vc.reset(); this.vc.reset();
this.bakeFluidState(state, layer, i); this.bakeFluidState(state, layer, i);
if (this.vc.isEmpty()) continue; if (this.vc.isEmpty()) continue;
isAnyShaded |= this.vc.anyShaded;
isAnyDarkend |= this.vc.anyDarkendTex;
BudgetBufferRenderer.setup(this.vc.getAddress(), this.vc.quadCount(), blockTextureId); BudgetBufferRenderer.setup(this.vc.getAddress(), this.vc.quadCount(), blockTextureId);
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height); glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
@@ -273,6 +290,7 @@ public class ModelTextureBakery {
} }
//Render block model entity data if it exists //Render block model entity data if it exists
/*
if (bbem != null) { if (bbem != null) {
//Rerender everything again ;-; but is ok (is not) //Rerender everything again ;-; but is ok (is not)
@@ -298,7 +316,7 @@ public class ModelTextureBakery {
glBindVertexArray(0); glBindVertexArray(0);
bbem.release(); bbem.release();
} }*/
@@ -314,10 +332,12 @@ public class ModelTextureBakery {
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id); glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
glClearDepth(1); glClearDepth(1);
glClear(GL_DEPTH_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT);
if (layer == BlockRenderLayer.TRANSLUCENT) { if (layer == ChunkSectionLayer.TRANSLUCENT) {
//reset the blend func //reset the blend func
GL14.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); GL14.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
} }
return (isAnyShaded?1:0)|(isAnyDarkend?2:0);
} }
@@ -336,14 +356,14 @@ public class ModelTextureBakery {
} }
private static void addView(int i, float pitch, float yaw, float rotation, int flip) { private static void addView(int i, float pitch, float yaw, float rotation, int flip) {
var stack = new MatrixStack(); var stack = new PoseStack();
stack.translate(0.5f,0.5f,0.5f); stack.translate(0.5f,0.5f,0.5f);
stack.multiply(makeQuatFromAxisExact(new Vector3f(0,0,1), rotation)); stack.mulPose(makeQuatFromAxisExact(new Vector3f(0,0,1), rotation));
stack.multiply(makeQuatFromAxisExact(new Vector3f(1,0,0), pitch)); stack.mulPose(makeQuatFromAxisExact(new Vector3f(1,0,0), pitch));
stack.multiply(makeQuatFromAxisExact(new Vector3f(0,1,0), yaw)); stack.mulPose(makeQuatFromAxisExact(new Vector3f(0,1,0), yaw));
stack.multiplyPositionMatrix(new Matrix4f().scale(1-2*(flip&1), 1-(flip&2), 1-((flip>>1)&2))); stack.mulPose(new Matrix4f().scale(1-2*(flip&1), 1-(flip&2), 1-((flip>>1)&2)));
stack.translate(-0.5f,-0.5f,-0.5f); stack.translate(-0.5f,-0.5f,-0.5f);
VIEWS[i] = new Matrix4f(stack.peek().getPositionMatrix()); VIEWS[i] = new Matrix4f(stack.last().pose());
} }
private static Quaternionf makeQuatFromAxisExact(Vector3f vec, float angle) { private static Quaternionf makeQuatFromAxisExact(Vector3f vec, float angle) {

View File

@@ -2,18 +2,24 @@ package me.cortex.voxy.client.core.model.bakery;
import me.cortex.voxy.common.util.MemoryBuffer; import me.cortex.voxy.common.util.MemoryBuffer;
import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.model.geom.builders.UVPair;
import net.minecraft.client.render.model.BakedQuad; import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.MipmapStrategy;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import static me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer.VERTEX_FORMAT_SIZE; 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 { public final class ReuseVertexConsumer implements VertexConsumer {
private MemoryBuffer buffer = new MemoryBuffer(8192); private MemoryBuffer buffer = new MemoryBuffer(8192);
private long ptr; private long ptr;
private int count; private int count;
private int defaultMeta; private int defaultMeta;
public boolean anyShaded;
public boolean anyDarkendTex;
public ReuseVertexConsumer() { public ReuseVertexConsumer() {
this.reset(); this.reset();
} }
@@ -24,7 +30,7 @@ public final class ReuseVertexConsumer implements VertexConsumer {
} }
@Override @Override
public ReuseVertexConsumer vertex(float x, float y, float z) { public ReuseVertexConsumer addVertex(float x, float y, float z) {
this.ensureCanPut(); this.ensureCanPut();
this.ptr += VERTEX_FORMAT_SIZE; this.count++; //Goto next vertex this.ptr += VERTEX_FORMAT_SIZE; this.count++; //Goto next vertex
this.meta(this.defaultMeta); this.meta(this.defaultMeta);
@@ -40,43 +46,51 @@ public final class ReuseVertexConsumer implements VertexConsumer {
} }
@Override @Override
public ReuseVertexConsumer color(int red, int green, int blue, int alpha) { public ReuseVertexConsumer setColor(int red, int green, int blue, int alpha) {
return this; return this;
} }
@Override @Override
public ReuseVertexConsumer texture(float u, float v) { 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 + 16, u);
MemoryUtil.memPutFloat(this.ptr + 20, v); MemoryUtil.memPutFloat(this.ptr + 20, v);
return this; return this;
} }
@Override @Override
public ReuseVertexConsumer overlay(int u, int v) { public ReuseVertexConsumer setUv1(int u, int v) {
return this; return this;
} }
@Override @Override
public ReuseVertexConsumer light(int u, int v) { public ReuseVertexConsumer setUv2(int u, int v) {
return this; return this;
} }
@Override @Override
public ReuseVertexConsumer normal(float x, float y, float z) { public ReuseVertexConsumer setNormal(float x, float y, float z) {
return this; return this;
} }
@Override
public VertexConsumer setLineWidth(float f) {
return null;
}
public ReuseVertexConsumer quad(BakedQuad quad, int metadata) { public ReuseVertexConsumer quad(BakedQuad quad, int metadata) {
this.anyShaded |= quad.shade();
this.anyDarkendTex |= quad.sprite().contents().mipmapStrategy == MipmapStrategy.DARK_CUTOUT;
this.ensureCanPut(); this.ensureCanPut();
int[] data = quad.vertexData();
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
float x = Float.intBitsToFloat(data[i * 8]); var pos = quad.position(i);
float y = Float.intBitsToFloat(data[i * 8 + 1]); this.addVertex(pos.x(), pos.y(), pos.z());
float z = Float.intBitsToFloat(data[i * 8 + 2]); long puv = quad.packedUV(i);
this.vertex(x,y,z); this.setUv(UVPair.unpackU(puv),UVPair.unpackV(puv));
float u = Float.intBitsToFloat(data[i * 8 + 4]);
float v = Float.intBitsToFloat(data[i * 8 + 5]);
this.texture(u,v);
this.meta(metadata); this.meta(metadata);
} }
@@ -97,6 +111,8 @@ public final class ReuseVertexConsumer implements VertexConsumer {
} }
public ReuseVertexConsumer reset() { public ReuseVertexConsumer reset() {
this.anyShaded = false;
this.anyDarkendTex = false;
this.defaultMeta = 0;//RESET THE DEFAULT META this.defaultMeta = 0;//RESET THE DEFAULT META
this.count = 0; this.count = 0;
this.ptr = this.buffer.address - VERTEX_FORMAT_SIZE;//the thing is first time this gets incremented by FORMAT_STRIDE this.ptr = this.buffer.address - VERTEX_FORMAT_SIZE;//the thing is first time this gets incremented by FORMAT_STRIDE

View File

@@ -12,8 +12,7 @@ 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.SharedIndexBuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream; import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.Logger;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.Minecraft;
import net.minecraft.util.math.MathHelper;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.joml.Vector3i; import org.joml.Vector3i;
@@ -91,22 +90,22 @@ public class ChunkBoundRenderer {
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 128); long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 128);
long matPtr = ptr; ptr += 4*4*4; long matPtr = ptr; ptr += 4*4*4;
final float renderDistance = MinecraftClient.getInstance().options.getClampedViewDistance()*16;//In blocks final float renderDistance = Minecraft.getInstance().options.getEffectiveRenderDistance()*16;//In blocks
{//This is recomputed to be in chunk section space not worldsection {//This is recomputed to be in chunk section space not worldsection
int sx = MathHelper.floor(viewport.cameraX) >> 4; int sx = (int)(viewport.cameraX);
int sy = MathHelper.floor(viewport.cameraY) >> 4; int sy = (int)(viewport.cameraY);
int sz = MathHelper.floor(viewport.cameraZ) >> 4; int sz = (int)(viewport.cameraZ);
new Vector3i(sx, sy, sz).getToAddress(ptr); ptr += 4*4; new Vector3i(sx, sy, sz).getToAddress(ptr); ptr += 4*4;
var negInnerSec = new Vector3f( var negInnerSec = new Vector3f(
-(float) (viewport.cameraX - (sx << 4)), (float) (viewport.cameraX - sx),
-(float) (viewport.cameraY - (sy << 4)), (float) (viewport.cameraY - sy),
-(float) (viewport.cameraZ - (sz << 4))); (float) (viewport.cameraZ - sz));
viewport.MVP.translate(negInnerSec, new Matrix4f()).getToAddress(matPtr);
negInnerSec.getToAddress(ptr); ptr += 4*3; negInnerSec.getToAddress(ptr); ptr += 4*3;
viewport.MVP.translate(negInnerSec.negate(), new Matrix4f()).getToAddress(matPtr);
MemoryUtil.memPutFloat(ptr, renderDistance); ptr += 4; MemoryUtil.memPutFloat(ptr, renderDistance); ptr += 4;
} }
UploadStream.INSTANCE.commit(); UploadStream.INSTANCE.commit();
@@ -131,7 +130,7 @@ public class ChunkBoundRenderer {
//Batch the draws into groups of size 32 //Batch the draws into groups of size 32
int count = this.chunk2idx.size(); int count = this.chunk2idx.size();
if (count > 32) { if (count >= 32) {
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3 * 32, GL_UNSIGNED_BYTE, 0, count/32); glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3 * 32, GL_UNSIGNED_BYTE, 0, count/32);
} }
if (count%32 != 0) { if (count%32 != 0) {

View File

@@ -69,7 +69,7 @@ public class SectionUpdateRouter implements ISectionWatcher {
} }
lock.unlock(stamp); lock.unlock(stamp);
} }
if ((delta&UPDATE_TYPE_BLOCK_BIT)!=0) { if (((delta&types)&UPDATE_TYPE_BLOCK_BIT)!=0) {
//If we added it, immediately invoke for an update //If we added it, immediately invoke for an update
this.initialRenderMeshGen.accept(position); this.initialRenderMeshGen.accept(position);
} }
@@ -138,19 +138,32 @@ public class SectionUpdateRouter implements ISectionWatcher {
var lock = this.locks[idx]; var lock = this.locks[idx];
long stamp = lock.readLock(); long stamp = lock.readLock();
byte types = set.getOrDefault(position, (byte) 0); byte types = (byte) (set.getOrDefault(position, (byte) 0)&type);
lock.unlockRead(stamp); lock.unlockRead(stamp);
if (types!=0) { if (types!=0) {
if ((type&WorldEngine.UPDATE_TYPE_CHILD_EXISTENCE_BIT)!=0) { if ((types&WorldEngine.UPDATE_TYPE_CHILD_EXISTENCE_BIT)!=0) {
this.childUpdateCallback.accept(section); this.childUpdateCallback.accept(section);
} }
if ((type& UPDATE_TYPE_BLOCK_BIT)!=0) { if ((types&UPDATE_TYPE_BLOCK_BIT)!=0) {
this.renderMeshGen.accept(section.key); 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) { private static int getSliceIndex(long value) {
value = (value ^ value >>> 30) * -4658895280553007687L; value = (value ^ value >>> 30) * -4658895280553007687L;
value = (value ^ value >>> 27) * -7723592293110705685L; value = (value ^ value >>> 27) * -7723592293110705685L;

View File

@@ -4,7 +4,7 @@ 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.DepthFramebuffer;
import me.cortex.voxy.client.core.rendering.util.HiZBuffer; import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
import net.caffeinemc.mods.sodium.client.util.FogParameters; import net.caffeinemc.mods.sodium.client.util.FogParameters;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.Mth;
import org.joml.*; import org.joml.*;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@@ -101,9 +101,9 @@ public abstract class Viewport <A extends Viewport<A>> {
this.frustum.set(this.MVP, false); this.frustum.set(this.MVP, false);
//Translation vectors //Translation vectors
int sx = MathHelper.floor(this.cameraX)>>5; int sx = Mth.floor(this.cameraX)>>5;
int sy = MathHelper.floor(this.cameraY)>>5; int sy = Mth.floor(this.cameraY)>>5;
int sz = MathHelper.floor(this.cameraZ)>>5; int sz = Mth.floor(this.cameraZ)>>5;
this.section.set(sx, sy, sz); this.section.set(sx, sy, sz);
this.innerTranslation.set( this.innerTranslation.set(
@@ -111,7 +111,9 @@ public abstract class Viewport <A extends Viewport<A>> {
(float) (this.cameraY-(sy<<5)), (float) (this.cameraY-(sy<<5)),
(float) (this.cameraZ-(sz<<5))); (float) (this.cameraZ-(sz<<5)));
this.depthBoundingBuffer.resize(this.width, this.height); if (this.depthBoundingBuffer.resize(this.width, this.height)) {
this.depthBoundingBuffer.clear(0.0f);
}
return (A) this; return (A) this;
} }

View File

@@ -2,18 +2,20 @@ package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.util.IrisUtil; import me.cortex.voxy.client.core.util.IrisUtil;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import org.vivecraft.client_vr.ClientDataHolderVR; import org.vivecraft.api.client.VRRenderingAPI;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import static org.vivecraft.api.client.data.RenderPass.VANILLA;
public class ViewportSelector <T extends Viewport<?>> { public class ViewportSelector <T extends Viewport<?>> {
public static final boolean VIVECRAFT_INSTALLED = FabricLoader.getInstance().isModLoaded("vivecraft"); public static final boolean VIVECRAFT_INSTALLED = FabricLoader.getInstance().isModLoaded("vivecraft");
private final Supplier<T> creator; private final Supplier<T> creator;
private final T defaultViewport; private final T defaultViewport;
private final Map<Object, T> extraViewports = new HashMap<>(); 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) { public ViewportSelector(Supplier<T> viewportCreator) {
this.creator = viewportCreator; this.creator = viewportCreator;
@@ -25,24 +27,28 @@ public class ViewportSelector <T extends Viewport<?>> {
} }
private T getVivecraftViewport() { private T getVivecraftViewport() {
var cdh = ClientDataHolderVR.getInstance(); var pass = VRRenderingAPI.instance().getCurrentRenderPass();
var pass = cdh.currentPass; if (pass == null || pass == VANILLA) {
if (pass == null) { return null;
return this.defaultViewport;
} }
return this.getOrCreate(pass); return this.getOrCreate(pass);
} }
private static final Object IRIS_SHADOW_OBJECT = new Object(); private static final Object IRIS_SHADOW_OBJECT = new Object();
public T getViewport() { public T getViewport() {
if (VIVECRAFT_INSTALLED) { T viewport = null;
return getVivecraftViewport(); if (viewport == null && VIVECRAFT_INSTALLED) {
viewport = getVivecraftViewport();
} }
if (IrisUtil.irisShadowActive()) { if (viewport == null && IrisUtil.irisShadowActive()) {
return this.getOrCreate(IRIS_SHADOW_OBJECT); viewport = this.getOrCreate(IRIS_SHADOW_OBJECT);
} }
return this.defaultViewport;
if (viewport == null) {
viewport = this.defaultViewport;
}
return viewport;
} }
public void free() { public void free() {

View File

@@ -13,9 +13,10 @@ public final class BuiltSection {
public final int aabb; public final int aabb;
public final MemoryBuffer geometryBuffer; public final MemoryBuffer geometryBuffer;
public final int[] offsets; public final int[] offsets;
public final MemoryBuffer occupancy;
private BuiltSection(long position, byte children) { private BuiltSection(long position, byte children) {
this(position, children, -1, null, null); this(position, children, -1, null, null, null);
} }
public static BuiltSection empty(long position) { public static BuiltSection empty(long position) {
@@ -25,7 +26,7 @@ public final class BuiltSection {
return new BuiltSection(position, children); return new BuiltSection(position, children);
} }
public BuiltSection(long position, byte childExistence, int aabb, MemoryBuffer geometryBuffer, int[] offsets) { public BuiltSection(long position, byte childExistence, int aabb, MemoryBuffer geometryBuffer, int[] offsets, MemoryBuffer occupancy) {
this.position = position; this.position = position;
this.childExistence = childExistence; this.childExistence = childExistence;
this.aabb = aabb; this.aabb = aabb;
@@ -39,16 +40,20 @@ public final class BuiltSection {
} }
} }
} }
this.occupancy = occupancy;
} }
public BuiltSection clone() { public BuiltSection clone() {
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); 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() { public void free() {
if (this.geometryBuffer != null) { if (this.geometryBuffer != null) {
this.geometryBuffer.free(); this.geometryBuffer.free();
} }
if (this.occupancy != null) {
this.occupancy.free();
}
} }
public boolean isEmpty() { public boolean isEmpty() {

View File

@@ -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();
}
}
}
}
}

View File

@@ -17,8 +17,10 @@ import java.util.Arrays;
public class RenderDataFactory { public class RenderDataFactory {
private static final boolean BUILD_OCCUPANCY_SET = false;
private static final boolean CHECK_NEIGHBOR_FACE_OCCLUSION = true; private static final boolean CHECK_NEIGHBOR_FACE_OCCLUSION = true;
private static final boolean DISABLE_CULL_SAME_OCCLUDES = false; private static final boolean DISABLE_CULL_SAME_OCCLUDES = false;//TODO: FIX TRANSLUCENTS (e.g. stained glass) breaking on chunk boarders with this set to false (it might be something else????)
private static final boolean VERIFY_MESHING = VoxyCommon.isVerificationFlagOn("verifyMeshing"); private static final boolean VERIFY_MESHING = VoxyCommon.isVerificationFlagOn("verifyMeshing");
@@ -60,6 +62,8 @@ public class RenderDataFactory {
private int quadCount = 0; private int quadCount = 0;
private final OccupancySet occupancy = new OccupancySet();
//Wont work for double sided quads //Wont work for double sided quads
private final class Mesher extends ScanMesher2D { private final class Mesher extends ScanMesher2D {
public int auxiliaryPosition = 0; public int auxiliaryPosition = 0;
@@ -191,15 +195,16 @@ public class RenderDataFactory {
boolean a = ModelQueries.isTranslucent(metadata); boolean a = ModelQueries.isTranslucent(metadata);
boolean b = ModelQueries.isDoubleSided(metadata); boolean b = ModelQueries.isDoubleSided(metadata);
//Pre shift by 1 //Pre shift by 1
type = a|b?0:4; //type = a|b?0:4;
type |= b?2:0; //type |= b&!a?2:0;
type = a?0:(b?2:4);
} }
return type; return type;
} }
private static long packPartialQuadData(int modelId, long state, long metadata) { private static long packPartialQuadData(int modelId, long state, long metadata) {
//This uses hardcoded data to shuffle things //This uses hardcoded data to shuffle things
long lightAndBiome = (state&((0x1FFL<<47)|(0xFFL<<56)))>>1; long lightAndBiome = (state&((0x1FFL<<47)|(0xFFL<<56)))>>>1;
lightAndBiome &= ModelQueries.isBiomeColoured(metadata)?-1:~(0x1FFL<<46);//46 not 47 because is already shifted by 1 THIS WASTED 4 HOURS ;-; aaaaaAAAAAA lightAndBiome &= ModelQueries.isBiomeColoured(metadata)?-1:~(0x1FFL<<46);//46 not 47 because is already shifted by 1 THIS WASTED 4 HOURS ;-; aaaaaAAAAAA
lightAndBiome &= ModelQueries.isFullyOpaque(metadata)?~(0xFFL<<55):-1;//If its fully opaque it always uses neighbor light? lightAndBiome &= ModelQueries.isFullyOpaque(metadata)?~(0xFFL<<55):-1;//If its fully opaque it always uses neighbor light?
@@ -217,11 +222,11 @@ public class RenderDataFactory {
long pureFluid = 0; long pureFluid = 0;
long partialFluid = 0; long partialFluid = 0;
int neighborAcquireMsk = 0;//-+x, -+z, -+y int neighborAcquireMskAndFlags = 0;//-+x, -+z, -+y
for (int i = 0; i < 32*32*32;) { for (int i = 0; i < 32*32*32;) {
long block = rawSectionData[i];//Get the block mapping long block = rawSectionData[i];//Get the block mapping
if (Mapper.isAir(block)) {//If it is air, just emit lighting if (Mapper.isAir(block)) {//If it is air, just emit lighting
sectionData[i * 2] = (block&(0xFFL<<56))>>1; sectionData[i * 2] = (block&(0xFFL<<56))>>>1;
sectionData[i * 2 + 1] = 0; sectionData[i * 2 + 1] = 0;
} else { } else {
int modelId = rawModelIds[Mapper.getBlockId(block)]; int modelId = rawModelIds[Mapper.getBlockId(block)];
@@ -257,17 +262,17 @@ public class RenderDataFactory {
int neighborMsk = 0; int neighborMsk = 0;
//-+x //-+x
neighborMsk |= packedEmpty&1;//-x neighborMsk += packedEmpty&1;//-x
neighborMsk |= (packedEmpty>>>30)&0b10;//+x neighborMsk += (packedEmpty>>>30)&0b10;//+x
//notEmpty = (notEmpty != 0)?1:0; //notEmpty = (notEmpty != 0)?1:0;
neighborMsk |= ((((i - 1) >> 10) == 0) ? 0b100 : 0)*(packedEmpty!=0?1:0);//-y neighborMsk += ((((i - 1) >> 10) == 0) ? 0b100 : 0)*(packedEmpty!=0?1:0);//-y
neighborMsk |= ((((i - 1) >> 10) == 31) ? 0b1000 : 0)*(packedEmpty!=0?1:0);//+y neighborMsk += ((((i - 1) >> 10) == 31) ? 0b1000 : 0)*(packedEmpty!=0?1:0);//+y
neighborMsk |= (((((i - 33) >> 5) & 0x1F) == 0) ? 0b10000 : 0)*(((int)notEmpty)!=0?1:0);//-z neighborMsk += (((((i - 33) >> 5) & 0x1F) == 0) ? 0b10000 : 0)*(((int)notEmpty)!=0?1:0);//-z
neighborMsk |= (((((i - 1) >> 5) & 0x1F) == 31) ? 0b100000 : 0)*((notEmpty>>>32)!=0?1:0);//+z neighborMsk += (((((i - 1) >> 5) & 0x1F) == 31) ? 0b100000 : 0)*((notEmpty>>>32)!=0?1:0);//+z
neighborAcquireMsk |= neighborMsk;
neighborAcquireMskAndFlags |= neighborMsk;
neighborAcquireMskAndFlags |= opaque!=0?(1<<6):0;
opaque = 0; opaque = 0;
notEmpty = 0; notEmpty = 0;
@@ -275,7 +280,7 @@ public class RenderDataFactory {
partialFluid = 0; partialFluid = 0;
} }
} }
return neighborAcquireMsk; return neighborAcquireMskAndFlags;
} }
private void acquireNeighborData(WorldSection section, int msk) { private void acquireNeighborData(WorldSection section, int msk) {
@@ -341,10 +346,10 @@ public class RenderDataFactory {
private static final long LM = (0xFFL<<55); private static final long LM = (0xFFL<<55);
private static boolean shouldMeshNonOpaqueBlockFace(int face, long quad, long meta, long neighborQuad, long neighborMeta) { private static boolean shouldMeshNonOpaqueBlockFace(int face, long quad, long meta, long neighborQuad, long neighborMeta) {
if (((quad^neighborQuad)&(0xFFFFL<<26))==0 && (DISABLE_CULL_SAME_OCCLUDES || ModelQueries.faceOccludes(meta, face))) return false;//This is a hack, if the neigbor and this are the same, dont mesh the face// TODO: FIXME if (((quad^neighborQuad)&(0xFFFFL<<26))==0 && (DISABLE_CULL_SAME_OCCLUDES || (ModelQueries.cullsSame(meta)||ModelQueries.faceOccludes(meta, face)))) return false;//This is a hack, if the neigbor and this are the same, dont mesh the face// TODO: FIXME
if (!ModelQueries.faceExists(meta, face)) return false;//Dont mesh if no face if (!ModelQueries.faceExists(meta, face)) return false;//Dont mesh if no face
//if (ModelQueries.faceCanBeOccluded(meta, face)) //TODO: maybe enable this if (ModelQueries.faceCanBeOccluded(meta, face)) //TODO: maybe enable this
if (ModelQueries.faceOccludes(neighborMeta, face^1)) return false; if (ModelQueries.faceOccludes(neighborMeta, face^1)) return false;
return true; return true;
} }
@@ -397,16 +402,21 @@ public class RenderDataFactory {
int iA = idx * 2 + (facingForward == 1 ? 0 : shift); int iA = idx * 2 + (facingForward == 1 ? 0 : shift);
int iB = idx * 2 + (facingForward == 1 ? shift : 0); int iB = idx * 2 + (facingForward == 1 ? shift : 0);
long selfModel = this.sectionData[iA];
long nextModel = this.sectionData[iB];
//Check if next culls this face //Check if next culls this face
if (CHECK_NEIGHBOR_FACE_OCCLUSION) { if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
if (ModelQueries.faceOccludes(this.sectionData[iB + 1], (axis << 1) | (1 - facingForward))) { long neighbor = this.sectionData[iB + 1];
boolean culls = false;
culls |= ((selfModel^nextModel)&(0xFFFFL<<26))==0&&ModelQueries.cullsSame(neighbor);
culls |= ModelQueries.faceOccludes(neighbor, (axis << 1) | (1 - facingForward));
if (culls) {
this.blockMesher.skip(1); this.blockMesher.skip(1);
continue; continue;
} }
} }
long selfModel = this.sectionData[iA];
long nextModel = this.sectionData[iB];
this.blockMesher.putNext(((long) facingForward) |//Facing this.blockMesher.putNext(((long) facingForward) |//Facing
(selfModel&~LM) | (selfModel&~LM) |
(nextModel&LM)//Apply lighting (nextModel&LM)//Apply lighting
@@ -452,9 +462,12 @@ public class RenderDataFactory {
int neighborIdx = ((axis+1)*32*32 * 2)+(side)*32*32; int neighborIdx = ((axis+1)*32*32 * 2)+(side)*32*32;
long neighborId = this.neighboringFaces[neighborIdx + (other*32) + index]; long neighborId = this.neighboringFaces[neighborIdx + (other*32) + index];
long A = this.sectionData[idx * 2];
if (Mapper.getBlockId(neighborId) != 0) {//Not air int nib = Mapper.getBlockId(neighborId);
long meta = this.modelMan.getModelMetadataFromClientId(this.modelMan.getModelId(Mapper.getBlockId(neighborId))); if (nib != 0) {//Not air
int cid = this.modelMan.getModelId(nib);
long meta = this.modelMan.getModelMetadataFromClientId(cid);
if (ModelQueries.isFullyOpaque(meta)) {//Dont mesh this face if (ModelQueries.isFullyOpaque(meta)) {//Dont mesh this face
this.blockMesher.skip(1); this.blockMesher.skip(1);
continue; continue;
@@ -463,7 +476,10 @@ public class RenderDataFactory {
//This very funnily causes issues when not combined with meshing non full opaque geometry //This very funnily causes issues when not combined with meshing non full opaque geometry
//TODO:FIXME, when non opaque geometry is added //TODO:FIXME, when non opaque geometry is added
if (CHECK_NEIGHBOR_FACE_OCCLUSION) { if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
if (ModelQueries.faceOccludes(meta, (axis << 1) | (1 - side))) { boolean culls = false;
culls |= cid==((A>>26)&0xFFFF)&&ModelQueries.cullsSame(meta);
culls |= ModelQueries.faceOccludes(meta, (axis << 1) | (1 - side));
if (culls) {
this.blockMesher.skip(1); this.blockMesher.skip(1);
continue; continue;
} }
@@ -471,11 +487,10 @@ public class RenderDataFactory {
} }
long A = this.sectionData[idx * 2];
this.blockMesher.putNext(((side == 0) ? 0L : 1L) | this.blockMesher.putNext(((side == 0) ? 0L : 1L) |
(A&~LM) | (A&~LM) |
((neighborId & (0xFFL << 56)) >> 1) ((neighborId & (0xFFL << 56)) >>> 1)
); );
} }
} }
@@ -531,7 +546,7 @@ public class RenderDataFactory {
int bi = facingForward == 1 ? b : a; int bi = facingForward == 1 ? b : a;
//TODO: check if must cull against next entries face //TODO: check if must cull against next entries face
if (CHECK_NEIGHBOR_FACE_OCCLUSION) { if (CHECK_NEIGHBOR_FACE_OCCLUSION) {//TODO:SELF OCCLUSION
if (ModelQueries.faceOccludes(this.sectionData[bi + 1], (axis << 1) | (1 - facingForward))) { if (ModelQueries.faceOccludes(this.sectionData[bi + 1], (axis << 1) | (1 - facingForward))) {
this.blockMesher.skip(1); this.blockMesher.skip(1);
continue; continue;
@@ -552,7 +567,7 @@ public class RenderDataFactory {
A &= ~0b110L; A |= getQuadTyping(Am); A &= ~0b110L; A |= getQuadTyping(Am);
} }
long lighter = A; long lighter = this.sectionData[bi];
//if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right //if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right
// lighter = this.sectionData[bi]; // lighter = this.sectionData[bi];
//} //}
@@ -641,7 +656,7 @@ public class RenderDataFactory {
this.blockMesher.putNext((side == 0 ? 0L : 1L) | this.blockMesher.putNext((side == 0 ? 0L : 1L) |
(A&~LM) | (A&~LM) |
((neighborId&(0xFFL<<56))>>1) ((neighborId&(0xFFL<<56))>>>1)
); );
} }
} }
@@ -753,7 +768,10 @@ public class RenderDataFactory {
//Check and test if can cull W.R.T neighbor //Check and test if can cull W.R.T neighbor
if (Mapper.getBlockId(neighborId) != 0) {//Not air if (Mapper.getBlockId(neighborId) != 0) {//Not air
int modelId = this.modelMan.getModelId(Mapper.getBlockId(neighborId)); int modelId = this.modelMan.getModelId(Mapper.getBlockId(neighborId));
if (modelId == ((A>>26)&0xFFFF)) {//TODO: FIXME, this technically isnt correct as need to check self occulsion, thinks?
if (ModelQueries.cullsSame(B) && modelId == ((A>>26)&0xFFFF)) {//TODO: FIXME, this technically isnt correct as need to check self occulsion, thinks?
//TODO: check self occlsuion in the if statment
fail = true; fail = true;
} else { } else {
long meta = this.modelMan.getModelMetadataFromClientId(modelId); long meta = this.modelMan.getModelMetadataFromClientId(modelId);
@@ -767,7 +785,10 @@ public class RenderDataFactory {
long nA = this.sectionData[(idx+skipAmount) * 2]; long nA = this.sectionData[(idx+skipAmount) * 2];
long nB = this.sectionData[(idx+skipAmount) * 2 + 1]; long nB = this.sectionData[(idx+skipAmount) * 2 + 1];
boolean failB = false; boolean failB = false;
if ((nA&(0xFFFFL<<26)) == (A&(0xFFFFL<<26))) {//TODO: FIXME, this technically isnt correct as need to check self occulsion, thinks? //TODO: check self occlusion
if (ModelQueries.cullsSame(nB) && (nA&(0xFFFFL<<26)) == (A&(0xFFFFL<<26))) {//TODO: FIXME, this technically isnt correct as need to check self occulsion, thinks?
//TODO: check self occlsuion in the if statment
failB = true; failB = true;
} else { } else {
if (ModelQueries.faceOccludes(nB, (axis << 1) | (side))) { if (ModelQueries.faceOccludes(nB, (axis << 1) | (side))) {
@@ -930,6 +951,7 @@ public class RenderDataFactory {
//Check if next culls this face //Check if next culls this face
if (CHECK_NEIGHBOR_FACE_OCCLUSION) { if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
//TODO: check self occlsuion
if (ModelQueries.faceOccludes(this.sectionData[iB + 1], (2 << 1) | (1 - facingForward))) { if (ModelQueries.faceOccludes(this.sectionData[iB + 1], (2 << 1) | (1 - facingForward))) {
mesher.skip(1); mesher.skip(1);
continue; continue;
@@ -996,6 +1018,7 @@ public class RenderDataFactory {
if (ModelQueries.isFullyOpaque(meta)) { if (ModelQueries.isFullyOpaque(meta)) {
oki = false; oki = false;
} else if (CHECK_NEIGHBOR_FACE_OCCLUSION && ModelQueries.faceOccludes(meta, (2 << 1) | (1 - 1))) { } else if (CHECK_NEIGHBOR_FACE_OCCLUSION && ModelQueries.faceOccludes(meta, (2 << 1) | (1 - 1))) {
//TODO check self occlsion
oki = false; oki = false;
} }
} }
@@ -1004,7 +1027,7 @@ public class RenderDataFactory {
long A = this.sectionData[(i<<5) * 2]; long A = this.sectionData[(i<<5) * 2];
ma.putNext(0L | ma.putNext(0L |
(A&~LM) | (A&~LM) |
((neighborId&(0xFFL<<56))>>1) ((neighborId&(0xFFL<<56))>>>1)
); );
} else {skipA++;} } else {skipA++;}
} else {skipA++;} } else {skipA++;}
@@ -1017,6 +1040,7 @@ public class RenderDataFactory {
if (ModelQueries.isFullyOpaque(meta)) { if (ModelQueries.isFullyOpaque(meta)) {
oki = false; oki = false;
} else if (CHECK_NEIGHBOR_FACE_OCCLUSION && ModelQueries.faceOccludes(meta, (2 << 1) | (1 - 0))) { } else if (CHECK_NEIGHBOR_FACE_OCCLUSION && ModelQueries.faceOccludes(meta, (2 << 1) | (1 - 0))) {
//TODO check self occlsion
oki = false; oki = false;
} }
} }
@@ -1025,7 +1049,7 @@ public class RenderDataFactory {
long A = this.sectionData[(i*32+31) * 2]; long A = this.sectionData[(i*32+31) * 2];
mb.putNext(1L | mb.putNext(1L |
(A&~LM) | (A&~LM) |
((neighborId&(0xFFL<<56))>>1) ((neighborId&(0xFFL<<56))>>>1)
); );
} else {skipB++;} } else {skipB++;}
} else {skipB++;} } else {skipB++;}
@@ -1122,7 +1146,8 @@ public class RenderDataFactory {
if (CHECK_NEIGHBOR_FACE_OCCLUSION) { if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
if (ModelQueries.faceOccludes(this.sectionData[bi + 1], (2 << 1) | (1 - facingForward))) { if (ModelQueries.faceOccludes(this.sectionData[bi + 1], (2 << 1) | (1 - facingForward))) {
this.blockMesher.skip(1); //TODO check self occlsion
mesher.skip(1);
continue; continue;
} }
} }
@@ -1142,7 +1167,7 @@ public class RenderDataFactory {
A &= ~0b110L; A |= getQuadTyping(Am); A &= ~0b110L; A |= getQuadTyping(Am);
} }
long lighter = A; long lighter = this.sectionData[bi];
//if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right //if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right
// lighter = this.sectionData[bi]; // lighter = this.sectionData[bi];
//} //}
@@ -1246,7 +1271,7 @@ public class RenderDataFactory {
ma.skip(skipA); skipA = 0; ma.skip(skipA); skipA = 0;
//TODO: LIGHTING //TODO: LIGHTING
long lightData = ((neighborId&(0xFFL<<56))>>1);//A; long lightData = ((neighborId&(0xFFL<<56))>>>1);//A;
//if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right //if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right
// lighter = this.sectionData[bi]; // lighter = this.sectionData[bi];
//} //}
@@ -1307,7 +1332,7 @@ public class RenderDataFactory {
mb.skip(skipB); skipB = 0; mb.skip(skipB); skipB = 0;
//TODO: LIGHTING //TODO: LIGHTING
long lightData = ((neighborId&(0xFFL<<56))>>1);//A; long lightData = ((neighborId&(0xFFL<<56))>>>1);//A;
//if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right //if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right
// lighter = this.sectionData[bi]; // lighter = this.sectionData[bi];
//} //}
@@ -1472,6 +1497,7 @@ public class RenderDataFactory {
int msk = this.nonOpaqueMasks[i]; int msk = this.nonOpaqueMasks[i];
if ((msk & 1) != 0) {//-x if ((msk & 1) != 0) {//-x
long neighborId = this.neighboringFaces[i]; long neighborId = this.neighboringFaces[i];
//TODO also check self occlusion
int sidx = (i<<5) * 2; int sidx = (i<<5) * 2;
long A = this.sectionData[sidx]; long A = this.sectionData[sidx];
@@ -1492,6 +1518,8 @@ public class RenderDataFactory {
if ((msk & (1<<31)) != 0) {//+x if ((msk & (1<<31)) != 0) {//+x
long neighborId = this.neighboringFaces[i+32*32]; long neighborId = this.neighboringFaces[i+32*32];
//TODO also check self occlusion
int sidx = (i*32+31) * 2; int sidx = (i*32+31) * 2;
long A = this.sectionData[sidx]; long A = this.sectionData[sidx];
@@ -1544,6 +1572,29 @@ public class RenderDataFactory {
} }
} }
//Build the occupancy set (used for AO) from the set of fully opaque blocks (atm, this can change in the future if needed to a special occupancy bitset)
private final void buildOccupancy() {
//We basicly want to record all the points where we go from air to solid or solid to air (this is to just get better compression)
for (int i = 0; i < 32*32; i++) {
int occ = 0;
int msk = this.opaqueMasks[i];
//x
occ |= msk^(msk>>1);
occ |= msk^(msk<<1);
//y
occ |= i<32*31?msk^this.opaqueMasks[i+32]:0;
occ |= 31<i ?msk^this.opaqueMasks[i-32]:0;
//z
occ |= (i&31)<31?msk^this.opaqueMasks[i+1]:0;
occ |= 0< (i&31)?msk^this.opaqueMasks[i-1]:0;
//We now have our occlusion mask, fill in our occupancy set
for (;occ!=0;occ&=~Integer.lowestOneBit(occ)) {
this.occupancy.set(i*32+Integer.numberOfTrailingZeros(occ));
}
}
}
//section is already acquired and gets released by the parent //section is already acquired and gets released by the parent
public BuiltSection generateMesh(WorldSection section) { public BuiltSection generateMesh(WorldSection section) {
//TODO: FIXME: because of the exceptions that are thrown when aquiring modelId //TODO: FIXME: because of the exceptions that are thrown when aquiring modelId
@@ -1574,6 +1625,8 @@ public class RenderDataFactory {
} }
} }
this.occupancy.reset();
this.minX = Integer.MAX_VALUE; this.minX = Integer.MAX_VALUE;
this.minY = Integer.MAX_VALUE; this.minY = Integer.MAX_VALUE;
this.minZ = Integer.MAX_VALUE; this.minZ = Integer.MAX_VALUE;
@@ -1587,11 +1640,15 @@ public class RenderDataFactory {
Arrays.fill(this.fluidMasks, 0); Arrays.fill(this.fluidMasks, 0);
//Prepare everything //Prepare everything
int neighborMsk = this.prepareSectionData(section._unsafeGetRawDataArray()); int neighborMskAndFlags = this.prepareSectionData(section._unsafeGetRawDataArray());
if (neighborMsk>>31!=0) {//We failed to get everything so throw exception if ((neighborMskAndFlags&(1<<31))!=0) {//We failed to get everything so throw exception
throw new IdNotYetComputedException(neighborMsk&(~(1<<31)), true); throw new IdNotYetComputedException(neighborMskAndFlags&((1<<20)-1), true);
}
int neighborMsk = neighborMskAndFlags&0b11_11_11;
int flags = neighborMskAndFlags>>>6;
if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
this.acquireNeighborData(section, neighborMsk);
} }
this.acquireNeighborData(section, neighborMsk);
try { try {
this.generateYZFaces(); this.generateYZFaces();
@@ -1602,6 +1659,11 @@ public class RenderDataFactory {
throw e; throw e;
} }
//We only care if we have quads
if (BUILD_OCCUPANCY_SET && this.quadCount != 0 && (flags&1) != 0) {
this.buildOccupancy();
}
//TODO:NOTE! when doing face culling of translucent blocks, //TODO:NOTE! when doing face culling of translucent blocks,
// if the connecting type of the translucent block is the same AND the face is full, discard it // if the connecting type of the translucent block is the same AND the face is full, discard it
// this stops e.g. multiple layers of glass (and ocean) from having 3000 layers of quads etc // this stops e.g. multiple layers of glass (and ocean) from having 3000 layers of quads etc
@@ -1636,7 +1698,13 @@ public class RenderDataFactory {
aabb |= (this.maxY-this.minY-1)<<20; aabb |= (this.maxY-this.minY-1)<<20;
aabb |= (this.maxZ-this.minZ-1)<<25; aabb |= (this.maxZ-this.minZ-1)<<25;
return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets); MemoryBuffer occupancy = null;
if (BUILD_OCCUPANCY_SET && !this.occupancy.isEmpty()) {
occupancy = new MemoryBuffer(this.occupancy.writeSize());
this.occupancy.write(occupancy.address, false);
}
return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets, occupancy);
} }
public void free() { public void free() {

View File

@@ -4,8 +4,8 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import me.cortex.voxy.client.core.model.IdNotYetComputedException; import me.cortex.voxy.client.core.model.IdNotYetComputedException;
import me.cortex.voxy.client.core.model.ModelBakerySubsystem; import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
import me.cortex.voxy.common.thread.ServiceSlice; import me.cortex.voxy.common.thread.Service;
import me.cortex.voxy.common.thread.ServiceThreadPool; import me.cortex.voxy.common.thread.ServiceManager;
import me.cortex.voxy.common.util.Pair; import me.cortex.voxy.common.util.Pair;
import me.cortex.voxy.common.world.WorldEngine; import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection; import me.cortex.voxy.common.world.WorldSection;
@@ -15,7 +15,6 @@ import java.util.List;
import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.StampedLock; import java.util.concurrent.locks.StampedLock;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer; import java.util.function.Consumer;
//TODO: Add a render cache //TODO: Add a render cache
@@ -60,26 +59,27 @@ public class RenderGenerationService {
private Consumer<BuiltSection> resultConsumer; private Consumer<BuiltSection> resultConsumer;
private final boolean emitMeshlets; private final boolean emitMeshlets;
private final ServiceSlice threads; private final Service service;
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, boolean emitMeshlets) { /*
this(world, modelBakery, serviceThreadPool, emitMeshlets, ()->true); public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceManager sm, boolean emitMeshlets) {
} this(world, modelBakery, sm, emitMeshlets, ()->true);
}*/
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, boolean emitMeshlets, BooleanSupplier taskLimiter) { public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceManager sm, boolean emitMeshlets) {
this.emitMeshlets = emitMeshlets; this.emitMeshlets = emitMeshlets;
this.world = world; this.world = world;
this.modelBakery = modelBakery; this.modelBakery = modelBakery;
this.threads = serviceThreadPool.createService("Section mesh generation service", 100, ()->{ this.service = sm.createService(()->{
//Thread local instance of the factory //Thread local instance of the factory
var factory = new RenderDataFactory(this.world, this.modelBakery.factory, this.emitMeshlets); var factory = new RenderDataFactory(this.world, this.modelBakery.factory, this.emitMeshlets);
IntOpenHashSet seenMissed = new IntOpenHashSet(128); IntOpenHashSet seenMissed = new IntOpenHashSet(128);
return new Pair<>(() -> { return new Pair<>(() -> {
this.processJob(factory, seenMissed); this.processJob(factory, seenMissed);
}, factory::free); }, factory::free);
}, taskLimiter); }, 10, "Section mesh generation service", ()->modelBakery.getProcessingCount()<400||RenderGenerationService.MESH_FAILED_COUNTER.get()<500);
} }
public void setResultConsumer(Consumer<BuiltSection> consumer) { public void setResultConsumer(Consumer<BuiltSection> consumer) {
@@ -258,8 +258,8 @@ public class RenderGenerationService {
this.taskQueue.add(task); this.taskQueue.add(task);
this.taskQueueCount.incrementAndGet(); this.taskQueueCount.incrementAndGet();
if (this.threads.isAlive()) {//Only execute if were not dead if (this.service.isLive()) {//Only execute if were not dead
this.threads.execute();//Since we put in queue, release permit this.service.execute();//Since we put in queue, release permit
} }
} }
} }
@@ -282,7 +282,7 @@ public class RenderGenerationService {
public void enqueueTask(long pos) { public void enqueueTask(long pos) {
if (!this.threads.isAlive()) { if (!this.service.isLive()) {
return; return;
} }
boolean[] isOurs = new boolean[1]; boolean[] isOurs = new boolean[1];
@@ -298,7 +298,7 @@ public class RenderGenerationService {
task.updatePriority(); task.updatePriority();
this.taskQueue.add(task); this.taskQueue.add(task);
this.taskQueueCount.incrementAndGet(); this.taskQueueCount.incrementAndGet();
this.threads.execute(); this.service.execute();
} }
} }
@@ -310,8 +310,8 @@ public class RenderGenerationService {
public void shutdown() { public void shutdown() {
//Steal and free as much work as possible //Steal and free as much work as possible
while (this.threads.hasJobs()) { while (this.service.numJobs() != 0) {
int i = this.threads.drain(); int i = this.service.drain();
if (i == 0) break; if (i == 0) break;
{ {
long stamp = this.taskMapLock.writeLock(); long stamp = this.taskMapLock.writeLock();
@@ -331,7 +331,7 @@ public class RenderGenerationService {
} }
//Shutdown the threads //Shutdown the threads
this.threads.shutdown(); this.service.shutdown();
//Cleanup any remaining data //Cleanup any remaining data
while (!this.taskQueue.isEmpty()) { while (!this.taskQueue.isEmpty()) {
@@ -354,14 +354,12 @@ public class RenderGenerationService {
} }
private long lastChangedTime = 0; private long lastChangedTime = 0;
private int failedCounter = 0;
public void addDebugData(List<String> debug) { public void addDebugData(List<String> debug) {
if (System.currentTimeMillis()-this.lastChangedTime > 1000) { if (System.currentTimeMillis()-this.lastChangedTime > 100) {
this.failedCounter = 0; MESH_FAILED_COUNTER.set(0);
this.lastChangedTime = System.currentTimeMillis(); this.lastChangedTime = System.currentTimeMillis();
} }
this.failedCounter += MESH_FAILED_COUNTER.getAndSet(0); debug.add("RSSQ/TFC: " + this.taskQueueCount.get() + "/" + MESH_FAILED_COUNTER.get());//render section service queue, Task Fail Counter
debug.add("RSSQ/TFC: " + this.taskQueueCount.get() + "/" + this.failedCounter);//render section service queue, Task Fail Counter
} }

View File

@@ -20,6 +20,7 @@ import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.AllocationArena; import me.cortex.voxy.common.util.AllocationArena;
import me.cortex.voxy.common.util.MemoryBuffer; import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.util.UnsafeUtil; import me.cortex.voxy.common.util.UnsafeUtil;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection; import me.cortex.voxy.common.world.WorldSection;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
@@ -188,7 +189,7 @@ public class AsyncNodeManager {
} }
//This is a funny thing, wait a bit, this allows for better batching, but this thread is independent of everything else so waiting a bit should be mostly ok //This is a funny thing, wait a bit, this allows for better batching, but this thread is independent of everything else so waiting a bit should be mostly ok
try { try {
Thread.sleep(25); Thread.sleep(10);
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -248,7 +249,7 @@ public class AsyncNodeManager {
//Limit uploading as well as by geometry capacity being available //Limit uploading as well as by geometry capacity being available
// must have 50 mb of free geometry space to upload // must have 50 mb of free geometry space to upload
for (int limit = 0; limit < 200 && ((this.geometryCapacity-this.geometryManager.getGeometryUsedBytes())>50_000_000L); limit++) { for (int limit = 0; limit < 300 && ((this.geometryCapacity-this.geometryManager.getGeometryUsedBytes())>50_000_000L); limit++) {
var job = this.geometryUpdateQueue.poll(); var job = this.geometryUpdateQueue.poll();
if (job == null) if (job == null)
break; break;
@@ -477,7 +478,10 @@ public class AsyncNodeManager {
results.usedGeometry = this.geometryManager.getGeometryUsedBytes(); results.usedGeometry = this.geometryManager.getGeometryUsedBytes();
results.currentMaxNodeId = this.manager.getCurrentMaxNodeId(); results.currentMaxNodeId = this.manager.getCurrentMaxNodeId();
this.needsWaitForSync |= results.geometryUpload.currentElemCopyAmount*8L > 4L<<20;//4mb limit per frame this.needsWaitForSync |= results.geometryUpload.currentElemCopyAmount*8L > 2L<<20;//2mb limit per frame
this.needsWaitForSync |= results.cleanerOperations.size() > 1024;
this.needsWaitForSync |= results.scatterWriteLocationMap.size() > 4096;
this.needsWaitForSync |= results.tlnDelta.size() > 10;
if (!RESULT_HANDLE.compareAndSet(this, null, results)) { if (!RESULT_HANDLE.compareAndSet(this, null, results)) {
throw new IllegalArgumentException("Should always have null"); throw new IllegalArgumentException("Should always have null");
@@ -513,6 +517,7 @@ public class AsyncNodeManager {
var upload = results.geometryUpload; var upload = results.geometryUpload;
if (!upload.dataUploadPoints.isEmpty()) { if (!upload.dataUploadPoints.isEmpty()) {
((BasicSectionGeometryData)this.geometryData).ensureAccessable(upload.maxElementAccess);
TimingStatistics.A.start(); TimingStatistics.A.start();
int copies = upload.dataUploadPoints.size(); int copies = upload.dataUploadPoints.size();
@@ -759,11 +764,20 @@ public class AsyncNodeManager {
return this.workCounter.get()!=0 || RESULT_HANDLE.get(this) != null; return this.workCounter.get()!=0 || RESULT_HANDLE.get(this) != null;
} }
public void worldEvent(WorldSection section, int flags) { public void worldEvent(WorldSection section, int flags, int neighborMask) {
//If there is any change, we need to clear the geometry cache before emitting update //If there is any change, we need to clear the geometry cache before emitting update
this.geometryCache.clear(section.key); this.geometryCache.clear(section.key);
this.router.forwardEvent(section, flags); this.router.forwardEvent(section, flags);
if (neighborMask != 0) {//trigger rebuilds for neighbors
if ((neighborMask&0b000001)!=0) this.router.triggerRemesh(WorldEngine.getWorldSectionId(section.lvl, section.x, section.y-1, section.z));//-y
if ((neighborMask&0b000010)!=0) this.router.triggerRemesh(WorldEngine.getWorldSectionId(section.lvl, section.x, section.y+1, section.z));//+y
if ((neighborMask&0b000100)!=0) this.router.triggerRemesh(WorldEngine.getWorldSectionId(section.lvl, section.x-1, section.y, section.z));//-x
if ((neighborMask&0b001000)!=0) this.router.triggerRemesh(WorldEngine.getWorldSectionId(section.lvl, section.x+1, section.y, section.z));//+x
if ((neighborMask&0b010000)!=0) this.router.triggerRemesh(WorldEngine.getWorldSectionId(section.lvl, section.x, section.y, section.z-1));//-z
if ((neighborMask&0b100000)!=0) this.router.triggerRemesh(WorldEngine.getWorldSectionId(section.lvl, section.x, section.y, section.z+1));//+z
}
} }
//Results object, which is to be synced between the render thread and worker thread //Results object, which is to be synced between the render thread and worker thread
@@ -848,6 +862,7 @@ public class AsyncNodeManager {
private static class ComputeMemoryCopy { private static class ComputeMemoryCopy {
public int currentElemCopyAmount; public int currentElemCopyAmount;
public int maxElementAccess;
private MemoryBuffer scratchHeaderBuffer = new MemoryBuffer(1<<16); private MemoryBuffer scratchHeaderBuffer = new MemoryBuffer(1<<16);
private MemoryBuffer scratchDataBuffer = new MemoryBuffer(1<<20); private MemoryBuffer scratchDataBuffer = new MemoryBuffer(1<<20);
@@ -899,6 +914,7 @@ public class AsyncNodeManager {
public void upload(int point, MemoryBuffer data) { public void upload(int point, MemoryBuffer data) {
if ((data.size%8)!=0) throw new IllegalStateException("Data must be of size multiple 8"); if ((data.size%8)!=0) throw new IllegalStateException("Data must be of size multiple 8");
int elemSize = (int) (data.size / 8); int elemSize = (int) (data.size / 8);
this.maxElementAccess = Math.max(this.maxElementAccess, point + elemSize);
int header = this.dataUploadPoints.get(point); int header = this.dataUploadPoints.get(point);
if (header != -1) { if (header != -1) {
//If we already have a header location, we just need to reallocate the data //If we already have a header location, we just need to reallocate the data
@@ -973,6 +989,7 @@ public class AsyncNodeManager {
} }
public void reset() { public void reset() {
this.maxElementAccess = 0;
this.currentElemCopyAmount = 0; this.currentElemCopyAmount = 0;
this.dataUploadPoints.clear(); this.dataUploadPoints.clear();
this.arena.reset(); this.arena.reset();

View File

@@ -7,7 +7,7 @@ import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.Viewport; import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer; import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream; import me.cortex.voxy.client.core.rendering.util.UploadStream;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.Mth;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL15;
@@ -38,9 +38,9 @@ public class DebugRenderer {
private void uploadUniform(Viewport<?> viewport) { private void uploadUniform(Viewport<?> viewport) {
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 1024); long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 1024);
int sx = MathHelper.floor(viewport.cameraX)>>5; int sx = Mth.floor(viewport.cameraX)>>5;
int sy = MathHelper.floor(viewport.cameraY)>>5; int sy = Mth.floor(viewport.cameraY)>>5;
int sz = MathHelper.floor(viewport.cameraZ)>>5; int sz = Mth.floor(viewport.cameraZ)>>5;
new Matrix4f(viewport.projection).mul(viewport.modelView).getToAddress(ptr); ptr += 4*4*4; new Matrix4f(viewport.projection).mul(viewport.modelView).getToAddress(ptr); ptr += 4*4*4;

View File

@@ -2,7 +2,7 @@ package me.cortex.voxy.client.core.rendering.hierachical;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import me.cortex.voxy.client.RenderStatistics; import me.cortex.voxy.client.RenderStatistics;
import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.AutoBindingShader; 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.Shader;

View File

@@ -136,7 +136,7 @@ public class NodeManager {
z <<= lvl; z <<= lvl;
long p2 = WorldEngine.getWorldSectionId(0, x, y, z); long p2 = WorldEngine.getWorldSectionId(0, x, y, z);
if (WorldEngine.getLevel(p2) != 0 || WorldEngine.getX(p2) != x || WorldEngine.getY(p2) != y || WorldEngine.getZ(p2) != z) { if (WorldEngine.getLevel(p2) != 0 || WorldEngine.getX(p2) != x || WorldEngine.getY(p2) != y || WorldEngine.getZ(p2) != z) {
throw new IllegalStateException("Position not valid at all levels"); throw new IllegalStateException("Position not valid at all levels: " + pos + "-"+WorldEngine.pprintPos(pos) + ":"+WorldEngine.pprintPos(p2));
} }
} }
@@ -154,7 +154,7 @@ public class NodeManager {
var request = new SingleNodeRequest(pos); var request = new SingleNodeRequest(pos);
int id = this.singleRequests.put(request); int id = this.singleRequests.put(request);
this.watcher.watch(pos, WorldEngine.UPDATE_FLAGS); this.watcher.watch(pos, WorldEngine.DEFAULT_UPDATE_FLAGS);
this.activeSectionMap.put(pos, id|NODE_TYPE_REQUEST|REQUEST_TYPE_SINGLE); this.activeSectionMap.put(pos, id|NODE_TYPE_REQUEST|REQUEST_TYPE_SINGLE);
this.topLevelNodes.add(pos); this.topLevelNodes.add(pos);
} }
@@ -353,7 +353,7 @@ public class NodeManager {
throw new IllegalStateException("Child pos was in a request but not in active section map"); throw new IllegalStateException("Child pos was in a request but not in active section map");
} }
if (!this.watcher.unwatch(cPos, WorldEngine.UPDATE_FLAGS)) { if (!this.watcher.unwatch(cPos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
throw new IllegalStateException("Child pos was not being watched"); throw new IllegalStateException("Child pos was not being watched");
} }
} }
@@ -371,7 +371,7 @@ public class NodeManager {
if (this.activeSectionMap.put(cPos, requestId|NODE_TYPE_REQUEST|REQUEST_TYPE_CHILD) != -1) { if (this.activeSectionMap.put(cPos, requestId|NODE_TYPE_REQUEST|REQUEST_TYPE_CHILD) != -1) {
throw new IllegalStateException("Child pos was already in active section tracker but was part of a request"); throw new IllegalStateException("Child pos was already in active section tracker but was part of a request");
} }
if (!this.watcher.watch(cPos, WorldEngine.UPDATE_FLAGS)) { if (!this.watcher.watch(cPos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
throw new IllegalStateException("Child pos update router issue"); throw new IllegalStateException("Child pos update router issue");
} }
} }
@@ -428,7 +428,7 @@ public class NodeManager {
if (this.activeSectionMap.put(cPos, requestId|NODE_TYPE_REQUEST|REQUEST_TYPE_CHILD) != -1) { if (this.activeSectionMap.put(cPos, requestId|NODE_TYPE_REQUEST|REQUEST_TYPE_CHILD) != -1) {
throw new IllegalStateException("Child pos was already in active section tracker but was part of a request"); throw new IllegalStateException("Child pos was already in active section tracker but was part of a request");
} }
if (!this.watcher.watch(cPos, WorldEngine.UPDATE_FLAGS)) { if (!this.watcher.watch(cPos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
throw new IllegalStateException("Child pos update router issue"); throw new IllegalStateException("Child pos update router issue");
} }
} }
@@ -467,7 +467,7 @@ public class NodeManager {
if (cnid == -1 || (cnid&NODE_TYPE_MSK) != NODE_TYPE_REQUEST) {//TODO: verify the removed section is a request type of child and the request id matches this if (cnid == -1 || (cnid&NODE_TYPE_MSK) != NODE_TYPE_REQUEST) {//TODO: verify the removed section is a request type of child and the request id matches this
throw new IllegalStateException("Child pos was in a request but not in active section map"); throw new IllegalStateException("Child pos was in a request but not in active section map");
} }
if (!this.watcher.unwatch(cPos, WorldEngine.UPDATE_FLAGS)) { if (!this.watcher.unwatch(cPos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
throw new IllegalStateException("Child pos was not being watched"); throw new IllegalStateException("Child pos was not being watched");
} }
} }
@@ -663,7 +663,7 @@ public class NodeManager {
if ((cId&NODE_TYPE_MSK) != NODE_TYPE_REQUEST || (cId&REQUEST_TYPE_MSK) != REQUEST_TYPE_CHILD || (cId&NODE_ID_MSK) != reqId) { if ((cId&NODE_TYPE_MSK) != NODE_TYPE_REQUEST || (cId&REQUEST_TYPE_MSK) != REQUEST_TYPE_CHILD || (cId&NODE_ID_MSK) != reqId) {
throw new IllegalStateException("Invalid child active state map: " + cId); throw new IllegalStateException("Invalid child active state map: " + cId);
} }
if (!this.watcher.unwatch(childPos, WorldEngine.UPDATE_FLAGS)) { if (!this.watcher.unwatch(childPos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
throw new IllegalStateException("Pos was not being watched"); throw new IllegalStateException("Pos was not being watched");
} }
} }
@@ -785,7 +785,7 @@ public class NodeManager {
this.invalidateNode(nodeId); this.invalidateNode(nodeId);
//Unwatch position //Unwatch position
if (!this.watcher.unwatch(pos, WorldEngine.UPDATE_FLAGS)) { if (!this.watcher.unwatch(pos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
throw new IllegalStateException("Pos was not being watched"); throw new IllegalStateException("Pos was not being watched");
} }
} else { } else {
@@ -795,7 +795,7 @@ public class NodeManager {
this.invalidateNode(nodeId); this.invalidateNode(nodeId);
} }
} else if (type == NODE_TYPE_REQUEST) { } else if (type == NODE_TYPE_REQUEST) {
if (!this.watcher.unwatch(pos, WorldEngine.UPDATE_FLAGS)) { if (!this.watcher.unwatch(pos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
throw new IllegalStateException("Pos was not being watched"); throw new IllegalStateException("Pos was not being watched");
} }
if ((nodeId&REQUEST_TYPE_MSK) == REQUEST_TYPE_SINGLE) { if ((nodeId&REQUEST_TYPE_MSK) == REQUEST_TYPE_SINGLE) {
@@ -1072,7 +1072,8 @@ public class NodeManager {
public void processRequest(long pos) { public void processRequest(long pos) {
int nodeId = this.activeSectionMap.get(pos); int nodeId = this.activeSectionMap.get(pos);
if (nodeId == -1) { if (nodeId == -1) {
Logger.warn("Got request for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, ignoring!"); //TODO: make into timing thing
//Logger.warn("Got request for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, ignoring!");
return; return;
} }
int nodeType = nodeId&NODE_TYPE_MSK; int nodeType = nodeId&NODE_TYPE_MSK;
@@ -1183,7 +1184,7 @@ public class NodeManager {
} }
//Watch and request the child node at the given position //Watch and request the child node at the given position
if (!this.watcher.watch(childPos, WorldEngine.UPDATE_FLAGS)) { if (!this.watcher.watch(childPos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
throw new IllegalStateException("Failed to watch childPos"); throw new IllegalStateException("Failed to watch childPos");
} }
} }
@@ -1237,7 +1238,8 @@ public class NodeManager {
int nodeType = nodeId&NODE_TYPE_MSK; int nodeType = nodeId&NODE_TYPE_MSK;
nodeId &= NODE_ID_MSK; nodeId &= NODE_ID_MSK;
if (nodeType == NODE_TYPE_REQUEST) { if (nodeType == NODE_TYPE_REQUEST) {
Logger.warn("Tried removing geometry for pos: " + WorldEngine.pprintPos(pos) + " but its type was a request, ignoring!"); //TODO: only log a specific number of times
//Logger.warn("Tried removing geometry for pos: " + WorldEngine.pprintPos(pos) + " but its type was a request, ignoring!");
return; return;
} }
//this.clearId(nodeId); //this.clearId(nodeId);

View File

@@ -39,9 +39,15 @@ public class TestNodeManager {
this.removeSection(oldId); this.removeSection(oldId);
} }
int newId = this.allocation.allocateNext(); int newId = this.allocation.allocateNext();
if (newId == -1) {
Logger.error("Allocator full: "+this.allocation.getCount()+" " +section, new Throwable());
section.free();
return -1;
}
var entry = new Entry(section.position, section.geometryBuffer.size); var entry = new Entry(section.position, section.geometryBuffer.size);
if (this.sections.put(newId, entry) != null) { var old = this.sections.put(newId, entry);
throw new IllegalStateException(); if (old != null) {
throw new IllegalStateException(oldId + ","+newId+" "+old+","+entry);
} }
this.memoryInUse += entry.size; this.memoryInUse += entry.size;
section.free(); section.free();
@@ -139,7 +145,7 @@ public class TestNodeManager {
} }
private static String[] getPrettyTypes(int msk) { private static String[] getPrettyTypes(int msk) {
if ((msk&~UPDATE_FLAGS)!=0) { if ((msk&~(DEFAULT_UPDATE_FLAGS|UPDATE_TYPE_DONT_SAVE))!=0) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
String[] types = new String[Integer.bitCount(msk)]; String[] types = new String[Integer.bitCount(msk)];
@@ -150,6 +156,9 @@ public class TestNodeManager {
if ((msk&UPDATE_TYPE_CHILD_EXISTENCE_BIT)!=0) { if ((msk&UPDATE_TYPE_CHILD_EXISTENCE_BIT)!=0) {
types[i++] = "CHILD"; types[i++] = "CHILD";
} }
if ((msk&UPDATE_TYPE_DONT_SAVE)!=0) {
types[i++] = "DONT_SAVE";
}
return types; return types;
} }
} }
@@ -183,7 +192,7 @@ public class TestNodeManager {
if (geometrySize != 0) { if (geometrySize != 0) {
buff = new MemoryBuffer(geometrySize); buff = new MemoryBuffer(geometrySize);
} }
var builtGeometry = new BuiltSection(pos, (byte) childExistence, -2, buff, null); var builtGeometry = new BuiltSection(pos, (byte) childExistence, -2, buff, null, null);
this.nodeManager.processGeometryResult(builtGeometry); this.nodeManager.processGeometryResult(builtGeometry);
} }
@@ -279,22 +288,24 @@ public class TestNodeManager {
int ITER_COUNT = 5_000; int ITER_COUNT = 5_000;
int INNER_ITER_COUNT = 1_000_000; int INNER_ITER_COUNT = 1_000_000;
boolean GEO_REM = true; boolean GEO_REM = true;
boolean LIMIT_REQUEST_SEC_ALLOCATION = true;
AtomicInteger finished = new AtomicInteger(); AtomicInteger finished = new AtomicInteger();
HashSet<List<StackTraceElement>> seenTraces = new HashSet<>(); HashSet<List<StackTraceElement>> seenTraces = new HashSet<>();
Logger.SHUTUP_INFO = true;
Logger.SHUTUP = true; Logger.SHUTUP = true;
if (true) { if (false) {
for (int q = 0; q < ITER_COUNT; q++) { for (int q = 0; q < ITER_COUNT; q++) {
//Logger.info("Iteration "+ q); //Logger.info("Iteration "+ q);
if (runTest(INNER_ITER_COUNT, q, seenTraces, GEO_REM)) { if (runTest(INNER_ITER_COUNT, q, seenTraces, GEO_REM, LIMIT_REQUEST_SEC_ALLOCATION)) {
finished.incrementAndGet(); finished.incrementAndGet();
} }
} }
} else { } else {
IntStream.range(0, ITER_COUNT).parallel().forEach(i->{ IntStream.range(0, ITER_COUNT).parallel().forEach(i->{
if (runTest(INNER_ITER_COUNT, i, seenTraces, GEO_REM)) { if (runTest(INNER_ITER_COUNT, i, seenTraces, GEO_REM, LIMIT_REQUEST_SEC_ALLOCATION)) {
finished.incrementAndGet(); finished.incrementAndGet();
} }
}); });
@@ -311,7 +322,7 @@ public class TestNodeManager {
return WorldEngine.getWorldSectionId(lvl, r.nextInt(bound)+(WorldEngine.getX(top)<<4), r.nextInt(bound)+(WorldEngine.getY(top)<<4), r.nextInt(bound)+(WorldEngine.getZ(top)<<4)); return WorldEngine.getWorldSectionId(lvl, r.nextInt(bound)+(WorldEngine.getX(top)<<4), r.nextInt(bound)+(WorldEngine.getY(top)<<4), r.nextInt(bound)+(WorldEngine.getZ(top)<<4));
} }
private static boolean runTest(int ITERS, int testIdx, Set<List<StackTraceElement>> traces, boolean geoRemoval) { private static boolean runTest(int ITERS, int testIdx, Set<List<StackTraceElement>> traces, boolean geoRemoval, boolean requestLimiter) {
Random r = new Random(testIdx * 1234L); Random r = new Random(testIdx * 1234L);
try { try {
var test = new TestBase(); var test = new TestBase();
@@ -331,7 +342,7 @@ public class TestNodeManager {
//Fuzzy bruteforce everything //Fuzzy bruteforce everything
for (int x = -R; x<=R; x++) { for (int x = -R; x<=R; x++) {
for (int z = -R; z<=R; z++) { for (int z = -R; z<=R; z++) {
for (int y = -8; y<=7; y++) { for (int y = -1; y<=0; y++) {
tops.add(WorldEngine.getWorldSectionId(4, x, y, z)); tops.add(WorldEngine.getWorldSectionId(4, x, y, z));
} }
} }
@@ -348,28 +359,29 @@ public class TestNodeManager {
long pos = rPos(r, tops); long pos = rPos(r, tops);
int op = r.nextInt(5); int op = r.nextInt(5);
int extra = r.nextInt(256); int extra = r.nextInt(256);
boolean geoAddOk = ((!requestLimiter)||(test.geometryManager.allocation.getLimit()-test.geometryManager.allocation.getCount())>1000);
boolean hasGeometry = r.nextBoolean(); boolean hasGeometry = r.nextBoolean();
boolean addRemTLN = r.nextInt(64) == 0; boolean addRemTLN = r.nextInt(64) == 0;
boolean extraBool = r.nextBoolean(); boolean extraBool = r.nextBoolean();
if (op == 0 && addRemTLN) { if (op == 0 && addRemTLN) {
pos = WorldEngine.getWorldSectionId(4, r.nextInt(5)-2, r.nextInt(32)-16, r.nextInt(5)-2); pos = WorldEngine.getWorldSectionId(4, r.nextInt(5)-2, r.nextInt(2)-1, r.nextInt(5)-2);//r.nextInt(16)-8//for y
boolean cont = tops.contains(pos); boolean cont = tops.contains(pos);
if (cont&&extraBool&&tops.size()>1) { if (cont&&extraBool&&tops.size()>1) {
extraBool = true; extraBool = true;
test.remTopPos(pos); test.remTopPos(pos);
tops.rem(pos); tops.rem(pos);
} else if (!cont) { } else if ((!cont)&&geoAddOk) {
extraBool = false; extraBool = false;
test.putTopPos(pos); test.putTopPos(pos);
tops.add(pos); tops.add(pos);
} }
} else if (op == 0) { } else if (op == 0&&geoAddOk) {
test.request(pos); test.request(pos);
} }
if (op == 1) { if (op == 1) {
test.childUpdate(pos, extra); test.childUpdate(pos, extra);
} }
if (op == 2) { if (op == 2&&((!hasGeometry)||geoAddOk)) {
test.meshUpdate(pos, extra, hasGeometry ? 100 : 0); test.meshUpdate(pos, extra, hasGeometry ? 100 : 0);
} }
if (op == 3 && geoRemoval) { if (op == 3 && geoRemoval) {
@@ -589,10 +601,9 @@ public class TestNodeManager {
test.printNodeChanges(); test.printNodeChanges();
Logger.info("\n\n"); Logger.info("\n\n");
var positions = new ArrayList<>(aa.keySet().stream().filter(k->{ var positions = new ArrayList<>(aa.keySet().longStream().filter(k->{
return WorldEngine.getLevel(k)!=0; return WorldEngine.getLevel(k)!=0;
}).toList()); }).sorted().mapToObj(Long::valueOf).toList());
positions.sort(Long::compareTo);
Collections.shuffle(positions, r); Collections.shuffle(positions, r);
Logger.info("Removing", WorldEngine.pprintPos(positions.get(0))); Logger.info("Removing", WorldEngine.pprintPos(positions.get(0)));

View File

@@ -1,31 +0,0 @@
package me.cortex.voxy.client.core.rendering.section;
import me.cortex.voxy.client.core.model.ModelStore;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData;
import java.util.List;
//Takes in mesh ids from the hierachical traversal and may perform more culling then renders it
public abstract class AbstractSectionRenderer <T extends Viewport<T>, J extends IGeometryData> {
protected final J geometryManager;
protected final ModelStore modelStore;
protected AbstractSectionRenderer(ModelStore modelStore, J geometryManager) {
this.geometryManager = geometryManager;
this.modelStore = modelStore;
}
public abstract void renderOpaque(T viewport);
public abstract void buildDrawCalls(T viewport);
public abstract void renderTemporal(T viewport);
public abstract void renderTranslucent(T viewport);
public abstract T createViewport();
public abstract void free();
public J getGeometryManager() {
return this.geometryManager;
}
public void addDebug(List<String> lines) {}
}

View File

@@ -1,231 +0,0 @@
package me.cortex.voxy.client.core.rendering.section;
import me.cortex.voxy.client.RenderStatistics;
import me.cortex.voxy.client.core.AbstractRenderPipeline;
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.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.model.ModelStore;
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.LightMapHelper;
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 me.cortex.voxy.common.world.WorldEngine;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryUtil;
import java.util.List;
import static me.cortex.voxy.client.core.gl.EXTMeshShader.glDrawMeshTasksIndirectEXT;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL11C.GL_UNSIGNED_INT;
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_R32UI;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.*;
import static org.lwjgl.opengl.GL45.*;
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
//Uses MDIC to render the sections
public class MeshEXTSectionRenderer extends AbstractSectionRenderer<MeshViewport, BasicSectionGeometryData> {
private static final int STATISTICS_BUFFER_BINDING = 8;
private final Shader terrainShader = Shader.make()
.define("MESH_SIZE", 32)//16
.defineIf("HAS_STATISTICS", RenderStatistics.enabled)
.defineIf("STATISTICS_BUFFER_BINDING", RenderStatistics.enabled, STATISTICS_BUFFER_BINDING)
.add(ShaderType.TASK, "voxy:lod/meshext/task.glsl")
.add(ShaderType.MESH, "voxy:lod/meshext/mesh.glsl")
.add(ShaderType.FRAGMENT, "voxy:lod/meshext/frag.glsl")
.compile();
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 uniform = new GlBuffer(1024).zero();
private final GlBuffer cullAndMeshDrawCommand = new GlBuffer(8*4).zero();//TODO: this needs tobe in the viewport
//Statistics
private final GlBuffer statisticsBuffer = new GlBuffer(1024).zero();
private final AbstractRenderPipeline pipeline;
public MeshEXTSectionRenderer(AbstractRenderPipeline pipeline, ModelStore modelStore, BasicSectionGeometryData geometryData) {
super(modelStore, geometryData);
this.pipeline = pipeline;
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{6*2*3});//count
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,8, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{(1<<16)*6*2});//firstIndex
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,5*4+4, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{1});//y
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,5*4+8, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{1});//z
}
private void uploadUniformBuffer(MeshViewport viewport) {
long ptr = UploadStream.INSTANCE.upload(this.uniform, 0, 1024);
var mat = new Matrix4f(viewport.MVP);
mat.translate(-viewport.innerTranslation.x, -viewport.innerTranslation.y, -viewport.innerTranslation.z);
mat.getToAddress(ptr); ptr += 4*4*4;
viewport.section.getToAddress(ptr); ptr += 4*3;
if (viewport.frameId<0) {
Logger.error("Frame ID negative, this will cause things to break, wrapping around");
viewport.frameId &= 0x7fffffff;
}
MemoryUtil.memPutInt(ptr, viewport.frameId&0x7fffffff); ptr += 4;
viewport.innerTranslation.getToAddress(ptr); ptr += 4*3;
ptr += 4;// padd
MemoryUtil.memPutFloat(ptr, viewport.width); ptr += 4;
MemoryUtil.memPutFloat(ptr, viewport.height); ptr += 4;
UploadStream.INSTANCE.commit();
}
private void bindRenderingBuffers(MeshViewport viewport) {
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, viewport.getRenderList().id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.geometryManager.getMetadataBuffer().id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.visibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometryManager.getGeometryBuffer().id);
this.modelStore.bind(5, 6, 0);
LightMapHelper.bind(1);
glBindTextureUnit(2, viewport.depthBoundingBuffer.getDepthTex().id);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.cullAndMeshDrawCommand.id);
if (RenderStatistics.enabled) {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, STATISTICS_BUFFER_BINDING, this.statisticsBuffer.id);
}
}
private void renderTerrain(MeshViewport viewport) {
//RenderLayer.getCutoutMipped().startDrawing();
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
this.terrainShader.bind();
this.pipeline.setupAndBindOpaque(viewport);
this.bindRenderingBuffers(viewport);
glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed
glDrawMeshTasksIndirectEXT(20);
glEnable(GL_CULL_FACE);
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
glBindSampler(1, 0);
glBindTextureUnit(1, 0);
//RenderLayer.getCutoutMipped().endDrawing();
}
@Override
public void renderOpaque(MeshViewport viewport) {
if (this.geometryManager.getSectionCount() == 0) return;
this.uploadUniformBuffer(viewport);
this.renderTerrain(viewport);
//We need todo the statistics here as rastering is part of them, download then clear
if (RenderStatistics.enabled) {
DownloadStream.INSTANCE.download(this.statisticsBuffer, down->{
final int LAYERS = WorldEngine.MAX_LOD_LAYER+1;
for (int i = 0; i < LAYERS; i++) {
RenderStatistics.visibleSections[i] = MemoryUtil.memGetInt(down.address+i*4L);
}
for (int i = 0; i < LAYERS; i++) {
RenderStatistics.quadCount[i] = MemoryUtil.memGetInt(down.address+LAYERS*4L+i*4L);
}
});
this.statisticsBuffer.zero();
}
}
@Override
public void renderTranslucent(MeshViewport viewport) {
return;
}
@Override
public void buildDrawCalls(MeshViewport viewport) {
if (this.geometryManager.getSectionCount() == 0) return;
this.uploadUniformBuffer(viewport);
//Can do a sneeky trick, since the sectionRenderList is a list to things to render, it invokes the culler
// which only marks visible sections
{//Test occlusion
glCopyNamedBufferSubData(viewport.getRenderList().id, this.cullAndMeshDrawCommand.id, 0, 4, 4);//Copy counts to the draw buffer
glCopyNamedBufferSubData(viewport.getRenderList().id, this.cullAndMeshDrawCommand.id, 0, 20, 4);//Copy counts to the draw buffer
this.cullShader.bind();
if (Capabilities.INSTANCE.repFragTest) {
glEnable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
}
glBindVertexArray(GlVertexArray.STATIC_VAO);
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometryManager.getMetadataBuffer().id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, viewport.visibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.getRenderList().id);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.cullAndMeshDrawCommand.id);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
glEnable(GL_DEPTH_TEST);
glColorMask(false, false, false, false);
glDepthMask(false);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT|GL_COMMAND_BARRIER_BIT);
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_BYTE, 0);
glDepthMask(true);
glColorMask(true, true, true, true);
glDisable(GL_DEPTH_TEST);
if (Capabilities.INSTANCE.repFragTest) {
glDisable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
}
}
}
@Override
public void renderTemporal(MeshViewport viewport) {
return;
}
@Override
public void addDebug(List<String> lines) {
super.addDebug(lines);
//lines.add("SC/GS: " + this.geometryManager.getSectionCount() + "/" + (this.geometryManager.getGeometryUsed()/(1024*1024)));//section count/geometry size (MB)
}
@Override
public MeshViewport createViewport() {
return new MeshViewport(this.geometryManager.getMaxSectionCount());
}
@Override
public void free() {
this.cullAndMeshDrawCommand.free();
this.uniform.free();
this.terrainShader.free();
this.cullShader.free();
this.statisticsBuffer.free();
}
}

View File

@@ -1,26 +0,0 @@
package me.cortex.voxy.client.core.rendering.section;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
public class MeshViewport extends Viewport<MeshViewport> {
public final GlBuffer indirectLookupBuffer = new GlBuffer(HierarchicalOcclusionTraverser.MAX_QUEUE_SIZE *4+4);
public final GlBuffer visibilityBuffer;
public MeshViewport(int maxSectionCount) {
this.visibilityBuffer = new GlBuffer(maxSectionCount*4L);
}
@Override
protected void delete0() {
super.delete0();
this.visibilityBuffer.free();
this.indirectLookupBuffer.free();
}
@Override
public GlBuffer getRenderList() {
return this.indirectLookupBuffer;
}
}

View File

@@ -0,0 +1,104 @@
package me.cortex.voxy.client.core.rendering.section.backend;
import me.cortex.voxy.client.core.AbstractRenderPipeline;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.model.ModelStore;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData;
import me.cortex.voxy.common.Logger;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.Direction;
import net.minecraft.world.level.dimension.DimensionType;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
//Takes in mesh ids from the hierachical traversal and may perform more culling then renders it
public abstract class AbstractSectionRenderer <T extends Viewport<T>, J extends IGeometryData> {
public interface FactoryConstructor<VIEWPORT extends Viewport<VIEWPORT>, GEODATA extends IGeometryData> {
AbstractSectionRenderer<VIEWPORT, GEODATA> create(AbstractRenderPipeline pipeline, ModelStore modelStore, GEODATA geometryData);
}
public record Factory<VIEWPORT extends Viewport<VIEWPORT>, GEODATA extends IGeometryData>(Class<? extends AbstractSectionRenderer<VIEWPORT, GEODATA>> clz, FactoryConstructor<VIEWPORT, GEODATA> constructor) {
public AbstractSectionRenderer<VIEWPORT, GEODATA> create(AbstractRenderPipeline pipeline, ModelStore store, IGeometryData geometryData) {
return this.constructor.create(pipeline, store, (GEODATA) geometryData);
}
public static <VIEWPORT2 extends Viewport<VIEWPORT2>, GEODATA2 extends IGeometryData> Factory<VIEWPORT2, GEODATA2> create(Class<? extends AbstractSectionRenderer<VIEWPORT2, GEODATA2>> clz) {
var constructors = clz.getConstructors();
if (constructors.length != 1) {
Logger.error("Render backend " + clz.getCanonicalName() + " had more then 1 constructor");
return null;
}
var constructor = constructors[0];
var params = constructor.getParameterTypes();
if (params.length != 3 || params[0] != AbstractRenderPipeline.class || params[1] != ModelStore.class || !IGeometryData.class.isAssignableFrom(params[2])) {
Logger.error("Render backend " + clz.getCanonicalName() + " had invalid constructor");
return null;
}
return new Factory<>(clz, (a,b,c)-> {
try {
return (AbstractSectionRenderer<VIEWPORT2, GEODATA2>) constructor.newInstance(a,b,c);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
});
}
}
protected final J geometryManager;
protected final ModelStore modelStore;
protected AbstractSectionRenderer(ModelStore modelStore, J geometryManager) {
this.geometryManager = geometryManager;
this.modelStore = modelStore;
}
public abstract void renderOpaque(T viewport);
public abstract void buildDrawCalls(T viewport);
public abstract void renderTemporal(T viewport);
public abstract void renderTranslucent(T viewport);
public abstract T createViewport();
public abstract void free();
public J getGeometryManager() {
return this.geometryManager;
}
public void addDebug(List<String> lines) {}
protected static void addDirectionalFaceTint(Shader.Builder<?> builder, ClientLevel cl) {
builder.define("NO_SHADE_FACE_TINT", cl.getShade(Direction.UP, false));
builder.define("UP_FACE_TINT", cl.getShade(Direction.UP, true));
builder.define("DOWN_FACE_TINT", cl.getShade(Direction.DOWN, true));
builder.define("Z_AXIS_FACE_TINT", cl.getShade(Direction.NORTH, true));//assumed here that Direction.SOUTH returns the same value
builder.define("X_AXIS_FACE_TINT", cl.getShade(Direction.EAST, true));//assumed here that Direction.WEST returns the same value
/*
//TODO: generate the tinting table here and use the replacement feature
float[] tints = new float[7];
tints[6] = cl.getShade(Direction.UP, false);
for (Direction direction : Direction.values()) {
tints[direction.get3DDataValue()] = cl.getShade(direction, true);
}*/
}
protected static Shader tryCompilePatchedOrNormal(Shader.Builder<?> builder, String shader, String original) {
boolean patched = shader != original;//This is the correct comparison type (reference)
try {
return builder.clone()
.defineIf("PATCHED_SHADER", patched)
.addSource(ShaderType.FRAGMENT, shader)
.compile();
} catch (RuntimeException e) {
if (patched) {
Logger.error("Failed to compile shader patch, using normal pipeline to prevent errors", e);
return tryCompilePatchedOrNormal(builder, original, original);
} else {
throw e;
}
}
}
}

View File

@@ -1,7 +1,8 @@
package me.cortex.voxy.client.core.rendering.section; package me.cortex.voxy.client.core.rendering.section.backend.mdic;
import me.cortex.voxy.client.RenderStatistics; import me.cortex.voxy.client.RenderStatistics;
import me.cortex.voxy.client.VoxyClient;
import me.cortex.voxy.client.core.AbstractRenderPipeline; import me.cortex.voxy.client.core.AbstractRenderPipeline;
import me.cortex.voxy.client.core.gl.Capabilities; import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlBuffer;
@@ -10,6 +11,7 @@ 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.ShaderLoader;
import me.cortex.voxy.client.core.gl.shader.ShaderType; import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.model.ModelStore; import me.cortex.voxy.client.core.model.ModelStore;
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData; import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
import me.cortex.voxy.client.core.rendering.util.DownloadStream; import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.LightMapHelper; import me.cortex.voxy.client.core.rendering.util.LightMapHelper;
@@ -17,6 +19,8 @@ import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream; import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.world.WorldEngine; import me.cortex.voxy.common.world.WorldEngine;
import net.minecraft.client.Minecraft;
import net.minecraft.core.Direction;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
@@ -39,6 +43,8 @@ import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FR
//Uses MDIC to render the sections //Uses MDIC to render the sections
public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, BasicSectionGeometryData> { public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, BasicSectionGeometryData> {
public static final Factory<MDICViewport, BasicSectionGeometryData> FACTORY = AbstractSectionRenderer.Factory.create(MDICSectionRenderer.class);
private static final int TRANSLUCENT_OFFSET = 400_000;//in draw calls private static final int TRANSLUCENT_OFFSET = 400_000;//in draw calls
private static final int TEMPORAL_OFFSET = 500_000;//in draw calls private static final int TEMPORAL_OFFSET = 500_000;//in draw calls
private static final int STATISTICS_BUFFER_BINDING = 8; private static final int STATISTICS_BUFFER_BINDING = 8;
@@ -94,7 +100,7 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
this.pipeline = pipeline; this.pipeline = pipeline;
//The pipeline can be used to transform the renderer in abstract ways //The pipeline can be used to transform the renderer in abstract ways
String vertex = ShaderLoader.parse("voxy:lod/gl46/quads2.vert"); String vertex = ShaderLoader.parse("voxy:lod/gl46/quads3.vert");
String taa = pipeline.taaFunction("taaShift"); String taa = pipeline.taaFunction("taaShift");
if (taa != null) { if (taa != null) {
vertex += "\n"+taa;//inject it at the end vertex += "\n"+taa;//inject it at the end
@@ -102,8 +108,14 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
var builder = Shader.make() var builder = Shader.make()
.defineIf("TAA_PATCH", taa != null) .defineIf("TAA_PATCH", taa != null)
.defineIf("DEBUG_RENDER", false) .defineIf("DEBUG_RENDER", false)
//.defineIf("USE_NV_BARRY", Capabilities.INSTANCE.nvBarryCoords)
.addSource(ShaderType.VERTEX, vertex); .addSource(ShaderType.VERTEX, vertex);
//Apply per face tinting
addDirectionalFaceTint(builder, Minecraft.getInstance().level);
String frag = ShaderLoader.parse("voxy:lod/gl46/quads.frag"); String frag = ShaderLoader.parse("voxy:lod/gl46/quads.frag");
String opaqueFrag = pipeline.patchOpaqueShader(this, frag); String opaqueFrag = pipeline.patchOpaqueShader(this, frag);
@@ -113,28 +125,9 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
this.terrainShader = tryCompilePatchedOrNormal(builder, opaqueFrag, frag); this.terrainShader = tryCompilePatchedOrNormal(builder, opaqueFrag, frag);
String translucentFrag = pipeline.patchTranslucentShader(this, frag); String translucentFrag = pipeline.patchTranslucentShader(this, frag);
if (translucentFrag != null) { translucentFrag = translucentFrag==null?frag:translucentFrag;
this.translucentTerrainShader = tryCompilePatchedOrNormal(builder, translucentFrag, frag);
} else {
this.translucentTerrainShader = this.terrainShader;
}
}
private static Shader tryCompilePatchedOrNormal(Shader.Builder<?> builder, String shader, String original) { this.translucentTerrainShader = tryCompilePatchedOrNormal(builder.define("TRANSLUCENT"), translucentFrag, frag);
boolean patched = shader != original;//This is the correct comparison type (reference)
try {
return builder.clone()
.defineIf("PATCHED_SHADER", patched)
.addSource(ShaderType.FRAGMENT, shader)
.compile();
} catch (RuntimeException e) {
if (patched) {
Logger.error("Failed to compile shader patch, using normal pipeline to prevent errors", e);
return tryCompilePatchedOrNormal(builder, original, original);
} else {
throw e;
}
}
} }
private void uploadUniformBuffer(MDICViewport viewport) { private void uploadUniformBuffer(MDICViewport viewport) {
@@ -176,6 +169,7 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
glDisable(GL_CULL_FACE); glDisable(GL_CULL_FACE);
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
this.terrainShader.bind(); this.terrainShader.bind();
glBindVertexArray(GlVertexArray.STATIC_VAO);//Needs to be before binding glBindVertexArray(GlVertexArray.STATIC_VAO);//Needs to be before binding
@@ -184,7 +178,14 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION); glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
if (VoxyClient.getOcclusionDebugState()==3) {
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, indirectOffset, drawCountOffset, maxDrawCount, 0); glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, indirectOffset, drawCountOffset, maxDrawCount, 0);
if (VoxyClient.getOcclusionDebugState()==3) {
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
glBindVertexArray(0); glBindVertexArray(0);
@@ -356,11 +357,9 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
@Override @Override
public void free() { public void free() {
if (this.terrainShader != this.translucentTerrainShader) {
this.translucentTerrainShader.free();
}
this.uniform.free(); this.uniform.free();
this.distanceCountBuffer.free(); this.distanceCountBuffer.free();
this.translucentTerrainShader.free();
this.terrainShader.free(); this.terrainShader.free();
this.commandGenShader.free(); this.commandGenShader.free();
this.cullShader.free(); this.cullShader.free();

View File

@@ -1,4 +1,4 @@
package me.cortex.voxy.client.core.rendering.section; package me.cortex.voxy.client.core.rendering.section.backend.mdic;
import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.Viewport; import me.cortex.voxy.client.core.rendering.Viewport;

View File

@@ -11,11 +11,12 @@ import org.lwjgl.system.MemoryUtil;
import java.util.function.Consumer; import java.util.function.Consumer;
import static me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryManager.SECTION_METADATA_SIZE;
//Is basicly the manager for an "undefined" data store, the underlying store is irrelevant //Is basicly the manager for an "undefined" data store, the underlying store is irrelevant
// this manager serves as an overlay, that is, it allows an implementation to do "async management" of the data store // this manager serves as an overlay, that is, it allows an implementation to do "async management" of the data store
public class BasicAsyncGeometryManager implements IGeometryManager { public class BasicAsyncGeometryManager implements IGeometryManager {
public static final int SECTION_METADATA_SIZE = 32;
private static final long GEOMETRY_ELEMENT_SIZE = 8; private static final long GEOMETRY_ELEMENT_SIZE = 8;
private final HierarchicalBitSet allocationSet; private final HierarchicalBitSet allocationSet;
private final AllocationArena allocationHeap = new AllocationArena(); private final AllocationArena allocationHeap = new AllocationArena();

View File

@@ -34,12 +34,12 @@ public class BasicSectionGeometryData implements IGeometryData {
Logger.info("if your game crashes/exits here without any other log message, try manually decreasing the geometry capacity"); Logger.info("if your game crashes/exits here without any other log message, try manually decreasing the geometry capacity");
glGetError();//Clear any errors glGetError();//Clear any errors
GlBuffer buffer = null; GlBuffer buffer = null;
if (!(Capabilities.INSTANCE.isNvidia && ThreadUtils.isWindows)) { if (!(Capabilities.INSTANCE.isNvidia&&Capabilities.INSTANCE.sparseBuffer)) {//This hack makes it so it doesnt crash on renderdoc
buffer = new GlBuffer(geometryCapacity, false);//Only do this if we are not on nvidia buffer = new GlBuffer(geometryCapacity, false);//Only do this if we are not on nvidia
//TODO: FIXME: TEST, see if the issue is that we are trying to zero the entire buffer, try only zeroing increments //TODO: FIXME: TEST, see if the issue is that we are trying to zero the entire buffer, try only zeroing increments
// or dont zero it at all // or dont zero it at all
} else { } else {
Logger.info("Running on windows nvidia, using workaround sparse buffer allocation"); Logger.info("Running on nvidia, using workaround sparse buffer allocation");
} }
int error = glGetError(); int error = glGetError();
if (error != GL_NO_ERROR || buffer == null) { if (error != GL_NO_ERROR || buffer == null) {
@@ -49,9 +49,6 @@ public class BasicSectionGeometryData implements IGeometryData {
buffer.free(); buffer.free();
} }
buffer = new GlBuffer(geometryCapacity, GL_SPARSE_STORAGE_BIT_ARB); buffer = new GlBuffer(geometryCapacity, GL_SPARSE_STORAGE_BIT_ARB);
glBindBuffer(GL_ARRAY_BUFFER, buffer.id);
glBufferPageCommitmentARB(GL_ARRAY_BUFFER, 0, geometryCapacity, true);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//buffer.zero(); //buffer.zero();
error = glGetError(); error = glGetError();
if (error != GL_NO_ERROR) { if (error != GL_NO_ERROR) {
@@ -67,6 +64,21 @@ public class BasicSectionGeometryData implements IGeometryData {
Logger.info("Successfully allocated the geometry buffer in " + delta + "ms"); Logger.info("Successfully allocated the geometry buffer in " + delta + "ms");
} }
private long sparseCommitment = 0;//Tracks the current range of the allocated sparse buffer
public void ensureAccessable(int maxElementAccess) {
long size = (Integer.toUnsignedLong(maxElementAccess)*8L+65535L)&~65535L;
//If we are a sparse buffer, ensure the memory upto the requested size is allocated
if (this.geometryBuffer.isSparse()) {
if (this.sparseCommitment < size) {//if we try to access memory outside the allocation range, allocate it
glBindBuffer(GL_ARRAY_BUFFER, this.geometryBuffer.id);
size += 65536L*1024;//increase size by 64mb to prevent driver allocation thrashing
glBufferPageCommitmentARB(GL_ARRAY_BUFFER, this.sparseCommitment, size-this.sparseCommitment, true);
glBindBuffer(GL_ARRAY_BUFFER, 0);
this.sparseCommitment = size;
}
}
}
public GlBuffer getGeometryBuffer() { public GlBuffer getGeometryBuffer() {
return this.geometryBuffer; return this.geometryBuffer;
} }
@@ -100,11 +112,20 @@ public class BasicSectionGeometryData implements IGeometryData {
glFinish(); glFinish();
gpuMemory = Capabilities.INSTANCE.getFreeDedicatedGpuMemory(); gpuMemory = Capabilities.INSTANCE.getFreeDedicatedGpuMemory();
} }
if (this.geometryBuffer.isSparse()) {
glBindBuffer(GL_ARRAY_BUFFER, this.geometryBuffer.id);
glBufferPageCommitmentARB(GL_ARRAY_BUFFER, 0, this.sparseCommitment, false);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
glFinish(); glFinish();
this.geometryBuffer.free(); this.geometryBuffer.free();
glFinish(); glFinish();
if (Capabilities.INSTANCE.canQueryGpuMemory) { if (Capabilities.INSTANCE.canQueryGpuMemory) {
long releaseSize = (long) (this.geometryBuffer.size()*0.75);//if gpu memory usage drops by 75% of the expected value assume we freed it long releaseSize = (long) (this.geometryBuffer.size()*0.75);//if gpu memory usage drops by 75% of the expected value assume we freed it
if (this.geometryBuffer.isSparse()) {//If we are using sparse buffers, use the commited size instead
releaseSize = (long)(this.sparseCommitment*0.75);
}
if (Capabilities.INSTANCE.getFreeDedicatedGpuMemory()-gpuMemory<=releaseSize) { if (Capabilities.INSTANCE.getFreeDedicatedGpuMemory()-gpuMemory<=releaseSize) {
Logger.info("Attempting to wait for gpu memory to release"); Logger.info("Attempting to wait for gpu memory to release");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();

View File

@@ -135,7 +135,8 @@ public class BasicSectionGeometryManager extends AbstractSectionGeometryManager
} }
var oldMetadata = this.sectionMetadata.set(id, null); var oldMetadata = this.sectionMetadata.set(id, null);
this.geometry.downloadRemove(oldMetadata.geometryPtr, buffer -> this.geometry.downloadRemove(oldMetadata.geometryPtr, buffer ->
callback.accept(new BuiltSection(oldMetadata.position, oldMetadata.childExistence, oldMetadata.aabb, buffer.copy(), oldMetadata.offsets)) //TODO: occupancy
callback.accept(new BuiltSection(oldMetadata.position, oldMetadata.childExistence, oldMetadata.aabb, buffer.copy(), oldMetadata.offsets, null))
); );
//this.geometry.free(oldMetadata.geometryPtr); //this.geometry.free(oldMetadata.geometryPtr);
this.invalidatedSectionIds.add(id); this.invalidatedSectionIds.add(id);

View File

@@ -1,94 +0,0 @@
package me.cortex.voxy.client.core.rendering.util;
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 org.lwjgl.system.MemoryUtil;
import java.util.function.Supplier;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL43C.glDispatchCompute;
//Utilities for common operations not suited for basic gl functions
// such as sparse memory setting
//TODO CLEAN THIS SHIT UP
public class ComputeUtils {
private ComputeUtils() {}
public static ComputeUtils INSTANCE = new ComputeUtils();
private static final int SETTING_BUFFER_BINDING = 1;
private static final int ENTRY_BUFFER_BINDING = 2;
//TODO: FIXME! This should itself be just a raw streaming buffer/mapped ptr (probably)
private final GlBuffer SCRATCH = new GlBuffer(1<<20);//1 MB scratch buffer... this should be enough.. right?
private int maxCount;
private int count;
private long ptr;
private final Supplier<Shader> uintSetShader = makeCacheSetShader("uint");
public void prepSetUint(int maxCount) {
if (this.count != 0 || this.maxCount != 0 || this.ptr != 0) {
throw new IllegalStateException();
}
this.ptr = UploadStream.INSTANCE.upload(SCRATCH, 0, maxCount*8L);
this.maxCount = maxCount;
}
public void pushSetUint(int index, int value) {
//For uint it goes
// {uint value; uint index;}
if (this.maxCount <= this.count++) {
throw new IllegalStateException("Pushed to many values to prepared set");
}
MemoryUtil.memPutInt(this.ptr, value); this.ptr += 4;
MemoryUtil.memPutInt(this.ptr, index); this.ptr += 4;
}
public void finishSetUint(GlBuffer dst) {
UploadStream.INSTANCE.commit();
this.uintSetShader.get().bind();
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, SETTING_BUFFER_BINDING, dst.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, ENTRY_BUFFER_BINDING, this.SCRATCH.id);
glUniform1i(0, this.count);
glDispatchCompute((this.count+127)/128, 1, 1);
this.ptr = 0;
this.maxCount = 0;
this.count = 0;
}
private static Supplier<Shader> makeCacheSetShader(String type) {
return makeAndCache(()->makeSetShader(type));
}
private static Shader makeSetShader(String type) {
return Shader.make()
.define("TYPE", type)
.define("SETTING_BUFFER_BINDING", SETTING_BUFFER_BINDING)
.define("ENTRY_BUFFER_BINDING", ENTRY_BUFFER_BINDING)
.add(ShaderType.COMPUTE, "voxy:util/set.comp")
.compile();
}
private static <T> Supplier<T> makeAndCache(Supplier<T> maker) {
Object[] value = new Object[1];
boolean[] hasSet = new boolean[1];
return ()->{
if (hasSet[0]) {
return (T) value[0];
} else {
var val = maker.get();
hasSet[0] = true;
value[0] = val;
return val;
}
};
}
}

View File

@@ -1,25 +0,0 @@
package me.cortex.voxy.client.core.rendering.util;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.common.util.UnsafeUtil;
import org.lwjgl.system.MemoryUtil;
//Just a utility for making a deferred upload (make on other thread then upload on render thread)
public final class DeferredUpload {
public final long ptr;
private final long size;
private final long offset;
private final GlBuffer buffer;
public DeferredUpload(GlBuffer buffer, long offset, long size) {
this.ptr = MemoryUtil.nmemAlloc(size);
this.offset = offset;
this.buffer = buffer;
this.size = size;
}
public void upload() {
long upPtr = UploadStream.INSTANCE.upload(this.buffer, this.offset, this.size);
UnsafeUtil.memcpy(this.ptr, upPtr, this.size);
MemoryUtil.nmemFree(this.ptr);
}
}

View File

@@ -22,14 +22,20 @@ public class DepthFramebuffer {
this.depthType = depthType; this.depthType = depthType;
} }
public void resize(int width, int height) { public boolean resize(int width, int height) {
if (this.depthBuffer == null || this.depthBuffer.getWidth() != width || this.depthBuffer.getHeight() != height) { if (this.depthBuffer == null || this.depthBuffer.getWidth() != width || this.depthBuffer.getHeight() != height) {
if (this.depthBuffer != null) { if (this.depthBuffer != null) {
this.depthBuffer.free(); this.depthBuffer.free();
} }
this.depthBuffer = new GlTexture().store(this.depthType, 1, width, height); this.depthBuffer = new GlTexture().store(this.depthType, 1, width, height);
this.framebuffer.bind(this.depthType == GL_DEPTH24_STENCIL8?GL_DEPTH_STENCIL_ATTACHMENT: GL_DEPTH_ATTACHMENT, this.depthBuffer).verify(); this.framebuffer.bind(this.getDepthAttachmentType(), this.depthBuffer).verify();
return true;
} }
return false;
}
public int getDepthAttachmentType() {
return this.depthType == GL_DEPTH24_STENCIL8?GL_DEPTH_STENCIL_ATTACHMENT: GL_DEPTH_ATTACHMENT;
} }
public void clear() { public void clear() {
@@ -56,4 +62,8 @@ public class DepthFramebuffer {
public void bind() { public void bind() {
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer.id); glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer.id);
} }
public int getFormat() {
return this.depthType;
}
} }

View File

@@ -1,85 +0,0 @@
package me.cortex.voxy.client.core.rendering.util;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.GL_ACTIVE_TEXTURE;
import static org.lwjgl.opengl.GL13.glActiveTexture;
public class GlStateCapture {
private final int[] capabilityIds;
private final boolean[] enabledCaps;
private final int[] textureUnits;
private final int[] textures;
private GlStateCapture(int[] caps, int[] textureUnits) {
this.capabilityIds = caps;
this.enabledCaps = new boolean[caps.length];
this.textureUnits = textureUnits;
this.textures = new int[textureUnits.length];
}
public void capture() {
this.textureUnits[0] = glGetInteger(GL_ACTIVE_TEXTURE);
//Capture all the texture data
for (int i = 0; i < this.textures.length; i++) {
glActiveTexture(this.textureUnits[i]);
this.textures[i] = glGetInteger(GL_TEXTURE_BINDING_2D);
}
//Reset the original active texture
glActiveTexture(this.textureUnits[0]);
for (int i = 0; i < this.capabilityIds.length; i++) {
this.enabledCaps[i] = glIsEnabled(this.capabilityIds[i]);
}
}
public void restore() {
//Capture all the texture data
for (int i = 1; i < this.textures.length; i++) {
glActiveTexture(this.textureUnits[i]);
//glBindSampler(this.textureUnits[i]-GL_TEXTURE0, 0);
glBindTexture(GL_TEXTURE_2D, this.textures[i]);
}
//Reset the original active texture
glActiveTexture(this.textureUnits[0]);
glBindTexture(GL_TEXTURE_2D, this.textures[0]);
for (int i = 0; i < this.capabilityIds.length; i++) {
if (this.enabledCaps[i]) {
glEnable(this.capabilityIds[i]);
} else {
glDisable(this.capabilityIds[i]);
}
}
}
public static Builder make() {
return new Builder();
}
public static class Builder {
private final IntArrayList caps = new IntArrayList();
private final IntArrayList textures = new IntArrayList();
private Builder() {
this.addTexture(-1);//Special texture unit, used to capture the current texture unit
}
public Builder addCapability(int cap) {
this.caps.add(cap);
return this;
}
public Builder addTexture(int unit) {
this.textures.add(unit);
return this;
}
public GlStateCapture build() {
return new GlStateCapture(this.caps.toIntArray(), this.textures.toIntArray());
}
}
}

View File

@@ -45,7 +45,7 @@ public class HiZBuffer2 {
} }
private void alloc(int width, int height) { private void alloc(int width, int height) {
this.levels = (int)Math.ceil(Math.log(Math.max(width, height))/Math.log(2)); this.levels = Math.min(7,(int)Math.ceil(Math.log(Math.max(width, height))/Math.log(2)));
//We dont care about e.g. 1x1 size texture since you dont get meshlets that big to cover such a large area //We dont care about e.g. 1x1 size texture since you dont get meshlets that big to cover such a large area
//this.levels -= 1;//Arbitrary size, shinks the max level by alot and saves a significant amount of processing time //this.levels -= 1;//Arbitrary size, shinks the max level by alot and saves a significant amount of processing time
// (could probably increase it to be defined by a max meshlet coverage computation thing) // (could probably increase it to be defined by a max meshlet coverage computation thing)

View File

@@ -1,13 +1,13 @@
package me.cortex.voxy.client.core.rendering.util; package me.cortex.voxy.client.core.rendering.util;
import net.minecraft.client.MinecraftClient;
import static org.lwjgl.opengl.GL33.glBindSampler; import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL45.glBindTextureUnit; import static org.lwjgl.opengl.GL45.glBindTextureUnit;
import net.minecraft.client.Minecraft;
public class LightMapHelper { public class LightMapHelper {
public static void bind(int lightingIndex) { public static void bind(int lightingIndex) {
glBindSampler(lightingIndex, 0); glBindSampler(lightingIndex, 0);
glBindTextureUnit(lightingIndex, ((net.minecraft.client.texture.GlTexture)(MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().getGlTextureView().texture())).getGlId()); glBindTextureUnit(lightingIndex, ((com.mojang.blaze3d.opengl.GlTexture)(Minecraft.getInstance().gameRenderer.lightTexture().getTextureView().texture())).glId());
} }
} }

View File

@@ -80,6 +80,17 @@ public class RawDownloadStream {
} }
public void free() { public void free() {
glFinish();
this.tick();
GlFence fence = new GlFence();
while (!fence.signaled()) {
glFinish();
}
fence.free();
this.tick();
if (this.frames.size() != 0) {
throw new IllegalStateException();
}
this.frames.forEach(a->a.fence.free()); this.frames.forEach(a->a.fence.free());
this.downloadBuffer.free(); this.downloadBuffer.free();
} }

View File

@@ -17,6 +17,7 @@ import static org.lwjgl.opengl.ARBMapBufferRange.*;
import static org.lwjgl.opengl.GL11.glFinish; import static org.lwjgl.opengl.GL11.glFinish;
import static org.lwjgl.opengl.GL42.glMemoryBarrier; import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL42C.GL_BUFFER_UPDATE_BARRIER_BIT; import static org.lwjgl.opengl.GL42C.GL_BUFFER_UPDATE_BARRIER_BIT;
import static org.lwjgl.opengl.GL44.GL_CLIENT_STORAGE_BIT;
import static org.lwjgl.opengl.GL44.GL_MAP_COHERENT_BIT; import static org.lwjgl.opengl.GL44.GL_MAP_COHERENT_BIT;
import static org.lwjgl.opengl.GL45C.glFlushMappedNamedBufferRange; import static org.lwjgl.opengl.GL45C.glFlushMappedNamedBufferRange;
@@ -31,7 +32,7 @@ public class UploadStream {
private static final boolean USE_COHERENT = false; private static final boolean USE_COHERENT = false;
public UploadStream(long size) { public UploadStream(long size) {
this.uploadBuffer = new GlPersistentMappedBuffer(size,GL_MAP_WRITE_BIT|GL_MAP_UNSYNCHRONIZED_BIT|(USE_COHERENT?GL_MAP_COHERENT_BIT:GL_MAP_FLUSH_EXPLICIT_BIT)).name("UploadStream"); this.uploadBuffer = new GlPersistentMappedBuffer(size,GL_CLIENT_STORAGE_BIT|GL_MAP_WRITE_BIT|GL_MAP_UNSYNCHRONIZED_BIT|(USE_COHERENT?GL_MAP_COHERENT_BIT:GL_MAP_FLUSH_EXPLICIT_BIT)).name("UploadStream");
this.allocationArena.setLimit(size); this.allocationArena.setLimit(size);
} }

View File

@@ -30,17 +30,37 @@ public class GPUTiming {
private final GlTimestampQuerySet timingSet = new GlTimestampQuerySet(); private final GlTimestampQuerySet timingSet = new GlTimestampQuerySet();
private float[] timings = new float[0];
public void marker() { public void marker() {
this.timingSet.capture(0); this.timingSet.capture(0);
} }
public String getDebug() {
StringBuilder str = new StringBuilder("GpuTime: [");
for (int i = 0; i < this.timings.length; i++) {
str.append(String.format("%.2f", this.timings[i]));
if (i!=this.timings.length-1) {
str.append(',');
}
}
str.append(']');
return str.toString();
}
public void tick() { public void tick() {
this.timingSet.download((meta,data)->{ this.timingSet.download((meta,data)->{
long current = data[0]; long current = data[0];
if (data.length-1!=this.timings.length) {
this.timings = new float[data.length-1];
}
for (int i = 1; i < meta.length; i++) { for (int i = 1; i < meta.length; i++) {
long next = data[i]; long next = data[i];
long delta = next - current; long delta = next - current;
//System.out.println(delta); float time = (float) (((double)delta)/1_000_000);
this.timings[i-1] = Math.max(this.timings[i-1]*0.99f+time*0.01f, time);
current = next; current = next;
} }
}); });

View File

@@ -10,7 +10,10 @@ import net.irisshaders.iris.api.v0.IrisApi;
import net.irisshaders.iris.gl.IrisRenderSystem; import net.irisshaders.iris.gl.IrisRenderSystem;
import net.irisshaders.iris.shadows.ShadowRenderer; import net.irisshaders.iris.shadows.ShadowRenderer;
import java.io.IOException;
public class IrisUtil { public class IrisUtil {
public record CapturedViewportParameters(ChunkRenderMatrices matrices, FogParameters parameters, double x, double y, double z) { public record CapturedViewportParameters(ChunkRenderMatrices matrices, FogParameters parameters, double x, double y, double z) {
public Viewport<?> apply(VoxyRenderSystem vrs) { public Viewport<?> apply(VoxyRenderSystem vrs) {
return vrs.setupViewport(this.matrices, this.parameters, this.x, this.y, this.z); return vrs.setupViewport(this.matrices, this.parameters, this.x, this.y, this.z);
@@ -34,6 +37,19 @@ public class IrisUtil {
public static void clearIrisSamplers() { public static void clearIrisSamplers() {
if (IRIS_INSTALLED) clearIrisSamplers0(); if (IRIS_INSTALLED) clearIrisSamplers0();
} }
public static void reload() {
if (IRIS_INSTALLED) reload0();
}
private static void reload0() {
try {
if (IrisApi.getInstance().isShaderPackInUse()) {//Only reload if there is a shaderpack
Iris.reload();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void clearIrisSamplers0() { private static void clearIrisSamplers0() {
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {

View File

@@ -6,21 +6,19 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.Logger;
import net.irisshaders.iris.api.v0.IrisApi;
import net.irisshaders.iris.shaderpack.ShaderPack; import net.irisshaders.iris.shaderpack.ShaderPack;
import net.irisshaders.iris.shaderpack.include.AbsolutePackPath; import net.irisshaders.iris.shaderpack.include.AbsolutePackPath;
import org.lwjgl.opengl.ARBDrawBuffersBlend;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.IntSupplier; import java.util.function.IntSupplier;
import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL33.*; import static org.lwjgl.opengl.GL33.*;
import static org.lwjgl.opengl.GL40.glBlendFuncSeparatei;
public class IrisShaderPatch { public class IrisShaderPatch {
public static final int VERSION = ((IntSupplier)()->1).getAsInt(); public static final int VERSION = ((IntSupplier)()->1).getAsInt();
@@ -28,7 +26,6 @@ public class IrisShaderPatch {
public static final boolean IMPERSONATE_DISTANT_HORIZONS = System.getProperty("voxy.impersonateDHShader", "false").equalsIgnoreCase("true"); public static final boolean IMPERSONATE_DISTANT_HORIZONS = System.getProperty("voxy.impersonateDHShader", "false").equalsIgnoreCase("true");
private static final class SSBODeserializer implements JsonDeserializer<Int2ObjectOpenHashMap<String>> { private static final class SSBODeserializer implements JsonDeserializer<Int2ObjectOpenHashMap<String>> {
@Override @Override
public Int2ObjectOpenHashMap<String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { public Int2ObjectOpenHashMap<String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
@@ -79,7 +76,7 @@ public class IrisShaderPatch {
} }
} }
public record BlendState(int buffer, boolean off, int sRBG, int dRGb, int sA, int dA) { public record BlendState(int buffer, boolean off, int sRGB, int dRGB, int sA, int dA) {
public static BlendState ALL_OFF = new BlendState(-1, true, 0,0,0,0); public static BlendState ALL_OFF = new BlendState(-1, true, 0,0,0,0);
} }
@@ -87,6 +84,9 @@ public class IrisShaderPatch {
private static final class BlendStateDeserializer implements JsonDeserializer<Int2ObjectMap<BlendState>> { private static final class BlendStateDeserializer implements JsonDeserializer<Int2ObjectMap<BlendState>> {
private static int parseType(String type) { private static int parseType(String type) {
type = type.toUpperCase(); type = type.toUpperCase();
if (!type.startsWith("GL_")) {
type = "GL_"+type;
}
return switch (type) { return switch (type) {
case "GL_ZERO" -> GL_ZERO; case "GL_ZERO" -> GL_ZERO;
case "GL_ONE" -> GL_ONE; case "GL_ONE" -> GL_ONE;
@@ -102,7 +102,10 @@ public class IrisShaderPatch {
case "GL_SRC1_COLOR" -> GL_SRC1_COLOR; case "GL_SRC1_COLOR" -> GL_SRC1_COLOR;
case "GL_ONE_MINUS_SRC1_COLOR" -> GL_ONE_MINUS_SRC1_COLOR; case "GL_ONE_MINUS_SRC1_COLOR" -> GL_ONE_MINUS_SRC1_COLOR;
case "GL_ONE_MINUS_SRC1_ALPHA" -> GL_ONE_MINUS_SRC1_ALPHA; case "GL_ONE_MINUS_SRC1_ALPHA" -> GL_ONE_MINUS_SRC1_ALPHA;
default -> -1; default -> {
Logger.error("Unknown blend option " + type);
yield -1;
}
}; };
} }
@Override @Override
@@ -118,20 +121,31 @@ public class IrisShaderPatch {
} else if (json.isJsonObject()) { } else if (json.isJsonObject()) {
for (var entry : json.getAsJsonObject().entrySet()) { for (var entry : json.getAsJsonObject().entrySet()) {
int buffer = Integer.parseInt(entry.getKey()); int buffer = Integer.parseInt(entry.getKey());
BlendState state; BlendState state = null;
var val = entry.getValue(); var val = entry.getValue();
List<String> bs = null;
if (val.isJsonArray()) { if (val.isJsonArray()) {
int[] v = val.getAsJsonArray().asList().stream().mapToInt(a->parseType(a.getAsString())).toArray(); bs = val.getAsJsonArray().asList().stream().map(JsonElement::getAsString).toList();
state = new BlendState(buffer, false, v[0], v[1], v[2], v[3]);
} else if (val.isJsonPrimitive()) { } else if (val.isJsonPrimitive()) {
if (val.getAsString().equalsIgnoreCase("off")) { var str = val.getAsString();
if (str.equalsIgnoreCase("off")) {
state = new BlendState(buffer, true, 0,0,0,0); state = new BlendState(buffer, true, 0,0,0,0);
} else { } else {
state = new BlendState(buffer, true, -1,-1,-1,-1); var parts = str.split(" ");
if (parts.length < 4) {
state = new BlendState(buffer, true, -1, -1, -1, -1);
} else {
bs = List.of(parts);
}
} }
} else { } else {
Logger.error("Unknown blend state "+val);
state = null; state = null;
} }
if (bs != null) {
int[] v = bs.stream().mapToInt(BlendStateDeserializer::parseType).toArray();
state = new BlendState(buffer, false, v[0], v[1], v[2], v[3]);
}
ret.put(buffer, state); ret.put(buffer, state);
} }
return ret; return ret;
@@ -161,8 +175,34 @@ public class IrisShaderPatch {
public boolean excludeLodsFromVanillaDepth; public boolean excludeLodsFromVanillaDepth;
public float[] renderScale; public float[] renderScale;
public boolean useViewportDims; public boolean useViewportDims;
public boolean checkValid() { //public boolean deferTranslucentRendering;
return this.opaqueDrawBuffers != null && this.translucentDrawBuffers != null && this.uniforms != null && this.opaquePatchData != null; public String checkValid() {
if (this.blending != null) {
int i = 0;
for (BlendState state : this.blending.values()) {
if (state.buffer != -1 && (state.buffer<0||this.translucentDrawBuffers.length<=state.buffer)) {
if (state.buffer<0) {
return "Blending buffer is <0 at index: " + i;
} else {
return "Blending buffer index out of bounds at "+i+" was "+state.buffer+" maximum is " +(this.translucentDrawBuffers.length-1);
}
}
i++;
}
}
if (this.opaquePatchData == null) {
return "Opaque patch data is null";
}
if (this.uniforms == null) {
return "Uniforms are null";
}
if (this.opaqueDrawBuffers == null) {
return "Opaque draw buffers are null";
}
if (this.translucentDrawBuffers == null) {
return "Translucent draw buffers are null";
}
return null;
} }
} }
@@ -228,6 +268,10 @@ public class IrisShaderPatch {
return new float[]{Math.max(0.01f,this.patchData.renderScale[0]),Math.max(0.01f,this.patchData.renderScale[1])}; return new float[]{Math.max(0.01f,this.patchData.renderScale[0]),Math.max(0.01f,this.patchData.renderScale[1])};
} }
public boolean deferedTranslucentRendering() {
return false;//this.patchData.deferTranslucentRendering;
}
public Runnable createBlendSetup() { public Runnable createBlendSetup() {
if (this.patchData.blending == null || this.patchData.blending.isEmpty()) { if (this.patchData.blending == null || this.patchData.blending.isEmpty()) {
return ()->{};//No blending change return ()->{};//No blending change
@@ -241,7 +285,7 @@ public class IrisShaderPatch {
glDisable(GL_BLEND); glDisable(GL_BLEND);
} else { } else {
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFuncSeparate(init.sRBG, init.dRGb, init.sA, init.dA); glBlendFuncSeparate(init.sRGB, init.dRGB, init.sA, init.dA);
} }
} }
for (var entry:BS.int2ObjectEntrySet()) { for (var entry:BS.int2ObjectEntrySet()) {
@@ -251,7 +295,8 @@ public class IrisShaderPatch {
glDisablei(GL_BLEND, s.buffer); glDisablei(GL_BLEND, s.buffer);
} else { } else {
glEnablei(GL_BLEND, s.buffer); glEnablei(GL_BLEND, s.buffer);
glBlendFuncSeparatei(s.buffer, s.sRBG, s.dRGb, s.sA, s.dA); //_sigh_ thanks nvidia
ARBDrawBuffersBlend.glBlendFuncSeparateiARB(s.buffer, s.sRGB, s.dRGB, s.sA, s.dA);
} }
} }
}; };
@@ -296,22 +341,31 @@ public class IrisShaderPatch {
} }
patchData = GSON.fromJson(voxyPatchData, PatchGson.class); patchData = GSON.fromJson(voxyPatchData, PatchGson.class);
if (patchData == null) { if (patchData == null) {
return null; throw new IllegalStateException("Voxy patch json returned null, this is most likely due to malformed json file");
} }
{//Inject data from the auxilery files if they are present {//Inject data from the auxilery files if they are present
var opaque = sourceProvider.apply(directory.resolve("voxy_opaque.glsl")); var opaque = sourceProvider.apply(directory.resolve("voxy_opaque.glsl"));
if (opaque != null) { if (opaque != null) {
Logger.info("External opaque shader patch applied");
patchData.opaquePatchData = opaque; patchData.opaquePatchData = opaque;
} }
var translucent = sourceProvider.apply(directory.resolve("voxy_translucent.glsl")); var translucent = sourceProvider.apply(directory.resolve("voxy_translucent.glsl"));
if (translucent != null) { if (translucent != null) {
Logger.info("External translucent shader patch applied");
patchData.translucentPatchData = translucent; patchData.translucentPatchData = translucent;
} }
//This might be ok? not.. sure if is nice or not
var taa = sourceProvider.apply(directory.resolve("voxy_taa.glsl"));
if (taa != null) {
Logger.info("External taa shader patch applied");
patchData.taaOffset = taa;
}
} }
if (!patchData.checkValid()) { var invalidPatchDataReason = patchData.checkValid();
throw new IllegalStateException("voxy json patch not valid: " + voxyPatchData); if (invalidPatchDataReason!=null) {
throw new IllegalStateException("voxy json patch not valid: " + invalidPatchDataReason);
} }
} catch (Exception e) { } catch (Exception e) {
patchData = null; patchData = null;

View File

@@ -3,6 +3,7 @@ package me.cortex.voxy.client.iris;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction;
import kroppeb.stareval.function.FunctionReturn; import kroppeb.stareval.function.FunctionReturn;
import kroppeb.stareval.function.Type; import kroppeb.stareval.function.Type;
import me.cortex.voxy.client.core.IrisVoxyRenderPipeline; import me.cortex.voxy.client.core.IrisVoxyRenderPipeline;
@@ -13,6 +14,7 @@ import net.irisshaders.iris.gl.buffer.ShaderStorageBufferHolder;
import net.irisshaders.iris.gl.image.ImageHolder; import net.irisshaders.iris.gl.image.ImageHolder;
import net.irisshaders.iris.gl.sampler.GlSampler; import net.irisshaders.iris.gl.sampler.GlSampler;
import net.irisshaders.iris.gl.sampler.SamplerHolder; import net.irisshaders.iris.gl.sampler.SamplerHolder;
import net.irisshaders.iris.gl.state.FogMode;
import net.irisshaders.iris.gl.state.ValueUpdateNotifier; import net.irisshaders.iris.gl.state.ValueUpdateNotifier;
import net.irisshaders.iris.gl.texture.InternalTextureFormat; import net.irisshaders.iris.gl.texture.InternalTextureFormat;
import net.irisshaders.iris.gl.texture.TextureType; import net.irisshaders.iris.gl.texture.TextureType;
@@ -20,15 +22,14 @@ import net.irisshaders.iris.gl.uniform.*;
import net.irisshaders.iris.pipeline.IrisRenderingPipeline; import net.irisshaders.iris.pipeline.IrisRenderingPipeline;
import net.irisshaders.iris.targets.RenderTarget; import net.irisshaders.iris.targets.RenderTarget;
import net.irisshaders.iris.targets.RenderTargets; import net.irisshaders.iris.targets.RenderTargets;
import net.irisshaders.iris.uniforms.CommonUniforms;
import net.irisshaders.iris.uniforms.custom.CustomUniforms; import net.irisshaders.iris.uniforms.custom.CustomUniforms;
import net.irisshaders.iris.uniforms.custom.cached.*; import net.irisshaders.iris.uniforms.custom.cached.*;
import org.joml.*; import org.joml.*;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import java.util.*; import java.util.*;
import java.util.function.IntConsumer; import java.util.function.*;
import java.util.function.IntSupplier;
import java.util.function.LongConsumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.lwjgl.opengl.ARBDirectStateAccess.glBindTextureUnit; import static org.lwjgl.opengl.ARBDirectStateAccess.glBindTextureUnit;
@@ -50,6 +51,7 @@ public class IrisVoxyRenderPipelineData {
public final float[] resolutionScale; public final float[] resolutionScale;
public final String TAA; public final String TAA;
public final boolean useViewportDims; public final boolean useViewportDims;
public final boolean deferTranslucency;
private IrisVoxyRenderPipelineData(IrisShaderPatch patch, int[] opaqueDrawTargets, int[] translucentDrawTargets, StructLayout uniformSet, Runnable blendingSetup, ImageSet imageSet, SSBOSet ssboSet) { private IrisVoxyRenderPipelineData(IrisShaderPatch patch, int[] opaqueDrawTargets, int[] translucentDrawTargets, StructLayout uniformSet, Runnable blendingSetup, ImageSet imageSet, SSBOSet ssboSet) {
this.opaqueDrawTargets = opaqueDrawTargets; this.opaqueDrawTargets = opaqueDrawTargets;
@@ -64,6 +66,7 @@ public class IrisVoxyRenderPipelineData {
this.TAA = patch.getTAAShift(); this.TAA = patch.getTAAShift();
this.resolutionScale = patch.getRenderScale(); this.resolutionScale = patch.getRenderScale();
this.useViewportDims = patch.useViewportDims(); this.useViewportDims = patch.useViewportDims();
this.deferTranslucency = patch.deferedTranslucentRendering();
} }
public SSBOSet getSsboSet() { public SSBOSet getSsboSet() {
@@ -91,6 +94,7 @@ public class IrisVoxyRenderPipelineData {
public static IrisVoxyRenderPipelineData buildPipeline(IrisRenderingPipeline ipipe, IrisShaderPatch patch, CustomUniforms cu, ShaderStorageBufferHolder ssboHolder) { public static IrisVoxyRenderPipelineData buildPipeline(IrisRenderingPipeline ipipe, IrisShaderPatch patch, CustomUniforms cu, ShaderStorageBufferHolder ssboHolder) {
var uniforms = createUniformLayoutStructAndUpdater(createUniformSet(cu, patch)); var uniforms = createUniformLayoutStructAndUpdater(createUniformSet(cu, patch));
var imageSet = createImageSet(ipipe, patch); var imageSet = createImageSet(ipipe, patch);
var ssboSet = createSSBOLayouts(patch.getSSBOs(), ssboHolder); var ssboSet = createSSBOLayouts(patch.getSSBOs(), ssboHolder);
@@ -114,40 +118,60 @@ public class IrisVoxyRenderPipelineData {
return targetTextures; return targetTextures;
} }
private static String convertToGlslType(UniformType type) {
return switch (type) {
case INT -> "int";
case FLOAT -> "float";
case MAT3 -> "mat3";
case MAT4 -> "mat4";
case VEC2 -> "vec2";
case VEC2I -> "ivec2";
case VEC3 -> "vec3";
case VEC3I -> "ivec3";
case VEC4 -> "vec4";
case VEC4I -> "ivec4";
};
}
public boolean shouldDeferTranslucency() {
return false;
}
public record StructLayout(int size, String layout, LongConsumer updater) {} public record StructLayout(int size, String layout, LongConsumer updater) {}
private static StructLayout createUniformLayoutStructAndUpdater(CachedUniform[] uniforms) { private static StructLayout createUniformLayoutStructAndUpdater(List<UniformWritingHolder> uniforms) {
if (uniforms.length == 0) { if (uniforms.size() == 0) {
return null; return null;
} }
List<CachedUniform>[] ordering = new List[]{new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()}; List<UniformWritingHolder>[] ordering = new List[]{new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()};
//Creates an optimial struct layout for the uniforms //Creates an optimial struct layout for the uniforms
for (var uniform : uniforms) { for (var uniform : uniforms) {
int order = getUniformOrdering(Type.convert(uniform.getType())); int order = getUniformOrdering(uniform.type);
ordering[order].add(uniform); ordering[order].add(uniform);
} }
//Emit the ordering, note this is not optimial, but good enough, e.g. if have even number of align 2, emit that after align 4 //Emit the ordering, note this is not optimial, but good enough, e.g. if have even number of align 2, emit that after align 4
int pos = 0; int pos = 0;
Int2ObjectLinkedOpenHashMap<CachedUniform> layout = new Int2ObjectLinkedOpenHashMap<>(); Int2ObjectLinkedOpenHashMap<UniformWritingHolder> layout = new Int2ObjectLinkedOpenHashMap<>();
for (var uniform : ordering[0]) {//Emit exact align 4 for (var uniform : ordering[0]) {//Emit exact align 4
layout.put(pos, uniform); pos += getSizeAndAlignment(Type.convert(uniform.getType()))>>5; layout.put(pos, uniform); pos += getSizeAndAlignment(uniform.type)>>5;
} }
if (!ordering[1].isEmpty() && (ordering[1].size()&1)==0) { if (!ordering[1].isEmpty() && (ordering[1].size()&1)==0) {
//Emit all the align 2 as there is an even number of them //Emit all the align 2 as there is an even number of them
for (var uniform : ordering[1]) { for (var uniform : ordering[1]) {
layout.put(pos, uniform); pos += getSizeAndAlignment(Type.convert(uniform.getType()))>>5; layout.put(pos, uniform); pos += getSizeAndAlignment(uniform.type)>>5;
} }
ordering[1].clear(); ordering[1].clear();
} }
//Emit align 3 //Emit align 3
for (var uniform : ordering[2]) {//Emit size odd, alignment must be 4 for (var uniform : ordering[2]) {//Emit size odd, alignment must be 4
layout.put(pos, uniform); pos += getSizeAndAlignment(Type.convert(uniform.getType()))>>5; layout.put(pos, uniform); pos += getSizeAndAlignment(uniform.type)>>5;
//We must get a size 1 to pad to align 4 //We must get a size 1 to pad to align 4
if (!ordering[3].isEmpty()) {//Size 1 if (!ordering[3].isEmpty()) {//Size 1
uniform = ordering[3].removeFirst(); uniform = ordering[3].removeFirst();
layout.put(pos, uniform); pos += getSizeAndAlignment(Type.convert(uniform.getType()))>>5; layout.put(pos, uniform); pos += getSizeAndAlignment(uniform.type)>>5;
} else {//Padding must be injected } else {//Padding must be injected
pos += 1; pos += 1;
} }
@@ -155,15 +179,15 @@ public class IrisVoxyRenderPipelineData {
//Emit align 2 //Emit align 2
for (var uniform : ordering[1]) { for (var uniform : ordering[1]) {
layout.put(pos, uniform); pos += getSizeAndAlignment(Type.convert(uniform.getType()))>>5; layout.put(pos, uniform); pos += getSizeAndAlignment(uniform.type)>>5;
} }
//Emit align 1 //Emit align 1
for (var uniform : ordering[3]) { for (var uniform : ordering[3]) {
layout.put(pos, uniform); pos += getSizeAndAlignment(Type.convert(uniform.getType()))>>5; layout.put(pos, uniform); pos += getSizeAndAlignment(uniform.type)>>5;
} }
if (layout.size()!=uniforms.length) { if (layout.size()!=uniforms.size()) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
@@ -173,7 +197,7 @@ public class IrisVoxyRenderPipelineData {
{ {
StringBuilder struct = new StringBuilder("{\n"); StringBuilder struct = new StringBuilder("{\n");
for (var pair : layout.int2ObjectEntrySet()) { for (var pair : layout.int2ObjectEntrySet()) {
struct.append("\t").append(pair.getValue().getType().toString()).append(" ").append(pair.getValue().getName()).append(";\n"); struct.append("\t").append(convertToGlslType(pair.getValue().type)).append(" ").append(pair.getValue().name).append(";\n");
} }
struct.append("}"); struct.append("}");
structLayout = struct.toString(); structLayout = struct.toString();
@@ -181,11 +205,10 @@ public class IrisVoxyRenderPipelineData {
LongConsumer updater; LongConsumer updater;
{ {
FunctionReturn cacheRetObj = new FunctionReturn(); LongConsumer[] updaters = new LongConsumer[uniforms.size()];
LongConsumer[] updaters = new LongConsumer[uniforms.length];
int i = 0; int i = 0;
for (var pair : layout.int2ObjectEntrySet()) { for (var pair : layout.int2ObjectEntrySet()) {
updaters[i++] = createWriter(pair.getIntKey()*4L, cacheRetObj, pair.getValue()); updaters[i++] = pair.getValue().writingFactory.get(pair.getIntKey()*4L);
} }
updater = ptr -> { updater = ptr -> {
@@ -271,9 +294,81 @@ public class IrisVoxyRenderPipelineData {
}; };
} }
private static CachedUniform[] createUniformSet(CustomUniforms cu, IrisShaderPatch patch) { private record UniformWritingHolder(String name, UniformType type, Long2ObjectFunction<LongConsumer> writingFactory) {
}
private static List<UniformWritingHolder> createUniformSet(CustomUniforms cu, IrisShaderPatch patch) {
//This is a fking awful hack... but it works thinks //This is a fking awful hack... but it works thinks
LocationalUniformHolder uniformBuilder = new LocationalUniformHolder() {
List<UniformWritingHolder> uniforms = new ArrayList<>();
Set<String> seenUniforms = new HashSet<>();
DynamicLocationalUniformHolder uniformBuilder = new DynamicLocationalUniformHolder() {
@Override
public DynamicLocationalUniformHolder uniform1i(UniformUpdateFrequency updateFrequency, String name, IntSupplier value) {
return this.uniform1i(name, value, null);
}
@Override
public DynamicLocationalUniformHolder uniform1i(String name, IntSupplier value, ValueUpdateNotifier notifier) {
this.injectDynamicUniformType(name, UniformType.INT, offset->{
return ptr->{
MemoryUtil.memPutInt(ptr+offset, value.getAsInt());
};
});
return this;
}
@Override
public DynamicLocationalUniformHolder uniform1f(UniformUpdateFrequency updateFrequency, String name, FloatSupplier value) {
return this.uniform1f(name, value, null);
}
@Override
public DynamicLocationalUniformHolder uniform1f(String name, FloatSupplier value, ValueUpdateNotifier notifier) {
this.injectDynamicUniformType(name, UniformType.FLOAT, offset->{
return ptr->{
MemoryUtil.memPutFloat(ptr+offset, value.getAsFloat());
};
});
return this;
}
@Override
public DynamicLocationalUniformHolder uniform3f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector3f> value) {
return this.uniform3f(name, value, null);
}
@Override
public DynamicLocationalUniformHolder uniform3f(String name, Supplier<Vector3f> value, ValueUpdateNotifier notifier) {
this.injectDynamicUniformType(name, UniformType.VEC3, offset->{
return ptr->{
value.get().getToAddress(ptr+offset);
};
});
return this;
}
private void injectDynamicUniformType(String name, UniformType type, Long2ObjectFunction<LongConsumer> supplier) {
var names = patch.getUniformList();
for (int i = 0; i < names.length; i++) {
if (names[i].equals(name)) {
if (!seenUniforms.add(name)) {
throw new IllegalArgumentException("Already added uniform: " + name);
}
uniforms.add(new UniformWritingHolder(name, type, supplier));
break;
}
}
}
@Override
public DynamicLocationalUniformHolder addDynamicUniform(Uniform uniform, ValueUpdateNotifier valueUpdateNotifier) {
throw new IllegalStateException("Type not implemented for uniform: " + uniform);
//return this;
}
@Override @Override
public LocationalUniformHolder addUniform(UniformUpdateFrequency uniformUpdateFrequency, Uniform uniform) { public LocationalUniformHolder addUniform(UniformUpdateFrequency uniformUpdateFrequency, Uniform uniform) {
return this; return this;
@@ -296,31 +391,36 @@ public class IrisVoxyRenderPipelineData {
return null; return null;
} }
}; };
CommonUniforms.addDynamicUniforms(uniformBuilder, FogMode.PER_FRAGMENT);
cu.assignTo(uniformBuilder); cu.assignTo(uniformBuilder);
cu.mapholderToPass(uniformBuilder, patch); cu.mapholderToPass(uniformBuilder, patch);
CachedUniform[] uniforms = new CachedUniform[patch.getUniformList().length]; FunctionReturn cachedReturn = new FunctionReturn();
((CustomUniformsAccessor)cu).getLocationMap().get(patch).object2IntEntrySet().forEach(entry->uniforms[entry.getIntValue()] = entry.getKey()); ((CustomUniformsAccessor)cu).getLocationMap().get(patch).object2IntEntrySet().forEach(entry-> {
int i = 0; if (!seenUniforms.add(entry.getKey().getName())) {
int j = 0; throw new IllegalArgumentException("Already added uniform: " + entry.getKey().getName());
for (var uniform : uniforms) {
if (uniform == null) {
Logger.error("Unknown uniform at location "+j + " skipping");
} else {
uniforms[i++] = uniform;//This shuffles the uniforms down till its compacted
} }
j++; uniforms.add(new UniformWritingHolder(entry.getKey().getName(), Type.convert(entry.getKey().getType()),offset->createWriter(offset, cachedReturn, entry.getKey())));
});
if (uniforms.size() != patch.getUniformList().length) {
Set<String> uniformsUnseen = new HashSet<>(List.of(patch.getUniformList()));
for (var uniform : uniforms) {
uniformsUnseen.remove(uniform.name);
}
Logger.error("The following uniforms could not be found: [" + uniformsUnseen.stream().sorted(String::compareToIgnoreCase).collect(Collectors.joining(","))+"]");
} }
//In _theory_ this should work? //In _theory_ this should work?
return Arrays.copyOf(uniforms, i); return uniforms;
} }
private record TextureWSampler(String name, IntSupplier texture, int sampler) { } private record TextureWSampler(String name, IntSupplier texture, IntSupplier sampler) { }
public record ImageSet(String layout, IntConsumer bindingFunction) { public record ImageSet(String layout, IntConsumer bindingFunction) {
} }
private static ImageSet createImageSet(IrisRenderingPipeline ipipe, IrisShaderPatch patch) { private static ImageSet createImageSet(IrisRenderingPipeline ipipe, IrisShaderPatch patch) {
var samplerDataSet = patch.getSamplerSet(); var samplerDataSet = patch.getSamplerSet();
if (samplerDataSet == null) return null;
Set<String> samplerNameSet = new LinkedHashSet<>(samplerDataSet.keySet()); Set<String> samplerNameSet = new LinkedHashSet<>(samplerDataSet.keySet());
if (samplerNameSet.isEmpty()) return null; if (samplerNameSet.isEmpty()) return null;
Set<TextureWSampler> samplerSet = new LinkedHashSet<>(); Set<TextureWSampler> samplerSet = new LinkedHashSet<>();
@@ -345,27 +445,30 @@ public class IrisVoxyRenderPipelineData {
} }
@Override @Override
public boolean addDefaultSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) { public boolean addDefaultSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, Supplier<GlSampler> sampler, String... names) {
Logger.error("Unsupported default sampler"); Logger.error("Unsupported default sampler");
return false; return false;
} }
@Override @Override
public boolean addDynamicSampler(TextureType type, IntSupplier texture, GlSampler sampler, String... names) { public boolean addDynamicSampler(TextureType type, IntSupplier texture, Supplier<GlSampler> sampler, String... names) {
return this.addDynamicSampler(type, texture, null, sampler, names); return this.addDynamicSampler(type, texture, null, sampler, names);
} }
@Override @Override
public boolean addDynamicSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) { public boolean addDynamicSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, Supplier<GlSampler> sampler, String... names) {
if (!this.hasSampler(names)) return false; if (!this.hasSampler(names)) return false;
samplerSet.add(new TextureWSampler(this.name(names), texture, sampler!=null?sampler.getId():-1)); samplerSet.add(new TextureWSampler(this.name(names), texture, sampler!=null?()->{
var s = sampler.get();
return s!=null?s.getId():-1;
}:()->-1));
return true; return true;
} }
@Override @Override
public void addExternalSampler(int texture, String... names) { public void addExternalSampler(int texture, String... names) {
if (!this.hasSampler(names)) return; if (!this.hasSampler(names)) return;
samplerSet.add(new TextureWSampler(this.name(names), ()->texture, -1)); samplerSet.add(new TextureWSampler(this.name(names), ()->texture, ()->-1));
} }
}; };
@@ -386,7 +489,7 @@ public class IrisVoxyRenderPipelineData {
//samplerSet contains our samplers //samplerSet contains our samplers
if (samplerSet.size() != samplerNameSet.size()) { if (samplerSet.size() != samplerNameSet.size()) {
Logger.error("Did not find all requested samplers. Found [" + samplerSet.stream().map(a->a.name).collect(Collectors.joining()) + "] expected " + samplerNameSet); Logger.error("Did not find all requested samplers. Found [" + samplerSet.stream().map(a->a.name).collect(Collectors.joining(", ")) + "] expected " + samplerNameSet);
} }
//TODO: generate a layout (defines) for all the samplers with the correct types //TODO: generate a layout (defines) for all the samplers with the correct types
@@ -408,8 +511,9 @@ public class IrisVoxyRenderPipelineData {
int unit = j+base; int unit = j+base;
var ts = samplers[j]; var ts = samplers[j];
glBindTextureUnit(unit, ts.texture.getAsInt()); glBindTextureUnit(unit, ts.texture.getAsInt());
if (ts.sampler != -1) { int sampler = ts.sampler.getAsInt();
glBindSampler(unit, ts.sampler); if (sampler != -1) {
glBindSampler(unit, sampler);
}//TODO: might need to bind sampler 0 }//TODO: might need to bind sampler 0
} }
}; };

View File

@@ -1,5 +1,6 @@
package me.cortex.voxy.client.iris; package me.cortex.voxy.client.iris;
import net.irisshaders.iris.gl.sampler.GlSampler;
import net.irisshaders.iris.gl.sampler.SamplerHolder; import net.irisshaders.iris.gl.sampler.SamplerHolder;
import net.irisshaders.iris.gl.texture.TextureType; import net.irisshaders.iris.gl.texture.TextureType;
import net.irisshaders.iris.pipeline.IrisRenderingPipeline; import net.irisshaders.iris.pipeline.IrisRenderingPipeline;
@@ -32,7 +33,7 @@ public class VoxySamplers {
return 0; return 0;
} }
return dt.id; return dt.id;
}, null, opaqueNames); }, ()->GlSampler.MIPPED_NEAREST_NEAREST, opaqueNames);
samplers.addDynamicSampler(TextureType.TEXTURE_2D, () -> { samplers.addDynamicSampler(TextureType.TEXTURE_2D, () -> {
var pipeData = ((IGetIrisVoxyPipelineData)pipeline).voxy$getPipelineData(); var pipeData = ((IGetIrisVoxyPipelineData)pipeline).voxy$getPipelineData();
@@ -48,7 +49,7 @@ public class VoxySamplers {
return 0; return 0;
} }
return dt.id; return dt.id;
}, null, translucentNames); }, ()->GlSampler.MIPPED_NEAREST_NEAREST, translucentNames);
} }
} }
} }

View File

@@ -1,9 +1,9 @@
package me.cortex.voxy.client.iris; package me.cortex.voxy.client.iris;
import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem; import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import net.irisshaders.iris.gl.uniform.UniformHolder; import net.irisshaders.iris.gl.uniform.UniformHolder;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.Minecraft;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Matrix4fc; import org.joml.Matrix4fc;
@@ -14,7 +14,7 @@ import static net.irisshaders.iris.gl.uniform.UniformUpdateFrequency.PER_FRAME;
public class VoxyUniforms { public class VoxyUniforms {
public static Matrix4f getViewProjection() {//This is 1 frame late ;-; cries, since the update occurs _before_ the voxy render pipeline public static Matrix4f getViewProjection() {//This is 1 frame late ;-; cries, since the update occurs _before_ the voxy render pipeline
var getVrs = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer; var getVrs = (IGetVoxyRenderSystem) Minecraft.getInstance().levelRenderer;
if (getVrs == null || getVrs.getVoxyRenderSystem() == null) { if (getVrs == null || getVrs.getVoxyRenderSystem() == null) {
return new Matrix4f(); return new Matrix4f();
} }
@@ -23,7 +23,7 @@ public class VoxyUniforms {
} }
public static Matrix4f getModelView() {//This is 1 frame late ;-; cries, since the update occurs _before_ the voxy render pipeline public static Matrix4f getModelView() {//This is 1 frame late ;-; cries, since the update occurs _before_ the voxy render pipeline
var getVrs = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer; var getVrs = (IGetVoxyRenderSystem) Minecraft.getInstance().levelRenderer;
if (getVrs == null || getVrs.getVoxyRenderSystem() == null) { if (getVrs == null || getVrs.getVoxyRenderSystem() == null) {
return new Matrix4f(); return new Matrix4f();
} }
@@ -32,7 +32,7 @@ public class VoxyUniforms {
} }
public static Matrix4f getProjection() {//This is 1 frame late ;-; cries, since the update occurs _before_ the voxy render pipeline public static Matrix4f getProjection() {//This is 1 frame late ;-; cries, since the update occurs _before_ the voxy render pipeline
var getVrs = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer; var getVrs = (IGetVoxyRenderSystem) Minecraft.getInstance().levelRenderer;
if (getVrs == null || getVrs.getVoxyRenderSystem() == null) { if (getVrs == null || getVrs.getVoxyRenderSystem() == null) {
return new Matrix4f(); return new Matrix4f();
} }

View File

@@ -5,7 +5,7 @@ import com.moulberry.flashback.record.Recorder;
import me.cortex.voxy.client.VoxyClientInstance; import me.cortex.voxy.client.VoxyClientInstance;
import me.cortex.voxy.client.compat.IFlashbackMeta; import me.cortex.voxy.client.compat.IFlashbackMeta;
import me.cortex.voxy.commonImpl.VoxyCommon; import me.cortex.voxy.commonImpl.VoxyCommon;
import net.minecraft.registry.DynamicRegistryManager; import net.minecraft.core.RegistryAccess;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@@ -18,7 +18,7 @@ public class MixinFlashbackRecorder {
@Shadow @Final private FlashbackMeta metadata; @Shadow @Final private FlashbackMeta metadata;
@Inject(method = "<init>", at = @At("TAIL")) @Inject(method = "<init>", at = @At("TAIL"))
private void voxy$getStoragePath(DynamicRegistryManager registryAccess, CallbackInfo retInf) { private void voxy$getStoragePath(RegistryAccess registryAccess, CallbackInfo retInf) {
if (VoxyCommon.isAvailable()) { if (VoxyCommon.isAvailable()) {
var instance = VoxyCommon.getInstance(); var instance = VoxyCommon.getInstance();
if (instance instanceof VoxyClientInstance ci) { if (instance instanceof VoxyClientInstance ci) {

View File

@@ -6,7 +6,6 @@ import net.irisshaders.iris.Iris;
import net.irisshaders.iris.shaderpack.ShaderPack; import net.irisshaders.iris.shaderpack.ShaderPack;
import net.irisshaders.iris.shaderpack.materialmap.NamespacedId; import net.irisshaders.iris.shaderpack.materialmap.NamespacedId;
import net.irisshaders.iris.shaderpack.programs.ProgramSet; import net.irisshaders.iris.shaderpack.programs.ProgramSet;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Redirect;

View File

@@ -10,7 +10,7 @@ import net.irisshaders.iris.gl.buffer.ShaderStorageBufferHolder;
import net.irisshaders.iris.pipeline.IrisRenderingPipeline; import net.irisshaders.iris.pipeline.IrisRenderingPipeline;
import net.irisshaders.iris.shaderpack.programs.ProgramSet; import net.irisshaders.iris.shaderpack.programs.ProgramSet;
import net.irisshaders.iris.uniforms.custom.CustomUniforms; import net.irisshaders.iris.uniforms.custom.CustomUniforms;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@@ -41,10 +41,10 @@ public class MixinIrisRenderingPipeline implements IGetVoxyPatchData, IGetIrisVo
} }
} }
@Inject(method = "beginLevelRendering", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/opengl/GlStateManager;_activeTexture(I)V", shift = At.Shift.BEFORE), remap = true) @Inject(method = "beginLevelRendering", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/opengl/GlStateManager;_activeTexture(I)V", shift = At.Shift.BEFORE), remap = false)
private void voxy$injectViewportSetup(CallbackInfo ci) { private void voxy$injectViewportSetup(CallbackInfo ci) {
if (IrisUtil.CAPTURED_VIEWPORT_PARAMETERS != null) { if (IrisUtil.CAPTURED_VIEWPORT_PARAMETERS != null) {
var renderer = ((IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer).getVoxyRenderSystem(); var renderer = ((IGetVoxyRenderSystem) Minecraft.getInstance().levelRenderer).getVoxyRenderSystem();
if (renderer != null) { if (renderer != null) {
IrisUtil.CAPTURED_VIEWPORT_PARAMETERS.apply(renderer); IrisUtil.CAPTURED_VIEWPORT_PARAMETERS.apply(renderer);
} }

View File

@@ -1,16 +1,15 @@
package me.cortex.voxy.client.mixin.iris; package me.cortex.voxy.client.mixin.iris;
import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.buffers.GpuBufferSlice;
import com.mojang.blaze3d.resource.GraphicsResourceAllocator;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem; import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import me.cortex.voxy.client.core.VoxyRenderSystem;
import me.cortex.voxy.client.core.util.IrisUtil; import me.cortex.voxy.client.core.util.IrisUtil;
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices; import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
import net.caffeinemc.mods.sodium.client.util.FogStorage; import net.caffeinemc.mods.sodium.client.util.FogStorage;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.Camera;
import net.minecraft.client.render.Camera; import net.minecraft.client.DeltaTracker;
import net.minecraft.client.render.RenderTickCounter; import net.minecraft.client.Minecraft;
import net.minecraft.client.render.WorldRenderer; import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.util.ObjectAllocator;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Vector4f; import org.joml.Vector4f;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
@@ -22,30 +21,31 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static org.lwjgl.opengl.GL11C.glViewport; import static org.lwjgl.opengl.GL11C.glViewport;
@Mixin(WorldRenderer.class) @Mixin(LevelRenderer.class)
public class MixinWorldRenderer { public class MixinLevelRenderer {
@Shadow @Final private MinecraftClient client; @Shadow @Final private Minecraft minecraft;
@Inject(method = "render", at = @At("HEAD"), order = 100) @Inject(method = "renderLevel", at = @At("HEAD"), order = 100)
private void voxy$injectIrisCompat( private void voxy$injectIrisCompat(
ObjectAllocator allocator, GraphicsResourceAllocator allocator,
RenderTickCounter tickCounter, DeltaTracker tickCounter,
boolean renderBlockOutline, boolean renderBlockOutline,
Camera camera, Camera camera,
Matrix4f positionMatrix, Matrix4f positionMatrix,
Matrix4f projectionMatrix, Matrix4f projectionMatrix,
GpuBufferSlice fog, Matrix4f basicProjectionMatrix,
GpuBufferSlice fogBuffer,
Vector4f fogColor, Vector4f fogColor,
boolean shouldRenderSky, boolean renderSky,
CallbackInfo ci) { CallbackInfo ci) {
if (IrisUtil.irisShaderPackEnabled()) { if (IrisUtil.irisShaderPackEnabled()) {
var renderer = ((IGetVoxyRenderSystem) this).getVoxyRenderSystem(); var renderer = ((IGetVoxyRenderSystem) this).getVoxyRenderSystem();
if (renderer != null) { if (renderer != null) {
//Fixthe fucking viewport dims, fuck iris //Fixthe fucking viewport dims, fuck iris
glViewport(0,0,MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight); glViewport(0,0,Minecraft.getInstance().getMainRenderTarget().width, Minecraft.getInstance().getMainRenderTarget().height);
var pos = camera.getCameraPos(); var pos = camera.position();
IrisUtil.CAPTURED_VIEWPORT_PARAMETERS = new IrisUtil.CapturedViewportParameters(new ChunkRenderMatrices(projectionMatrix, positionMatrix), ((FogStorage) this.client.gameRenderer).sodium$getFogParameters(), pos.x, pos.y, pos.z); IrisUtil.CAPTURED_VIEWPORT_PARAMETERS = new IrisUtil.CapturedViewportParameters(new ChunkRenderMatrices(projectionMatrix, positionMatrix), ((FogStorage) this.minecraft.gameRenderer).sodium$getFogParameters(), pos.x, pos.y, pos.z);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
package me.cortex.voxy.client.mixin.iris; package me.cortex.voxy.client.mixin.iris;
import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.client.core.util.IrisUtil; import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.client.iris.VoxyUniforms; import me.cortex.voxy.client.iris.VoxyUniforms;
import net.irisshaders.iris.gl.uniform.UniformHolder; import net.irisshaders.iris.gl.uniform.UniformHolder;

View File

@@ -1,6 +1,6 @@
package me.cortex.voxy.client.mixin.iris; package me.cortex.voxy.client.mixin.iris;
import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.client.core.util.IrisUtil; import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.client.iris.IGetVoxyPatchData; import me.cortex.voxy.client.iris.IGetVoxyPatchData;
import me.cortex.voxy.client.iris.IrisShaderPatch; import me.cortex.voxy.client.iris.IrisShaderPatch;

View File

@@ -3,7 +3,7 @@ package me.cortex.voxy.client.mixin.iris;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.client.core.util.IrisUtil; import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.client.iris.IrisShaderPatch; import me.cortex.voxy.client.iris.IrisShaderPatch;
import net.irisshaders.iris.gl.shader.StandardMacros; import net.irisshaders.iris.gl.shader.StandardMacros;

View File

@@ -1,17 +1,18 @@
package me.cortex.voxy.client.mixin.minecraft; package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.LoadException; import me.cortex.voxy.client.LoadException;
import net.minecraft.util.thread.ThreadExecutor; import net.minecraft.util.thread.BlockableEventLoop;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(ThreadExecutor.class) @Mixin(BlockableEventLoop.class)
public abstract class MixinThreadExecutor { public abstract class MixinBlockableEventLoop {
@Shadow public static boolean isMemoryError(Throwable exception){return false;};
@Redirect(method = "executeTask", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/thread/ThreadExecutor;isMemoryError(Ljava/lang/Throwable;)Z")) @Shadow public static boolean isNonRecoverable(Throwable throwable){return false;}
@Redirect(method = "doRunTask", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/thread/BlockableEventLoop;isNonRecoverable(Ljava/lang/Throwable;)Z"))
private boolean voxy$forceCrashOnError(Throwable exception) { private boolean voxy$forceCrashOnError(Throwable exception) {
if (exception instanceof LoadException le) { if (exception instanceof LoadException le) {
if (le.getCause() instanceof RuntimeException cause) { if (le.getCause() instanceof RuntimeException cause) {
@@ -19,6 +20,6 @@ public abstract class MixinThreadExecutor {
} }
throw le; throw le;
} }
return isMemoryError(exception); return isNonRecoverable(exception);
} }
} }

View File

@@ -1,12 +1,12 @@
package me.cortex.voxy.client.mixin.minecraft; package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.ICheekyClientChunkManager; import me.cortex.voxy.client.ICheekyClientChunkCache;
import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.common.world.service.VoxelIngestService; import me.cortex.voxy.common.world.service.VoxelIngestService;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.world.ClientChunkManager; import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.util.math.ChunkPos; import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.chunk.WorldChunk; import net.minecraft.world.level.chunk.LevelChunk;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
@@ -14,20 +14,20 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientChunkManager.class) @Mixin(ClientChunkCache.class)
public class MixinClientChunkManager implements ICheekyClientChunkManager { public class MixinClientChunkCache implements ICheekyClientChunkCache {
@Unique @Unique
private static final boolean BOBBY_INSTALLED = FabricLoader.getInstance().isModLoaded("bobby"); private static final boolean BOBBY_INSTALLED = FabricLoader.getInstance().isModLoaded("bobby");
@Shadow volatile ClientChunkManager.ClientChunkMap chunks; @Shadow volatile ClientChunkCache.Storage storage;
@Override @Override
public WorldChunk voxy$cheekyGetChunk(int x, int z) { public LevelChunk voxy$cheekyGetChunk(int x, int z) {
//This doesnt do the in range check stuff, it just gets the chunk at all costs //This doesnt do the in range check stuff, it just gets the chunk at all costs
return this.chunks.getChunk(this.chunks.getIndex(x, z)); return this.storage.getChunk(this.storage.getIndex(x, z));
} }
@Inject(method = "unload", at = @At("HEAD")) @Inject(method = "drop", at = @At("HEAD"))
public void voxy$captureChunkBeforeUnload(ChunkPos pos, CallbackInfo ci) { public void voxy$captureChunkBeforeUnload(ChunkPos pos, CallbackInfo ci) {
if (VoxyConfig.CONFIG.ingestEnabled && BOBBY_INSTALLED) { if (VoxyConfig.CONFIG.ingestEnabled && BOBBY_INSTALLED) {
var chunk = this.voxy$cheekyGetChunk(pos.x, pos.z); var chunk = this.voxy$cheekyGetChunk(pos.x, pos.z);

View File

@@ -1,19 +1,19 @@
package me.cortex.voxy.client.mixin.minecraft; package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.LoadException; import me.cortex.voxy.client.LoadException;
import net.minecraft.client.network.ClientCommonNetworkHandler; import net.minecraft.client.multiplayer.ClientCommonPacketListenerImpl;
import net.minecraft.network.packet.Packet; import net.minecraft.network.protocol.Packet;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket; import net.minecraft.network.protocol.game.ClientboundLoginPacket;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientCommonNetworkHandler.class) @Mixin(ClientCommonPacketListenerImpl.class)
public class MixinClientCommonNetworkHandler { public class MixinClientCommonPacketListenerImpl {
@Inject(method = "onPacketException", at = @At("HEAD"), cancellable = true) @Inject(method = "onPacketError", at = @At("HEAD"), cancellable = true)
private void handleDisconnectAsCrash(Packet<?> packet, Exception exception, CallbackInfo ci) { private void handleDisconnectAsCrash(Packet<?> packet, Exception exception, CallbackInfo ci) {
if (packet instanceof GameJoinS2CPacket) { if (packet instanceof ClientboundLoginPacket) {
ci.cancel(); ci.cancel();
throw new LoadException("Force crashing due to exception during on game join", exception); throw new LoadException("Force crashing due to exception during on game join", exception);
} }

View File

@@ -0,0 +1,83 @@
package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.common.world.service.VoxelIngestService;
import me.cortex.voxy.commonImpl.WorldIdentifier;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.SectionPos;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.dimension.DimensionType;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientLevel.class)
public abstract class MixinClientLevel {
@Unique
private int bottomSectionY;
@Shadow @Final public LevelRenderer levelRenderer;
@Shadow public abstract ClientChunkCache getChunkSource();
@Inject(method = "<init>", at = @At("TAIL"))
private void voxy$getBottom(
ClientPacketListener networkHandler,
ClientLevel.ClientLevelData properties,
ResourceKey<Level> registryRef,
Holder<DimensionType> dimensionType,
int loadDistance,
int simulationDistance,
LevelRenderer worldRenderer,
boolean debugWorld,
long seed,
int seaLevel,
CallbackInfo cir) {
this.bottomSectionY = ((Level)(Object)this).getMinY()>>4;
}
@Inject(method = "setBlocksDirty", at = @At("TAIL"))
private void voxy$injectIngestOnStateChange(BlockPos pos, BlockState old, BlockState updated, CallbackInfo cir) {
if (old == updated) return;
//TODO: is this _really_ needed, we should have enough processing power to not need todo it if its only a
// block removal
if (!updated.isAir()) return;
if (!VoxyConfig.CONFIG.ingestEnabled) return;//Only ingest if setting enabled
var self = (Level)(Object)this;
var wi = WorldIdentifier.of(self);
if (wi == null) {
return;
}
int x = pos.getX()&15;
int y = pos.getY()&15;
int z = pos.getZ()&15;
if (x == 0 || x==15 || y==0 || y==15 || z==0||z==15) {//Update if there is a statechange on the boarder
var csp = SectionPos.of(pos);
var section = self.getChunk(pos).getSection(csp.y()-this.bottomSectionY);
var lp = self.getLightEngine();
var blp = lp.getLayerListener(LightLayer.BLOCK).getDataLayerData(csp);
var slp = lp.getLayerListener(LightLayer.SKY).getDataLayerData(csp);
VoxelIngestService.rawIngest(wi, section, csp.x(), csp.y(), csp.z(), blp==null?null:blp.copy(), slp==null?null:slp.copy());
}
}
}

View File

@@ -1,20 +1,19 @@
package me.cortex.voxy.client.mixin.minecraft; package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.VoxyClient;
import me.cortex.voxy.client.VoxyClientInstance; import me.cortex.voxy.client.VoxyClientInstance;
import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.commonImpl.VoxyCommon; import me.cortex.voxy.commonImpl.VoxyCommon;
import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket; import net.minecraft.network.protocol.game.ClientboundLoginPacket;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientPlayNetworkHandler.class) @Mixin(ClientPacketListener.class)
public class MixinClientLoginNetworkHandler { public class MixinClientPacketListener {
@Inject(method = "onGameJoin", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/GameJoinS2CPacket;commonPlayerSpawnInfo()Lnet/minecraft/network/packet/s2c/play/CommonPlayerSpawnInfo;")) @Inject(method = "handleLogin", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/protocol/game/ClientboundLoginPacket;commonPlayerSpawnInfo()Lnet/minecraft/network/protocol/game/CommonPlayerSpawnInfo;"))
private void voxy$init(GameJoinS2CPacket packet, CallbackInfo ci) { private void voxy$init(ClientboundLoginPacket packet, CallbackInfo ci) {
if (VoxyCommon.isAvailable() && !VoxyClientInstance.isInGame) { if (VoxyCommon.isAvailable() && !VoxyClientInstance.isInGame) {
VoxyClientInstance.isInGame = true; VoxyClientInstance.isInGame = true;
if (VoxyConfig.CONFIG.enabled) { if (VoxyConfig.CONFIG.enabled) {

View File

@@ -1,30 +0,0 @@
package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.hud.DebugHud;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
@Mixin(DebugHud.class)
public class MixinDebugHud {
@Inject(method = "getRightText", at = @At("RETURN"))
private void injectDebug(CallbackInfoReturnable<List<String>> cir) {
var ret = cir.getReturnValue();
var instance = VoxyCommon.getInstance();
if (instance != null) {
ret.add("");
ret.add("");
instance.addDebug(ret);
}
var renderer = ((IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer).getVoxyRenderSystem();
if (renderer != null) {
renderer.addDebugInfo(ret);
}
}
}

View File

@@ -0,0 +1,28 @@
package me.cortex.voxy.client.mixin.minecraft;
import net.minecraft.client.gui.components.debug.DebugScreenEntryList;
import net.minecraft.resources.Identifier;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
@Mixin(DebugScreenEntryList.class)
public abstract class MixinDebugScreenEntryList {
@Shadow @Final private List<Identifier> currentlyEnabled;
@Shadow public abstract boolean isOverlayVisible();
@Inject(method = "rebuildCurrentList", at = @At(value = "INVOKE", target = "Ljava/util/List;sort(Ljava/util/Comparator;)V"))
private void voxy$injectVersionDisplay(CallbackInfo cir) {
if (this.isOverlayVisible()) {
var id = Identifier.fromNamespaceAndPath("voxy", "version");
if (!this.currentlyEnabled.contains(id)) {
this.currentlyEnabled.add(id);
}
}
}
}

View File

@@ -1,30 +1,40 @@
package me.cortex.voxy.client.mixin.minecraft; package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.config.VoxyConfig; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.sugar.Local;
import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem; import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.Camera;
import net.minecraft.client.render.fog.FogData; import net.minecraft.client.DeltaTracker;
import net.minecraft.client.render.fog.FogRenderer; import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.fog.FogData;
import net.minecraft.client.renderer.fog.FogRenderer;
import org.joml.Vector4f;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(FogRenderer.class) @Mixin(value = FogRenderer.class,remap = true)
public class MixinFogRenderer { public class MixinFogRenderer {
@Redirect(method = "applyFog(Lnet/minecraft/client/render/Camera;IZLnet/minecraft/client/render/RenderTickCounter;FLnet/minecraft/client/world/ClientWorld;)Lorg/joml/Vector4f;", at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/fog/FogData;renderDistanceEnd:F", opcode = Opcodes.PUTFIELD), require = 0) @Inject(method = "setupFog", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;getDevice()Lcom/mojang/blaze3d/systems/GpuDevice;", remap = false))
private void voxy$modifyFog(FogData instance, float distance) { private void voxy$modifyFog(Camera camera, int rdInt, DeltaTracker tracker, float pTick, ClientLevel lvl, CallbackInfoReturnable<Vector4f> cir, @Local(type=FogData.class) FogData data) {
var vrs = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer; if (!VoxyConfig.CONFIG.isRenderingEnabled()) return;
if (VoxyConfig.CONFIG.renderVanillaFog || vrs == null || vrs.getVoxyRenderSystem() == null) { var vrs = IGetVoxyRenderSystem.getNullable();
instance.renderDistanceEnd = distance; if (vrs == null) return;
} else {
instance.renderDistanceStart = 999999999; data.renderDistanceStart = 999999999;
instance.renderDistanceEnd = 999999999; data.renderDistanceEnd = 999999999;
if (!VoxyConfig.CONFIG.useEnvironmentalFog) { /*
instance.environmentalStart = 99999999; if (!VoxyConfig.CONFIG.useRenderFog) {
instance.environmentalEnd = 99999999; }*/
} if (!VoxyConfig.CONFIG.useEnvironmentalFog) {
data.environmentalStart = 99999999;
data.environmentalEnd = 99999999;
} }
} }
} }

View File

@@ -2,20 +2,23 @@ package me.cortex.voxy.client.mixin.minecraft;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.client.gl.GlDebug; import com.mojang.blaze3d.opengl.GlDebug;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import java.io.PrintWriter;
import java.io.StringWriter;
@Mixin(GlDebug.class) @Mixin(GlDebug.class)
public class MixinGlDebug { public class MixinGlDebug {
@WrapOperation(method = "onDebugMessage", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;Ljava/lang/Object;)V", remap = false)) @WrapOperation(method = "printDebugLog", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;Ljava/lang/Object;)V", remap = false))
private void voxy$wrapDebug(Logger instance, String base, Object msgObj, Operation<Void> original) { private void voxy$wrapDebug(Logger instance, String base, Object msgObj, Operation<Void> original) {
if (msgObj instanceof GlDebug.DebugMessage msg) { if (msgObj instanceof GlDebug.LogEntry msg) {
var throwable = new Throwable(msg.toString()); var throwable = new Throwable(msg.toString());
if (isCausedByVoxy(throwable.getStackTrace())) { if (isCausedByVoxy(throwable.getStackTrace())) {
original.call(instance, base, throwable); original.call(instance, base+"\n"+getStackTraceAsString(throwable), throwable);
} else { } else {
original.call(instance, base, msg); original.call(instance, base, msg);
} }
@@ -24,6 +27,14 @@ public class MixinGlDebug {
} }
} }
@Unique
private static String getStackTraceAsString(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
return sw.toString();
}
@Unique @Unique
private boolean isCausedByVoxy(StackTraceElement[] trace) { private boolean isCausedByVoxy(StackTraceElement[] trace) {
for (var elem : trace) { for (var elem : trace) {

View File

@@ -0,0 +1,16 @@
package me.cortex.voxy.client.mixin.minecraft;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import net.minecraft.world.level.chunk.DataLayer;
import net.minecraft.world.level.lighting.LayerLightSectionStorage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(LayerLightSectionStorage.class)
public class MixinLayerLightSectionStorage {
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/longs/Long2ObjectMaps;synchronize(Lit/unimi/dsi/fastutil/longs/Long2ObjectMap;)Lit/unimi/dsi/fastutil/longs/Long2ObjectMap;"), remap = false)
private static Long2ObjectMap<DataLayer> voxy$removeSynchronized(Long2ObjectMap<DataLayer> map) {
return map;
}
}

View File

@@ -1,7 +1,7 @@
package me.cortex.voxy.client.mixin.minecraft; package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.VoxyClientInstance; import me.cortex.voxy.client.VoxyClientInstance;
import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem; import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import me.cortex.voxy.client.core.VoxyRenderSystem; import me.cortex.voxy.client.core.VoxyRenderSystem;
import me.cortex.voxy.client.core.util.IrisUtil; import me.cortex.voxy.client.core.util.IrisUtil;
@@ -9,9 +9,8 @@ import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.world.WorldEngine; import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.commonImpl.VoxyCommon; import me.cortex.voxy.commonImpl.VoxyCommon;
import me.cortex.voxy.commonImpl.WorldIdentifier; import me.cortex.voxy.commonImpl.WorldIdentifier;
import net.minecraft.client.render.Frustum; import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.render.WorldRenderer; import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.world.ClientWorld;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@@ -20,10 +19,9 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(WorldRenderer.class) @Mixin(LevelRenderer.class)
public abstract class MixinWorldRenderer implements IGetVoxyRenderSystem { public abstract class MixinLevelRenderer implements IGetVoxyRenderSystem {
@Shadow private Frustum frustum; @Shadow private @Nullable ClientLevel level;
@Shadow private @Nullable ClientWorld world;
@Unique private VoxyRenderSystem renderer; @Unique private VoxyRenderSystem renderer;
@Override @Override
@@ -31,17 +29,17 @@ public abstract class MixinWorldRenderer implements IGetVoxyRenderSystem {
return this.renderer; return this.renderer;
} }
@Inject(method = "reload()V", at = @At("RETURN"), order = 900)//We want to inject before sodium @Inject(method = "allChanged()V", at = @At("RETURN"), order = 900)//We want to inject before sodium
private void reloadVoxyRenderer(CallbackInfo ci) { private void reloadVoxyRenderer(CallbackInfo ci) {
this.shutdownRenderer(); this.shutdownRenderer();
if (this.world != null) { if (this.level != null) {
this.createRenderer(); this.createRenderer();
} }
} }
@Inject(method = "setWorld", at = @At("HEAD")) @Inject(method = "setLevel", at = @At("HEAD"))
private void voxy$captureSetWorld(ClientWorld world, CallbackInfo ci) { private void voxy$captureSetWorld(ClientLevel world, CallbackInfo ci) {
if (this.world != world) { if (this.level != world) {
this.shutdownRenderer(); this.shutdownRenderer();
} }
} }
@@ -62,11 +60,15 @@ public abstract class MixinWorldRenderer implements IGetVoxyRenderSystem {
@Override @Override
public void createRenderer() { public void createRenderer() {
if (this.renderer != null) throw new IllegalStateException("Cannot have multiple renderers"); if (this.renderer != null) throw new IllegalStateException("Cannot have multiple renderers");
if (!VoxyConfig.CONFIG.isRenderingEnabled()) { if (!VoxyConfig.CONFIG.enabled) {
Logger.info("Not creating renderer due to disabled"); Logger.info("Not creating renderer due to disabled");
return; return;
} }
if (this.world == null) { if (!VoxyConfig.CONFIG.isRenderingEnabled()) {
Logger.info("Not creating renderer due to disabled rendering");
return;
}
if (this.level == null) {
Logger.error("Not creating renderer due to null world"); Logger.error("Not creating renderer due to null world");
return; return;
} }
@@ -75,13 +77,13 @@ public abstract class MixinWorldRenderer implements IGetVoxyRenderSystem {
Logger.error("Not creating renderer due to null instance"); Logger.error("Not creating renderer due to null instance");
return; return;
} }
WorldEngine world = WorldIdentifier.ofEngine(this.world); WorldEngine world = WorldIdentifier.ofEngine(this.level);
if (world == null) { if (world == null) {
Logger.error("Null world selected"); Logger.error("Null world selected");
return; return;
} }
try { try {
this.renderer = new VoxyRenderSystem(world, instance.getThreadPool()); this.renderer = new VoxyRenderSystem(world, instance.getServiceManager());
} catch (RuntimeException e) { } catch (RuntimeException e) {
if (IrisUtil.irisShaderPackEnabled()) { if (IrisUtil.irisShaderPackEnabled()) {
IrisUtil.disableIrisShaders(); IrisUtil.disableIrisShaders();
@@ -89,5 +91,6 @@ public abstract class MixinWorldRenderer implements IGetVoxyRenderSystem {
throw e; throw e;
} }
} }
instance.updateDedicatedThreads();
} }
} }

View File

@@ -2,15 +2,15 @@ package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.VoxyClientInstance; import me.cortex.voxy.client.VoxyClientInstance;
import me.cortex.voxy.commonImpl.VoxyCommon; import me.cortex.voxy.commonImpl.VoxyCommon;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MinecraftClient.class) @Mixin(Minecraft.class)
public class MixinMinecraftClient { public class MixinMinecraft {
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;Z)V", at = @At("TAIL")) @Inject(method = "disconnect", at = @At("TAIL"))
private void voxy$injectWorldClose(CallbackInfo ci) { private void voxy$injectWorldClose(CallbackInfo ci) {
if (VoxyCommon.isAvailable() && VoxyClientInstance.isInGame) { if (VoxyCommon.isAvailable() && VoxyClientInstance.isInGame) {
VoxyCommon.shutdownInstance(); VoxyCommon.shutdownInstance();

View File

@@ -1,10 +1,11 @@
package me.cortex.voxy.client.mixin.minecraft; package me.cortex.voxy.client.mixin.minecraft;
import com.mojang.blaze3d.shaders.ShaderSource;
import com.mojang.blaze3d.shaders.ShaderType; import com.mojang.blaze3d.shaders.ShaderType;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import me.cortex.voxy.client.VoxyClient; import me.cortex.voxy.client.VoxyClient;
import net.minecraft.util.Identifier; import net.minecraft.resources.Identifier;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
@@ -17,7 +18,7 @@ import java.util.function.BiFunction;
public class MixinRenderSystem { public class MixinRenderSystem {
//We need to inject before iris to initalize our systems //We need to inject before iris to initalize our systems
@Inject(method = "initRenderer", order = 900, remap = false, at = @At("RETURN")) @Inject(method = "initRenderer", order = 900, remap = false, at = @At("RETURN"))
private static void voxy$injectInit(long windowHandle, int debugVerbosity, boolean sync, BiFunction<Identifier, ShaderType, String> shaderSourceGetter, boolean renderDebugLabels, CallbackInfo ci) { private static void voxy$injectInit(long windowHandle, int debugVerbosity, boolean sync, ShaderSource source, boolean renderDebugLabels, CallbackInfo ci) {
VoxyClient.initVoxyClient(); VoxyClient.initVoxyClient();
} }
} }

Some files were not shown because too many files have changed in this diff Show More