919 Commits
master ... dev

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
mcrcortex
a012cead98 silly 2025-09-08 11:00:07 +10:00
mcrcortex
3513192907 ret null 2025-09-08 07:30:46 +10:00
mcrcortex
99ee9a5ad2 stupid idiot 2025-09-08 07:28:43 +10:00
mcrcortex
cb9b41baf6 move patch data to seperate files 2025-09-08 00:25:47 +10:00
mcrcortex
effabf95fd flashback compat 2025-09-07 19:12:25 +10:00
mcrcortex
8615f29132 add useViewportDims 2025-09-07 12:01:58 +10:00
mcrcortex
dc94b60121 a 2025-09-06 08:51:27 +10:00
mcrcortex
8c5672d5fb License 2025-09-05 16:26:48 +10:00
mcrcortex
75a9df969b Support render scaling factor in shaders 2025-09-03 14:51:42 +10:00
mcrcortex
c268306bd6 tweeks for scaling 2025-09-03 14:35:10 +10:00
mcrcortex
e2342a75dd Inital support for lower resolution rendering 2025-09-03 14:00:26 +10:00
mcrcortex
37323f9170 very very _very_ iritated with iris atm 2025-09-03 12:59:34 +10:00
mcrcortex
aa48dbbf86 idiotic moment 2025-09-02 23:04:17 +10:00
mcrcortex
ae54c6ebea attempted taa support 2025-09-02 22:51:10 +10:00
mcrcortex
8384b9f88b Report invalid block bake errors and dont crash (will probably cause an insane amount of spam) 2025-09-02 11:19:11 +10:00
mcrcortex
ea884f1583 changes to samplers 2025-09-02 11:05:06 +10:00
mcrcortex
c506865d7b dont zero the geometry buffer 2025-09-02 10:50:05 +10:00
mcrcortex
920d7db5f6 optionally disable emission to vanilla depth 2025-09-02 03:00:06 +10:00
mcrcortex
35f93122e5 extreme override 2025-09-02 02:17:54 +10:00
mcrcortex
ed04e21861 Version bump, fix dh impersonation uniform, add 4 more colourtexs to iris 2025-09-02 02:12:41 +10:00
mcrcortex
c25643525a todos 2025-08-31 18:33:07 +10:00
mcrcortex
f031d761b3 Use work around sparse buffer allocation on nvidia 2025-08-30 12:03:06 +10:00
mcrcortex
f3fa371ef9 Fixed full screen blitting, blend with sky 2025-08-29 15:50:01 +10:00
mcrcortex
0d09f4c11f small improvement 2025-08-29 10:19:00 +10:00
mcrcortex
dd4f1de695 Massivly improved fog rendering 2025-08-29 10:03:12 +10:00
mcrcortex
269c7da0ac woops 2025-08-28 08:58:03 +10:00
mcrcortex
0ed453fe1d Fix issues with mesa stencil, added self culling option, 2025-08-28 08:47:35 +10:00
mcrcortex
f2bcfca8e8 changes and attempted fixes 2025-08-27 21:21:29 +10:00
mcrcortex
d8324dacd4 combine log msg 2025-08-27 18:58:44 +10:00
mcrcortex
544c1df366 Added waiting for gpu memory collection 2025-08-27 18:56:06 +10:00
mcrcortex
63a969e5df Dont crash if key is null 2025-08-27 17:30:32 +10:00
mcrcortex
552a6e33a6 Slight attempt improve detection, and dont fking require the extensions in the no extension workaround shader ;-; 2025-08-26 01:33:43 +10:00
mcrcortex
3533d2356c tweeked how chunkbound renderer detects changes, auto ingest section when section status changes while surounded by loaded chunks 2025-08-25 23:42:44 +10:00
mcrcortex
120f1e2018 Preset matrices, add modelview uniforms, add extra logging when creating geometry buffer 2025-08-25 21:58:34 +10:00
mcrcortex
c92c1d5b4a Improve detection of subgroup Capabilities in compute shader 2025-08-25 12:47:27 +10:00
mcrcortex
2fd686a5e6 todo and fixes 2025-08-23 23:45:55 +10:00
mcrcortex
aa185f11d7 Add extra protections against shutdown and startup events firing incorrectly, version bump 2025-08-22 16:50:31 +10:00
mcrcortex
7fab36ff3e shader faker thing 2025-08-20 23:01:20 +10:00
mcrcortex
cc609bbb07 Image and ssbo bindings 2025-08-20 13:44:31 +10:00
mcrcortex
9c74f92147 blend state setup 2025-08-18 12:37:23 +10:00
mcrcortex
3b113e4914 delete postprocessing 2025-08-18 00:48:49 +10:00
mcrcortex
33d50aed67 rem unused import 2025-08-18 00:42:34 +10:00
mcrcortex
a9c2d57bc5 Add non subgroup based prefix sum 2025-08-18 00:41:59 +10:00
mcrcortex
3d2693796d beans 2025-08-17 13:23:58 +10:00
mcrcortex
70a937bcaa Iris add 2025-08-16 08:09:52 +10:00
mcrcortex
3e50c95c91 remove 2025-08-14 22:41:49 +10:00
mcrcortex
a0f9b78162 java imports cleanup 2025-08-14 22:20:45 +10:00
mcrcortex
d507429b9b more changes 2025-08-14 22:19:01 +10:00
mcrcortex
634d187c11 painn 2025-08-11 23:15:08 +10:00
mcrcortex
11f7041df0 misc 2025-08-10 09:44:20 +10:00
mcrcortex
2956872970 bean 2025-08-10 09:42:42 +10:00
mcrcortex
c8b0df6ff9 Part A 2025-08-07 10:44:08 +10:00
mcrcortex
16952e13e4 cleanup java imports 2 2025-07-31 16:10:14 +10:00
mcrcortex
e9fba367c0 cleanup java imports 2025-07-31 16:06:16 +10:00
mcrcortex
db06516f97 Remove some unused imports 2025-07-30 14:16:38 +10:00
mcrcortex
ff8e96e293 Possibly fix some weirdness on screen resize and with MiB 2025-07-28 13:08:22 +10:00
mcrcortex
454a5a0e11 bean 2025-07-28 12:46:36 +10:00
mcrcortex
7b456e3d98 dont overcomplicate things 2025-07-20 18:52:56 +10:00
mcrcortex
0514528a4c attempted micro optimization of memcpy writer 2025-07-20 18:42:11 +10:00
mcrcortex
bdcdae791e slight logic error in flushing 2025-07-20 18:23:04 +10:00
mcrcortex
4d59d05ad6 tweek flush and replace exception with error log 2025-07-20 13:01:21 +10:00
mcrcortex
00928fdb88 Fix render generation service task map entries not being removed 2025-07-19 23:21:04 +10:00
mcrcortex
4a34b29b46 improve ingest and saving perf 2025-07-19 12:52:48 +10:00
mcrcortex
f590ad704e fix build 2025-07-19 12:52:48 +10:00
mcrcortex
99a1bb1dc9 1.21.8 2025-07-19 12:52:48 +10:00
mcrcortex
5fe5ebc0a2 Log 2025-07-13 21:37:25 +10:00
mcrcortex
aff30961bd tweek section meta 2025-07-13 18:13:15 +10:00
mcrcortex
64d211b333 use normal hiz 2025-07-13 17:40:23 +10:00
mcrcortex
606d3b2282 hiz2 2025-07-13 17:39:48 +10:00
mcrcortex
132c6aa2e8 thing 2025-07-12 18:48:25 +10:00
mcrcortex
4a140c110f Add more types 2025-07-12 14:14:53 +10:00
mcrcortex
1c8d052544 add todo and optimized imports 2025-07-12 14:13:16 +10:00
mcrcortex
3199b77ae5 Reorder operations in attempt to fix race conditions 2025-07-12 14:12:06 +10:00
mcrcortex
f0e1f18379 mark as compatible with both 1.21.7 and 1.21.6 2025-07-07 22:26:36 +10:00
mcrcortex
492e2a707a Fix compatibility when joml.fastmath is enabled, fixes physics mod causing everything to detonate
Version bump
2025-07-07 22:20:46 +10:00
mcrcortex
7551ca3484 readd override thing 2025-07-07 12:23:09 +10:00
mcrcortex
8f3fa2e7f2 start on no subgroup impl 2025-07-06 20:01:44 +10:00
mcrcortex
936619ce12 no abstract 2025-07-06 17:28:51 +10:00
mcrcortex
d6a42f8ef3 c 2025-07-03 11:06:15 +10:00
mcrcortex
bf43e405ff b 2025-07-03 00:49:42 +10:00
mcrcortex
0c7c33304d Attempt to fix login unable to get object 2025-07-03 00:31:55 +10:00
mcrcortex
f9b1d8a9e1 Check render type before baking model 2025-07-02 18:53:45 +10:00
mcrcortex
7b4fe4bd5c thing 2025-07-02 11:49:13 +10:00
mcrcortex
6ba3111ada fix default biomes on no biome section data biome 2025-07-02 00:03:14 +10:00
mcrcortex
258ccf89e0 move init 2025-07-01 10:33:47 +10:00
mcrcortex
3e193bb675 update 1.21.7 2025-07-01 09:04:31 +10:00
mcrcortex
69b96eee96 L 2025-06-30 19:23:07 +10:00
mcrcortex
e1ba2c4ebb make null before shutdown 2025-06-30 14:20:42 +10:00
mcrcortex
dfce9dae46 Logging and checking 2025-06-30 13:53:03 +10:00
mcrcortex
f4fca865bb add git ignore 2025-06-30 11:23:31 +10:00
mcrcortex
08fa0725d3 Beans 2025-06-30 11:21:43 +10:00
mcrcortex
b92b769f7b Attempt to fix weirdness on thread change while importing 2025-06-30 11:21:01 +10:00
mcrcortex
726517a8b6 Move to global cleaner + enable tracking for all but memory buffers 2025-06-27 22:35:52 +10:00
mcrcortex
51f54c6edd version bump 2025-06-27 20:14:44 +10:00
mcrcortex
f7f260777a changes to ingest 2025-06-26 11:58:40 +10:00
mcrcortex
dd9ac2819d version bump 2025-06-25 00:28:16 +10:00
mcrcortex
1a7bb8498e fast path on not windows 2025-06-25 00:17:26 +10:00
mcrcortex
fb2d26153d attempt ultimit jank to fix shader 2025-06-24 23:44:15 +10:00
mcrcortex
a640c0e62c Add geometry override 2025-06-24 20:14:22 +10:00
mcrcortex
784322db6f Added amd shader compiler driver segfault workaround 2025-06-24 20:01:45 +10:00
mcrcortex
355a63c46f Final attempt at fixing ingest lighting 2025-06-24 00:12:38 +10:00
mcrcortex
155eb75b82 Attempt fix tracking 2025-06-24 00:03:11 +10:00
mcrcortex
64d4ef0c03 attempt improve lighting thing 2025-06-23 23:22:48 +10:00
mcrcortex
edb15db8fa add classifier if not in gha 2025-06-23 22:29:06 +10:00
mcrcortex
883f140b41 Allow enabling debug flags 2025-06-23 22:26:53 +10:00
mcrcortex
90a6765e8a Move chunk ingest into client chunk manager 2025-06-23 22:22:40 +10:00
mcrcortex
b8ede978c2 fix very theoretical incorrect ordering issue 2025-06-23 21:30:43 +10:00
mcrcortex
c1091acc6b service name logging on error 2025-06-23 20:53:30 +10:00
mcrcortex
0034940082 manual artifact workflow 2025-06-23 20:30:24 +10:00
mcrcortex
4d35fad772 Try to get the chunk at all costs 2025-06-23 20:21:18 +10:00
mcrcortex
d86c3b2eb8 Decrease cache size if max memory is small 2025-06-23 19:39:18 +10:00
mcrcortex
b3556813a9 bvec3 2025-06-23 11:21:57 +10:00
mcrcortex
a94dcf1949 Fix mesa 2025-06-23 11:13:10 +10:00
mcrcortex
7fa07ae5ea Add support for vanilla enviromental fog 2025-06-23 00:51:54 +10:00
mcrcortex
cf60d31b75 update chunky 2025-06-22 23:03:54 +10:00
mcrcortex
e1b4e1ea6a micro optimizations 2025-06-22 21:51:07 +10:00
mcrcortex
4f6b0aa04d use textureLod 2025-06-22 17:42:32 +10:00
mcrcortex
8b5e2780c7 fix 64 sized warps 2025-06-22 16:52:08 +10:00
mcrcortex
0dd730d8de nope cant do that am stupid 2025-06-22 12:14:14 +10:00
mcrcortex
0f865c7afb dont remap pipeline 2025-06-22 11:58:00 +10:00
mcrcortex
688f24a409 bean 2025-06-22 11:52:30 +10:00
mcrcortex
dcacd279b3 readd nvidium support 2025-06-22 11:31:38 +10:00
mcrcortex
37d0b755af Fix issues 2025-06-22 11:06:15 +10:00
mcrcortex
26672ce34b Move more computation into frag shader 2025-06-22 10:48:51 +10:00
mcrcortex
d1be49f474 massivly shrinked interstage attributes 2025-06-21 20:55:53 +10:00
mcrcortex
87072a4edc attempt to improve mipping 2025-06-21 15:03:28 +10:00
mcrcortex
5f8679e5d2 Fix memory leak on reload while importing 2025-06-21 12:32:24 +10:00
mcrcortex
1a7cd37741 attempted to improve ingest perfomance by only saving on section unload 2025-06-21 12:25:46 +10:00
mcrcortex
ed181c1dcd changed priority 2025-06-19 22:43:27 +10:00
mcrcortex
4d839e3662 Attempt to reduce reaquires on miss 2025-06-19 22:15:29 +10:00
mcrcortex
156b30756d Attempted optimizations for world processing 2025-06-19 16:03:31 +10:00
mcrcortex
6326870525 Attempt to fix race condition.... _again_ 2025-06-19 15:33:38 +10:00
mcrcortex
a360c9349a woops 2025-06-19 13:08:45 +10:00
mcrcortex
9e6276e0fa Attempt fix capture index buffer before it gets large 2025-06-19 13:03:39 +10:00
mcrcortex
2bbc7a8999 change fence query 2025-06-19 12:45:21 +10:00
mcrcortex
fc3e05434f add fog override (hackily) back 2025-06-18 10:05:29 +10:00
mcrcortex
388764e9c8 mostly finished 1.21.6, except fog 2025-06-18 09:11:25 +10:00
mcrcortex
3fb8323dd0 Merge branch 'mc_1215' into mc_1216 2025-06-18 08:55:06 +10:00
mcrcortex
2327c6baf8 thing 2025-06-18 08:53:32 +10:00
mcrcortex
144faf5b21 move frex check loop 2025-06-17 12:52:55 +10:00
mcrcortex
dc6dd4bb11 remove pow for now 2025-06-16 23:40:10 +10:00
mcrcortex
84482e8998 Make the request queue dynamically capped, greatly increasing snappyness 2025-06-16 23:39:36 +10:00
mcrcortex
072ece7a3d add max 2025-06-16 21:36:39 +10:00
mcrcortex
22d557ed01 am stupid 2025-06-16 21:33:48 +10:00
mcrcortex
b454d54a99 it works 2025-06-16 20:45:49 +10:00
mcrcortex
341119386a poc 2025-06-16 20:17:44 +10:00
mcrcortex
1575d7319c big progress in fixing culling 2025-06-16 20:03:28 +10:00
mcrcortex
950e92d7c7 h 2025-06-16 20:03:12 +10:00
mcrcortex
0e98f52580 improved logger 2025-06-16 19:28:32 +10:00
mcrcortex
caf2703102 small thing 2025-06-16 12:41:29 +10:00
mcrcortex
b79923de3d Remove setup 2025-06-16 12:22:26 +10:00
mcrcortex
ee6d171ef6 Note 2025-06-16 12:21:37 +10:00
mcrcortex
1c30198347 Remove an info log 2025-06-14 13:54:31 +10:00
mcrcortex
4ed9199e1c attempt improvments is not :( 2025-06-14 00:20:23 +10:00
mcrcortex
2027cb064c works 2025-06-14 00:14:18 +10:00
mcrcortex
a00eec69b7 a 2025-06-13 23:19:23 +10:00
mcrcortex
3aa1c94c6a inital 1.21.6 2025-06-13 14:49:03 +10:00
mcrcortex
84c07c4115 tweeked fences? 2025-06-10 20:45:39 +10:00
mcrcortex
6398164d42 attempt to improve the data preperation of render factory 2025-06-10 20:37:34 +10:00
mcrcortex
fa42ad5a03 remove copies 2025-06-10 19:25:56 +10:00
mcrcortex
22553eb1f9 attempted to improve mipping by computing dx,dy accross entire uv instead of local uv 2025-06-10 13:08:26 +10:00
mcrcortex
cb599eea0b misc fixes 2025-06-09 20:47:40 +10:00
mcrcortex
f73413e7c0 Dont deadlock 2025-06-09 20:26:19 +10:00
mcrcortex
5b752d3f87 remove unused 2025-06-09 19:25:48 +10:00
mcrcortex
4f37d3b597 remove old memory patch 2025-06-07 20:32:42 +10:00
mcrcortex
21b497d2d4 force gl finish 2025-06-07 19:29:56 +10:00
mcrcortex
3bfc0c266d change to stable hash 2025-06-07 16:10:46 +10:00
mcrcortex
f252fa3a7a Make into error 2025-06-07 14:41:18 +10:00
mcrcortex
66266fb426 Note 2025-06-07 14:38:02 +10:00
mcrcortex
225e2d9d1a is ment to be * not + idiot 2025-06-07 14:37:02 +10:00
mcrcortex
3b4aa75890 am dumb stupid or also dumb, how this did not cause multiple things to immediatly explode 18 times, have no idea 2025-06-07 14:04:45 +10:00
mcrcortex
0c1917d56e hopefully fix issues 2025-06-07 12:02:30 +10:00
mcrcortex
35850082d5 Shuffled around shaders 2025-06-06 17:00:25 +10:00
mcrcortex
d24b719a93 Move tests 2025-06-06 12:02:54 +10:00
mcrcortex
a0c33a439b subtract 1gb from memory limit instead of 512 mb 2025-06-04 16:21:14 +10:00
mcrcortex
6bbd2c521a Use representitive fragment when possible 2025-06-03 22:14:46 +10:00
mcrcortex
7575c35b02 tweek build script 2025-06-03 16:18:31 +10:00
mcrcortex
f78a8df275 fix possible issue of deadlock with geometry cleaner 2025-06-03 01:02:57 +10:00
mcrcortex
8462dde374 use provoking vertex 2025-06-03 00:08:01 +10:00
mcrcortex
075e8f2897 attempt to improve rocksdb options 2025-06-02 22:12:29 +10:00
mcrcortex
204989b909 tweeks + version number in config 2025-06-02 21:28:15 +10:00
mcrcortex
c023e3b4f2 Fix face baking not being flipped, hopefully 2025-06-02 12:46:38 +10:00
mcrcortex
e7c4d6f132 more state checking 2025-06-02 12:23:06 +10:00
mcrcortex
9d0cf33a45 Attempted fix for world timeout 2025-06-02 11:47:12 +10:00
mcrcortex
34c5c71d77 change to logger 2025-05-30 20:48:37 +10:00
mcrcortex
03bede4067 more logic errors fixed 2025-05-29 23:32:33 +10:00
mcrcortex
f624f85698 nvm am just stupid 2025-05-29 23:29:44 +10:00
mcrcortex
985fa4b53c apparently . is converted into a folder and becomes .\ instead of .voxy 2025-05-29 23:26:53 +10:00
mcrcortex
6c6c08d188 better debug logging + fix tracking crash 2025-05-29 23:13:20 +10:00
mcrcortex
fe2e6522ed Refactored voxy frontend to use "WorldIdentifier" system 2025-05-29 22:03:39 +10:00
mcrcortex
ea930ad917 Fix flickering due to incorrect state restoration 2025-05-29 18:51:31 +10:00
mcrcortex
f9d1d9961b Attempt to fix some woopsies 2025-05-28 12:11:42 +10:00
mcrcortex
efda204001 Merge remote-tracking branch 'origin/mc_1215' into mc_1215 2025-05-27 15:00:51 +10:00
mcrcortex
0c5882af8e Am a Fking idiot ;-; 2025-05-27 15:00:22 +10:00
MCRcortex
56b75e38e2 Merge pull request #105 from zea64/mc_1215
Implement setThreadAffinity for Linux.
2025-05-27 13:30:54 +10:00
mcrcortex
b899dde5fe Fixes 2025-05-26 18:17:25 +10:00
mcrcortex
50e0634a94 Move things to correct viewport 2025-05-26 18:08:29 +10:00
mcrcortex
28f2e1e881 Fix future leak 2025-05-26 16:11:04 +10:00
mcrcortex
e8e89f022b Wip on geometry cache 2025-05-26 16:10:01 +10:00
mcrcortex
5d8cc2b3c4 barrier 2025-05-26 13:10:05 +10:00
mcrcortex
3b261d9989 tweek 2025-05-26 13:09:27 +10:00
Natalya McKay
4660ab927c Implement setThreadAffinity for Linux. 2025-05-25 00:31:44 -04:00
mcrcortex
c283f6eac4 Update iris 2025-05-24 11:14:06 +10:00
mcrcortex
d7d930c4fa coment ret false 2025-05-23 15:22:29 +10:00
mcrcortex
b8a16fb087 just use the currently bound fb (and pray its correct) 2025-05-23 12:13:38 +10:00
mcrcortex
74dac668fb Hardcode leaves to solid layers 2025-05-23 12:10:14 +10:00
mcrcortex
a1ace12042 things 2025-05-23 09:48:16 +10:00
mcrcortex
90a3a16cc2 Fix intel 2025-05-23 00:21:57 +10:00
mcrcortex
0882b71a9f tweek setting 2025-05-22 22:31:13 +10:00
mcrcortex
cdfa15c1f6 Add support for FREX flawless frames, failed attempt to fix intel igpus 2025-05-22 19:50:25 +10:00
mcrcortex
a314c26b89 Prefix sum based translucency 2025-05-22 11:45:28 +10:00
mcrcortex
5c2024bab4 Fix multi gson 2025-05-20 10:09:48 +10:00
mcrcortex
5c8f2a43cb Fix cleaning, add assertion 2025-05-20 10:09:37 +10:00
mcrcortex
055be2aa2a Reduce compression to 1 2025-05-20 09:30:33 +10:00
mcrcortex
973f61e428 Missed runtime only component 2025-05-19 21:46:53 +10:00
mcrcortex
3a13071edb add for future ref 2025-05-19 21:40:14 +10:00
mcrcortex
68e6621e7e Accept world 2025-05-19 21:38:28 +10:00
mcrcortex
d808486cd0 Tweeked buildscript 2025-05-19 21:38:15 +10:00
mcrcortex
128f8eda98 Bind threads to cores 2025-05-19 12:40:46 +10:00
mcrcortex
4a5961d656 more util 2025-05-19 12:20:47 +10:00
mcrcortex
8adc708e4d Add cpu layout 2025-05-19 12:20:41 +10:00
mcrcortex
2962b47939 Add validation of top level position + MiB if path 2025-05-18 12:41:26 +10:00
mcrcortex
fd22ea4153 move thingie to insert 2025-05-18 10:39:51 +10:00
mcrcortex
9be68ac2f4 Beans 2025-05-18 10:35:03 +10:00
mcrcortex
fc612c608f Fix world importer breaking if there are no files to import 2025-05-17 15:01:47 +10:00
mcrcortex
40c6d50d5e dont remap args 2025-05-17 14:21:26 +10:00
mcrcortex
5d91a5bc09 Disable voxy if on unsupported system 2025-05-17 14:18:02 +10:00
mcrcortex
2249e96496 Gl debug msg logging 2025-05-17 12:12:47 +10:00
mcrcortex
5bee5dd1d1 suppress warnings 2025-05-17 11:50:00 +10:00
mcrcortex
0b1d8b9fd9 Added gpu compute memcpy + cpu side timing statistics 2025-05-17 11:35:56 +10:00
mcrcortex
f3aecbe944 Format change 2025-05-17 11:35:01 +10:00
mcrcortex
b9e5870a7f Added reset 2025-05-17 11:34:53 +10:00
mcrcortex
f872b995c3 Barrier 2025-05-17 11:34:39 +10:00
mcrcortex
74ecc39921 Tweeked priotiy 2025-05-17 11:34:18 +10:00
mcrcortex
947802d5ed move from coheriant stuff 2025-05-17 11:34:00 +10:00
mcrcortex
95fbd77a0e Model fixes (namely on intel) 2025-05-17 11:33:35 +10:00
mcrcortex
2618c6a978 random things (tinker with barriers :ohno:) 2025-05-16 14:25:16 +10:00
mcrcortex
717def2b1a Add implicit alignment 2025-05-16 00:47:43 +10:00
mcrcortex
a8e2876586 Downgrade from error to warning 2025-05-16 00:34:06 +10:00
mcrcortex
802b853b75 random dep cleanup 2025-05-15 23:15:10 +10:00
mcrcortex
9bc0f46be9 remove print 2025-05-15 22:27:16 +10:00
mcrcortex
73f27b65b1 metadata 2025-05-15 22:10:07 +10:00
mcrcortex
28ef19a1dc Todo 2025-05-15 22:09:51 +10:00
mcrcortex
7d9b735f8b uhoh 2025-05-15 21:40:35 +10:00
mcrcortex
e58cbaa6de Geometry cleaner works again 2025-05-15 21:39:32 +10:00
mcrcortex
48799d8ea1 Continue work on adding node cleaner support 2025-05-15 20:00:46 +10:00
mcrcortex
c49f5309cd Fixed multiple issues with mipmaps and alpha discard 2025-05-15 20:00:14 +10:00
mcrcortex
566e1da6e7 oops 2025-05-15 10:56:38 +10:00
mcrcortex
88b166d297 beans 2025-05-15 10:55:47 +10:00
mcrcortex
c2be58b0f2 Micro improvements 2025-05-14 22:52:49 +10:00
mcrcortex
3221702fc2 compute scatter based setting 2025-05-14 20:19:50 +10:00
mcrcortex
0d27de5ba7 bean 2025-05-14 10:14:45 +10:00
mcrcortex
3ade56c582 things 2025-05-13 17:26:49 +10:00
mcrcortex
0028e5ec8f Continues work 2025-05-12 22:25:38 +10:00
mcrcortex
d1957680e8 inital implementation of async manager 2025-05-12 19:48:17 +10:00
mcrcortex
1acb063b83 start 2025-05-09 21:36:17 +10:00
mcrcortex
86ff9cebd2 A 2025-05-09 20:49:14 +10:00
mcrcortex
93ea97b8dc Attempt to fix crash 2025-05-09 09:52:27 +10:00
mcrcortex
ef8a5af94b Added missing .0, and shader dumping 2025-05-09 09:46:00 +10:00
mcrcortex
4aa04c7cce increase budget 2025-05-09 00:41:49 +10:00
mcrcortex
d9c4116449 log rate limiter 2025-05-09 00:40:30 +10:00
mcrcortex
a10aa444bb tweeked miping 2025-05-09 00:08:59 +10:00
mcrcortex
aa4120433e fuck 2025-05-08 22:59:01 +10:00
mcrcortex
79f42760b0 Fix a bunch of lighting issues 2025-05-08 22:56:58 +10:00
mcrcortex
cbc8428e23 note 2025-05-08 19:25:06 +10:00
mcrcortex
28507cc8b0 Barrier pain 2025-05-08 19:19:31 +10:00
mcrcortex
af863ae608 move barrier 2025-05-08 17:44:03 +10:00
mcrcortex
6a42bcccc7 move inside if 2025-05-08 16:20:42 +10:00
mcrcortex
09666e2dfa select right render distance 2025-05-08 16:03:57 +10:00
mcrcortex
1b5c787b3a match sodium culling 2025-05-08 16:00:14 +10:00
mcrcortex
ff4c1b267e Cleanup 2025-05-07 09:48:21 +10:00
mcrcortex
ec866fa1b8 double max traversal size 2025-05-07 09:30:24 +10:00
mcrcortex
3851b07294 barriers 2025-05-07 09:24:36 +10:00
mcrcortex
e42802a2bd Remove old render factory, rename RenderDataFactory45 to just RenderDataFactory 2025-05-07 01:19:22 +10:00
mcrcortex
fb52dea6fa Finished the modelfactory stuff, is now renders block entities like shulkers 2025-05-06 22:52:00 +10:00
mcrcortex
d072f474a4 Mostly finish non-opaque model support 2025-05-06 22:07:22 +10:00
mcrcortex
6087dba91b notes and tweeks 2025-05-06 20:33:21 +10:00
mcrcortex
7a4e01faab Added task failure counter to f3 2025-05-06 01:53:24 +10:00
mcrcortex
45aaf9b981 rewrote the render side of texture bakery, is very significantly better 2025-05-06 01:14:32 +10:00
mcrcortex
553935626e some? improvements maybe? 2025-05-04 23:25:20 +10:00
mcrcortex
636b680c87 Threading stuff, locking stuff, hopefully fix for section tracker data race?? 2025-05-04 22:23:10 +10:00
mcrcortex
da36c6abd1 woops 2025-05-04 15:17:13 +10:00
mcrcortex
99f0335d36 locks and replacing O(N) size queries with counters 2025-05-04 15:11:35 +10:00
mcrcortex
94223738ec tinker 2025-05-04 11:50:49 +10:00
mcrcortex
74bda196f0 WIP extended meshing 2025-05-04 11:50:39 +10:00
mcrcortex
365ae58c70 timing + other 2025-05-03 15:34:34 +10:00
mcrcortex
834253f0ae thing 2025-05-03 15:10:41 +10:00
mcrcortex
86c4c8f09e replace full locks with stamped locks 2025-05-03 13:05:21 +10:00
mcrcortex
79cd18d310 Remaining things for outline culling 2025-05-02 23:45:18 +10:00
mcrcortex
fce99fbde3 stamps and notes 2025-05-02 23:44:49 +10:00
mcrcortex
917e308ece releaseNow when trying to shutdown service 2025-05-02 23:44:12 +10:00
mcrcortex
f74992a37a fix issue with autoboxing 2025-05-02 23:43:55 +10:00
mcrcortex
7c4a3fe7b4 more jank 2025-05-02 23:08:28 +10:00
mcrcortex
e82d2a875c _spent 3 hours on a single missing *32_ 2025-05-02 22:58:55 +10:00
mcrcortex
8b04be72ee other 2025-05-02 18:39:14 +10:00
mcrcortex
6aebddb773 Attempted improvements to frametime (dont think it helped) 2025-05-02 17:48:12 +10:00
mcrcortex
b78d389a06 change height 2025-05-02 16:46:04 +10:00
mcrcortex
b064032d8f remove update thingie 2025-05-02 16:45:18 +10:00
mcrcortex
ad0d1ede41 giveup _screams_ 2025-05-02 16:31:23 +10:00
mcrcortex
cc1f63b443 sighN 2025-05-02 16:30:17 +10:00
mcrcortex
da1c382259 sigh3 2025-05-02 16:25:06 +10:00
mcrcortex
50cc7d983c sigh2 2025-05-02 16:23:34 +10:00
mcrcortex
7ad3d8c1c7 sigh 2025-05-02 16:16:01 +10:00
mcrcortex
2829ddb085 b 2025-05-02 16:13:30 +10:00
mcrcortex
d2ec4c359f a 2025-05-02 16:13:05 +10:00
mcrcortex
84a32581b7 Attempted improvements for outline performance 2025-05-02 16:12:47 +10:00
mcrcortex
3fa50a8965 Hyper jank hackfix for loading issues 2025-05-02 00:22:14 +10:00
mcrcortex
0b88a6f824 Added min chunk bounds + fix other bugs in model baking and other places 2025-05-01 23:25:06 +10:00
mcrcortex
fbcef60f67 change things to use the constant 2025-05-01 14:36:28 +10:00
mcrcortex
1d7e985593 dont include musl rocksdb natives 2025-05-01 12:27:52 +10:00
mcrcortex
ba788062c5 remove single use daemon 2025-05-01 12:08:10 +10:00
mcrcortex
8a8cc8c423 Shuffle classes and remove some unused classes 2025-05-01 12:05:48 +10:00
mcrcortex
fce9ca3824 dont add src jar (trying improve build speed) 2025-05-01 12:05:18 +10:00
mcrcortex
d12a0f1ba4 work on chunk outline renderer 2025-05-01 11:20:55 +10:00
mcrcortex
dd46c43c73 Attempt to fix stupid concurrency issue by force stealing jobs 2025-04-30 23:25:16 +10:00
mcrcortex
b526d5a51a Wip fix overdraw 2025-04-30 13:19:08 +10:00
mcrcortex
c209cc82af a 2025-04-29 09:24:11 +10:00
mcrcortex
fc439459cb tweeking 2025-04-28 23:12:25 +10:00
mcrcortex
a2cfe942c6 Heavily reworked the render service priority ordering 2025-04-28 22:54:31 +10:00
mcrcortex
7a88e1f893 Major tinkering with improving frametimes 2025-04-28 21:23:19 +10:00
mcrcortex
d91d41de79 Timing stats and not uploading the entire TLN node list every fking frame 2025-04-28 11:58:37 +10:00
mcrcortex
aed8366206 tweeks 2025-04-27 11:53:39 +10:00
mcrcortex
6d99f45412 Attempt at even more agressive optimizations 2025-04-27 02:15:14 +10:00
mcrcortex
4a3291f4a6 hahaha screams _even more_ 2025-04-27 01:48:37 +10:00
mcrcortex
9cdf1282da hahaha _screams_ 2025-04-27 01:47:47 +10:00
mcrcortex
a5361eaad2 Further try to improve stuttering (it did not go well) 2025-04-27 00:30:47 +10:00
mcrcortex
65480c5e9f Attempt to speedup workflow build 2025-04-26 21:46:52 +10:00
mcrcortex
75685d57d6 work 2025-04-26 21:32:09 +10:00
mcrcortex
b6aa53483d no daemon 2025-04-26 21:32:01 +10:00
mcrcortex
e032ea43e2 error catch 2025-04-26 21:12:02 +10:00
mcrcortex
ec42f47d9e TODO: note 2025-04-26 10:39:50 +10:00
mcrcortex
5f1c89144c AMD crashfix 2025-04-25 22:40:09 +10:00
mcrcortex
71c181fb2c Mem leak fix 2025-04-25 21:39:06 +10:00
mcrcortex
b34792ff45 More tinker 2025-04-25 21:25:24 +10:00
mcrcortex
985e0beafd Try to reduce stutter when loading 2025-04-25 20:55:36 +10:00
mcrcortex
7c27a4d8fd Slightly optimize model factory 2025-04-25 18:30:23 +10:00
mcrcortex
bbe7cd2099 Temporal coherance 2025-04-25 18:30:07 +10:00
mcrcortex
862afc498e Start prep for temporal 2025-04-25 16:37:24 +10:00
mcrcortex
f023f8e8f1 change git workflow 2025-04-25 14:18:41 +10:00
mcrcortex
987427b893 multi gson 2025-04-25 12:15:50 +10:00
mcrcortex
03e79db0ba Tweeks 2025-04-25 00:27:54 +10:00
mcrcortex
f7e8ceaa3f better hiz 2025-04-22 09:27:12 +10:00
mcrcortex
07bb8e0475 More work on fixing remaining hiz issues 2025-04-22 09:20:45 +10:00
mcrcortex
306951ebee Depth patch 2025-04-21 20:18:37 +10:00
mcrcortex
3684174b67 Cap save queue 2025-04-21 16:51:33 +10:00
mcrcortex
c2e95876d0 Change upload stream full from warning to error 2025-04-21 01:56:41 +10:00
mcrcortex
0c7421f36c comment 2025-04-21 01:39:48 +10:00
mcrcortex
d3bf885042 Add proper frustum check 2025-04-21 00:16:41 +10:00
mcrcortex
4bed271258 newline 2025-04-21 00:16:27 +10:00
mcrcortex
bbfc451956 change hiz 2025-04-21 00:16:14 +10:00
mcrcortex
539ac56548 Enable output caching 2025-04-20 21:17:33 +10:00
mcrcortex
b00223a351 Embed xz stream (even if only used for dh importer) 2025-04-20 21:13:52 +10:00
mcrcortex
c81a1d015e Fix more issues 2025-04-20 20:52:26 +10:00
mcrcortex
543a683a5f fixed deprecations in buildscript 2025-04-20 20:37:38 +10:00
mcrcortex
89eddde7d4 Merge remote-tracking branch 'origin/mc_1215' into mc_1215 2025-04-20 19:53:28 +10:00
mcrcortex
67b4aab39b Check that it builds 2025-04-20 19:53:12 +10:00
MCRcortex
68aaa878d3 Merge pull request #103 from Samalando/mc_1215
fix: graldew perms
2025-04-20 19:47:17 +10:00
Samalando
4629a9f96c fix: graldew perms 2025-04-20 10:46:33 +01:00
mcrcortex
e7c123fcf8 action to build on commit (verifies build) 2025-04-20 19:38:02 +10:00
mcrcortex
1456756ea7 Improve build script significantly 2025-04-20 17:55:40 +10:00
mcrcortex
5d01bc8f03 screenspace tweeks 2025-04-20 12:44:42 +10:00
mcrcortex
7d0cbefe17 (sob) 2025-04-19 16:43:06 +10:00
mcrcortex
1794b1ac4b Tweeks to frustum issues with how did things 2025-04-19 16:34:19 +10:00
mcrcortex
62f4116cc8 Ahahaah PAIN 2025-04-19 15:33:04 +10:00
mcrcortex
550a75460f Hate java loading classes order thing 2025-04-19 15:31:59 +10:00
mcrcortex
d226146213 Added or 2025-04-19 15:24:09 +10:00
mcrcortex
b9d4a4116a fix block states inflight not being correct 2025-04-19 14:55:58 +10:00
mcrcortex
becd958c5e Fixed incorrect typing of fluid quads when generating mesh 2025-04-19 11:32:06 +10:00
mcrcortex
d1322990b3 Fix biome dependency check to include embedded fluid state 2025-04-19 11:16:14 +10:00
mcrcortex
325983152a more testing stuff 2025-04-19 11:00:46 +10:00
mcrcortex
d847f7f7b0 Am a complete and utter idiot 2025-04-19 10:59:26 +10:00
mcrcortex
0234c09495 Add check for lzma 2025-04-18 20:54:01 +10:00
mcrcortex
3b2614631d Conditionally add DH import command 2025-04-18 20:51:54 +10:00
mcrcortex
432d078212 Am unsure if fix thing, hate this meshing code 2025-04-18 08:55:30 +10:00
mcrcortex
1ffcaea80f Fix uhoh 2025-04-18 08:24:28 +10:00
mcrcortex
8ef53b3c4f AAA 2025-04-18 08:08:51 +10:00
mcrcortex
b0bd063ed6 Work on node manager 2025-04-16 09:21:42 +10:00
mcrcortex
99cfbb07b9 x fluid geometry emittion 2025-04-15 12:40:32 +10:00
mcrcortex
7e3a73671b Build tweek 2025-04-14 17:45:50 +10:00
mcrcortex
523cb55889 barrier 2025-04-14 14:46:14 +10:00
mcrcortex
1121b5e6dd compile/runtime 2025-04-14 12:16:55 +10:00
mcrcortex
79b7ddda8d sigh 2025-04-14 11:41:40 +10:00
mcrcortex
d6c49ea142 Modmenu not required 2025-04-14 11:15:36 +10:00
mcrcortex
a521e34f49 Beans 2025-04-14 11:02:12 +10:00
mcrcortex
c6aa690eee Random things 2025-04-14 01:28:57 +10:00
mcrcortex
5163cbd26e Fix issues with world importer 2025-04-14 01:18:50 +10:00
mcrcortex
f98ee30491 thingires 2025-04-13 09:40:00 +10:00
mcrcortex
2a128bd69c Config option for enabling/disabling vanilla fog (only applies when the voxy renderer is active) 2025-04-11 09:24:09 +10:00
mcrcortex
198f0f9c31 readd funny frustum thing (fustum is still borked as fk) 2025-04-10 21:00:28 +10:00
mcrcortex
b7713815a4 slightly improve hiz 2025-04-10 20:53:51 +10:00
mcrcortex
47592b4e6d qq 2025-04-09 18:32:58 +10:00
mcrcortex
802cea001c qq 2025-04-09 18:31:48 +10:00
mcrcortex
c5cf08365f thigies 2025-04-08 16:26:01 +10:00
mcrcortex
7af77296ed Tweeks to tracker 2025-04-08 14:26:39 +10:00
mcrcortex
6ccef6d1a5 Note + bound 2025-04-08 13:24:10 +10:00
mcrcortex
4fd84cb811 Added random extra check that is probably wrong 2025-04-08 13:23:57 +10:00
mcrcortex
7cd2ecfcde Greatly accelerated geometry cleaner gpu code 2025-04-08 12:35:02 +10:00
mcrcortex
3569e1d889 fence + getter 2025-04-08 00:43:02 +10:00
mcrcortex
89dfc3c9f2 Nonlinear subdiv slider 2025-04-08 00:27:51 +10:00
mcrcortex
a7cf766c27 remove verify 2025-04-07 20:42:11 +10:00
mcrcortex
63983f6c26 Minor changes 2025-04-07 20:19:39 +10:00
mcrcortex
183746ba4d Replace cloth config with sodium gui 2025-04-07 20:19:29 +10:00
mcrcortex
4d77096589 fix lighting uhoh 2025-04-07 17:13:25 +10:00
mcrcortex
1dc6d2a6bf LRU change 2025-04-07 14:51:50 +10:00
mcrcortex
7027c34941 what the fuck opengl 2025-04-07 14:51:31 +10:00
mcrcortex
2ff6f4cb4e partial yz fluid rendering 2025-04-06 12:41:50 +10:00
mcrcortex
d7d9365a63 add note + check 2025-04-05 18:14:31 +10:00
mcrcortex
7cc92a533d basic translucency 2025-04-05 16:55:47 +10:00
mcrcortex
9e5e5e654d tweeks 2025-04-04 15:09:42 +10:00
mcrcortex
856b5b5b56 A 2025-04-04 13:20:20 +10:00
mcrcortex
3c079397cd Tweeks to iteration 2025-04-02 12:31:27 +10:00
mcrcortex
3f972856ad Partial improvement to selector 2025-04-01 19:40:59 +10:00
mcrcortex
74630504ae Fix more issues 1.21.5 2025-04-01 18:04:37 +10:00
mcrcortex
e67ed69cc9 1.21.5 partial 2025-04-01 16:10:21 +10:00
mcrcortex
9e442ef854 1.21.5 partial 2025-04-01 16:10:10 +10:00
mcrcortex
7f3be8c1ac Merge branch 'hierachial_rewrite_improved_meshing' into mc_1215 2025-04-01 11:54:30 +10:00
mcrcortex
0b86620a4e slight rework on how world unload works, tweek rocksdb, move to WorldUpdater 2025-04-01 11:53:33 +10:00
mcrcortex
b85e6367b4 new save system + rework some things 2025-03-31 16:56:19 +10:00
mcrcortex
40c4101bec Added render distance slider and implementation 2025-03-31 00:57:54 +10:00
mcrcortex
aba9129460 Funny dodgy gpu selector 2025-03-30 11:11:10 +10:00
mcrcortex
9cf7652ae3 Aaa 2025-03-30 11:10:27 +10:00
mcrcortex
9f604dfc2e Changes and removals 2025-03-28 10:05:20 +10:00
mcrcortex
12404348e5 remove #line 1 2025-03-28 09:54:47 +10:00
mcrcortex
e0314de4f6 A 2025-03-28 09:54:32 +10:00
mcrcortex
8b3f8812de Changes 2025-03-28 09:49:12 +10:00
mcrcortex
426ae35a44 start 0.5 2025-03-26 19:39:33 +10:00
mcrcortex
e812b13ef7 Things 2025-03-26 16:03:17 +10:00
mcrcortex
d73f844b10 Reshuffle to methods 2025-03-25 11:43:13 +10:00
mcrcortex
76bad0be68 Revert 2025-03-25 11:42:57 +10:00
mcrcortex
560ca9092c Things 2025-03-25 11:20:56 +10:00
mcrcortex
3702467178 Bound computation 2025-03-25 11:20:42 +10:00
mcrcortex
f6eefdb336 Fix importing very old updated worlds 2025-03-25 01:04:27 +10:00
mcrcortex
33f5772ecd Add global index pallet 2025-03-25 00:16:16 +10:00
mcrcortex
1d7e24537f fix mesa detection 2025-03-24 22:31:14 +10:00
mcrcortex
02b4fee507 sodium update 2025-03-24 22:15:30 +10:00
mcrcortex
b387ab069c Mesa depth stencil bug bypass 2025-03-24 22:15:22 +10:00
mcrcortex
6cadf85b6a Changed config, fixed crashes and issues with world importing 2025-03-24 17:48:17 +10:00
mcrcortex
4a613ff145 Fix crash when changing dimensions 2025-03-24 13:57:52 +10:00
mcrcortex
68a17f0578 Fixes and tweeks for things 2025-03-24 13:49:15 +10:00
mcrcortex
8b7a3c4bb1 Rewired import system 2025-03-24 13:48:14 +10:00
mcrcortex
a992a44f40 Rewired data imports 2025-03-23 23:00:22 +10:00
mcrcortex
5e5c63d109 stuff 2025-03-22 17:54:13 +10:00
mcrcortex
cca9ac0e56 Fix critical issue with meshing 2025-03-22 15:59:37 +10:00
mcrcortex
7a2fe748ea Logging for gpu stream overflow 2025-03-22 15:50:37 +10:00
mcrcortex
2d95cdfd01 Fix fluids overriding the colour parameter 2025-03-22 15:46:18 +10:00
mcrcortex
1c23394415 Disable partial non opaque meshing 2025-03-22 11:34:06 +10:00
mcrcortex
41c31976d6 patch 2025-03-21 22:56:03 +10:00
mcrcortex
eb7172aaba Stuff kinda works 2025-03-21 22:47:16 +10:00
mcrcortex
ac1427d125 a 2025-03-19 16:54:56 +10:00
mcrcortex
755d410430 partial geometry removal imp 2025-03-19 15:02:21 +10:00
mcrcortex
78b2f09e76 Replace WorldSection sync array cache with concurrent implementation 2025-03-18 22:00:46 +10:00
mcrcortex
4858ad586b DH importer v1 2025-03-17 00:19:42 +10:00
mcrcortex
daa3cab313 Remove old stuff 2025-03-16 17:03:19 +10:00
mcrcortex
ba30742630 Change error to warning 2025-03-16 16:55:02 +10:00
mcrcortex
ef389b7ed0 Am such a bloody clown 2025-03-16 16:46:26 +10:00
mcrcortex
104bdf070c Config and fixes for world managing 2025-03-16 16:10:57 +10:00
mcrcortex
aea98cb6e4 Handle empty child list in traversal shader 2025-03-16 14:51:09 +10:00
mcrcortex
0dd1eb443f Testing system for NodeManager as well as completion of basic functionality of NodeManager 2025-03-16 14:46:11 +10:00
mcrcortex
77e3b95d6b stuff2 2025-03-15 11:17:35 +10:00
mcrcortex
4368f5bc4d stuff 2025-03-15 11:17:26 +10:00
mcrcortex
699ccb90b2 aa 2025-03-14 11:19:43 +10:00
mcrcortex
b6a9adce28 Improve world importer 2025-03-14 10:27:46 +10:00
mcrcortex
7b11670d55 More work 2025-03-13 11:26:13 +10:00
mcrcortex
c6fd3b19f9 Shuffling 2025-03-11 08:09:52 +10:00
mcrcortex
aae089404d more da more thing 2025-03-10 12:40:36 +10:00
mcrcortex
fd2ee3f71f More things 2025-03-09 12:19:45 +10:00
mcrcortex
60b878d53c More error logging 2025-03-09 12:05:08 +10:00
mcrcortex
fd4d1d7910 More stuff 2025-03-09 11:53:46 +10:00
mcrcortex
c0a578cdd5 aaa 2025-03-06 14:37:23 +10:00
mcrcortex
ff82c2d761 Tinker 2025-03-04 12:14:28 +10:00
mcrcortex
743c423ba5 Random things 2025-03-04 12:05:30 +10:00
mcrcortex
fc55d5fc3b More work on manager 2025-03-03 20:25:07 +10:00
mcrcortex
a8971166b4 Method to allow mod to run without mc 2025-03-03 10:45:15 +10:00
mcrcortex
ce5f792500 Convert ingest to be per section 2025-03-03 10:44:45 +10:00
mcrcortex
a6ed760304 Tinkering 2025-03-03 10:44:22 +10:00
mcrcortex
d475b114ee remove print 2025-02-24 12:00:31 +10:00
mcrcortex
6e05679b3e use exact screenspace area of AABB instead of screenspace bounding box of AABB for computing decending 2025-02-24 11:57:39 +10:00
mcrcortex
241897dbd9 woops 2025-02-07 11:18:06 +10:00
mcrcortex
f37c5353d0 Fix import shutdown 2025-02-06 06:48:03 +10:00
mcrcortex
5d187589b4 Fix crash on server 2025-02-05 18:03:50 +10:00
mcrcortex
6d683503cd Improve ingest and import speed 2025-02-05 17:08:44 +10:00
mcrcortex
d77addb416 Command improvements 2025-02-05 15:15:29 +10:00
mcrcortex
49aa9c2dd9 Add support for importing directly from zip files 2025-02-05 14:05:44 +10:00
mcrcortex
b849686564 "Remove" lzma 2025-02-05 12:38:48 +10:00
mcrcortex
57a8661b67 Remove old stuff 2025-02-05 11:19:35 +10:00
mcrcortex
e336cee557 Fix likly corruption from multiple dimensions accessing things incorrectly 2025-02-05 11:16:14 +10:00
mcrcortex
cf0afbb545 Refactor storage to include a section backend 2025-02-05 11:15:44 +10:00
mcrcortex
db43e26b8a Wip stuff 2025-02-05 09:39:55 +10:00
mcrcortex
9bd84ae3e8 Am going to scream 2025-02-04 21:34:06 +10:00
mcrcortex
613c4c744a Change default config value 2025-02-02 17:53:43 +10:00
mcrcortex
4ff28bc323 Save config if not exist 2025-02-02 01:40:27 +10:00
mcrcortex
8f1d89cb95 debug util 2025-02-02 01:26:47 +10:00
mcrcortex
34666c7994 nonOpaqueMask 2025-02-02 01:06:59 +10:00
mcrcortex
ac135d1907 More logging system use 2025-02-01 05:39:01 +10:00
mcrcortex
f29f626a2f Use logging system 2025-02-01 05:38:00 +10:00
mcrcortex
bc72eaeb0f Eliminated file seeking and removed bytebuffers instead using raw memory under the assumption the system is always little endian 2025-02-01 05:37:10 +10:00
mcrcortex
d373b806ed Replace spinlock with a wait loop, freeing up a cpu thread during import 2025-01-31 18:39:05 +10:00
mcrcortex
64067d9ba5 Change default zstd compression level to 3 2025-01-31 04:37:27 +10:00
mcrcortex
4d20f4747a "Remove" default serialization config from config 2025-01-31 04:37:09 +10:00
mcrcortex
72ef321bfb Compile only lzma (its terrible so not included) 2025-01-31 04:29:58 +10:00
mcrcortex
ec67222855 Add LZ4 compressor
- provides significant performance improvement for saving (37% in testing)
- at the cost of double the storage size (in testing)
2025-01-31 01:50:28 +10:00
mcrcortex
a0132b8c67 configuration for cache size 2025-01-30 21:25:43 +10:00
mcrcortex
71349d7404 Add fps based adaptive quality 2025-01-30 21:24:56 +10:00
mcrcortex
28af11ca2e Start decomposing minecraft getters for terrain ingesting 2025-01-30 07:14:25 +10:00
mcrcortex
9e20eac7ab improved lru cache and improved world import speed slightly 2025-01-30 06:55:39 +10:00
mcrcortex
772ec27ea1 Secondary caching system implemented with ~1gb of heap allocated to it 2025-01-30 05:15:55 +10:00
mcrcortex
5421452429 Cache 2025-01-30 00:06:21 +10:00
mcrcortex
900e72d1fc Things 2025-01-29 23:24:04 +10:00
mcrcortex
b6289a901c More crying 2025-01-28 06:18:06 +10:00
mcrcortex
4d6859db43 node clean something, its still broken mode 2025-01-28 06:03:06 +10:00
mcrcortex
aac3db90d3 Fix MixinThreadExecutor 2025-01-28 04:11:39 +10:00
mcrcortex
1048d38b4d Work on geometry cleaning 2025-01-28 03:14:14 +10:00
mcrcortex
9ded4d4b13 Sorting works 2025-01-27 07:28:08 +10:00
mcrcortex
d0e969dcf6 Remove unused import 2025-01-26 20:00:51 +10:00
mcrcortex
783f87f22d More force crash on failed initial load packet 2025-01-26 20:00:09 +10:00
mcrcortex
402d682d6e Revert render service 2025-01-25 04:15:06 +10:00
mcrcortex
d6500d2227 Continued work on geometry removal 2025-01-25 04:14:00 +10:00
mcrcortex
d0b5f32e2d Update the child mask after removing nodes 2025-01-25 02:19:15 +10:00
mcrcortex
7454179313 Add auto fragmenting storage option 2025-01-22 23:37:28 +10:00
mcrcortex
194cf672ee lower value + extra debug 2025-01-22 08:07:47 +10:00
mcrcortex
5a3ef0fe69 Performance benching 2025-01-22 05:49:17 +10:00
mcrcortex
7f62b76691 Woops 2025-01-21 21:51:44 +10:00
mcrcortex
75ad35e057 Add scratch buffer for position.
Fixes lods "disappearing" when you zoom in
2025-01-21 21:50:44 +10:00
mcrcortex
44c66d5c26 Wip 2025-01-21 05:35:22 +10:00
mcrcortex
3dcfd196a1 More logging 2025-01-15 03:40:14 +10:00
mcrcortex
b7d8f82a3d Added subpixel range 2025-01-15 03:00:55 +10:00
mcrcortex
dbf1188b12 Added opacity based mipping 2025-01-15 01:24:37 +10:00
mcrcortex
6539e69fb0 added devauth
Added some extra checks
Made RD 1024
2025-01-14 19:27:48 +10:00
mcrcortex
07b00bc710 Wip on frame tracking,
add import statistics
2025-01-13 06:42:28 +10:00
mcrcortex
d090703d32 Sigh build script 2025-01-13 00:27:22 +10:00
mcrcortex
b08ba23889 Force crash on join game packet failure 2025-01-11 20:37:42 +10:00
mcrcortex
b8fda63ef3 Update to 1.21.4 2025-01-11 20:04:37 +10:00
mcrcortex
316f42cf03 Change loading time 2025-01-05 08:40:20 +10:00
mcrcortex
7b9c31b770 git commit time Closure 2025-01-05 08:35:55 +10:00
mcrcortex
67347048b5 Who knows if this works 2025-01-05 08:31:34 +10:00
mcrcortex
73cc91fa0d Tweek build thing temporarily 2025-01-05 08:29:21 +10:00
mcrcortex
a1ed14e8cb Dont know if this is a nice place to put it 2025-01-05 02:17:26 +10:00
mcrcortex
8321d3feee Imports 2025-01-05 02:17:04 +10:00
mcrcortex
46283fec0e Ref D 2025-01-05 02:14:33 +10:00
mcrcortex
65244bc43d Ref C 2025-01-05 02:13:43 +10:00
mcrcortex
ed0bd1a2b5 Ref B 2025-01-05 02:13:11 +10:00
mcrcortex
7def1b487c Ref A 2025-01-05 02:09:38 +10:00
mcrcortex
2f7f7f4f6b Exclude unused shared objects/jni, reducing the jar final size by ~4.5x
Thanks earth for helping with hell
2025-01-04 00:22:22 +10:00
mcrcortex
1a3ad7d701 Support other depth types 2024-12-25 11:59:00 +10:00
mcrcortex
5242104d87 Other stuff 2024-12-24 12:57:17 +10:00
mcrcortex
a369d61024 Sort region files 2024-12-24 12:57:02 +10:00
mcrcortex
df2611f0b7 Compute utility 2024-12-24 12:56:30 +10:00
mcrcortex
663a352586 Fix error in resizing 2024-12-24 12:56:05 +10:00
mcrcortex
412768c57e Wip on node removal support on child existence change 2024-12-23 09:08:25 +10:00
mcrcortex
5bb91bb1eb Wip on many things
Added partial inner node child update support
2024-12-22 18:10:28 +10:00
mcrcortex
d9d433f47c Things 2024-12-16 14:10:58 +10:00
mcrcortex
c3701ad903 gl debug utils 2024-12-15 12:59:34 +10:00
mcrcortex
6ed4c92c94 Reduced and reuse memory section, remove copies during section fetch pipeline, remove stack gathering and string forming from IdNotYetComputedException wip on cleaner 2024-12-14 16:23:46 +10:00
mcrcortex
fdeed5c257 Wip, shader tweeks, manager tweeks, wip on cleaner 2024-12-14 13:42:14 +10:00
mcrcortex
aa1ba08f70 More debug 2024-12-13 23:03:32 +10:00
mcrcortex
a62413ed0b Thanks gore for the help, add extra frustum check 2024-12-13 15:57:51 +10:00
mcrcortex
2057ced33d More 2024-12-13 15:14:42 +10:00
mcrcortex
f8d9cb8855 Fixing hiz Slowly 2024-12-13 15:03:17 +10:00
mcrcortex
09b1029030 Hackfix meshing to remove gaps 2024-12-13 14:36:59 +10:00
mcrcortex
2c778ce5ec Remove todo 2024-12-13 12:50:15 +10:00
mcrcortex
029a7df71a Added taskbar progress on windows when importing worlds
Fixed memory leak in RenderDataFactory4
Moved shader debug options
Inital work on visibility sorting and node cleanup
Added optional cleanup system to services.
Track (and added to f3) GlBuffer and MemoryBuffer count and sizes.

More work on finishing node manager (its still broken when importing for some reason, requiring reload)
2024-12-13 12:47:59 +10:00
mcrcortex
8a3d7a9da5 Forgot a single bitshift, breaking many things 2024-12-07 00:57:37 +10:00
mcrcortex
69f0c63831 Dont reload when not changing reloadables 2024-12-05 22:50:19 +10:00
mcrcortex
523fd89f97 Added option to change subdivision size 2024-12-05 22:38:31 +10:00
mcrcortex
d9fc4b3c05 Added extra state checking and fixed empty geometry not returning child existence bits, added lighting back 2024-12-05 19:39:18 +10:00
mcrcortex
8c36021746 Added compression and decompression contexts 2024-12-05 12:15:05 +10:00
mcrcortex
1b82b3020f Fix broken memcpy 2024-12-05 11:36:58 +10:00
mcrcortex
eeeecb982c Attempted to move to native buffer directly 2024-12-05 10:40:49 +10:00
mcrcortex
70a1fe36c7 Merged zy into single method 2024-12-05 10:19:43 +10:00
mcrcortex
622c40f188 Change err.println to Logging.error 2024-12-05 00:28:35 +10:00
mcrcortex
3b25d2d535 Fix postprocessing being weird 2024-12-05 00:28:06 +10:00
mcrcortex
017bf78d4c removed unused config options 2024-12-04 21:47:42 +10:00
mcrcortex
775bf46b8e Fix issue when overflow happens 2024-12-03 23:34:16 +10:00
mcrcortex
9c05fdc11e Increase performance when sections to be rendered are wait on model bakery 2024-12-03 23:33:55 +10:00
mcrcortex
58ae91afbe Change some settings 2024-12-03 22:39:02 +10:00
mcrcortex
acb7f3aa17 Greatly accelerated bitset consecutive allocation 2024-12-03 22:24:02 +10:00
mcrcortex
3c5d79ce77 Made x-axis processing _much much_ faster 2024-12-03 21:23:06 +10:00
mcrcortex
812342b4da A 2024-12-03 21:22:39 +10:00
mcrcortex
c6fe0a1bed Much changes, wip 2024-12-03 17:35:08 +10:00
mcrcortex
00a123b150 Tweeked mesher 2024-12-02 22:56:06 +10:00
mcrcortex
56d96ddd84 Heavily reduced allocation rates 2024-12-02 22:55:52 +10:00
mcrcortex
5a6819757b Very fast scanline mesher 2024-12-02 20:26:27 +10:00
mcrcortex
3618c136f7 New mesher 2024-12-02 18:27:18 +10:00
mcrcortex
4312472534 fixed/greatly improved hiz culling maybe 2024-12-01 23:35:25 +10:00
mcrcortex
bdf3319204 Perf tester 2024-11-28 18:12:48 +10:00
mcrcortex
53672d3ace Stuff 2024-11-28 17:36:53 +10:00
mcrcortex
fac00a81d3 Manually expanded shader 2024-11-17 22:40:01 +10:00
mcrcortex
c0be96e797 fix build script if not git cloned 2024-11-17 22:06:18 +10:00
mcrcortex
5979b17891 Fix lighting 2024-11-17 21:18:05 +10:00
mcrcortex
5e5d0c8051 partial 1.21.3 update 2024-11-17 21:09:31 +10:00
mcrcortex
0a7c0c573c blub 2024-11-17 19:26:11 +10:00
mcrcortex
a15978d1fb Funny very incorrect tinkering of culling 2024-11-01 17:28:30 +10:00
mcrcortex
7fa62375c6 QQQQ 2024-10-24 23:38:29 +10:00
mcrcortex
182e66daff cc 2024-09-30 13:18:33 +10:00
mcrcortex
640da6c1be oops 2024-09-29 10:44:50 +10:00
mcrcortex
3d2129c1d4 BB 2024-09-29 10:44:16 +10:00
mcrcortex
5e72b945c4 debug pain 2024-09-24 13:40:15 +10:00
mcrcortex
3a800ec9ca Added build task removal 2024-09-24 11:05:58 +10:00
mcrcortex
10391c48c7 AaAaA 2024-09-24 10:40:51 +10:00
mcrcortex
00cf0c73e3 Fixed other issues? 2024-09-24 00:19:25 +10:00
mcrcortex
f53a81099e Fixed culling? 2024-09-24 00:11:10 +10:00
mcrcortex
f9a8f9b1c2 Tweeks 2024-09-23 22:48:47 +10:00
mcrcortex
27ed49fcd9 BBBB 2024-09-23 10:03:09 +10:00
mcrcortex
ad7660e4d6 More and more working? 2024-09-23 03:02:18 +10:00
mcrcortex
7eaa8483b8 aaa 2024-09-23 01:58:33 +10:00
mcrcortex
87238bdb45 It works EVEN BETTER 2024-09-23 01:10:43 +10:00
mcrcortex
aafc475843 IT WORKS!!!!!! 2024-09-22 23:57:08 +10:00
mcrcortex
78d5c224a8 More node 2024-09-22 11:09:25 +10:00
mcrcortex
b875d5fc25 Prep v2 2024-09-19 00:28:36 +10:00
mcrcortex
df5c34c626 Even more pain, but gpu side should fully work ™️ 2024-09-17 01:00:03 +10:00
mcrcortex
0aeb0fbf21 implemented gpu traversal i think? 2024-09-16 15:46:09 +10:00
mcrcortex
d5045731ad Prepare 2024-09-16 15:08:48 +10:00
mcrcortex
1c0e0cc666 Added memory verification option 2024-09-15 19:54:00 +10:00
mcrcortex
b9a3d18b56 Incremental traversal system works 2024-09-15 12:10:32 +10:00
mcrcortex
76aaf3824d AA 2024-09-11 22:49:00 +10:00
mcrcortex
756431b581 World and work stuff 2024-09-02 08:10:13 +10:00
mcrcortex
c0cc236c40 Per world voxel engine 2024-08-23 00:02:11 +10:00
mcrcortex
1a42f115fb More work 2024-08-22 09:19:29 +10:00
mcrcortex
fbdd65bc00 Rework to SectionRouter 2024-08-20 10:36:18 +10:00
mcrcortex
306956839a Continue work 2024-08-16 16:22:22 +10:00
mcrcortex
d4714989b4 More work on node manager 2024-08-13 09:18:56 +10:00
mcrcortex
1d1e244f03 Working on leaf 2024-08-12 08:28:34 +10:00
mcrcortex
f055234463 More work on update types + hacky add override to dummy render everything as true 2024-08-09 22:15:20 +10:00
mcrcortex
6450069d8a Move to SectionUpdate as a data wrapper 2024-08-09 00:09:32 +10:00
mcrcortex
c8bcfc3b6d Fixed counting issue 2024-08-08 20:14:06 +10:00
mcrcortex
3ba1c76414 Only import full chunk sections 2024-08-08 19:54:22 +10:00
mcrcortex
7f25f5ff5d fixed uhoh 2024-08-08 19:46:39 +10:00
mcrcortex
15391a6f91 child emptiness tracking 2024-08-08 19:37:38 +10:00
mcrcortex
b81fd46929 Another flag 2024-08-08 02:05:23 +10:00
mcrcortex
fce77cbd5f add launch flag to disable verifying world section access is correct 2024-08-08 01:44:09 +10:00
mcrcortex
163d1f9f43 Move thread package 2024-08-08 00:41:33 +10:00
mcrcortex
d2483283f5 Attempt to improve lighting 2024-08-08 00:40:17 +10:00
mcrcortex
f536b0cd74 cache 2024-08-07 23:10:42 +10:00
mcrcortex
445c583a5b Config for threading 2024-08-07 14:11:44 +10:00
mcrcortex
a5122d31a4 Attempted more fixes for ServiceThreadPool race conditions when removing services 2024-08-07 12:42:36 +10:00
mcrcortex
0135c63b88 Attempted last resort fix for thread pool 2024-08-07 02:18:06 +10:00
mcrcortex
395112fc40 A 2024-08-07 02:01:22 +10:00
mcrcortex
323cd95bff Swap to chunk count estimation 2024-08-07 01:54:21 +10:00
mcrcortex
4bb05252aa woops 2024-08-07 01:50:27 +10:00
mcrcortex
8797ee56a8 World importer uses service system 2024-08-07 01:47:22 +10:00
mcrcortex
6eed71656d Optimizations aswell as the option to disable section verification 2024-08-07 00:06:02 +10:00
mcrcortex
ee5bca7db5 Swap storage backend type from ByteBuffer to MemoryBuffer 2024-08-06 23:41:31 +10:00
mcrcortex
082143ed15 Improve object tracking configurations 2024-08-06 23:15:57 +10:00
mcrcortex
377c51f365 Tweeks 2024-08-06 22:57:15 +10:00
mcrcortex
cd3b40399c Fix service pools 2024-08-06 22:09:37 +10:00
mcrcortex
10ff5dda7d Merge branch 'master' into hierachial_rewrite
# Conflicts:
#	src/main/java/me/cortex/voxy/client/config/VoxyConfigScreenFactory.java
#	src/main/java/me/cortex/voxy/client/core/VoxelCore.java
#	src/main/java/me/cortex/voxy/client/mixin/chunky/MixinFabricWorld.java
2024-08-06 21:52:50 +10:00
mcrcortex
403317fd29 Swap to a single ServiceThreadPool workload 2024-08-06 21:50:05 +10:00
mcrcortex
351fac9052 Work on nodes 2024-08-06 12:49:28 +10:00
mcrcortex
2d6d948e80 Update forwarding and checking 2024-08-05 22:10:21 +10:00
mcrcortex
593f222760 Fix biome 2024-08-05 16:42:12 +10:00
mcrcortex
0fdba1bf64 Remove old render stuff 2024-08-04 22:10:14 +10:00
mcrcortex
e4fe056586 Render basics 2024-08-04 22:08:49 +10:00
mcrcortex
0bb04b2f8e Very Close 2024-08-04 19:17:56 +10:00
mcrcortex
264e26cb90 aa 2024-08-04 12:56:06 +10:00
mcrcortex
0366c42cf3 _basic_ Rendering 2024-08-04 12:52:33 +10:00
mcrcortex
7d883913bd dont leak memory 2024-08-03 14:42:55 +10:00
mcrcortex
57ad9f56be Premark area for gen 2024-08-03 14:41:25 +10:00
mcrcortex
082885a5eb Remove nvmesh and gl46 meshlets for the time being 2024-08-03 13:27:23 +10:00
mcrcortex
945cd68672 Work on basic memory manager for section rendering 2024-08-03 13:26:08 +10:00
mcrcortex
3194f1d23e Added global shader debug printf util 2024-08-03 00:03:30 +10:00
mcrcortex
154f016a96 More work on setting up render system 2024-08-02 23:38:30 +10:00
mcrcortex
0362399ba7 Set the default clear value to 0 2024-08-02 22:02:33 +10:00
mcrcortex
7dc8bbcd9a AA 2024-08-02 14:09:32 +10:00
mcrcortex
5225bca007 More work 2024-08-02 12:20:04 +10:00
mcrcortex
9b08c6e5ff Fixes for tasks never finishing 2024-08-01 15:51:20 +10:00
mcrcortex
7f5b84e6e7 Remove debug blit 2024-08-01 13:30:22 +10:00
mcrcortex
2f07b8420b Fk enhanced forloop bullshit 2024-08-01 13:27:05 +10:00
mcrcortex
6fdcde856b Compute shader buffer transfer converter 2024-08-01 12:44:09 +10:00
mcrcortex
004bd1e751 "works" 2024-07-31 18:58:13 +10:00
mcrcortex
aa65197749 Prep for async texture fetch 2024-07-31 09:54:51 +10:00
mcrcortex
7703e2fb0f Aa 2024-07-30 15:08:49 +10:00
mcrcortex
433f3ace9f Fix bakery ™️ 2024-07-30 10:42:33 +10:00
mcrcortex
87f7d71c94 Use onthread bakery until all the issues related to offthread baking can be resolved (related to tile entity baking) 2024-07-30 09:14:36 +10:00
mcrcortex
ca899dd506 Uhhhhh refactor??? ig??? 2024-07-29 09:09:57 +10:00
mcrcortex
25712a850e Bebap 2024-07-21 21:00:26 +10:00
mcrcortex
9329a7885d CCC 2024-07-20 20:27:22 +10:00
mcrcortex
c04bf8b351 Automaticly ingest chunks created by chunky 2024-07-20 20:27:17 +10:00
mcrcortex
d5cf684215 Beans3 2024-07-18 19:59:23 +10:00
mcrcortex
4f02eca7c9 Beans2 2024-07-18 12:33:08 +10:00
mcrcortex
d7b555fca6 Beans 2024-07-18 11:22:57 +10:00
mcrcortex
9d4f912808 AA 2024-07-18 10:52:43 +10:00
mcrcortex
26825e358f Fixes and tweeks 2024-07-16 20:47:49 +10:00
mcrcortex
5d58e9e4da ITS WORKING EVEN MORE 2024-07-16 18:02:52 +10:00
mcrcortex
72e35557a4 it works! 2024-07-16 00:14:53 +10:00
mcrcortex
0ff2db1881 Working even better 2024-07-15 20:40:13 +10:00
mcrcortex
4cbd7af3d0 Working better 2024-07-15 19:34:22 +10:00
mcrcortex
5b5939855f World tinkering 2024-07-15 12:24:50 +10:00
mcrcortex
91e93dea2b Performance tinkering 2024-07-15 10:41:04 +10:00
mcrcortex
37b85057c3 Restructure B 2024-07-13 13:19:47 +10:00
mcrcortex
3cec8b281c Restructure A 2024-07-13 10:47:11 +10:00
mcrcortex
86f1770af3 much wow 2024-07-12 10:47:46 +10:00
mcrcortex
707c0b1c85 Created PrintfInjector to use printf inside shaders 2024-07-11 22:34:59 +10:00
mcrcortex
6ba56a133e Small change 2024-07-11 19:55:19 +10:00
mcrcortex
1e855a0ed0 Shader compiles 2024-07-11 16:50:32 +10:00
mcrcortex
716bf097bf Alot of gpu work 2024-07-10 23:02:00 +10:00
mcrcortex
8ed121f71e Slight reorg 2024-07-10 00:16:32 +09:00
mcrcortex
e5333cb2e9 Merge branch 'master' into meshlet_culling 2024-07-10 00:04:13 +09:00
mcrcortex
a7c6768449 Much work much wow 2024-07-10 00:03:59 +09:00
mcrcortex
e1c7bb18a9 Fix format hopefully 2024-07-02 22:35:28 +09:00
mcrcortex
12b2dce779 Node 2024-07-02 22:35:14 +09:00
370 changed files with 30067 additions and 10306 deletions

View File

@@ -0,0 +1,24 @@
name: check-does-build-pr
on: [ pull_request ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: true
- name: Gradle build
run: ./gradlew build

32
.github/workflows/check-does-build.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: check-does-build
on: [ push ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Loom Cache
uses: actions/cache@v4
with:
path: "**/.gradle/loom-cache"
key: "${{ runner.os }}-gradle-${{ hashFiles('**/libs.versions.*', '**/*.gradle*', '**/gradle-wrapper.properties') }}"
restore-keys: "${{ runner.os }}-gradle-"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: false
cache-cleanup: never
- name: Gradle build
run: ./gradlew -I init.gradle build

31
.github/workflows/manual-artifact.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: manual-artifact
on: [ workflow_dispatch ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: false
cache-cleanup: always
- name: Gradle build
run: ./gradlew build
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: voxy-artifacts
path: build/libs/*.jar

6
.gitignore vendored
View File

@@ -1,3 +1,7 @@
# Project exclude paths
/.gradle/
/build/
/.idea/
/build/
/run/
/out/
/logs/

5
LICENSE.md Normal file
View File

@@ -0,0 +1,5 @@
# Copyright 2025 MCRcortex
All rights reserved.
Do not redistribute.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

1
README.md Normal file
View File

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

View File

@@ -1,11 +1,17 @@
plugins {
id 'fabric-loom' version "1.7.1"
id 'fabric-loom' version "1.14-SNAPSHOT"
id 'maven-publish'
}
def isInGHA = System.getenv("GITHUB_ACTIONS") == "true"
version = project.mod_version
group = project.maven_group
base {
archivesName = project.archives_base_name
}
repositories {
exclusiveContent {
forRepository {
@@ -18,15 +24,72 @@ repositories {
includeGroup "maven.modrinth"
}
}
maven { url "https://maven.shedaniel.me/" }
maven { url "https://maven.terraformersmc.com/releases/" }
exclusiveContent {
forRepository {
maven {
name "caffeinemcRepository"
url "https://maven.caffeinemc.net/snapshots"
}
}
filter {
includeGroup "net.caffeinemc"
}
}
maven { url = "https://maven.shedaniel.me/" }
maven { url = "https://maven.terraformersmc.com/releases/" }
exclusiveContent {
forRepository {
ivy {
name = "github"
url = "https://github.com/"
patternLayout {
artifact '/[organisation]/[module]/releases/download/[revision]/[module]-[revision]-[classifier].[ext]'
}
metadataSources {
artifact()
}
}
}
filter {
includeModuleByRegex("[^\\.]+", "nvidium")
}
}
}
def gitCommitHash = { ->
try {
ExecOutput result = providers.exec {
commandLine = ['git', 'rev-parse', '--short', 'HEAD']
ignoreExitValue = true
}
if (result.getResult().get().getExitValue() != 0) {
return "<UnknownCommit>";
} else {
return result.standardOutput.asText.get().strip();
}
} catch (Exception e) {
e.printStackTrace()
return "<UnknownCommit>";
}
}
def buildtime = {System.currentTimeSeconds()}
processResources {
inputs.property "version", project.version
archivesBaseName = "voxy"
def time = buildtime()
def hash = gitCommitHash()
def version = project.version
inputs.properties("version": version, "commit": hash, "buildtime": time)
filesMatching("fabric.mod.json") {
expand "version": project.version
expand "version": version, "commit": hash, "buildtime": time
}
}
@@ -34,56 +97,70 @@ loom {
accessWidenerPath = file("src/main/resources/voxy.accesswidener")
}
def modRuntimeOnlyMsk = {arg->}
if (!isInGHA) {
modRuntimeOnlyMsk = { arg ->
dependencies {
modRuntimeOnly(arg)
}
}
}
dependencies {
// To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
mappings loom.officialMojangMappings()
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation(fabricApi.module("fabric-api-base", project.fabric_version))
modImplementation(fabricApi.module("fabric-rendering-fluids-v1", project.fabric_version))
modImplementation(fabricApi.module("fabric-resource-loader-v0", project.fabric_version))
modImplementation(fabricApi.module("fabric-command-api-v2", project.fabric_version))
modImplementation("net.fabricmc.fabric-api:fabric-rendering-data-attachment-v1:0.3.38+73761d2e9a")
modRuntimeOnlyMsk(fabricApi.module("fabric-api-base", project.fabric_version))
modRuntimeOnlyMsk(fabricApi.module("fabric-rendering-fluids-v1", project.fabric_version))
modRuntimeOnlyMsk(fabricApi.module("fabric-resource-loader-v0", project.fabric_version))
modRuntimeOnlyMsk(fabricApi.module("fabric-command-api-v2", project.fabric_version))
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
//TODO: this is to eventually not need sodium installed as atm its just used for parsing shaders
modRuntimeOnly "maven.modrinth:sodium:mc1.21-0.5.9"
modCompileOnly "maven.modrinth:sodium:mc1.21-0.5.9"
if (true) {
modImplementation "maven.modrinth:sodium:mc1.21.11-0.8.2-fabric"
} else {
modImplementation "net.caffeinemc:sodium-fabric:0.8.2-SNAPSHOT+mc1.21.11+"
}
//modRuntimeOnly "maven.modrinth:nvidium:0.2.6-beta"
modCompileOnly "maven.modrinth:nvidium:0.2.8-beta"
modImplementation("maven.modrinth:lithium:mc1.21.11-0.21.0-fabric")
//modRuntimeOnlyMsk "drouarb:nvidium:0.4.1-beta4:1.21.6@jar"
modCompileOnly "drouarb:nvidium:0.4.1-beta4:1.21.6@jar"
modCompileOnly("maven.modrinth:modmenu:17.0.0-alpha.1")
modRuntimeOnlyMsk("maven.modrinth:modmenu:17.0.0-alpha.1")
modCompileOnly("maven.modrinth:iris:1.10.4+1.21.11-fabric")
modRuntimeOnlyMsk("maven.modrinth:iris:1.10.4+1.21.11-fabric")
//modCompileOnly("maven.modrinth:starlight:1.1.3+1.20.4")
modImplementation("maven.modrinth:cloth-config:13.0.121+fabric")
modImplementation("maven.modrinth:modmenu:11.0.1")
modCompileOnly("maven.modrinth:iris:1.7.3+1.21")
//modRuntimeOnly("maven.modrinth:iris:1.6.17+1.20.4")
modCompileOnly("maven.modrinth:starlight:1.1.3+1.20.4")
//modCompileOnly("maven.modrinth:immersiveportals:v5.1.7-mc1.20.4")
modCompileOnly("maven.modrinth:vivecraft:1.20.4-1.1.6-fabric")
modCompileOnly("maven.modrinth:chunky:1.4.16-fabric")
modRuntimeOnly("maven.modrinth:chunky:1.4.16-fabric")
modCompileOnly("maven.modrinth:chunky:1.4.54-fabric")
//modRuntimeOnlyMsk("maven.modrinth:chunky:1.4.40-fabric")
modRuntimeOnly("maven.modrinth:spark:1.10.73-fabric")
modRuntimeOnly("maven.modrinth:fabric-permissions-api:0.3.1")
modRuntimeOnlyMsk("maven.modrinth:spark:1.10.152-fabric")
modRuntimeOnlyMsk("maven.modrinth:fabric-permissions-api:0.3.3")
//modRuntimeOnly("maven.modrinth:nsight-loader:1.2.0")
modImplementation('io.github.douira:glsl-transformer:2.0.1')
//modImplementation('io.github.douira:glsl-transformer:2.0.1')
modCompileOnly("maven.modrinth:vivecraft:1.21.9-1.3.2-fabric")
modCompileOnly("maven.modrinth:flashback:rNCr1Rbs")
}
def targetJavaVersion = 21
tasks.withType(JavaCompile).configureEach {
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
// If Javadoc is generated, this must be specified in that task too.
it.options.encoding = "UTF-8"
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
it.options.release = targetJavaVersion
}
it.options.release = targetJavaVersion
it.options.deprecation = true
}
java {
@@ -91,16 +168,67 @@ java {
if (JavaVersion.current() < javaVersion) {
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
}
archivesBaseName = project.archives_base_name
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
//withSourcesJar()
}
jar {
from("LICENSE") {
rename { "${it}_${project.archivesBaseName}"}
rename { "${it}_${project.archives_base_name}"}
}
}
tasks.register('makeExcludedRocksDB', Zip) {
archiveExtension.set("jar")
entryCompression = ZipEntryCompression.STORED
destinationDirectory.set temporaryDir
dependsOn configurations.includeInternal
def files = configurations.includeInternal.incoming.getArtifacts().getArtifactFiles().filter {
it.name.startsWith('rocksdb')
}
from {->zipTree(files.first())}
archiveFileName.set(providers.provider{files.first().name})
exclude {
def file = it.name
if (file.endsWith(".jnilib")) {
return true
}
if (!file.endsWith(".so")) {
return false
}
return ["osx", "linux32", "musl", "s390x", "riscv64", "ppc64le", "aarch64"].any(file::contains)
}
}
processIncludeJars {
outputs.cacheIf {true}
dependsOn makeExcludedRocksDB
jars = jars.filter {!it.name.startsWith('rocksdb')}
jars.from(makeExcludedRocksDB)
}
remapJar {
doFirst {
delete fileTree(getDestinationDirectory().get())
}
if (!isInGHA) {
def hash = gitCommitHash();
if (!hash.equals("<UnknownCommit>")) {
archiveClassifier.set(hash);
}
}
}
@@ -111,22 +239,49 @@ repositories {
mavenCentral()
}
dependencies {
implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
implementation "org.lwjgl:lwjgl"
include(implementation "org.lwjgl:lwjgl-lmdb")
include(implementation "org.lwjgl:lwjgl-zstd")
//include(implementation "org.lwjgl:lwjgl-zstd")
runtimeOnly "org.lwjgl:lwjgl:$lwjglVersion:natives-windows"
runtimeOnly "org.lwjgl:lwjgl:$lwjglVersion:natives-linux"
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-windows")
include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-windows")
//include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-windows")
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-linux")
include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-linux")
//include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-linux")
include(implementation 'org.rocksdb:rocksdbjni:8.10.0')
include(implementation 'redis.clients:jedis:5.1.0')
include(implementation('org.rocksdb:rocksdbjni:10.2.1'))
include(implementation 'org.apache.commons:commons-pool2:2.12.0')
include(implementation 'org.lz4:lz4-java:1.8.0')
include(implementation('org.tukaani:xz:1.10'))
include(implementation 'com.github.luben:zstd-jni:1.5.5-1')
if (true) {
if (!isInGHA) {
minecraftRuntimeLibraries('org.xerial:sqlite-jdbc:3.49.1.0')
}
} else {
include(implementation('org.xerial:sqlite-jdbc:3.49.1.0'))
}
//implementation 'org.rocksdb:rocksdbjni:8.10.0'
//implementation 'redis.clients:jedis:5.1.0'
}
if (!isInGHA) {
repositories {
maven {
url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1"
}
}
dependencies {
/*
modRuntimeOnly('me.djtheredstoner:DevAuth-fabric:1.2.1') {
exclude group: 'net.fabricmc', module: 'fabric-loader'
}*/
}
}

View File

@@ -1,15 +1,21 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G
org.gradle.jvmargs=-Xmx2G
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.daemon = false
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=1.21
yarn_mappings=1.21+build.2
loader_version=0.15.11
minecraft_version=1.21.11
loader_version=0.18.2
loom_version=1.14-SNAPSHOT
# Fabric API
fabric_version=0.140.2+1.21.11
# Mod Properties
mod_version = 0.1.6-alpha
mod_version = 0.2.9-alpha
maven_group = me.cortex
archives_base_name = voxy
fabric_version=0.100.1+1.21
archives_base_name = voxy

View File

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

0
gradlew vendored Normal file → Executable file
View File

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

@@ -1,28 +0,0 @@
package me.cortex.voxy.api;
import me.cortex.voxy.common.world.WorldSection;
public class VoxyApi {
private VoxyApi(String useIdentifier) {
}
public static VoxyApi makeAPI(String identifier) {
return new VoxyApi(identifier);
}
public interface ArbitraryGenerateCallback {
int ALL_DARK = -1;
int OK = 0;
int ALL_SKY = 1;
int generate(int lodLevel, int x, int y, int z, long[] outputBuffer);
}
public void setArbitraryGenerator(ArbitraryGenerateCallback generator) {
}
public WorldSection acquireSection(int level, int x, int y, int z) {
return null;
}
}

View File

@@ -0,0 +1,57 @@
package me.cortex.voxy.client;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.commonImpl.ImportManager;
import me.cortex.voxy.commonImpl.importers.IDataImporter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.LerpingBossEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import net.minecraft.world.BossEvent;
import java.util.UUID;
public class ClientImportManager extends ImportManager {
protected class ClientImportTask extends ImportTask {
private final UUID bossbarUUID;
private final LerpingBossEvent bossBar;
protected ClientImportTask(IDataImporter importer) {
super(importer);
this.bossbarUUID = Mth.createInsecureUUID();
this.bossBar = new LerpingBossEvent(this.bossbarUUID, Component.nullToEmpty("Voxy world importer"), 0.0f, BossEvent.BossBarColor.GREEN, BossEvent.BossBarOverlay.PROGRESS, false, false, false);
Minecraft.getInstance().execute(()->{
Minecraft.getInstance().gui.getBossOverlay().events.put(bossBar.getId(), bossBar);
});
}
@Override
protected boolean onUpdate(int completed, int outOf) {
if (!super.onUpdate(completed, outOf)) {
return false;
}
Minecraft.getInstance().execute(()->{
this.bossBar.setProgress((float) (((double)completed) / ((double) Math.max(1, outOf))));
this.bossBar.setName(Component.nullToEmpty("Voxy import: " + completed + "/" + outOf + " chunks"));
});
return true;
}
@Override
protected void onCompleted(int total) {
super.onCompleted(total);
Minecraft.getInstance().execute(()->{
Minecraft.getInstance().gui.getBossOverlay().events.remove(this.bossbarUUID);
long delta = Math.max(System.currentTimeMillis() - this.startTime, 1);
String msg = "Voxy world import finished in " + (delta/1000) + " seconds, averaging " + (int)(total/(delta/1000f)) + " chunks per second";
Minecraft.getInstance().gui.getChat().addMessage(Component.literal(msg));
Logger.info(msg);
});
}
}
@Override
protected synchronized ImportTask createImportTask(IDataImporter importer) {
return new ClientImportTask(importer);
}
}

View File

@@ -0,0 +1,315 @@
package me.cortex.voxy.client;
import me.cortex.voxy.common.Logger;
import org.lwjgl.system.JNI;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.system.windows.GDI32;
import org.lwjgl.system.windows.Kernel32;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import static org.lwjgl.system.APIUtil.apiGetFunctionAddressOptional;
public class GPUSelectorWindows2 {
private static final long D3DKMTSetProperties = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTSetProperties");
private static final long D3DKMTEnumAdapters2 = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTEnumAdapters2");
private static final long D3DKMTCloseAdapter = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTCloseAdapter");
private static final long D3DKMTQueryAdapterInfo = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTQueryAdapterInfo");
private static int setPCIProperties(int type, int vendor, int device, int subSys) {
try (var stack = MemoryStack.stackPush()) {
var buff = stack.calloc(0x10).order(ByteOrder.nativeOrder());
buff.putInt(0, vendor);
buff.putInt(4, device);
buff.putInt(8, subSys);
buff.putInt(12, 0);
return setProperties(type, buff);
}
}
private static int setProperties(int type, ByteBuffer payload) {
if (D3DKMTSetProperties == 0) {
return -1;
}
try (var stack = MemoryStack.stackPush()) {
var buff = stack.calloc(0x18).order(ByteOrder.nativeOrder());
buff.putInt(0, type);
buff.putInt(4, payload.remaining());
buff.putLong(16, MemoryUtil.memAddress(payload));
return JNI.callPI(MemoryUtil.memAddress(buff), D3DKMTSetProperties);
}
}
private static int query(int handle, int type, ByteBuffer payload) {
if (D3DKMTQueryAdapterInfo == 0) {
return -1;
}
try (var stack = MemoryStack.stackPush()) {
var buff = stack.calloc(0x14).order(ByteOrder.nativeOrder());
buff.putInt(0, handle);
buff.putInt(4, type);
buff.putLong(8, MemoryUtil.memAddress(payload));
buff.putInt(16, payload.remaining());
return JNI.callPI(MemoryUtil.memAddress(buff), D3DKMTQueryAdapterInfo);
}
}
private static int closeHandle(int handle) {
if (D3DKMTCloseAdapter == 0) {
return -1;
}
try (var stack = MemoryStack.stackPush()) {
var buff = stack.calloc(0x4).order(ByteOrder.nativeOrder());
buff.putInt(0, handle);
return JNI.callPI(MemoryUtil.memAddress(buff), D3DKMTCloseAdapter);
}
}
private static int queryAdapterType(int handle, int[] out) {
int ret;
try (var stack = MemoryStack.stackPush()) {
var buff = stack.calloc(0x4).order(ByteOrder.nativeOrder());
//KMTQAITYPE_ADAPTERTYPE
if ((ret=query(handle, 15, buff))<0) {
return ret;
}
out[0] = buff.getInt(0);
}
return 0;
}
private static int queryAdapterIcd(int handle, String[] out) {
int ret;
try (var stack = MemoryStack.stackPush()) {
var buff = stack.calloc(260*2+8).order(ByteOrder.nativeOrder());
//KMTQAITYPE_UMOPENGLINFO
if ((ret=query(handle, 2, buff))<0) {
return ret;
}
int len = Math.min(MemoryUtil.memLengthNT2(buff), 260*2);
out[0] = MemoryUtil.memUTF16(buff.limit(len));
}
return 0;
}
private record AdapterInfo(String icdPath, int type, long luid, int vendor, int device, int subSystem) {
@Override
public String toString() {
String LUID = Integer.toHexString((int) ((luid>>>32)&0xFFFFFFFFL))+"-"+Integer.toHexString((int) (luid&0xFFFFFFFFL));
return "{type=%s, luid=%s, vendor=%s, device=%s, subSys=%s, icd=\"%s\"}".formatted(Integer.toString(type),LUID, Integer.toHexString(vendor), Integer.toHexString(device), Integer.toHexString(subSystem), icdPath);
}
}
private record PCIDeviceId(int vendor, int device, int subVendor, int subSystem, int revision, int busType) {}
private static int queryPCIAddress(int handle, int index, PCIDeviceId[] deviceOut) {
int ret = 0;
try (var stack = MemoryStack.stackPush()) {
var buff = stack.calloc(4*7).order(ByteOrder.nativeOrder());
buff.putInt(0, index);
//KMTQAITYPE_PHYSICALADAPTERDEVICEIDS
if ((ret = query(handle, 31, buff)) < 0) {
return ret;
}
deviceOut[0] = new PCIDeviceId(buff.getInt(4),buff.getInt(8),buff.getInt(12),buff.getInt(16),buff.getInt(20),buff.getInt(24));
return 0;
}
}
private static int enumAdapters(Consumer<AdapterInfo> consumer) {
if (D3DKMTEnumAdapters2 == 0) {
return -1;
}
int ret = 0;
try (var stack = MemoryStack.stackPush()) {
var query = stack.calloc(0x10).order(ByteOrder.nativeOrder());
if ((ret = JNI.callPI(MemoryUtil.memAddress(query), D3DKMTEnumAdapters2)) < 0) {
return ret;
}
int adapterCount = query.getInt(0);
var adapterList = stack.calloc(0x14 * adapterCount).order(ByteOrder.nativeOrder());
query.putLong(8, MemoryUtil.memAddress(adapterList));
if ((ret = JNI.callPI(MemoryUtil.memAddress(query), D3DKMTEnumAdapters2)) < 0) {
return ret;
}
adapterCount = query.getInt(0);
for (int adapterIndex = 0; adapterIndex < adapterCount; adapterIndex++) {
var adapter = adapterList.slice(adapterIndex*0x14, 0x14).order(ByteOrder.nativeOrder());
//We only care about these 2
int handle = adapter.getInt(0);
long luid = adapter.getLong(4);
int[] type = new int[1];
if ((ret = queryAdapterType(handle, type))<0) {
Logger.error("Query type error: " + ret);
//We errored
if (closeHandle(handle) < 0) {
throw new IllegalStateException();
}
continue;
}
String[] icd = new String[1];
if ((ret = queryAdapterIcd(handle, icd))<0) {
Logger.error("Query icd error: " + ret);
//We errored
if (closeHandle(handle) < 0) {
throw new IllegalStateException();
}
continue;
}
PCIDeviceId[] out = new PCIDeviceId[1];
//Get the root adapter device
if ((ret = queryPCIAddress(handle, 0, out)) < 0) {
Logger.error("Query pci error: " + ret);
//We errored
if (closeHandle(handle) < 0) {
throw new IllegalStateException();
}
continue;
}
int subSys = (out[0].subSystem<<16)|out[0].subVendor;//It seems the pci subsystem address is a joined integer
consumer.accept(new AdapterInfo(icd[0], type[0], luid, out[0].vendor, out[0].device, subSys));
if (closeHandle(handle) < 0) {
throw new IllegalStateException();
}
}
}
return 0;
}
//=======================================================================================
private static final int[] HDC_STUB = { 0x48, 0x83, 0xC1, 0x0C, 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x48, 0x89, 0x01, 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2F, 0x51, 0xFF, 0xD0, 0x59, 0x8B, 0x41, 0x08, 0x89, 0x41, 0xFC, 0x48, 0x31, 0xC0, 0x89, 0x41, 0x08, 0xC3 };
private static void insertLong(long l, byte[] out, int offset) {
for (int i = 0; i < 8; i++) {
out[i+offset] = (byte)(l & 0xFF);
l >>= 8;
}
}
private static byte[] createFinishedHDCStub(long luid) {
byte[] stub = new byte[HDC_STUB.length];
for (int i = 0; i < stub.length; i++) {
stub[i] = (byte) HDC_STUB[i];
}
insertLong(luid, stub, 6);
insertLong(D3DKMTOpenAdapterFromLuid, stub, 19);
return stub;
}
private static final long D3DKMTOpenAdapterFromLuid = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTOpenAdapterFromLuid");
private static final long D3DKMTOpenAdapterFromHdc = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTOpenAdapterFromHdc");
private static final long VirtualProtect = apiGetFunctionAddressOptional(Kernel32.getLibrary(), "VirtualProtect");
private static byte[] toByteArray(int... array) {
byte[] res = new byte[array.length];
for (int i = 0; i < array.length; i++) {
res[i] = (byte) array[i];
}
return res;
}
private static void VirtualProtect(long addr, long size) {
try (var stack = MemoryStack.stackPush()) {
var oldProtection = stack.calloc(4);
JNI.callPPPPI(addr, size, 0x40/*PAGE_EXECUTE_READWRITE*/, MemoryUtil.memAddress(oldProtection), VirtualProtect);
}
}
private static void memcpy(long ptr, byte[] data) {
for (int i = 0; i < data.length; i++) {
MemoryUtil.memPutByte(ptr + i, data[i]);
}
}
private static void installHDCStub(long adapterLuid) {
if (D3DKMTOpenAdapterFromHdc == 0 || VirtualProtect == 0 || D3DKMTOpenAdapterFromLuid == 0) {
return;
}
Logger.info("AdapterLuid callback at: " + Long.toHexString(D3DKMTOpenAdapterFromLuid));
var stub = createFinishedHDCStub(adapterLuid);
VirtualProtect(D3DKMTOpenAdapterFromHdc, stub.length);
memcpy(D3DKMTOpenAdapterFromHdc, stub);
}
private static byte[] createIntelStub(long origA, long origB, long jmpA, long jmpB) {
byte[] stub = toByteArray(0xFE, 0x0D, 0x63, 0x00, 0x00, 0x00, 0x80, 0x3D, 0x5C, 0x00, 0x00, 0x00, 0x02, 0x75, 0x07, 0x48, 0x31, 0xC0, 0x48, 0xF7, 0xD0, 0xC3, 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x48, 0x8B, 0x0D, 0x43, 0x00, 0x00, 0x00, 0x48, 0x89, 0x08, 0x48, 0x8B, 0x0D, 0x41, 0x00, 0x00, 0x00, 0x48, 0x89, 0x48, 0x08, 0x80, 0x3D, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x74, 0x1D, 0x50, 0xFF, 0xD0, 0x58, 0x48, 0x8B, 0x0D, 0x31, 0x00, 0x00, 0x00, 0x48, 0x89, 0x08, 0x48, 0x8B, 0x0D, 0x2F, 0x00, 0x00, 0x00, 0x48, 0x89, 0x48, 0x08, 0x48, 0x31, 0xC0, 0xC3, 0x48, 0x8B, 0x41, 0x08, 0xC7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x31, 0xC0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0);
insertLong(D3DKMTQueryAdapterInfo, stub, 24);
stub[0x69] = 3;
insertLong(origA, stub, 0x6a);
insertLong(origB, stub, 0x6a+8);
insertLong(jmpA, stub, 0x6a+16);
insertLong(jmpB, stub, 0x6a+24);
return stub;
}
private static byte[] createSimpleStub(long origA, long origB) {
byte[] stub = toByteArray(0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, 0x48, 0x8B, 0x0D, 0x15, 0x00, 0x00, 0x00, 0x48, 0x89, 0x08, 0x48, 0x8B, 0x0D, 0x13, 0x00, 0x00, 0x00, 0x48, 0x89, 0x48, 0x08, 0x48, 0x31, 0xC0, 0x48, 0xF7, 0xD0, 0xC3, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0);
insertLong(D3DKMTQueryAdapterInfo, stub, 2);
insertLong(origA, stub, 38);
insertLong(origB, stub, 38+8);
return stub;
}
private static void installQueryStub(boolean installIntelBypass) {
if (D3DKMTQueryAdapterInfo == 0 || VirtualProtect == 0) {
return;
}
VirtualProtect(D3DKMTQueryAdapterInfo, 0x10);
int MAX_STUB_SIZE = 1024;
long stubPtr = MemoryUtil.nmemAlloc(MAX_STUB_SIZE);
VirtualProtect(stubPtr, MAX_STUB_SIZE);
Logger.info("Do stub at: " + Long.toHexString(stubPtr));
long origA = MemoryUtil.memGetLong(D3DKMTQueryAdapterInfo);
long origB = MemoryUtil.memGetLong(D3DKMTQueryAdapterInfo+8);
var jmpStub = new byte[]{ 0x48, (byte) 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xFF, (byte) 0xE0};
insertLong(stubPtr, jmpStub, 2);
memcpy(D3DKMTQueryAdapterInfo, jmpStub);
Logger.info("D3DKMTQueryAdapterInfo at: " + Long.toHexString(D3DKMTQueryAdapterInfo));
long jmpA = MemoryUtil.memGetLong(D3DKMTQueryAdapterInfo);
long jmpB = MemoryUtil.memGetLong(D3DKMTQueryAdapterInfo+8);
byte[] stub;
if (installIntelBypass) {
stub = createIntelStub(origA, origB, jmpA, jmpB);
} else {
stub = createSimpleStub(origA, origB);
}
memcpy(stubPtr, stub);
Logger.info("QueryAdapterInfo stubs installed");
}
public static void doSelector(int index) {
List<AdapterInfo> adapters = new ArrayList<>();
//Must support rendering and must not be software renderer
if (enumAdapters(adapter->{if ((adapter.type&5)==1) adapters.add(adapter); }) < 0) {
return;
}
for (var adapter : adapters) {
Logger.error(adapter.toString());
}
var adapter = adapters.get(index);
installHDCStub(adapter.luid);
installQueryStub(adapter.icdPath.matches("\\\\ig[a-z0-9]+icd(32|64)\\.dll$"));
setPCIProperties(1/*USER*/, adapter.vendor, adapter.device, adapter.subSystem);
setPCIProperties(2/*USER GLOBAL*/, adapter.vendor, adapter.device, adapter.subSystem);
}
}

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

@@ -0,0 +1,7 @@
package me.cortex.voxy.client;
public class LoadException extends RuntimeException {
public LoadException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@@ -0,0 +1,36 @@
package me.cortex.voxy.client;
import me.cortex.voxy.common.world.WorldEngine;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class RenderStatistics {
public static boolean enabled = false;
public static final int[] hierarchicalTraversalCounts = new int[WorldEngine.MAX_LOD_LAYER+1];
public static final int[] hierarchicalRenderSections = new int[WorldEngine.MAX_LOD_LAYER+1];
public static final int[] visibleSections = new int[WorldEngine.MAX_LOD_LAYER+1];
public static final int[] quadCount = new int[WorldEngine.MAX_LOD_LAYER+1];
public static void addDebug(List<String> debug) {
if (!enabled) {
return;
}
debug.add("HTC: [" + Arrays.stream(flipCopy(RenderStatistics.hierarchicalTraversalCounts)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
debug.add("HRS: [" + Arrays.stream(flipCopy(RenderStatistics.hierarchicalRenderSections)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
debug.add("VS: [" + Arrays.stream(flipCopy(RenderStatistics.visibleSections)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
debug.add("QC: [" + Arrays.stream(flipCopy(RenderStatistics.quadCount)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
}
private static int[] flipCopy(int[] array) {
int[] ret = new int[array.length];
int i = ret.length;
for (int j : array) {
ret[--i] = j;
}
return ret;
}
}

View File

@@ -0,0 +1,89 @@
package me.cortex.voxy.client;
import java.util.ArrayList;
public class TimingStatistics {
public static double ROLLING_WEIGHT = 0.96;
private static final ArrayList<TimeSampler> allSamplers = new ArrayList<>();
public static final class TimeSampler {
private boolean running;
private long timestamp;
private long runtime;
private double rolling;
public TimeSampler() {
TimingStatistics.allSamplers.add(this);
}
private void reset() {
if (this.running) {
throw new IllegalStateException();
}
this.runtime = 0;
}
public void start() {
if (this.running) {
throw new IllegalStateException();
}
this.running = true;
this.timestamp = System.nanoTime();
}
public void stop() {
if (!this.running) {
throw new IllegalStateException();
}
this.running = false;
this.runtime += System.nanoTime() - this.timestamp;
}
public void subtract(TimeSampler sampler) {
this.runtime -= sampler.runtime;
}
private void update() {
double time = ((double) (this.runtime / 1000)) / 1000;
this.rolling = Math.max(this.rolling * ROLLING_WEIGHT + time * (1-ROLLING_WEIGHT), time);
}
public double getRolling() {
return this.rolling;
}
public String pVal() {
return String.format("%6.3f", this.rolling);
}
}
public static void resetSamplers() {
TimingStatistics.allSamplers.forEach(TimeSampler::reset);
}
private static void updateSamplers() {
TimingStatistics.allSamplers.forEach(TimeSampler::update);
}
public static TimeSampler all = new TimeSampler();
public static TimeSampler main = new TimeSampler();
public static TimeSampler dynamic = new TimeSampler();
public static TimeSampler postDynamic = new TimeSampler();
public static TimeSampler A = new TimeSampler();
public static TimeSampler B = new TimeSampler();
public static TimeSampler C = new TimeSampler();
public static TimeSampler D = new TimeSampler();
public static TimeSampler E = new TimeSampler();
public static TimeSampler F = new TimeSampler();
public static TimeSampler G = new TimeSampler();
public static TimeSampler H = new TimeSampler();
public static TimeSampler I = new TimeSampler();
public static void update() {
updateSamplers();
}
}

View File

@@ -1,39 +0,0 @@
package me.cortex.voxy.client;
import me.cortex.voxy.client.core.VoxelCore;
import me.cortex.voxy.client.saver.ContextSelectionSystem;
import me.cortex.voxy.client.terrain.WorldImportCommand;
import me.cortex.voxy.common.config.Serialization;
import me.cortex.voxy.common.storage.compressors.ZSTDCompressor;
import me.cortex.voxy.common.storage.config.StorageConfig;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.client.world.ClientWorld;
public class Voxy implements ClientModInitializer {
public static final String VERSION;
static {
ModContainer mod = (ModContainer) FabricLoader.getInstance().getModContainer("voxy").orElseThrow(NullPointerException::new);
VERSION = mod.getMetadata().getVersion().getFriendlyString();
Serialization.init();
}
@Override
public void onInitializeClient() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
dispatcher.register(WorldImportCommand.register());
});
}
private static final ContextSelectionSystem selector = new ContextSelectionSystem();
public static VoxelCore createVoxelCore(ClientWorld world) {
var selection = selector.getBestSelectionOrCreate(world);
return new VoxelCore(selection);
}
}

View File

@@ -0,0 +1,142 @@
package me.cortex.voxy.client;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import me.cortex.voxy.client.core.VoxyRenderSystem;
import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer;
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.debug.DebugScreenDisplayer;
import net.minecraft.client.gui.components.debug.DebugScreenEntries;
import net.minecraft.client.gui.components.debug.DebugScreenEntry;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.LevelChunk;
import org.jspecify.annotations.Nullable;
import java.util.HashSet;
import java.util.function.Consumer;
import java.util.function.Function;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import me.cortex.voxy.common.network.VoxyNetwork;
import me.cortex.voxy.commonImpl.WorldIdentifier;
public class VoxyClient implements ClientModInitializer {
private static final HashSet<String> FREX = new HashSet<>();
public static void initVoxyClient() {
Capabilities.init();//Ensure clinit is called
if (Capabilities.INSTANCE.hasBrokenDepthSampler) {
Logger.error("AMD broken depth sampler detected, voxy does not work correctly and has been disabled, this will hopefully be fixed in the future");
}
boolean systemSupported = Capabilities.INSTANCE.compute && Capabilities.INSTANCE.indirectParameters && !Capabilities.INSTANCE.hasBrokenDepthSampler;
if (systemSupported) {
SharedIndexBuffer.INSTANCE.id();
BudgetBufferRenderer.init();
VoxyCommon.setInstanceFactory(VoxyClientInstance::new);
if (!Capabilities.INSTANCE.subgroup) {
Logger.warn("GPU does not support subgroup operations, expect some performance degradation");
}
} else {
Logger.error("Voxy is unsupported on your system.");
}
}
@Override
public void onInitializeClient() {
DebugScreenEntries.register(Identifier.fromNamespaceAndPath("voxy", "version"), new DebugScreenEntry() {
@Override
public void display(DebugScreenDisplayer lines, @Nullable Level level, @Nullable LevelChunk levelChunk, @Nullable LevelChunk levelChunk2) {
if (!VoxyCommon.isAvailable()) {
lines.addLine(ChatFormatting.RED + "voxy-"+VoxyCommon.MOD_VERSION);//Voxy installed, not avalible
return;
}
var instance = VoxyCommon.getInstance();
if (instance == null) {
lines.addLine(ChatFormatting.YELLOW + "voxy-" + VoxyCommon.MOD_VERSION);//Voxy avalible, no instance active
return;
}
VoxyRenderSystem vrs = null;
var wr = Minecraft.getInstance().levelRenderer;
if (wr != null) vrs = ((IGetVoxyRenderSystem) wr).getVoxyRenderSystem();
//Voxy instance active
lines.addLine((vrs==null?ChatFormatting.DARK_GREEN:ChatFormatting.GREEN)+"voxy-"+VoxyCommon.MOD_VERSION);
}
});
DebugScreenEntries.register(Identifier.fromNamespaceAndPath("voxy","debug"), new VoxyDebugScreenEntry());
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
if (VoxyCommon.isAvailable()) {
dispatcher.register(VoxyCommands.register());
}
});
FabricLoader.getInstance()
.getEntrypoints("frex_flawless_frames", Consumer.class)
.forEach(api -> ((Consumer<Function<String,Consumer<Boolean>>>)api).accept(name->active->{if (active) {
FREX.add(name);
} else {
FREX.remove(name);
}}));
ClientPlayNetworking.registerGlobalReceiver(VoxyNetwork.ConfigSyncPayload.TYPE, (payload, context) -> {
context.client().execute(() -> {
// Update client render distance cap if needed, or store server settings
// For now, we can perhaps log it or update a transient config
Logger.info("Received server view distance: " + payload.viewDistance());
// We might want to clamp the local render distance to the server's max if strictly enforced,
// or just use it as a hint for where to expect data.
// The user requirement says: "server sends max view distance to client... client renders up to client setting, server updates outside sim distance up to server max"
// So we should probably store this "server max view distance" somewhere in VoxyClientInstance.
if (VoxyCommon.getInstance() instanceof VoxyClientInstance clientInstance) {
clientInstance.setServerViewDistance(payload.viewDistance());
}
});
});
ClientPlayNetworking.registerGlobalReceiver(VoxyNetwork.LodUpdatePayload.TYPE, (payload, context) -> {
// Deserialize off-thread if possible? Packet handling is on netty thread or main thread depending on configuration.
// But we can just schedule it.
// Actually deserialize needs Mapper which is world specific?
// The packet doesn't contain world ID?
// We assume it's for the current client world.
// Wait, we need to know WHICH world this update is for if we have dimensions?
// Usually packets are for the current world the player is in.
context.client().execute(() -> {
// Logger.info("Received LOD update packet, size: " + payload.data().length);
if (VoxyCommon.getInstance() instanceof VoxyClientInstance clientInstance) {
clientInstance.handleLodUpdate(payload);
}
});
});
}
public static boolean isFrexActive() {
return !FREX.isEmpty();
}
public static int getOcclusionDebugState() {
return 0;
}
public static boolean disableSodiumChunkRender() {
return false;// getOcclusionDebugState() != 0;
}
}

View File

@@ -0,0 +1,171 @@
package me.cortex.voxy.client;
import me.cortex.voxy.client.compat.FlashbackCompat;
import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.client.mixin.sodium.AccessorSodiumWorldRenderer;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.StorageConfigUtil;
import me.cortex.voxy.common.config.ConfigBuildCtx;
import me.cortex.voxy.common.config.Serialization;
import me.cortex.voxy.common.config.compressors.ZSTDCompressor;
import me.cortex.voxy.common.config.section.SectionSerializationStorage;
import me.cortex.voxy.common.config.section.SectionStorage;
import me.cortex.voxy.common.config.section.SectionStorageConfig;
import me.cortex.voxy.common.config.storage.other.CompressionStorageAdaptor;
import me.cortex.voxy.common.config.storage.rocksdb.RocksDBStorageBackend;
import me.cortex.voxy.commonImpl.ImportManager;
import me.cortex.voxy.commonImpl.VoxyInstance;
import me.cortex.voxy.commonImpl.WorldIdentifier;
import net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer;
import net.minecraft.client.Minecraft;
import net.minecraft.world.level.storage.LevelResource;
import java.nio.file.Files;
import java.nio.file.Path;
public class VoxyClientInstance extends VoxyInstance {
public static boolean isInGame = false;
private final SectionStorageConfig storageConfig;
private final Path basePath;
private final boolean noIngestOverride;
private int serverViewDistance = 32;
public VoxyClientInstance() {
super();
var path = FlashbackCompat.getReplayStoragePath();
this.noIngestOverride = path != null;
if (path == null) {
path = getBasePath();
}
this.basePath = path;
this.storageConfig = StorageConfigUtil.getCreateStorageConfig(Config.class, c->c.version==1&&c.sectionStorageConfig!=null, ()->DEFAULT_STORAGE_CONFIG, path).sectionStorageConfig;
this.updateDedicatedThreads();
}
public long getLastLodUpdate() {
return lastLodUpdate;
}
public int getLodUpdatesReceived() {
return lodUpdatesReceived;
}
public void setServerViewDistance(int distance) {
this.serverViewDistance = distance;
// Trigger a re-evaluation of render distance?
Logger.info("Updating client view distance from server to: " + distance);
}
public int getServerViewDistance() {
return this.serverViewDistance;
}
private long lastLodUpdate;
private int lodUpdatesReceived;
public void handleLodUpdate(me.cortex.voxy.common.network.VoxyNetwork.LodUpdatePayload payload) {
this.lastLodUpdate = System.currentTimeMillis();
this.lodUpdatesReceived++;
// 1. Get current client world
var player = Minecraft.getInstance().player;
if (player == null) return;
var level = player.level();
var wi = WorldIdentifier.of(level);
if (wi == null) return;
var engine = this.getNullable(wi);
if (engine == null) return;
// 2. Deserialize payload using engine's mapper
// Note: Payload deserialization requires Mapper to resolve IDs.
// We need to ensure the engine's mapper is up to date with the server's palette?
// No, the payload contains palette strings/states.
// The deserializer maps them to LOCAL IDs using the provided mapper.
try {
var section = payload.deserialize(engine.getMapper());
// 3. Insert update into engine
me.cortex.voxy.common.world.WorldUpdater.insertUpdate(engine, section);
} catch (Exception e) {
Logger.error("Failed to handle LOD update", e);
}
}
@Override
public void updateDedicatedThreads() {
int target = VoxyConfig.CONFIG.serviceThreads;
if (!VoxyConfig.CONFIG.dontUseSodiumBuilderThreads) {
var swr = SodiumWorldRenderer.instanceNullable();
if (swr != null) {
var rsm = ((AccessorSodiumWorldRenderer) swr).getRenderSectionManager();
if (rsm != null) {
this.setNumThreads(Math.max(1, target - rsm.getBuilder().getTotalThreadCount()));
return;
}
}
}
this.setNumThreads(target);
}
@Override
protected ImportManager createImportManager() {
return new ClientImportManager();
}
@Override
protected SectionStorage createStorage(WorldIdentifier identifier) {
var ctx = new ConfigBuildCtx();
ctx.setProperty(ConfigBuildCtx.BASE_SAVE_PATH, this.basePath.toString());
ctx.setProperty(ConfigBuildCtx.WORLD_IDENTIFIER, identifier.getWorldId());
ctx.pushPath(ConfigBuildCtx.DEFAULT_STORAGE_PATH);
return this.storageConfig.build(ctx);
}
public Path getStorageBasePath() {
return this.basePath;
}
@Override
public boolean isIngestEnabled(WorldIdentifier worldId) {
return (!this.noIngestOverride) && VoxyConfig.CONFIG.ingestEnabled;
}
private static class Config {
public int version = 1;
public SectionStorageConfig sectionStorageConfig;
}
private static final Config DEFAULT_STORAGE_CONFIG;
static {
var config = new Config();
config.sectionStorageConfig = StorageConfigUtil.createDefaultSerializer();
DEFAULT_STORAGE_CONFIG = config;
}
private static Path getBasePath() {
Path basePath = Minecraft.getInstance().gameDirectory.toPath().resolve(".voxy").resolve("saves");
var iserver = Minecraft.getInstance().getSingleplayerServer();
if (iserver != null) {
basePath = iserver.getWorldPath(LevelResource.ROOT).resolve("voxy");
} else {
var netHandle = Minecraft.getInstance().gameMode;
if (netHandle == null) {
Logger.error("Network handle null");
basePath = basePath.resolve("UNKNOWN");
} else {
var info = netHandle.connection.getServerData();
if (info == null) {
Logger.error("Server info null");
basePath = basePath.resolve("UNKNOWN");
} else {
if (info.isRealm()) {
basePath = basePath.resolve("realms");
} else {
basePath = basePath.resolve(info.ip.replace(":", "_"));
}
}
}
}
return basePath.toAbsolutePath();
}
}

View File

@@ -0,0 +1,275 @@
package me.cortex.voxy.client;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.commonImpl.VoxyCommon;
import me.cortex.voxy.commonImpl.WorldIdentifier;
import me.cortex.voxy.commonImpl.importers.DHImporter;
import me.cortex.voxy.commonImpl.importers.WorldImporter;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.Minecraft;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.dimension.DimensionType;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
public class VoxyCommands {
public static LiteralArgumentBuilder<FabricClientCommandSource> register() {
var imports = ClientCommandManager.literal("import")
.then(ClientCommandManager.literal("world")
.then(ClientCommandManager.argument("world_name", StringArgumentType.string())
.suggests(VoxyCommands::importWorldSuggester)
.executes(VoxyCommands::importWorld)))
.then(ClientCommandManager.literal("bobby")
.then(ClientCommandManager.argument("world_name", StringArgumentType.string())
.suggests(VoxyCommands::importBobbySuggester)
.executes(VoxyCommands::importBobby)))
.then(ClientCommandManager.literal("raw")
.then(ClientCommandManager.argument("path", StringArgumentType.string())
.executes(VoxyCommands::importRaw)))
.then(ClientCommandManager.literal("zip")
.then(ClientCommandManager.argument("zipPath", StringArgumentType.string())
.executes(VoxyCommands::importZip)
.then(ClientCommandManager.argument("innerPath", StringArgumentType.string())
.executes(VoxyCommands::importZip))))
.then(ClientCommandManager.literal("cancel")
.executes(VoxyCommands::cancelImport));
if (DHImporter.HasRequiredLibraries) {
imports = imports
.then(ClientCommandManager.literal("distant_horizons")
.then(ClientCommandManager.argument("sqlDbPath", StringArgumentType.string())
.executes(VoxyCommands::importDistantHorizons)));
}
return ClientCommandManager.literal("voxy")//.requires((ctx)-> VoxyCommon.getInstance() != null)
.then(ClientCommandManager.literal("reload")
.executes(VoxyCommands::reloadInstance))
.then(imports);
}
private static int reloadInstance(CommandContext<FabricClientCommandSource> ctx) {
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
if (instance == null) {
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
return 1;
}
var wr = Minecraft.getInstance().levelRenderer;
if (wr!=null) {
((IGetVoxyRenderSystem)wr).shutdownRenderer();
}
VoxyCommon.shutdownInstance();
System.gc();
VoxyCommon.createInstance();
var r = Minecraft.getInstance().levelRenderer;
if (r != null) r.allChanged();
return 0;
}
private static int importDistantHorizons(CommandContext<FabricClientCommandSource> ctx) {
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
if (instance == null) {
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
return 1;
}
var dbFile = new File(ctx.getArgument("sqlDbPath", String.class));
if (!dbFile.exists()) {
return 1;
}
if (dbFile.isDirectory()) {
dbFile = dbFile.toPath().resolve("DistantHorizons.sqlite").toFile();
if (!dbFile.exists()) {
return 1;
}
}
File dbFile_ = dbFile;
var engine = WorldIdentifier.ofEngine(Minecraft.getInstance().level);
if (engine==null)return 1;
return instance.getImportManager().makeAndRunIfNone(engine, ()->
new DHImporter(dbFile_, engine, Minecraft.getInstance().level, instance.getServiceManager(), instance.savingServiceRateLimiter))?0:1;
}
private static boolean fileBasedImporter(File directory) {
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
if (instance == null) {
return false;
}
var engine = WorldIdentifier.ofEngine(Minecraft.getInstance().level);
if (engine==null) return false;
return instance.getImportManager().makeAndRunIfNone(engine, ()->{
var importer = new WorldImporter(engine, Minecraft.getInstance().level, instance.getServiceManager(), instance.savingServiceRateLimiter);
importer.importRegionDirectoryAsync(directory);
return importer;
});
}
private static int importRaw(CommandContext<FabricClientCommandSource> ctx) {
if (VoxyCommon.getInstance() == null) {
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
return 1;
}
return fileBasedImporter(new File(ctx.getArgument("path", String.class)))?0:1;
}
private static int importBobby(CommandContext<FabricClientCommandSource> ctx) {
if (VoxyCommon.getInstance() == null) {
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
return 1;
}
var file = new File(".bobby").toPath().resolve(ctx.getArgument("world_name", String.class)).toFile();
return fileBasedImporter(file)?0:1;
}
private static CompletableFuture<Suggestions> importWorldSuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
return fileDirectorySuggester(Minecraft.getInstance().gameDirectory.toPath().resolve("saves"), sb);
}
private static CompletableFuture<Suggestions> importBobbySuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
return fileDirectorySuggester(Minecraft.getInstance().gameDirectory.toPath().resolve(".bobby"), sb);
}
private static CompletableFuture<Suggestions> fileDirectorySuggester(Path dir, SuggestionsBuilder sb) {
var str = sb.getRemaining().replace("\\\\", "\\").replace("\\", "/");
if (str.startsWith("\"")) {
str = str.substring(1);
}
if (str.endsWith("\"")) {
str = str.substring(0,str.length()-1);
}
var remaining = str;
if (str.contains("/")) {
int idx = str.lastIndexOf('/');
remaining = str.substring(idx+1);
try {
dir = dir.resolve(str.substring(0, idx));
} catch (Exception e) {
return Suggestions.empty();
}
str = str.substring(0, idx+1);
} else {
str = "";
}
try {
var worlds = Files.list(dir).toList();
for (var world : worlds) {
if (!world.toFile().isDirectory()) {
continue;
}
var wn = world.getFileName().toString();
if (wn.equals(remaining)) {
continue;
}
if (SharedSuggestionProvider.matchesSubStr(remaining, wn) || SharedSuggestionProvider.matchesSubStr(remaining, '"'+wn)) {
wn = str+wn + "/";
sb.suggest(StringArgumentType.escapeIfRequired(wn));
}
}
} catch (IOException e) {}
return sb.buildFuture();
}
private static int importWorld(CommandContext<FabricClientCommandSource> ctx) {
if (VoxyCommon.getInstance() == null) {
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
return 1;
}
var name = ctx.getArgument("world_name", String.class);
var file = new File("saves").toPath().resolve(name);
name = name.toLowerCase(Locale.ROOT);
if (name.endsWith("/")) {
name = name.substring(0, name.length()-1);
}
if (file.resolve("level.dat").toFile().exists()) {
var dimFile = DimensionType.getStorageFolder(Minecraft.getInstance().level.dimension(), file)
.resolve("region")
.toFile();
if (!dimFile.isDirectory()) return 1;
return fileBasedImporter(dimFile)?0:1;
//We are in a world directory, so import the current dimension we are in
/*
for (var dim : new String[]{"overworld", "the_nether", "the_end"}) {//This is so annoying that you cant loop through all the dimensions
var id = ResourceKey.create(Registries.DIMENSION, Identifier.withDefaultNamespace(dim));
var dimPath = DimensionType.getStorageFolder(id, file);
dimPath = dimPath.resolve("region");
var dimFile = dimPath.toFile();
if (dimFile.isDirectory()) {//exists and is a directory
if (!fileBasedImporter(dimFile)) {
Logger.error("Failed to import dimension: " + id);
}
}
}*/
} else {
if (!(name.endsWith("region"))) {
file = file.resolve("region");
}
return fileBasedImporter(file.toFile()) ? 0 : 1;
}
}
private static int importZip(CommandContext<FabricClientCommandSource> ctx) {
var zip = new File(ctx.getArgument("zipPath", String.class));
var innerDir = "region/";
try {
innerDir = ctx.getArgument("innerPath", String.class);
} catch (Exception e) {}
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
if (instance == null) {
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
return 1;
}
String finalInnerDir = innerDir;
var engine = WorldIdentifier.ofEngine(Minecraft.getInstance().level);
if (engine != null) {
return instance.getImportManager().makeAndRunIfNone(engine, () -> {
var importer = new WorldImporter(engine, Minecraft.getInstance().level, instance.getServiceManager(), instance.savingServiceRateLimiter);
importer.importZippedRegionDirectoryAsync(zip, finalInnerDir);
return importer;
}) ? 0 : 1;
}
return 1;
}
private static int cancelImport(CommandContext<FabricClientCommandSource> ctx) {
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
if (instance == null) {
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
return 1;
}
var world = WorldIdentifier.ofEngineNullable(Minecraft.getInstance().level);
if (world != null) {
return instance.getImportManager().cancelImport(world)?0:1;
}
return 1;
}
}

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

@@ -0,0 +1,41 @@
package me.cortex.voxy.client.compat;
import com.moulberry.flashback.Flashback;
import com.moulberry.flashback.playback.ReplayServer;
import com.moulberry.flashback.record.FlashbackMeta;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.config.section.SectionStorageConfig;
import net.fabricmc.loader.api.FabricLoader;
import java.nio.file.Path;
public class FlashbackCompat {
public static final boolean FLASHBACK_INSTALLED = FabricLoader.getInstance().isModLoaded("flashback");
public static Path getReplayStoragePath() {
if (!FLASHBACK_INSTALLED) {
return null;
}
return getReplayStoragePath0();
}
private static Path getReplayStoragePath0() {
ReplayServer replayServer = Flashback.getReplayServer();
if (replayServer != null) {
FlashbackMeta meta = replayServer.getMetadata();
if (meta != null) {
var path = ((IFlashbackMeta)meta).getVoxyPath();
if (path != null) {
Logger.info("Flashback replay server exists and meta exists");
if (path.exists()) {
Logger.info("Flashback voxy path exists in filesystem, using this as lod data source");
return path.toPath();
} else {
Logger.warn("Flashback meta had voxy path saved but path doesnt exist");
}
}
}
}
return null;
}
}

View File

@@ -0,0 +1,8 @@
package me.cortex.voxy.client.compat;
import java.io.File;
public interface IFlashbackMeta {
void setVoxyPath(File path);
File getVoxyPath();
}

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

@@ -1,28 +0,0 @@
package me.cortex.voxy.client.config;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
public class BlockConfig {
public Block block = Blocks.AIR;
public boolean ignoreBlock = false;
public final Face[] faces = new Face[6];
public float colourMultiplier = 1.0f;
public BlockConfig() {
for (int i = 0; i < this.faces.length; i++) {
this.faces[i] = new Face();
}
}
public static class Face {
public BooleanChoice occludes = BooleanChoice.DEFAULT;
public BooleanChoice canBeOccluded = BooleanChoice.DEFAULT;
}
public enum BooleanChoice {
DEFAULT,
TRUE,
FALSE
}
}

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

@@ -0,0 +1,25 @@
package me.cortex.voxy.client.config;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.caffeinemc.mods.sodium.client.config.ConfigManager;
import net.caffeinemc.mods.sodium.client.config.structure.OptionPage;
import net.caffeinemc.mods.sodium.client.gui.VideoSettingsScreen;
public class ModMenuIntegration implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return parent -> {
if (VoxyCommon.isAvailable()) {
var screen = (VideoSettingsScreen)VideoSettingsScreen.createScreen(parent);
var page = (OptionPage) ConfigManager.CONFIG.getModOptions().stream().filter(a->a.configId().equals("voxy")).findFirst().get().pages().get(0);
((IConfigPageSetter)screen).voxy$setPageJump(page);
return screen;
} else {
return null;
}
};
}
}

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

@@ -1,76 +0,0 @@
package me.cortex.voxy.client.config;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import me.cortex.voxy.client.core.Capabilities;
import me.cortex.voxy.client.saver.ContextSelectionSystem;
import net.fabricmc.loader.api.FabricLoader;
import org.lwjgl.opengl.GL;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
public class VoxyConfig {
private static final Gson GSON = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.setPrettyPrinting()
.excludeFieldsWithModifiers(Modifier.PRIVATE)
.create();
public static VoxyConfig CONFIG = loadOrCreate();
public boolean enabled = true;
public boolean ingestEnabled = true;
public int qualityScale = 12;
public int maxSections = 200_000;
public int renderDistance = 128;
public int geometryBufferSize = (1<<30)/8;
public int ingestThreads = 2;
public int savingThreads = 4;
public int renderThreads = 5;
public boolean useMeshShaderIfPossible = true;
public String defaultSaveConfig;
public static VoxyConfig loadOrCreate() {
var path = getConfigPath();
if (Files.exists(path)) {
try (FileReader reader = new FileReader(path.toFile())) {
var cfg = GSON.fromJson(reader, VoxyConfig.class);
if (cfg.defaultSaveConfig == null) {
//Shitty gson being a pain TODO: replace with a proper fix
cfg.defaultSaveConfig = ContextSelectionSystem.DEFAULT_STORAGE_CONFIG;
}
return cfg;
} catch (IOException e) {
System.err.println("Could not parse config");
e.printStackTrace();
}
}
var config = new VoxyConfig();
config.defaultSaveConfig = ContextSelectionSystem.DEFAULT_STORAGE_CONFIG;
return config;
}
public void save() {
//Unsafe, todo: fixme! needs to be atomic!
try {
Files.writeString(getConfigPath(), GSON.toJson(this));
} catch (IOException e) {
System.err.println("Failed to write config file");
e.printStackTrace();
}
}
private static Path getConfigPath() {
return FabricLoader.getInstance()
.getConfigDir()
.resolve("voxy-config.json");
}
public boolean useMeshShaders() {
return this.useMeshShaderIfPossible && Capabilities.INSTANCE.meshShaders;
}
}

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,151 +0,0 @@
package me.cortex.voxy.client.config;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import me.cortex.voxy.client.core.IGetVoxelCore;
import me.shedaniel.clothconfig2.ClothConfigDemo;
import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigCategory;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.text.Text;
import java.util.List;
public class VoxyConfigScreenFactory implements ModMenuApi {
private static VoxyConfig DEFAULT;
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return parent -> buildConfigScreen(parent, VoxyConfig.CONFIG);
}
private static Screen buildConfigScreen(Screen parent, VoxyConfig config) {
if (DEFAULT == null) {
DEFAULT = new VoxyConfig();
}
ConfigBuilder builder = ConfigBuilder.create()
.setParentScreen(parent)
.setTitle(Text.translatable("voxy.config.title"));
addGeneralCategory(builder, config);
addThreadsCategory(builder, config);
addStorageCategory(builder, config);
builder.setSavingRunnable(() -> {
//After saving the core should be reloaded/reset
var world = (IGetVoxelCore)MinecraftClient.getInstance().worldRenderer;
if (world != null) {
world.reloadVoxelCore();
}
VoxyConfig.CONFIG.save();
});
return builder.build();//
}
private static void addGeneralCategory(ConfigBuilder builder, VoxyConfig config) {
ConfigCategory category = builder.getOrCreateCategory(Text.translatable("voxy.config.general"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
/*
category.addEntry(entryBuilder.startSubCategory(Text.translatable("aaa"), List.of(entryBuilder.startBooleanToggle(Text.translatable("voxy.config.general.enabled"), config.enabled)
.setTooltip(Text.translatable("voxy.config.general.enabled.tooltip"))
.setSaveConsumer(val -> config.enabled = val)
.setDefaultValue(DEFAULT.enabled)
.build(), entryBuilder.startSubCategory(Text.translatable("bbb"), List.of(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.geometryBuffer"), config.geometryBufferSize, (1<<27)/8, ((1<<31)-1)/8)
.setTooltip(Text.translatable("voxy.config.general.geometryBuffer.tooltip"))
.setSaveConsumer(val -> config.geometryBufferSize = val)
.setDefaultValue(DEFAULT.geometryBufferSize)
.build())).build()
)).build());
*/
category.addEntry(entryBuilder.startBooleanToggle(Text.translatable("voxy.config.general.enabled"), config.enabled)
.setTooltip(Text.translatable("voxy.config.general.enabled.tooltip"))
.setSaveConsumer(val -> config.enabled = val)
.setDefaultValue(DEFAULT.enabled)
.build());
category.addEntry(entryBuilder.startBooleanToggle(Text.translatable("voxy.config.general.ingest"), config.ingestEnabled)
.setTooltip(Text.translatable("voxy.config.general.ingest.tooltip"))
.setSaveConsumer(val -> config.ingestEnabled = val)
.setDefaultValue(DEFAULT.ingestEnabled)
.build());
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.quality"), config.qualityScale, 8, 32)
.setTooltip(Text.translatable("voxy.config.general.quality.tooltip"))
.setSaveConsumer(val -> config.qualityScale = val)
.setDefaultValue(DEFAULT.qualityScale)
.build());
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.geometryBuffer"), config.geometryBufferSize, (1<<27)/8, ((1<<31)-1)/8)
.setTooltip(Text.translatable("voxy.config.general.geometryBuffer.tooltip"))
.setSaveConsumer(val -> config.geometryBufferSize = val)
.setDefaultValue(DEFAULT.geometryBufferSize)
.build());
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.maxSections"), config.maxSections, 100_000, 400_000)
.setTooltip(Text.translatable("voxy.config.general.maxSections.tooltip"))
.setSaveConsumer(val -> config.maxSections = val)
.setDefaultValue(DEFAULT.maxSections)
.build());
category.addEntry(entryBuilder.startIntField(Text.translatable("voxy.config.general.renderDistance"), config.renderDistance)
.setTooltip(Text.translatable("voxy.config.general.renderDistance.tooltip"))
.setSaveConsumer(val -> config.renderDistance = val)
.setDefaultValue(DEFAULT.renderDistance)
.build());
category.addEntry(entryBuilder.startBooleanToggle(Text.translatable("voxy.config.general.nvmesh"), config.useMeshShaderIfPossible)
.setTooltip(Text.translatable("voxy.config.general.nvmesh.tooltip"))
.setSaveConsumer(val -> config.useMeshShaderIfPossible = val)
.setDefaultValue(DEFAULT.useMeshShaderIfPossible)
.build());
//category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.compression"), config.savingCompressionLevel, 1, 21)
// .setTooltip(Text.translatable("voxy.config.general.compression.tooltip"))
// .setSaveConsumer(val -> config.savingCompressionLevel = val)
// .setDefaultValue(DEFAULT.savingCompressionLevel)
// .build());
}
private static void addThreadsCategory(ConfigBuilder builder, VoxyConfig config) {
ConfigCategory category = builder.getOrCreateCategory(Text.translatable("voxy.config.threads"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.threads.ingest"), config.ingestThreads, 1, Runtime.getRuntime().availableProcessors())
.setTooltip(Text.translatable("voxy.config.threads.ingest.tooltip"))
.setSaveConsumer(val -> config.ingestThreads = val)
.setDefaultValue(DEFAULT.ingestThreads)
.build());
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.threads.saving"), config.savingThreads, 1, Runtime.getRuntime().availableProcessors())
.setTooltip(Text.translatable("voxy.config.threads.saving.tooltip"))
.setSaveConsumer(val -> config.savingThreads = val)
.setDefaultValue(DEFAULT.savingThreads)
.build());
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.threads.render"), config.renderThreads, 1, Runtime.getRuntime().availableProcessors())
.setTooltip(Text.translatable("voxy.config.threads.render.tooltip"))
.setSaveConsumer(val -> config.renderThreads = val)
.setDefaultValue(DEFAULT.renderThreads)
.build());
}
private static void addStorageCategory(ConfigBuilder builder, VoxyConfig config) {
ConfigCategory category = builder.getOrCreateCategory(Text.translatable("voxy.config.storage"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
////Temporary until i figure out how to do more complex multi layer configuration for storage
//category.addEntry(entryBuilder.startStrField(Text.translatable("voxy.config.storage.path"), config.storagePath)
// .setTooltip(Text.translatable("voxy.config.storage.path.tooltip"))
// .setSaveConsumer(val -> config.storagePath = val)
// .setDefaultValue(DEFAULT.storagePath)
// .build());
}
}

View File

@@ -1,17 +0,0 @@
package me.cortex.voxy.client.config.screens;
import me.cortex.voxy.client.config.BlockConfig;
import me.shedaniel.clothconfig2.api.AbstractConfigEntry;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.minecraft.block.Blocks;
import net.minecraft.text.Text;
public class BlockConfigScreen {
private static final ConfigEntryBuilder ENTRY_BUILDER = ConfigEntryBuilder.create();
public static AbstractConfigListEntry<BlockConfig> makeScreen(BlockConfig config) {
var entry = ENTRY_BUILDER.startSubCategory(config.block.getName());
entry.add(UtilityScreens.makeBlockSelectionScreen(Text.literal("a"), Blocks.AIR, null));
return null;
}
}

View File

@@ -1,9 +0,0 @@
package me.cortex.voxy.client.config.screens;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
public class StorageConfigScreen {
public static AbstractConfigListEntry makeScreen() {
return null;
}
}

View File

@@ -1,29 +0,0 @@
package me.cortex.voxy.client.config.screens;
import me.cortex.voxy.client.config.BlockConfig;
import me.shedaniel.clothconfig2.api.AbstractConfigEntry;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import me.shedaniel.clothconfig2.impl.builders.DropdownMenuBuilder;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.registry.Registries;
import net.minecraft.text.Text;
import java.util.function.Consumer;
public class UtilityScreens {
private static final ConfigEntryBuilder ENTRY_BUILDER = ConfigEntryBuilder.create();
public static AbstractConfigListEntry<Block> makeBlockSelectionScreen(Text name, Block selectedBlock, Consumer<Block> onSave) {
return makeBlockSelectionScreen(name, Blocks.AIR, selectedBlock, onSave);
}
public static AbstractConfigListEntry<Block> makeBlockSelectionScreen(Text name, Block defaultBlock, Block selectedBlock, Consumer<Block> onSave) {
return ENTRY_BUILDER.startDropdownMenu(name,
DropdownMenuBuilder.TopCellElementBuilder.ofBlockObject(selectedBlock),
DropdownMenuBuilder.CellCreatorBuilder.ofBlockObject())
.setDefaultValue(defaultBlock)
.setSelections(Registries.BLOCK.stream().toList())
.setSaveConsumer(onSave)
.build();
}
}

View File

@@ -0,0 +1,268 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.RenderStatistics;
import me.cortex.voxy.client.TimingStatistics;
import me.cortex.voxy.client.VoxyClient;
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
import me.cortex.voxy.client.core.rendering.post.FullscreenBlit;
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.common.util.TrackedObject;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
import java.util.List;
import java.util.function.BooleanSupplier;
import static org.lwjgl.opengl.GL11C.GL_ALWAYS;
import static org.lwjgl.opengl.GL11C.GL_DEPTH_TEST;
import static org.lwjgl.opengl.GL11C.GL_EQUAL;
import static org.lwjgl.opengl.GL11C.GL_KEEP;
import static org.lwjgl.opengl.GL11C.GL_REPLACE;
import static org.lwjgl.opengl.GL11C.GL_STENCIL_TEST;
import static org.lwjgl.opengl.GL11C.glColorMask;
import static org.lwjgl.opengl.GL11C.glDisable;
import static org.lwjgl.opengl.GL11C.glEnable;
import static org.lwjgl.opengl.GL11C.glStencilFunc;
import static org.lwjgl.opengl.GL11C.glStencilMask;
import static org.lwjgl.opengl.GL11C.glStencilOp;
import static org.lwjgl.opengl.GL30C.GL_DEPTH24_STENCIL8;
import static org.lwjgl.opengl.GL30C.GL_FRAMEBUFFER;
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
import static org.lwjgl.opengl.GL42.GL_LEQUAL;
import static org.lwjgl.opengl.GL42.GL_NOTEQUAL;
import static org.lwjgl.opengl.GL42.glDepthFunc;
import static org.lwjgl.opengl.GL42.*;
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi;
import static org.lwjgl.opengl.GL45.glGetNamedFramebufferAttachmentParameteri;
import static org.lwjgl.opengl.GL45C.glBindTextureUnit;
public abstract class AbstractRenderPipeline extends TrackedObject {
private final BooleanSupplier frexStillHasWork;
private final AsyncNodeManager nodeManager;
private final NodeCleaner nodeCleaner;
private final HierarchicalOcclusionTraverser traversal;
protected AbstractSectionRenderer<?,?> sectionRenderer;
private final FullscreenBlit depthMaskBlit = new FullscreenBlit("voxy:post/fullscreen2.vert", "voxy:post/noop.frag");
private final FullscreenBlit depthSetBlit = new FullscreenBlit("voxy:post/fullscreen2.vert", "voxy:post/depth0.frag");
private final FullscreenBlit depthCopy = new FullscreenBlit("voxy:post/fullscreen2.vert", "voxy:post/depth_copy.frag");
public final DepthFramebuffer fb = new DepthFramebuffer(GL_DEPTH24_STENCIL8);
protected final boolean deferTranslucency;
private static final int DEPTH_SAMPLER = glGenSamplers();
static {
glSamplerParameteri(DEPTH_SAMPLER, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glSamplerParameteri(DEPTH_SAMPLER, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
protected AbstractRenderPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier, boolean deferTranslucency) {
this.frexStillHasWork = frexSupplier;
this.nodeManager = nodeManager;
this.nodeCleaner = nodeCleaner;
this.traversal = traversal;
this.deferTranslucency = deferTranslucency;
}
//Allows pipelines to configure model baking system
public void setupExtraModelBakeryData(ModelBakerySubsystem modelService) {}
public final void setSectionRenderer(AbstractSectionRenderer<?,?> sectionRenderer) {//Stupid java ordering not allowing something pre super
if (this.sectionRenderer != null) throw new IllegalStateException();
this.sectionRenderer = sectionRenderer;
}
//Called before the pipeline starts running, used to update uniforms etc
public void preSetup(Viewport<?> viewport) {
}
protected abstract int setup(Viewport<?> viewport, int sourceFramebuffer, int srcWidth, int srcHeight);
protected abstract void postOpaquePreTranslucent(Viewport<?> viewport);
protected void finish(Viewport<?> viewport, int sourceFrameBuffer, int srcWidth, int srcHeight) {
glDisable(GL_STENCIL_TEST);
glBindFramebuffer(GL_FRAMEBUFFER, sourceFrameBuffer);
}
public void runPipeline(Viewport<?> viewport, int sourceFrameBuffer, int srcWidth, int srcHeight) {
int depthTexture = this.setup(viewport, sourceFrameBuffer, srcWidth, srcHeight);
var rs = ((AbstractSectionRenderer)this.sectionRenderer);
rs.renderOpaque(viewport);
var occlusionDebug = VoxyClient.getOcclusionDebugState();
if (occlusionDebug==0) {
this.innerPrimaryWork(viewport, depthTexture);
}
if (occlusionDebug<=1) {
rs.buildDrawCalls(viewport);
}
rs.renderTemporal(viewport);
this.postOpaquePreTranslucent(viewport);
if (!this.deferTranslucency) {
rs.renderTranslucent(viewport);
}
this.finish(viewport, sourceFrameBuffer, srcWidth, srcHeight);
glBindFramebuffer(GL_FRAMEBUFFER, sourceFrameBuffer);
}
protected void initDepthStencil(int sourceFrameBuffer, int targetFb, int srcWidth, int srcHeight, int width, int height) {
glClearNamedFramebufferfi(targetFb, GL_DEPTH_STENCIL, 0, 1.0f, 1);
// using blit to copy depth from mismatched depth formats is not portable so instead a full screen pass is performed for a depth copy
// the mismatched formats in this case is the d32 to d24s8
glBindFramebuffer(GL30.GL_FRAMEBUFFER, targetFb);
this.depthCopy.bind();
int depthTexture = glGetNamedFramebufferAttachmentParameteri(sourceFrameBuffer, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
glBindTextureUnit(0, depthTexture);
glBindSampler(0, DEPTH_SAMPLER);
glUniform2f(1,((float)width)/srcWidth, ((float)height)/srcHeight);
glColorMask(false,false,false,false);
this.depthCopy.blit();
/*
if (Capabilities.INSTANCE.isMesa){
glClearStencil(1);
glClear(GL_STENCIL_BUFFER_BIT);
}*/
//This whole thing is hell, we basicly want to create a mask stenicel/depth mask specificiclly
// in theory we could do this in a single pass by passing in the depth buffer from the sourceFrambuffer
// but the current implmentation does a 2 pass system
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 0, 0xFF);
glStencilMask(0xFF);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_NOTEQUAL);//If != 1 pass
//We do here
this.depthMaskBlit.blit();
glDisable(GL_DEPTH_TEST);
//Blit depth 0 where stencil is 0
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilFunc(GL_EQUAL, 0, 0xFF);
this.depthSetBlit.blit();
glDepthFunc(GL_LEQUAL);
glColorMask(true,true,true,true);
//Make voxy terrain render only where there isnt mc terrain
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilFunc(GL_EQUAL, 1, 0xFF);
}
private static final long SCRATCH = MemoryUtil.nmemAlloc(4*4*4);
protected static void transformBlitDepth(FullscreenBlit blitShader, int srcDepthTex, int dstFB, Viewport<?> viewport, Matrix4f targetTransform) {
// at this point the dst frame buffer doesn't have a stencil attachment so we don't need to keep the stencil test on for the blit
// in the worst case the dstFB does have a stencil attachment causing this pass to become 'corrupted'
glDisable(GL_STENCIL_TEST);
glBindFramebuffer(GL30.GL_FRAMEBUFFER, dstFB);
blitShader.bind();
glBindTextureUnit(0, srcDepthTex);
new Matrix4f(viewport.MVP).invert().getToAddress(SCRATCH);
nglUniformMatrix4fv(1, 1, false, SCRATCH);//inverse fromProjection
targetTransform.getToAddress(SCRATCH);//new Matrix4f(tooProjection).mul(vp.modelView).get(data);
nglUniformMatrix4fv(2, 1, false, SCRATCH);//tooProjection
glEnable(GL_DEPTH_TEST);
blitShader.blit();
glDisable(GL_STENCIL_TEST);
glDisable(GL_DEPTH_TEST);
}
protected void innerPrimaryWork(Viewport<?> viewport, int depthBuffer) {
//Compute the mip chain
viewport.hiZBuffer.buildMipChain(depthBuffer, viewport.width, viewport.height);
do {
TimingStatistics.main.stop();
TimingStatistics.dynamic.start();
TimingStatistics.D.start();
//Tick download stream
DownloadStream.INSTANCE.tick();
TimingStatistics.D.stop();
this.nodeManager.tick(this.traversal.getNodeBuffer(), this.nodeCleaner);
//glFlush();
this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here??
TimingStatistics.dynamic.stop();
TimingStatistics.main.start();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT | GL_PIXEL_BUFFER_BARRIER_BIT);
TimingStatistics.F.start();
this.traversal.doTraversal(viewport);
TimingStatistics.F.stop();
} while (this.frexStillHasWork.getAsBoolean());
}
@Override
protected void free0() {
this.fb.free();
this.sectionRenderer.free();
this.depthMaskBlit.delete();
this.depthSetBlit.delete();
this.depthCopy.delete();
super.free0();
}
public void addDebug(List<String> debug) {
this.sectionRenderer.addDebug(debug);
RenderStatistics.addDebug(debug);
}
//Binds the framebuffer and any other bindings needed for rendering
public abstract void setupAndBindOpaque(Viewport<?> viewport);
public abstract void setupAndBindTranslucent(Viewport<?> viewport);
public void bindUniforms() {
this.bindUniforms(-1);
}
public void bindUniforms(int index) {
}
//null means no function, otherwise return the taa injection function
public String taaFunction(String functionName) {
return this.taaFunction(-1, functionName);
}
public String taaFunction(int uboBindingPoint, String functionName) {
return null;
}
//null means dont transform the shader
public String patchOpaqueShader(AbstractSectionRenderer<?,?> renderer, String input) {
return null;
}
//Returning null means apply the same patch as the opaque
public String patchTranslucentShader(AbstractSectionRenderer<?,?> renderer, String input) {
return null;
}
//Null means no scaling factor
public float[] getRenderScalingFactor() {return null;}
}

View File

@@ -1,41 +0,0 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL20C;
public class Capabilities {
public static final Capabilities INSTANCE = new Capabilities();
public final boolean meshShaders;
public final boolean INT64_t;
public Capabilities() {
var cap = GL.getCapabilities();
this.meshShaders = cap.GL_NV_mesh_shader && cap.GL_NV_representative_fragment_test;
//this.INT64_t = cap.GL_ARB_gpu_shader_int64 || cap.GL_AMD_gpu_shader_int64;
//The only reliable way to test for int64 support is to try compile a shader
this.INT64_t = testShaderCompilesOk(ShaderType.COMPUTE, """
#version 430
#extension GL_ARB_gpu_shader_int64 : require
layout(local_size_x=32) in;
void main() {
uint64_t a = 1234;
}
""");
}
public static void init() {
}
private static boolean testShaderCompilesOk(ShaderType type, String src) {
int shader = GL20C.glCreateShader(type.gl);
GL20C.glShaderSource(shader, src);
GL20C.glCompileShader(shader);
int result = GL20C.glGetShaderi(shader, GL20C.GL_COMPILE_STATUS);
GL20C.glDeleteShader(shader);
return result == GL20C.GL_TRUE;
}
}

View File

@@ -1,460 +0,0 @@
package me.cortex.voxy.client.core;
//Contains the logic to determine what is loaded and at what LoD level, dispatches render changes
// also determines what faces are built etc
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import me.cortex.voxy.client.core.rendering.RenderTracker;
import me.cortex.voxy.client.core.util.RingUtil;
import net.minecraft.client.MinecraftClient;
//Can use ring logic
// i.e. when a player moves the rings of each lod change (how it was doing in the original attempt)
// also have it do directional quad culling and rebuild the chunk if needed (this shouldent happen very often) (the reason is to significantly reduce draw calls)
// make the rebuild range like +-5 chunks along each axis (that means at higher levels, should only need to rebuild like)
// 4 sections or something
public class DistanceTracker {
private final TransitionRing2D[] loDRings;
private final TransitionRing2D[] cacheLoadRings;
private final TransitionRing2D[] cacheUnloadRings;
private final TransitionRing2D mostOuterNonClampedRing;
private final RenderTracker tracker;
private final int minYSection;
private final int maxYSection;
private final int renderDistance;
public DistanceTracker(RenderTracker tracker, int[] lodRingScales, int renderDistance, int minY, int maxY) {
this.loDRings = new TransitionRing2D[lodRingScales.length];
this.cacheLoadRings = new TransitionRing2D[lodRingScales.length];
this.cacheUnloadRings = new TransitionRing2D[lodRingScales.length];
this.tracker = tracker;
this.minYSection = minY;
this.maxYSection = maxY;
this.renderDistance = renderDistance;
boolean wasRdClamped = false;
//The rings 0+ start at 64 vanilla rd, no matter what the game is set at, that is if the game is set to 32 rd
// there will still be 32 chunks untill the first lod drop
// if the game is set to 16, then there will be 48 chunks until the drop
for (int i = 0; i < this.loDRings.length; i++) {
int scaleP = lodRingScales[i];
boolean isTerminatingRing = ((lodRingScales[i]+2)<<(1+i) >= renderDistance)&&renderDistance>0;
if (isTerminatingRing) {
scaleP = Math.max(renderDistance >> (1+i), 1);
wasRdClamped = true;
}
int scale = scaleP;
//TODO: FIXME: check that the level shift is right when inc/dec
int capRing = i;
this.loDRings[i] = new TransitionRing2D((isTerminatingRing?5:6)+i, isTerminatingRing?scale<<1:scale, (x, z) -> {
if (isTerminatingRing) {
add(capRing, x, z);
} else
this.dec(capRing+1, x, z);
}, (x, z) -> {
if (isTerminatingRing) {
remove(capRing, x, z);
//remove(capRing, (x<<1), (z<<1));
} else
this.inc(capRing+1, x, z);
});
//TODO:FIXME i think the radius is wrong and (lodRingScales[i]) needs to be (lodRingScales[i]<<1) since the transition ring (the thing above)
// acts on LoD level + 1
//TODO: check this is actually working lmao and make it generate parent level lods on the exit instead of entry so it looks correct when flying backwards
if (!isTerminatingRing) {
//TODO: COMPLETLY REDO THE CACHING SYSTEM CAUSE THE LOGIC IS COMPLETLY INCORRECT
// we want basicly 2 rings offset by an amount such that when a position is near an lod transition point
// it will be meshed (both the higher and lower quality lods), enabling semless loading
// the issue is when to uncache these methods
/*
//TODO: FIX AND FINISH!!!
this.cacheLoadRings[i] = new TransitionRing2D(5 + i, (scale << 1) + 2, (x, z) -> {
//When entering a cache ring, trigger a mesh op and inject into cache
for (int y = this.minYSection >> capRing; y <= this.maxYSection >> capRing; y++) {
this.tracker.addCache(capRing, x, y, z);
}
}, (x, z) -> {
for (int y = this.minYSection >> capRing; y <= this.maxYSection >> capRing; y++) {
this.tracker.removeCache(capRing, x, y, z);
}
});
this.cacheUnloadRings[i] = new TransitionRing2D(5 + i, Math.max(1, (scale << 1) - 2), (x, z) -> {
for (int y = this.minYSection >> capRing; y <= this.maxYSection >> capRing; y++) {
this.tracker.removeCache(capRing, x, y, z);
}
}, (x, z) -> {
});
*/
}
if (isTerminatingRing) {
break;
}
}
if (!wasRdClamped) {
this.mostOuterNonClampedRing = new TransitionRing2D(5+this.loDRings.length, Math.max(renderDistance, 2048)>>this.loDRings.length, (x,z)->
add(this.loDRings.length, x, z), (x,z)->{
if (renderDistance > 0) {
remove(this.loDRings.length,x,z);
}
});
} else {
this.mostOuterNonClampedRing = null;
}
}
private void inc(int lvl, int x, int z) {
for (int y = this.minYSection>>lvl; y <= this.maxYSection>>lvl; y++) {
this.tracker.inc(lvl, x, y, z);
}
}
private void dec(int lvl, int x, int z) {
for (int y = this.minYSection>>lvl; y <= this.maxYSection>>lvl; y++) {
this.tracker.dec(lvl, x, y, z);
}
}
private void add(int lvl, int x, int z) {
for (int y = this.minYSection>>lvl; y <= this.maxYSection>>lvl; y++) {
this.tracker.add(lvl, x, y, z);
}
}
private void remove(int lvl, int x, int z) {
for (int y = this.minYSection>>lvl; y <= this.maxYSection>>lvl; y++) {
this.tracker.remove(lvl, x, y, z);
this.tracker.removeCache(lvl, x, y, z);
}
}
//How it works is there are N ring zones (one zone for each lod boundary)
// the transition zone is what determines what lods are rendered etc (and it biases higher lod levels cause its easier)
// the transition zone is only ever checked when the player moves 1<<(4+lodlvl) blocks, its position is set
//if the center suddenly changes (say more than 1<<(7+lodlvl) block) then invalidate the entire ring and recompute
// the lod sections
public void setCenter(int x, int y, int z) {
for (var ring : this.cacheLoadRings) {
if (ring!=null)
ring.update(x, z);
}
if (this.mostOuterNonClampedRing!=null)
this.mostOuterNonClampedRing.update(x, z);
//Update in reverse order (biggest lod to smallest lod)
for (int i = this.loDRings.length-1; -1<i; i-- ) {
var ring = this.loDRings[i];
if (ring != null)
ring.update(x, z);
}
for (var ring : this.cacheUnloadRings) {
if (ring!=null)
ring.update(x, z);
}
}
public void init(int x, int z) {
for (var ring : this.cacheLoadRings) {
if (ring != null)
ring.setCenter(x, z);
}
for (var ring : this.cacheUnloadRings) {
if (ring != null)
ring.setCenter(x, z);
}
for (var ring : this.loDRings) {
if (ring != null)
ring.setCenter(x, z);
}
if (this.mostOuterNonClampedRing!=null)
this.mostOuterNonClampedRing.setCenter(x, z);
var thread = new Thread(()-> {
for (var ring : this.cacheLoadRings) {
if (ring != null)
ring.fill(x, z);
}
for (var ring : this.cacheUnloadRings) {
if (ring != null)
ring.fill(x, z);
}
//This is an ungodly terrible hack to make the lods load in a semi ok order
for (var ring : this.loDRings)
if (ring != null)
ring.fill(x, z);
if (this.mostOuterNonClampedRing!=null)
this.mostOuterNonClampedRing.fill(x, z);
for (int i = this.loDRings.length - 1; 0 <= i; i--) {
if (this.loDRings[i] != null) {
this.loDRings[i].fill(x, z);
}
}
});
thread.setName("LoD Ring Initializer");
thread.start();
//TODO: FIXME: need to destory on shutdown
}
//TODO: add a new class thing that can track the central axis point so that
// geometry can be rebuilt with new flags with correct facing geometry built
// (could also make it so that it emits 3x the amount of draw calls, but that seems very bad idea)
private interface Transition2DCallback {
void callback(int x, int z);
}
private static final class TransitionRing2D {
private final int triggerRangeSquared;
private final int shiftSize;
private final Transition2DCallback enter;
private final Transition2DCallback exit;
private final int[] cornerPoints;
private final int radius;
private int lastUpdateX;
private int lastUpdateZ;
private int currentX;
private int currentZ;
//Note radius is in shiftScale
private TransitionRing2D(int shiftSize, int radius, Transition2DCallback onEntry, Transition2DCallback onExit) {
this(shiftSize, radius, onEntry, onExit, 0, 0, 0);
}
private TransitionRing2D(int shiftSize, int radius, Transition2DCallback onEntry, Transition2DCallback onExit, int ix, int iy, int iz) {
//trigger just less than every shiftSize scale
this.triggerRangeSquared = 1<<((shiftSize<<1) - 1);
this.shiftSize = shiftSize;
this.enter = onEntry;
this.exit = onExit;
this.cornerPoints = RingUtil.generatingBoundingCorner2D(radius);
this.radius = radius;
}
private long Prel(int x, int z) {
return (Integer.toUnsignedLong(this.currentZ + z)<<32)|Integer.toUnsignedLong(this.currentX + x);
}
public void update(int x, int z) {
long dx = this.lastUpdateX - x;
long dz = this.lastUpdateZ - z;
long distSquared = dx*dx + dz*dz;
if (distSquared < this.triggerRangeSquared) {
return;
}
//Update the last update position
int maxStep = this.triggerRangeSquared/2;
this.lastUpdateX += Math.min(maxStep,Math.max(-maxStep, x-this.lastUpdateX));
this.lastUpdateZ += Math.min(maxStep,Math.max(-maxStep, z-this.lastUpdateZ));
//Compute movement if it happened
int nx = x>>this.shiftSize;
int nz = z>>this.shiftSize;
if (nx == this.currentX && nz == this.currentZ) {
//No movement
return;
}
//FIXME: not right, needs to only call load/unload on entry and exit, cause atm its acting like a loaded circle
Long2IntOpenHashMap ops = new Long2IntOpenHashMap();
while (true) {
int dir = nz < this.currentZ ? -1 : 1;
if (nz != this.currentZ) {
for (int corner : this.cornerPoints) {
int cx = corner >>> 16;
int cz = corner & 0xFFFF;
ops.addTo(Prel(cx, cz + Math.max(0, dir)), dir);
ops.addTo(Prel(cx, -cz + Math.min(0, dir)), -dir);
if (cx != 0) {
ops.addTo(Prel(-cx, cz + Math.max(0, dir)), dir);
ops.addTo(Prel(-cx, -cz + Math.min(0, dir)), -dir);
}
}
this.currentZ += dir;
}
dir = nx < this.currentX ? -1 : 1;
if (nx != this.currentX) {
for (int corner : this.cornerPoints) {
int cx = corner & 0xFFFF;
int cz = corner >>> 16;
ops.addTo(Prel(cx + Math.max(0, dir), cz), dir);
ops.addTo(Prel(-cx + Math.min(0, dir), cz), -dir);
if (cz != 0) {
ops.addTo(Prel(cx + Math.max(0, dir), -cz), dir);
ops.addTo(Prel(-cx + Math.min(0, dir), -cz), -dir);
}
}
this.currentX += dir;
}
//Only break once the coords match
if (nx == this.currentX && nz == this.currentZ) {
break;
}
}
ops.forEach((pos,val)->{
if (val > 0) {
this.enter.callback((int) (long)pos, (int) (pos>>32));
}
if (val < 0) {
this.exit.callback((int) (long)pos, (int) (pos>>32));
}
});
ops.clear();
}
public void fill(int x, int z) {
this.fill(x, z, null);
}
public void fill(int x, int z, Transition2DCallback outsideCallback) {
int cx = x>>this.shiftSize;
int cz = z>>this.shiftSize;
int r2 = this.radius*this.radius;
for (int a = -this.radius; a <= this.radius; a++) {
//IntStream.range(-this.radius, this.radius+1).parallel().forEach(a->{
int b = (int) Math.floor(Math.sqrt(r2-(a*a)));
for (int c = -b; c <= b; c++) {
this.enter.callback(a + cx, c + cz);
}
if (outsideCallback != null) {
for (int c = -this.radius; c < -b; c++) {
outsideCallback.callback(a + cx, c + cz);
}
for (int c = b+1; c <= this.radius; c++) {
outsideCallback.callback(a + cx, c + cz);
}
}
}//);
}
public void setCenter(int x, int z) {
int cx = x>>this.shiftSize;
int cz = z>>this.shiftSize;
this.currentX = cx;
this.currentZ = cz;
this.lastUpdateX = x + (((int)(Math.random()*4))<<(this.shiftSize-4));
this.lastUpdateZ = z + (((int)(Math.random()*4))<<(this.shiftSize-4));
}
}
}
/*
public void update(int x, int z) {
int MAX_STEPS_PER_UPDATE = 1;
long dx = this.lastUpdateX - x;
long dz = this.lastUpdateZ - z;
long distSquared = dx*dx + dz*dz;
if (distSquared < this.triggerRangeSquared) {
return;
}
//TODO: fixme: this last update needs to be incremented by a delta since
//Update the last update position
int maxStep = this.triggerRangeSquared/2;
this.lastUpdateX += Math.min(maxStep,Math.max(-maxStep, x-this.lastUpdateX));
this.lastUpdateZ += Math.min(maxStep,Math.max(-maxStep, z-this.lastUpdateZ));
//Compute movement if it happened
int nx = x>>this.shiftSize;
int nz = z>>this.shiftSize;
if (nx == this.currentX && nz == this.currentZ) {
//No movement
return;
}
//FIXME: not right, needs to only call load/unload on entry and exit, cause atm its acting like a loaded circle
Long2IntOpenHashMap ops = new Long2IntOpenHashMap();
int zcount = MAX_STEPS_PER_UPDATE;
int dir = nz<this.currentZ?-1:1;
while (nz != this.currentZ) {
for (int corner : this.cornerPoints) {
int cx = corner>>>16;
int cz = corner&0xFFFF;
ops.addTo(Prel( cx, cz+Math.max(0, dir)), dir);
ops.addTo(Prel( cx,-cz+Math.min(0, dir)),-dir);
if (cx != 0) {
ops.addTo(Prel(-cx, cz+Math.max(0, dir)), dir);
ops.addTo(Prel(-cx,-cz+Math.min(0, dir)),-dir);
}
}
//ops.addTo(Prel(0, this.radius+Math.max(0, dir)), dir);
//ops.addTo(Prel(0, -this.radius+Math.min(0, dir)), -dir);
this.currentZ += dir;
if (--zcount == 0) break;
}
int xcount = MAX_STEPS_PER_UPDATE;
dir = nx<this.currentX?-1:1;
while (nx != this.currentX) {
for (int corner : this.cornerPoints) {
int cx = corner&0xFFFF;
int cz = corner>>>16;
ops.addTo(Prel( cx+Math.max(0, dir), cz), dir);
ops.addTo(Prel(-cx+Math.min(0, dir), cz),-dir);
if (cz != 0) {
ops.addTo(Prel(cx + Math.max(0, dir), -cz), dir);
ops.addTo(Prel(-cx + Math.min(0, dir), -cz), -dir);
}
}
this.currentX += dir;
if (--xcount == 0) break;
}
ops.forEach((pos,val)->{
if (val > 0) {
this.enter.callback((int) (long)pos, (int) (pos>>32));
}
if (val < 0) {
this.exit.callback((int) (long)pos, (int) (pos>>32));
}
});
ops.clear();
}*/

View File

@@ -1,9 +0,0 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.core.VoxelCore;
public interface IGetVoxelCore {
VoxelCore getVoxelCore();
void reloadVoxelCore();
}

View File

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

View File

@@ -0,0 +1,264 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
import me.cortex.voxy.client.core.rendering.post.FullscreenBlit;
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.iris.IrisVoxyRenderPipelineData;
import net.irisshaders.iris.shaderpack.materialmap.WorldRenderingSettings;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL30;
import java.util.List;
import java.util.function.BooleanSupplier;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL30C.*;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL45C.*;
public class IrisVoxyRenderPipeline extends AbstractRenderPipeline {
private final IrisVoxyRenderPipelineData data;
private final FullscreenBlit depthBlit = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag");
public final DepthFramebuffer fbTranslucent = new DepthFramebuffer(this.fb.getFormat());
private final GlBuffer shaderUniforms;
public IrisVoxyRenderPipeline(IrisVoxyRenderPipelineData data, AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
super(nodeManager, nodeCleaner, traversal, frexSupplier, data.shouldDeferTranslucency());
this.data = data;
if (this.data.thePipeline != null) {
throw new IllegalStateException("Pipeline data already bound");
}
this.data.thePipeline = this;
//Bind the drawbuffers
var oDT = this.data.opaqueDrawTargets;
int[] binding = new int[oDT.length];
for (int i = 0; i < oDT.length; i++) {
binding[i] = GL30.GL_COLOR_ATTACHMENT0+i;
glNamedFramebufferTexture(this.fb.framebuffer.id, GL30.GL_COLOR_ATTACHMENT0+i, oDT[i], 0);
}
glNamedFramebufferDrawBuffers(this.fb.framebuffer.id, binding);
var tDT = this.data.translucentDrawTargets;
binding = new int[tDT.length];
for (int i = 0; i < tDT.length; i++) {
binding[i] = GL30.GL_COLOR_ATTACHMENT0+i;
glNamedFramebufferTexture(this.fbTranslucent.framebuffer.id, GL30.GL_COLOR_ATTACHMENT0+i, tDT[i], 0);
}
glNamedFramebufferDrawBuffers(this.fbTranslucent.framebuffer.id, binding);
this.fb.framebuffer.verify();
this.fbTranslucent.framebuffer.verify();
if (data.getUniforms() != null) {
this.shaderUniforms = new GlBuffer(data.getUniforms().size());
} else {
this.shaderUniforms = null;
}
}
@Override
public void setupExtraModelBakeryData(ModelBakerySubsystem modelService) {
modelService.factory.setCustomBlockStateMapping(WorldRenderingSettings.INSTANCE.getBlockStateIds());
}
@Override
public void free() {
if (this.data.thePipeline != this) {
throw new IllegalStateException();
}
this.data.thePipeline = null;
this.depthBlit.delete();
this.fbTranslucent.free();
if (this.shaderUniforms != null) {
this.shaderUniforms.free();
}
super.free0();
}
@Override
public void preSetup(Viewport<?> viewport) {
super.preSetup(viewport);
if (this.shaderUniforms != null) {
//Update the uniforms
long ptr = UploadStream.INSTANCE.uploadTo(this.shaderUniforms);
this.data.getUniforms().updater().accept(ptr);
UploadStream.INSTANCE.commit();
}
}
@Override
protected int setup(Viewport<?> viewport, int sourceFramebuffer, int srcWidth, int srcHeight) {
this.fb.resize(viewport.width, viewport.height);
this.fbTranslucent.resize(viewport.width, viewport.height);
if (false) {//TODO: only do this if shader specifies
//Clear the colour component
glBindFramebuffer(GL_FRAMEBUFFER, this.fb.framebuffer.id);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
}
if (!this.data.useViewportDims) {
srcWidth = viewport.width;
srcHeight = viewport.height;
}
this.initDepthStencil(sourceFramebuffer, this.fb.framebuffer.id, srcWidth, srcHeight, viewport.width, viewport.height);
return this.fb.getDepthTex().id;
}
@Override
protected void postOpaquePreTranslucent(Viewport<?> viewport) {
int msk = GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT;
if (true) {//TODO: make shader specified
if (false) {//TODO: only do this if shader specifies
glBindFramebuffer(GL_FRAMEBUFFER, this.fbTranslucent.framebuffer.id);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
}
} else {
msk |= GL_COLOR_BUFFER_BIT;
}
glBlitNamedFramebuffer(this.fb.framebuffer.id, this.fbTranslucent.framebuffer.id, 0,0, viewport.width, viewport.height, 0,0, viewport.width, viewport.height, msk, GL_NEAREST);
}
@Override
protected void finish(Viewport<?> viewport, int sourceFrameBuffer, int srcWidth, int srcHeight) {
if (this.data.renderToVanillaDepth && srcWidth == viewport.width && srcHeight == viewport.height) {//We can only depthblit out if destination size is the same
glColorMask(false, false, false, false);
AbstractRenderPipeline.transformBlitDepth(this.depthBlit,
this.fbTranslucent.getDepthTex().id, sourceFrameBuffer,
viewport, new Matrix4f(viewport.vanillaProjection).mul(viewport.modelView));
glColorMask(true, true, true, true);
} else {
// normally disabled by AbstractRenderPipeline but since we are skipping it we do it here
glDisable(GL_STENCIL_TEST);
glDisable(GL_DEPTH_TEST);
}
}
@Override
public void bindUniforms() {
this.bindUniforms(UNIFORM_BINDING_POINT);
}
@Override
public void bindUniforms(int bindingPoint) {
if (this.shaderUniforms != null) {
GL30.glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, this.shaderUniforms.id);// todo: dont randomly select this to 5
}
}
private void doBindings() {
this.bindUniforms();
if (this.data.getSsboSet() != null) {
this.data.getSsboSet().bindingFunction().accept(10);
}
if (this.data.getImageSet() != null) {
this.data.getImageSet().bindingFunction().accept(6);
}
}
@Override
public void setupAndBindOpaque(Viewport<?> viewport) {
this.fb.bind();
this.doBindings();
}
@Override
public void setupAndBindTranslucent(Viewport<?> viewport) {
this.fbTranslucent.bind();
this.doBindings();
if (this.data.getBlender() != null) {
this.data.getBlender().run();
}
}
@Override
public void addDebug(List<String> debug) {
debug.add("Using: " + this.getClass().getSimpleName());
super.addDebug(debug);
}
private static final int UNIFORM_BINDING_POINT = 5;//TODO make ths binding point... not randomly 5
private StringBuilder buildGenericShaderHeader(AbstractSectionRenderer<?, ?> renderer, String input) {
StringBuilder builder = new StringBuilder(input).append("\n\n\n");
if (this.data.getUniforms() != null) {
builder.append("layout(binding = "+UNIFORM_BINDING_POINT+", std140) uniform ShaderUniformBindings ")
.append(this.data.getUniforms().layout())
.append(";\n\n");
}
if (this.data.getSsboSet() != null) {
builder.append("#define BUFFER_BINDING_INDEX_BASE 10\n");//TODO: DONT RANDOMLY MAKE THIS 10
builder.append(this.data.getSsboSet().layout()).append("\n\n");
}
if (this.data.getImageSet() != null) {
builder.append("#define BASE_SAMPLER_BINDING_INDEX 6\n");//TODO: DONT RANDOMLY MAKE THIS 6
builder.append(this.data.getImageSet().layout()).append("\n\n");
}
return builder.append("\n\n");
}
@Override
public String patchOpaqueShader(AbstractSectionRenderer<?, ?> renderer, String input) {
var builder = this.buildGenericShaderHeader(renderer, input);
builder.append(this.data.opaqueFragPatch());
return builder.toString();
}
@Override
public String patchTranslucentShader(AbstractSectionRenderer<?, ?> renderer, String input) {
if (this.data.translucentFragPatch() == null) return null;
var builder = this.buildGenericShaderHeader(renderer, input);
builder.append(this.data.translucentFragPatch());
return builder.toString();
}
@Override
public String taaFunction(String functionName) {
return this.taaFunction(UNIFORM_BINDING_POINT, functionName);
}
@Override
public String taaFunction(int uboBindingPoint, String functionName) {
var builder = new StringBuilder();
if (this.data.getUniforms() != null) {
builder.append("layout(binding = "+uboBindingPoint+", std140) uniform ShaderUniformBindings ")
.append(this.data.getUniforms().layout())
.append(";\n\n");
}
builder.append("vec2 ").append(functionName).append("()\n");
builder.append(this.data.TAA);
builder.append("\n");
return builder.toString();
}
@Override
public float[] getRenderScalingFactor() {
return this.data.resolutionScale;
}
}

View File

@@ -0,0 +1,155 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.client.core.gl.GlFramebuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
import me.cortex.voxy.client.core.rendering.post.FullscreenBlit;
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
import net.minecraft.client.Minecraft;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryStack;
import java.util.function.BooleanSupplier;
import static org.lwjgl.opengl.ARBComputeShader.glDispatchCompute;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glBindImageTexture;
import static org.lwjgl.opengl.GL11.GL_BLEND;
import static org.lwjgl.opengl.GL11.GL_ONE;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
import static org.lwjgl.opengl.GL11C.GL_RGBA8;
import static org.lwjgl.opengl.GL14.glBlendFuncSeparate;
import static org.lwjgl.opengl.GL15.GL_READ_WRITE;
import static org.lwjgl.opengl.GL30C.*;
import static org.lwjgl.opengl.GL43.GL_DEPTH_STENCIL_TEXTURE_MODE;
import static org.lwjgl.opengl.GL45C.glBindTextureUnit;
import static org.lwjgl.opengl.GL45C.glTextureParameterf;
public class NormalRenderPipeline extends AbstractRenderPipeline {
private GlTexture colourTex;
private GlTexture colourSSAOTex;
private final GlFramebuffer fbSSAO = new GlFramebuffer();
private final boolean useEnvFog;
private final FullscreenBlit finalBlit;
private final Shader ssaoCompute = Shader.make()
.add(ShaderType.COMPUTE, "voxy:post/ssao.comp")
.compile();
protected NormalRenderPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
super(nodeManager, nodeCleaner, traversal, frexSupplier, false);
this.useEnvFog = VoxyConfig.CONFIG.useEnvironmentalFog;
this.finalBlit = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag",
a->a.defineIf("USE_ENV_FOG", this.useEnvFog).define("EMIT_COLOUR"));
}
@Override
protected int setup(Viewport<?> viewport, int sourceFB, int srcWidth, int srcHeight) {
if (this.colourTex == null || this.colourTex.getHeight() != viewport.height || this.colourTex.getWidth() != viewport.width) {
if (this.colourTex != null) {
this.colourTex.free();
this.colourSSAOTex.free();
}
this.fb.resize(viewport.width, viewport.height);
this.colourTex = new GlTexture().store(GL_RGBA8, 1, viewport.width, viewport.height);
this.colourSSAOTex = new GlTexture().store(GL_RGBA8, 1, viewport.width, viewport.height);
this.fb.framebuffer.bind(GL_COLOR_ATTACHMENT0, this.colourTex).verify();
this.fbSSAO.bind(this.fb.getDepthAttachmentType(), this.fb.getDepthTex()).bind(GL_COLOR_ATTACHMENT0, this.colourSSAOTex).verify();
glTextureParameterf(this.colourTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameterf(this.colourTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameterf(this.colourSSAOTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameterf(this.colourSSAOTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameterf(this.fb.getDepthTex().id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
}
this.initDepthStencil(sourceFB, this.fb.framebuffer.id, viewport.width, viewport.height, viewport.width, viewport.height);
return this.fb.getDepthTex().id;
}
@Override
protected void postOpaquePreTranslucent(Viewport<?> viewport) {
this.ssaoCompute.bind();
try (var stack = MemoryStack.stackPush()) {
long ptr = stack.nmalloc(4*4*4);
viewport.MVP.getToAddress(ptr);
nglUniformMatrix4fv(3, 1, false, ptr);//MVP
viewport.MVP.invert(new Matrix4f()).getToAddress(ptr);
nglUniformMatrix4fv(4, 1, false, ptr);//invMVP
}
glBindImageTexture(0, this.colourSSAOTex.id, 0, false,0, GL_READ_WRITE, GL_RGBA8);
glBindTextureUnit(1, this.fb.getDepthTex().id);
glBindTextureUnit(2, this.colourTex.id);
glDispatchCompute((viewport.width+31)/32, (viewport.height+31)/32, 1);
glBindFramebuffer(GL_FRAMEBUFFER, this.fbSSAO.id);
}
@Override
protected void finish(Viewport<?> viewport, int sourceFrameBuffer, int srcWidth, int srcHeight) {
this.finalBlit.bind();
if (this.useEnvFog) {
float start = viewport.fogParameters.environmentalStart();
float end = viewport.fogParameters.environmentalEnd();
if (Math.abs(end-start)>1) {
float invEndFogDelta = 1f / (end - start);
float endDistance = Math.max(Minecraft.getInstance().gameRenderer.getRenderDistance(), 20*16);//TODO: make this constant a config option
endDistance *= (float)Math.sqrt(3);
float startDelta = -start * invEndFogDelta;
glUniform4f(4, invEndFogDelta, startDelta, Math.clamp(endDistance*invEndFogDelta+startDelta, 0, 1),0);//
glUniform4f(5, viewport.fogParameters.red(), viewport.fogParameters.green(), viewport.fogParameters.blue(), viewport.fogParameters.alpha());
} else {
glUniform4f(4, 0, 0, 0, 0);
glUniform4f(5, 0, 0, 0, 0);
}
}
glBindTextureUnit(3, this.colourSSAOTex.id);
//Do alpha blending
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
AbstractRenderPipeline.transformBlitDepth(this.finalBlit, this.fb.getDepthTex().id, sourceFrameBuffer, viewport, new Matrix4f(viewport.vanillaProjection).mul(viewport.modelView));
glDisable(GL_BLEND);
//glBlitNamedFramebuffer(this.fbSSAO.id, sourceFrameBuffer, 0,0, viewport.width, viewport.height, 0,0, viewport.width, viewport.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
@Override
public void setupAndBindOpaque(Viewport<?> viewport) {
this.fb.bind();
}
@Override
public void setupAndBindTranslucent(Viewport<?> viewport) {
glBindFramebuffer(GL_FRAMEBUFFER, this.fbSSAO.id);
}
@Override
public void free() {
this.finalBlit.delete();
this.ssaoCompute.free();
this.fbSSAO.free();
if (this.colourTex != null) {
this.colourTex.free();
this.colourSSAOTex.free();
}
super.free0();
}
}

View File

@@ -0,0 +1,48 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.client.iris.IGetIrisVoxyPipelineData;
import me.cortex.voxy.common.Logger;
import net.irisshaders.iris.Iris;
import net.irisshaders.iris.api.v0.IrisApi;
import java.util.function.BooleanSupplier;
public class RenderPipelineFactory {
public static AbstractRenderPipeline createPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
//Note this is where will choose/create e.g. IrisRenderPipeline or normal pipeline
AbstractRenderPipeline pipeline = null;
if (IrisUtil.IRIS_INSTALLED && IrisUtil.SHADER_SUPPORT) {
pipeline = createIrisPipeline(nodeManager, nodeCleaner, traversal, frexSupplier);
}
if (pipeline == null) {
pipeline = new NormalRenderPipeline(nodeManager, nodeCleaner, traversal, frexSupplier);
}
return pipeline;
}
private static AbstractRenderPipeline createIrisPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
var irisPipe = Iris.getPipelineManager().getPipelineNullable();
if (irisPipe == null) {
return null;
}
if (irisPipe instanceof IGetIrisVoxyPipelineData getVoxyPipeData) {
var pipeData = getVoxyPipeData.voxy$getPipelineData();
if (pipeData == null) {
return null;
}
Logger.info("Creating voxy iris render pipeline");
try {
return new IrisVoxyRenderPipeline(pipeData, nodeManager, nodeCleaner, traversal, frexSupplier);
} catch (Exception e) {
Logger.error("Failed to create iris render pipeline", e);
IrisUtil.disableIrisShaders();
return null;
}
}
return null;
}
}

View File

@@ -1,45 +0,0 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.core.rendering.AbstractFarWorldRenderer;
import me.cortex.voxy.client.core.rendering.Viewport;
import net.fabricmc.loader.api.FabricLoader;
import org.vivecraft.client_vr.ClientDataHolderVR;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class ViewportSelector <T extends Viewport> {
public static final boolean VIVECRAFT_INSTALLED = FabricLoader.getInstance().isModLoaded("vivecraft");
private final Supplier<T> creator;
private final T defaultViewport;
private final Map<Object, T> extraViewports = new HashMap<>();
public ViewportSelector(Supplier<T> viewportCreator) {
this.creator = viewportCreator;
this.defaultViewport = viewportCreator.get();
}
private T getVivecraftViewport() {
var cdh = ClientDataHolderVR.getInstance();
var pass = cdh.currentPass;
if (pass == null) {
return this.defaultViewport;
}
return this.extraViewports.computeIfAbsent(pass, a->this.creator.get());
}
public T getViewport() {
if (VIVECRAFT_INSTALLED) {
return getVivecraftViewport();
}
return this.defaultViewport;
}
public void free() {
this.defaultViewport.delete();
this.extraViewports.values().forEach(Viewport::delete);
this.extraViewports.clear();
}
}

View File

@@ -1,278 +0,0 @@
package me.cortex.voxy.client.core;
import com.mojang.blaze3d.systems.RenderSystem;
import me.cortex.voxy.client.Voxy;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.rendering.*;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.client.saver.ContextSelectionSystem;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.client.importers.WorldImporter;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.hud.ClientBossBar;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Frustum;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.boss.BossBar;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.text.Text;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraft.world.chunk.WorldChunk;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import java.io.File;
import java.util.*;
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
//Core class that ingests new data from sources and updates the required systems
//3 primary services:
// ingest service: this takes in unloaded chunk events from the client, processes the chunk and critically also updates the lod view of the world
// render data builder service: this service builds the render data from build requests it also handles the collecting of build data for the selected region (only axis aligned single lod tasks)
// serialization service: serializes changed world data and ensures that the database and any loaded data are in sync such that the database can never be more updated than loaded data, also performs compression on serialization
//there are multiple subsystems
//player tracker system (determines what lods are loaded and used by the player)
//updating system (triggers render data rebuilds when something from the ingest service causes an LOD change)
//the render system simply renders what data it has, its responsable for gpu memory layouts in arenas and rendering in an optimal way, it makes no requests back to any of the other systems or services, it just applies render data updates
//There is strict forward only dataflow
//Ingest -> world engine -> raw render data -> render data
public class VoxelCore {
private final WorldEngine world;
private final DistanceTracker distanceTracker;
private final RenderGenerationService renderGen;
private final RenderTracker renderTracker;
private final AbstractFarWorldRenderer renderer;
private final ViewportSelector viewportSelector;
private final PostProcessing postProcessing;
//private final Thread shutdownThread = new Thread(this::shutdown);
private WorldImporter importer;
public VoxelCore(ContextSelectionSystem.Selection worldSelection) {
this.world = worldSelection.createEngine();
var cfg = worldSelection.getConfig();
System.out.println("Initializing voxy core");
//Trigger the shared index buffer loading
SharedIndexBuffer.INSTANCE.id();
Capabilities.init();//Ensure clinit is called
this.renderer = this.createRenderBackend();
this.viewportSelector = new ViewportSelector<>(this.renderer::createViewport);
System.out.println("Renderer initialized");
this.renderTracker = new RenderTracker(this.world, this.renderer);
this.renderGen = new RenderGenerationService(this.world, this.renderer.getModelManager(), VoxyConfig.CONFIG.renderThreads, this.renderTracker::processBuildResult, this.renderer.usesMeshlets());
this.world.setDirtyCallback(this.renderTracker::sectionUpdated);
this.renderTracker.setRenderGen(this.renderGen);
System.out.println("Render tracker and generator initialized");
//To get to chunk scale multiply the scale by 2, the scale is after how many chunks does the lods halve
int q = VoxyConfig.CONFIG.qualityScale;
int minY = MinecraftClient.getInstance().world.getBottomSectionCoord()/2;
int maxY = MinecraftClient.getInstance().world.getTopSectionCoord()/2;
if (cfg.minYOverride != Integer.MAX_VALUE) {
minY = cfg.minYOverride;
}
if (cfg.maxYOverride != Integer.MIN_VALUE) {
maxY = cfg.maxYOverride;
}
this.distanceTracker = new DistanceTracker(this.renderTracker, new int[]{q,q,q,q},
(VoxyConfig.CONFIG.renderDistance<0?VoxyConfig.CONFIG.renderDistance:((VoxyConfig.CONFIG.renderDistance+1)/2)),
minY, maxY);
System.out.println("Distance tracker initialized");
this.postProcessing = new PostProcessing();
this.world.getMapper().setCallbacks(this.renderer::addBlockState, this.renderer::addBiome);
////Resave the db incase it failed a recovery
//this.world.getMapper().forceResaveStates();
var biomeRegistry = MinecraftClient.getInstance().world.getRegistryManager().get(RegistryKeys.BIOME);
for (var biome : this.world.getMapper().getBiomeEntries()) {
//this.renderer.getModelManager().addBiome(biome.id, biomeRegistry.get(new Identifier(biome.biome)));
this.renderer.addBiome(biome);
}
for (var state : this.world.getMapper().getStateEntries()) {
//this.renderer.getModelManager().addEntry(state.id, state.state);
this.renderer.addBlockState(state);
}
//this.renderer.getModelManager().updateEntry(0, Blocks.GRASS_BLOCK.getDefaultState());
System.out.println("Voxy core initialized");
}
private AbstractFarWorldRenderer<?,?> createRenderBackend() {
if (false) {
System.out.println("Using Gl46MeshletFarWorldRendering");
return new Gl46MeshletsFarWorldRenderer(VoxyConfig.CONFIG.geometryBufferSize, VoxyConfig.CONFIG.maxSections);
} else {
if (VoxyConfig.CONFIG.useMeshShaders()) {
System.out.println("Using NvMeshFarWorldRenderer");
return new NvMeshFarWorldRenderer(VoxyConfig.CONFIG.geometryBufferSize, VoxyConfig.CONFIG.maxSections);
} else {
System.out.println("Using Gl46FarWorldRenderer");
return new Gl46FarWorldRenderer(VoxyConfig.CONFIG.geometryBufferSize, VoxyConfig.CONFIG.maxSections);
}
}
}
public void enqueueIngest(WorldChunk worldChunk) {
this.world.ingestService.enqueueIngest(worldChunk);
}
boolean firstTime = true;
public void renderSetup(Frustum frustum, Camera camera) {
if (this.firstTime) {
this.distanceTracker.init(camera.getBlockPos().getX(), camera.getBlockPos().getZ());
this.firstTime = false;
//this.renderTracker.addLvl0(0,6,0);
}
this.distanceTracker.setCenter(camera.getBlockPos().getX(), camera.getBlockPos().getY(), camera.getBlockPos().getZ());
this.renderer.setupRender(frustum, camera);
}
private static Matrix4f makeProjectionMatrix(float near, float far) {
//TODO: use the existing projection matrix use mulLocal by the inverse of the projection and then mulLocal our projection
var projection = new Matrix4f();
var client = MinecraftClient.getInstance();
var gameRenderer = client.gameRenderer;//tickCounter.getTickDelta(true);
float fov = (float) gameRenderer.getFov(gameRenderer.getCamera(), client.getRenderTickCounter().getTickDelta(true), true);
projection.setPerspective(fov * 0.01745329238474369f,
(float) client.getWindow().getFramebufferWidth() / (float)client.getWindow().getFramebufferHeight(),
near, far);
return projection;
}
private static Matrix4f computeProjectionMat() {
return new Matrix4f(RenderSystem.getProjectionMatrix()).mulLocal(
makeProjectionMatrix(0.05f, MinecraftClient.getInstance().gameRenderer.getFarPlaneDistance()).invert()
).mulLocal(makeProjectionMatrix(16, 16*3000));
}
public void renderOpaque(MatrixStack matrices, double cameraX, double cameraY, double cameraZ) {
if (IrisUtil.irisShadowActive()) {
return;
}
matrices.push();
matrices.translate(-cameraX, -cameraY, -cameraZ);
matrices.pop();
//this.renderer.getModelManager().updateEntry(0, Blocks.DIRT_PATH.getDefaultState());
//this.renderer.getModelManager().updateEntry(0, Blocks.COMPARATOR.getDefaultState());
//this.renderer.getModelManager().updateEntry(0, Blocks.OAK_LEAVES.getDefaultState());
//var fb = Iris.getPipelineManager().getPipelineNullable().getSodiumTerrainPipeline().getTerrainSolidFramebuffer();
//fb.bind();
var projection = computeProjectionMat();
//var projection = RenderSystem.getProjectionMatrix();//computeProjectionMat();
var viewport = this.viewportSelector.getViewport();
viewport
.setProjection(projection)
.setModelView(matrices.peek().getPositionMatrix())
.setCamera(cameraX, cameraY, cameraZ)
.setScreenSize(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight);
int boundFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
this.postProcessing.setup(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight, boundFB);
this.renderer.renderFarAwayOpaque(viewport);
//Compute the SSAO of the rendered terrain
this.postProcessing.computeSSAO(projection, matrices);
//We can render the translucent directly after as it is the furthest translucent objects
this.renderer.renderFarAwayTranslucent(viewport);
this.postProcessing.renderPost(projection, RenderSystem.getProjectionMatrix(), boundFB);
}
public void addDebugInfo(List<String> debug) {
debug.add("");
debug.add("");
debug.add("Voxy Core: " + Voxy.VERSION);
/*
debug.add("Ingest service tasks: " + this.world.ingestService.getTaskCount());
debug.add("Saving service tasks: " + this.world.savingService.getTaskCount());
debug.add("Render service tasks: " + this.renderGen.getTaskCount());
*/
debug.add("I/S/R tasks: " + this.world.ingestService.getTaskCount() + "/"+this.world.savingService.getTaskCount()+"/"+this.renderGen.getTaskCount());
debug.add("Loaded cache sizes: " + Arrays.toString(this.world.getLoadedSectionCacheSizes()));
debug.add("Mesh cache count: " + this.renderGen.getMeshCacheCount());
this.renderer.addDebugData(debug);
}
//Note: when doing translucent rendering, only need to sort when generating the geometry, or when crossing into the center zone
// cause in 99.99% of cases the sections dont need to be sorted
// since they are AABBS crossing the normal is impossible without one of the axis being equal
public void shutdown() {
//if (Thread.currentThread() != this.shutdownThread) {
// Runtime.getRuntime().removeShutdownHook(this.shutdownThread);
//}
//this.world.getMapper().forceResaveStates();
if (this.importer != null) {
System.out.println("Shutting down importer");
try {this.importer.shutdown();this.importer = null;} catch (Exception e) {System.err.println(e);}
}
System.out.println("Shutting down voxel core");
try {this.renderGen.shutdown();} catch (Exception e) {System.err.println(e);}
System.out.println("Render gen shut down");
try {this.world.shutdown();} catch (Exception e) {System.err.println(e);}
System.out.println("World engine shut down");
try {this.renderer.shutdown(); this.viewportSelector.free();} catch (Exception e) {System.err.println(e);}
System.out.println("Renderer shut down");
if (this.postProcessing!=null){try {this.postProcessing.shutdown();} catch (Exception e) {System.err.println(e);}}
System.out.println("Voxel core shut down");
}
public boolean createWorldImporter(World mcWorld, File worldPath) {
if (this.importer != null) {
return false;
}
var importer = new WorldImporter(this.world, mcWorld);
var bossBar = new ClientBossBar(MathHelper.randomUuid(), Text.of("Voxy world importer"), 0.0f, BossBar.Color.GREEN, BossBar.Style.PROGRESS, false, false, false);
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.put(bossBar.getUuid(), bossBar);
importer.importWorldAsyncStart(worldPath, 4, (a,b)->
MinecraftClient.getInstance().executeSync(()-> {
bossBar.setPercent(((float) a)/((float) b));
bossBar.setName(Text.of("Voxy import: "+ a+"/"+b + " region files"));
}),
()-> {
MinecraftClient.getInstance().executeSync(()-> {
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.remove(bossBar.getUuid());
String msg = "Voxy world import finished";
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.literal(msg));
System.err.println(msg);
});
this.importer = null;
});
this.importer = importer;
return true;
}
public WorldEngine getWorldEngine() {
return this.world;
}
}

View File

@@ -0,0 +1,513 @@
package me.cortex.voxy.client.core;
import com.mojang.blaze3d.opengl.GlConst;
import com.mojang.blaze3d.opengl.GlStateManager;
import me.cortex.voxy.client.TimingStatistics;
import me.cortex.voxy.client.VoxyClient;
import me.cortex.voxy.common.config.VoxyConfig;
import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
import me.cortex.voxy.client.core.model.ModelStore;
import me.cortex.voxy.client.core.rendering.ChunkBoundRenderer;
import me.cortex.voxy.client.core.rendering.RenderDistanceTracker;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.ViewportSelector;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
import me.cortex.voxy.client.core.rendering.section.IUsesMeshlets;
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
import me.cortex.voxy.client.core.rendering.section.backend.mdic.MDICSectionRenderer;
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.core.util.GPUTiming;
import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.thread.ServiceManager;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
import net.caffeinemc.mods.sodium.client.util.FogParameters;
import net.minecraft.client.Minecraft;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
import java.util.Arrays;
import java.util.List;
import static org.lwjgl.opengl.GL11.GL_VIEWPORT;
import static org.lwjgl.opengl.GL11.glGetIntegerv;
import static org.lwjgl.opengl.GL11C.*;
import static org.lwjgl.opengl.GL30C.*;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL43C.GL_SHADER_STORAGE_BUFFER_BINDING;
import me.cortex.voxy.client.VoxyClientInstance;
import me.cortex.voxy.common.config.VoxyServerConfig;
public class VoxyRenderSystem {
private final WorldEngine worldIn;
private final ModelBakerySubsystem modelService;
private final RenderGenerationService renderGen;
private final IGeometryData geometryData;
private final AsyncNodeManager nodeManager;
private final NodeCleaner nodeCleaner;
private final HierarchicalOcclusionTraverser traversal;
private final RenderDistanceTracker renderDistanceTracker;
public final ChunkBoundRenderer chunkBoundRenderer;
private final ViewportSelector<?> viewportSelector;
private final AbstractRenderPipeline pipeline;
private static AbstractSectionRenderer.Factory<?,? extends IGeometryData> getRenderBackendFactory() {
//TODO: need todo a thing where selects optimal section render based on if supports the pipeline and geometry data type
return MDICSectionRenderer.FACTORY;
}
public VoxyRenderSystem(WorldEngine world, ServiceManager sm) {
//Keep the world loaded, NOTE: this is done FIRST, to keep and ensure that even if the rest of loading takes more
// than timeout, we keep the world acquired
world.acquireRef();
System.gc();
if (Minecraft.getInstance().options.getEffectiveRenderDistance()<3) {
Logger.warn("Having a vanilla render distance of 2 can cause rare culling near the edge of your screen issues, please use 3 or more");
}
//Fking HATE EVERYTHING AAAAAAAAAAAAAAAA
int[] oldBufferBindings = new int[10];
for (int i = 0; i < oldBufferBindings.length; i++) {
oldBufferBindings[i] = glGetIntegeri(GL_SHADER_STORAGE_BUFFER_BINDING, i);
}
try {
//wait for opengl to be finished, this should hopefully ensure all memory allocations are free
glFinish();
glFinish();
this.worldIn = world;
long geometryCapacity = getGeometryBufferSize();
var backendFactory = getRenderBackendFactory();
{
this.modelService = new ModelBakerySubsystem(world.getMapper());
this.renderGen = new RenderGenerationService(world, this.modelService, sm, IUsesMeshlets.class.isAssignableFrom(backendFactory.clz()));
this.geometryData = new BasicSectionGeometryData(1 << 20, geometryCapacity);
this.nodeManager = new AsyncNodeManager(1 << 21, this.geometryData, this.renderGen);
this.nodeCleaner = new NodeCleaner(this.nodeManager);
this.traversal = new HierarchicalOcclusionTraverser(this.nodeManager, this.nodeCleaner, this.renderGen);
world.setDirtyCallback(this.nodeManager::worldEvent);
Arrays.stream(world.getMapper().getBiomeEntries()).forEach(this.modelService::addBiome);
world.getMapper().setBiomeCallback(this.modelService::addBiome);
this.nodeManager.start();
}
this.pipeline = RenderPipelineFactory.createPipeline(this.nodeManager, this.nodeCleaner, this.traversal, this::frexStillHasWork);
this.pipeline.setupExtraModelBakeryData(this.modelService);//Configure the model service
var sectionRenderer = backendFactory.create(this.pipeline, this.modelService.getStore(), this.geometryData);
this.pipeline.setSectionRenderer(sectionRenderer);
this.viewportSelector = new ViewportSelector<>(sectionRenderer::createViewport);
{
int minSec = Minecraft.getInstance().level.getMinSectionY() >> 5;
int maxSec = (Minecraft.getInstance().level.getMaxSectionY() - 1) >> 5;
//Do some very cheeky stuff for MiB
if (VoxyCommon.IS_MINE_IN_ABYSS) {//TODO: make this somehow configurable
minSec = -8;
maxSec = 7;
}
this.renderDistanceTracker = new RenderDistanceTracker(20,
minSec,
maxSec,
this.nodeManager::addTopLevel,
this.nodeManager::removeTopLevel);
this.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
}
this.chunkBoundRenderer = new ChunkBoundRenderer(this.pipeline);
Logger.info("Voxy render system created with " + geometryCapacity + " geometry capacity, using pipeline '" + this.pipeline.getClass().getSimpleName() + "' with renderer '" + sectionRenderer.getClass().getSimpleName() + "'");
} catch (RuntimeException e) {
world.releaseRef();//If something goes wrong, we must release the world first
throw e;
}
for (int i = 0; i < oldBufferBindings.length; i++) {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, i, oldBufferBindings[i]);
}
for (int i = 0; i < 12; i++) {
GlStateManager._activeTexture(GlConst.GL_TEXTURE0+i);
GlStateManager._bindTexture(0);
glBindSampler(i, 0);
}
}
public Viewport<?> setupViewport(ChunkRenderMatrices matrices, FogParameters fogParameters, double cameraX, double cameraY, double cameraZ) {
var viewport = this.getViewport();
if (viewport == null) {
return null;
}
//Do some very cheeky stuff for MiB
if (VoxyCommon.IS_MINE_IN_ABYSS) {
int sector = (((int)Math.floor(cameraX)>>4)+512)>>10;
cameraX -= sector<<14;//10+4
cameraY += (16+(256-32-sector*30))*16;
}
//cameraY += 100;
var projection = computeProjectionMat(matrices.projection());//RenderSystem.getProjectionMatrix();
//var projection = ShadowMatrices.createOrthoMatrix(160, -16*300, 16*300);
//var projection = new Matrix4f(matrices.projection());
int[] dims = new int[4];
glGetIntegerv(GL_VIEWPORT, dims);
int width = dims[2];
int height = dims[3];
{//Apply render scaling factor
var factor = this.pipeline.getRenderScalingFactor();
if (factor != null) {
width = (int) (width*factor[0]);
height = (int) (height*factor[1]);
}
}
viewport
.setVanillaProjection(matrices.projection())
.setProjection(projection)
.setModelView(new Matrix4f(matrices.modelView()))
.setCamera(cameraX, cameraY, cameraZ)
.setScreenSize(width, height)
.setFogParameters(fogParameters)
.update();
if (VoxyClient.getOcclusionDebugState()==0) {
viewport.frameId++;
}
return viewport;
}
public void renderOpaque(Viewport<?> viewport) {
if (viewport == null) {
return;
}
TimingStatistics.resetSamplers();
long startTime = System.nanoTime();
TimingStatistics.all.start();
GPUTiming.INSTANCE.marker();//Start marker
TimingStatistics.main.start();
//TODO: optimize
int[] oldBufferBindings = new int[10];
for (int i = 0; i < oldBufferBindings.length; i++) {
oldBufferBindings[i] = glGetIntegeri(GL_SHADER_STORAGE_BUFFER_BINDING, i);
}
int oldFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
int boundFB = oldFB;
int[] dims = new int[4];
glGetIntegerv(GL_VIEWPORT, dims);
glViewport(0,0, viewport.width, viewport.height);
//var target = DefaultTerrainRenderPasses.CUTOUT.getTarget();
//boundFB = ((net.minecraft.client.texture.GlTexture) target.getColorAttachment()).getOrCreateFramebuffer(((GlBackend) RenderSystem.getDevice()).getFramebufferManager(), target.getDepthAttachment());
if (boundFB == 0) {
throw new IllegalStateException("Cannot use the default framebuffer as cannot source from it");
}
//this.autoBalanceSubDivSize();
this.pipeline.preSetup(viewport);
TimingStatistics.E.start();
if ((!VoxyClient.disableSodiumChunkRender())&&!IrisUtil.irisShadowActive()) {
this.chunkBoundRenderer.render(viewport);
} else {
viewport.depthBoundingBuffer.clear(0);
}
TimingStatistics.E.stop();
GPUTiming.INSTANCE.marker();
//The entire rendering pipeline (excluding the chunkbound thing)
this.pipeline.runPipeline(viewport, boundFB, dims[2], dims[3]);
GPUTiming.INSTANCE.marker();
TimingStatistics.main.stop();
TimingStatistics.postDynamic.start();
PrintfDebugUtil.tick();
//As much dynamic runtime stuff here
{
//Tick upload stream (this is ok to do here as upload ticking is just memory management)
UploadStream.INSTANCE.tick();
while (this.renderDistanceTracker.setCenterAndProcess(viewport.cameraX, viewport.cameraZ) && VoxyClient.isFrexActive());//While FF is active, run until everything is processed
TimingStatistics.H.start();
//Done here as is allows less gl state resetup
do { this.modelService.tick(900_000); } while (VoxyClient.isFrexActive() && !this.modelService.areQueuesEmpty());
TimingStatistics.H.stop();
}
GPUTiming.INSTANCE.marker();
TimingStatistics.postDynamic.stop();
GPUTiming.INSTANCE.tick();
glBindFramebuffer(GlConst.GL_FRAMEBUFFER, oldFB);
glViewport(dims[0], dims[1], dims[2], dims[3]);
{//Reset state manager stuffs
glUseProgram(0);
glEnable(GL_DEPTH_TEST);
GlStateManager._glBindVertexArray(0);//Clear binding
GlStateManager._activeTexture(GlConst.GL_TEXTURE1);
for (int i = 0; i < 12; i++) {
GlStateManager._activeTexture(GlConst.GL_TEXTURE0+i);
GlStateManager._bindTexture(0);
glBindSampler(i, 0);
}
IrisUtil.clearIrisSamplers();//Thanks iris (sigh)
//TODO: should/needto actually restore all of these, not just clear them
//Clear all the bindings
for (int i = 0; i < oldBufferBindings.length; i++) {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, i, oldBufferBindings[i]);
}
//((SodiumShader) Iris.getPipelineManager().getPipelineNullable().getSodiumPrograms().getProgram(DefaultTerrainRenderPasses.CUTOUT).getInterface()).setupState(DefaultTerrainRenderPasses.CUTOUT, fogParameters);
}
TimingStatistics.all.stop();
//TimingStatistics.I.start();
//glFlush();
//TimingStatistics.I.stop();
/*
TimingStatistics.F.start();
this.postProcessing.setup(viewport.width, viewport.height, boundFB);
TimingStatistics.F.stop();
this.renderer.renderFarAwayOpaque(viewport, this.chunkBoundRenderer.getDepthBoundTexture());
TimingStatistics.F.start();
//Compute the SSAO of the rendered terrain, TODO: fix it breaking depth or breaking _something_ am not sure what
this.postProcessing.computeSSAO(viewport.MVP);
TimingStatistics.F.stop();
TimingStatistics.G.start();
//We can render the translucent directly after as it is the furthest translucent objects
this.renderer.renderFarAwayTranslucent(viewport, this.chunkBoundRenderer.getDepthBoundTexture());
TimingStatistics.G.stop();
TimingStatistics.F.start();
this.postProcessing.renderPost(viewport, matrices.projection(), boundFB);
TimingStatistics.F.stop();
*/
}
private void autoBalanceSubDivSize() {
//only increase quality while there are very few mesh queues, this stops,
// e.g. while flying and is rendering alot of low quality chunks
boolean canDecreaseSize = this.renderGen.getTaskCount() < 300;
int MIN_FPS = 55;
int MAX_FPS = 65;
float INCREASE_PER_SECOND = 60;
float DECREASE_PER_SECOND = 30;
//Auto fps targeting
if (Minecraft.getInstance().getFps() < MIN_FPS) {
VoxyConfig.CONFIG.subDivisionSize = Math.min(VoxyConfig.CONFIG.subDivisionSize + INCREASE_PER_SECOND / Math.max(1f, Minecraft.getInstance().getFps()), 256);
}
if (MAX_FPS < Minecraft.getInstance().getFps() && canDecreaseSize) {
VoxyConfig.CONFIG.subDivisionSize = Math.max(VoxyConfig.CONFIG.subDivisionSize - DECREASE_PER_SECOND / Math.max(1f, Minecraft.getInstance().getFps()), 28);
}
}
private static Matrix4f makeProjectionMatrix(float near, float far) {
//TODO: use the existing projection matrix use mulLocal by the inverse of the projection and then mulLocal our projection
var projection = new Matrix4f();
var client = Minecraft.getInstance();
var gameRenderer = client.gameRenderer;//tickCounter.getTickDelta(true);
float fov = gameRenderer.getFov(gameRenderer.getMainCamera(), client.getDeltaTracker().getGameTimeDeltaPartialTick(true), true);
projection.setPerspective(fov * 0.01745329238474369f,
(float) client.getWindow().getWidth() / (float)client.getWindow().getHeight(),
near, far);
return projection;
}
//TODO: Make a reverse z buffer
private static Matrix4f computeProjectionMat(Matrix4fc base) {
//THis is a wild and insane problem to have
// at short render distances the vanilla terrain doesnt end up covering the 16f near plane voxy uses
// meaning that it explodes (due to near plane clipping).. _badly_ with the rastered culling being wrong in rare cases for the immediate
// sections rendered after the vanilla render distance
float nearVoxy = Minecraft.getInstance().gameRenderer.getRenderDistance()<=32.0f?8f:16f;
nearVoxy = VoxyClient.disableSodiumChunkRender()?0.1f:nearVoxy;
return base.mulLocal(
makeProjectionMatrix(0.05f, Minecraft.getInstance().gameRenderer.getDepthFar()).invert(),
new Matrix4f()
).mulLocal(makeProjectionMatrix(nearVoxy, 16*3000));
}
private boolean frexStillHasWork() {
if (!VoxyClient.isFrexActive()) {
return false;
}
//If frex is running we must tick everything to ensure correctness
UploadStream.INSTANCE.tick();
//Done here as is allows less gl state resetup
this.modelService.tick(100_000_000);
GL11.glFinish();
return this.nodeManager.hasWork() || this.renderGen.getTaskCount()!=0 || !this.modelService.areQueuesEmpty();
}
public void setRenderDistance(int renderDistance) {
this.renderDistanceTracker.setRenderDistance(renderDistance);
}
public Viewport<?> getViewport() {
if (IrisUtil.irisShadowActive()) {
return null;
}
return this.viewportSelector.getViewport();
}
public void addDebugInfo(List<String> debug) {
debug.add("Buf/Tex [#/Mb]: [" + GlBuffer.getCount() + "/" + (GlBuffer.getTotalSize()/1_000_000) + "],[" + GlTexture.getCount() + "/" + (GlTexture.getEstimatedTotalSize()/1_000_000)+"]");
{
this.modelService.addDebugData(debug);
this.renderGen.addDebugData(debug);
this.nodeManager.addDebug(debug);
this.pipeline.addDebug(debug);
}
{
TimingStatistics.update();
debug.add("Voxy frame runtime (millis): " + TimingStatistics.dynamic.pVal() + ", " + TimingStatistics.main.pVal()+ ", " + TimingStatistics.postDynamic.pVal()+ ", " + TimingStatistics.all.pVal());
debug.add("Extra time: " + TimingStatistics.A.pVal() + ", " + TimingStatistics.B.pVal() + ", " + TimingStatistics.C.pVal() + ", " + TimingStatistics.D.pVal());
debug.add("Extra 2 time: " + TimingStatistics.E.pVal() + ", " + TimingStatistics.F.pVal() + ", " + TimingStatistics.G.pVal() + ", " + TimingStatistics.H.pVal() + ", " + TimingStatistics.I.pVal());
}
debug.add(GPUTiming.INSTANCE.getDebug());
if (VoxyCommon.getInstance() instanceof VoxyClientInstance clientInstance) {
long lastUpdate = clientInstance.getLastLodUpdate();
long timeSince = System.currentTimeMillis() - lastUpdate;
debug.add("LOD Updates: " + clientInstance.getLodUpdatesReceived() + " (Last: " + timeSince + "ms ago)");
debug.add("Server View Dist: " + clientInstance.getServerViewDistance());
}
PrintfDebugUtil.addToOut(debug);
}
public void shutdown() {
Logger.info("Flushing download stream");
DownloadStream.INSTANCE.flushWaitClear();
Logger.info("Shutting down rendering");
try {
//Cleanup callbacks
this.worldIn.setDirtyCallback(null);
this.worldIn.getMapper().setBiomeCallback(null);
this.worldIn.getMapper().setStateCallback(null);
this.nodeManager.stop();
this.modelService.shutdown();
this.renderGen.shutdown();
this.traversal.free();
this.nodeCleaner.free();
this.geometryData.free();
this.chunkBoundRenderer.free();
this.viewportSelector.free();
} catch (Exception e) {Logger.error("Error shutting down renderer components", e);}
Logger.info("Shutting down render pipeline");
try {this.pipeline.free();} catch (Exception e){Logger.error("Error releasing render pipeline", e);}
Logger.info("Flushing download stream");
DownloadStream.INSTANCE.flushWaitClear();
//Release hold on the world
this.worldIn.releaseRef();
Logger.info("Render shutdown completed");
}
private static long getGeometryBufferSize() {
long geometryCapacity = Math.min((1L<<(64-Long.numberOfLeadingZeros(Capabilities.INSTANCE.ssboMaxSize-1)))<<1, 1L<<32)-1024/*(1L<<32)-1024*/;
if (Capabilities.INSTANCE.isIntel) {
geometryCapacity = Math.max(geometryCapacity, 1L<<30);//intel moment, force min 1gb
}
//Limit to available dedicated memory if possible
if (Capabilities.INSTANCE.canQueryGpuMemory) {
//512mb less than avalible,
long limit = Capabilities.INSTANCE.getFreeDedicatedGpuMemory() - (long)(1.5*1024*1024*1024);//1.5gb vram buffer
// Give a minimum of 512 mb requirement
limit = Math.max(512*1024*1024, limit);
geometryCapacity = Math.min(geometryCapacity, limit);
}
//geometryCapacity = 1<<28;
//geometryCapacity = 1<<30;//1GB test
var override = System.getProperty("voxy.geometryBufferSizeOverrideMB", "");
if (!override.isEmpty()) {
geometryCapacity = Long.parseLong(override)*1024L*1024L;
}
return geometryCapacity;
}
public WorldEngine getEngine() {
return this.worldIn;
}
}

View File

@@ -0,0 +1,217 @@
package me.cortex.voxy.client.core.gl;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.common.Logger;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.opengl.GL20C;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
import java.util.Locale;
import java.util.Random;
import static org.lwjgl.opengl.GL11.GL_NEAREST;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL15.glDeleteBuffers;
import static org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL;
import static org.lwjgl.opengl.GL30C.GL_MAP_READ_BIT;
import static org.lwjgl.opengl.GL32.glGetInteger64;
import static org.lwjgl.opengl.GL43C.GL_MAX_SHADER_STORAGE_BLOCK_SIZE;
import static org.lwjgl.opengl.GL44.GL_DYNAMIC_STORAGE_BIT;
import static org.lwjgl.opengl.GL44.GL_MAP_COHERENT_BIT;
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi;
import static org.lwjgl.opengl.GL45C.*;
import static org.lwjgl.opengl.GL45C.glCreateFramebuffers;
import static org.lwjgl.opengl.NVXGPUMemoryInfo.*;
public class Capabilities {
public static final Capabilities INSTANCE = new Capabilities();
public final boolean repFragTest;
public final boolean meshShaders;
public final boolean INT64_t;
public final long ssboMaxSize;
public final boolean isMesa;
public final boolean canQueryGpuMemory;
public final long totalDedicatedMemory;//Bytes, dedicated memory
public final long totalDynamicMemory;//Bytes, total allocation memory - dedicated memory
public final boolean compute;
public final boolean indirectParameters;
public final boolean isIntel;
public final boolean subgroup;
public final boolean sparseBuffer;
public final boolean isNvidia;
public final boolean isAmd;
public final boolean nvBarryCoords;
public final boolean hasBrokenDepthSampler;
public Capabilities() {
var cap = GL.getCapabilities();
this.sparseBuffer = cap.GL_ARB_sparse_buffer;
this.compute = cap.glDispatchComputeIndirect != 0;
this.indirectParameters = cap.glMultiDrawElementsIndirectCountARB != 0;
this.repFragTest = cap.GL_NV_representative_fragment_test;
this.meshShaders = cap.GL_NV_mesh_shader;
this.canQueryGpuMemory = cap.GL_NVX_gpu_memory_info;
//this.INT64_t = cap.GL_ARB_gpu_shader_int64 || cap.GL_AMD_gpu_shader_int64;
//The only reliable way to test for int64 support is to try compile a shader
this.INT64_t = testShaderCompilesOk(ShaderType.COMPUTE, """
#version 430
#extension GL_ARB_gpu_shader_int64 : require
layout(local_size_x=32) in;
void main() {
uint64_t a = 1234;
}
""");
if (cap.GL_KHR_shader_subgroup) {
this.subgroup = testShaderCompilesOk(ShaderType.COMPUTE, """
#version 430
#extension GL_KHR_shader_subgroup_basic : require
#extension GL_KHR_shader_subgroup_arithmetic : require
layout(local_size_x=32) in;
void main() {
uint a = subgroupExclusiveAdd(gl_LocalInvocationIndex);
}
""");
} else {
this.subgroup = false;
}
this.ssboMaxSize = glGetInteger64(GL_MAX_SHADER_STORAGE_BLOCK_SIZE);
this.isMesa = glGetString(GL_VERSION).toLowerCase(Locale.ROOT).contains("mesa");
var vendor = glGetString(GL_VENDOR).toLowerCase(Locale.ROOT);
this.isIntel = vendor.contains("intel");
this.isNvidia = vendor.contains("nvidia");
this.isAmd = vendor.contains("amd")||vendor.contains("radeon");
if (this.canQueryGpuMemory) {
this.totalDedicatedMemory = glGetInteger64(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX)*1024;//Since its in Kb
this.totalDynamicMemory = (glGetInteger64(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX)*1024) - this.totalDedicatedMemory;//Since its in Kb
} else {
this.totalDedicatedMemory = -1;
this.totalDynamicMemory = -1;
}
this.nvBarryCoords = cap.GL_NV_fragment_shader_barycentric;
if (this.compute&&this.isAmd) {
this.hasBrokenDepthSampler = testDepthSampler();
if (this.hasBrokenDepthSampler) {
throw new IllegalStateException("it bork, amd is bork");
}
} else {
this.hasBrokenDepthSampler = false;
}
}
public static void init() {
}
private static boolean testDepthSampler() {
String src = """
#version 460 core
layout(local_size_x=16,local_size_y=16) in;
layout(binding = 0) uniform sampler2D depthSampler;
layout(binding = 1) buffer OutData {
float[] outData;
};
layout(location = 2) uniform int dynamicSampleThing;
layout(location = 3) uniform float sampleData;
void main() {
if (abs(texelFetch(depthSampler, ivec2(gl_GlobalInvocationID.xy), dynamicSampleThing).r-sampleData)>0.000001f) {
outData[0] = 1.0;
}
}
""";
int program = GL20C.glCreateProgram();
{
int shader = GL20C.glCreateShader(ShaderType.COMPUTE.gl);
GL20C.glShaderSource(shader, src);
GL20C.glCompileShader(shader);
if (GL20C.glGetShaderi(shader, GL20C.GL_COMPILE_STATUS) != 1) {
GL20C.glDeleteShader(shader);
throw new IllegalStateException("Shader compile fail");
}
GL20C.glAttachShader(program, shader);
GL20C.glLinkProgram(program);
glDeleteShader(shader);
}
int buffer = glCreateBuffers();
glNamedBufferStorage(buffer, 4096, GL_DYNAMIC_STORAGE_BIT|GL_MAP_READ_BIT);
int tex = glCreateTextures(GL_TEXTURE_2D);
glTextureStorage2D(tex, 2, GL_DEPTH24_STENCIL8, 256, 256);
glTextureParameteri(tex, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameteri(tex, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
int fb = glCreateFramebuffers();
boolean isCorrect = true;
for (int lvl = 0; lvl <= 1; lvl++) {
glNamedFramebufferTexture(fb, GL_DEPTH_STENCIL_ATTACHMENT, tex, lvl);
for (int i = 0; i <= 10; i++) {
float value = (float) (i / 10.0);
nglClearNamedBufferSubData(buffer, GL_R32F, 0, 4096, GL_RED, GL_FLOAT, 0);//Zero the buffer
glClearNamedFramebufferfi(fb, GL_DEPTH_STENCIL, 0, value, 1);//Set the depth texture
glUseProgram(program);
glUniform1i(2, lvl);
glUniform1f(3, value);
glBindTextureUnit(0, tex);
GL30.glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, buffer);
glDispatchCompute(256>>(lvl+4), 256>>(lvl+4), 1);
glFinish();
long ptr = nglMapNamedBuffer(buffer, GL_READ_ONLY);
float gottenValue = MemoryUtil.memGetFloat(ptr);
glUnmapNamedBuffer(buffer);
glUseProgram(0);
glBindTextureUnit(0, 0);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
boolean localCorrect = gottenValue==0.0f;
if (!localCorrect) {
Logger.error("Depth read test failed at value: " + value);
}
isCorrect &= localCorrect;
}
}
glDeleteFramebuffers(fb);
glDeleteTextures(tex);
glDeleteBuffers(buffer);
glDeleteProgram(program);
return !isCorrect;
}
private static boolean testShaderCompilesOk(ShaderType type, String src) {
int shader = GL20C.glCreateShader(type.gl);
GL20C.glShaderSource(shader, src);
GL20C.glCompileShader(shader);
int result = GL20C.glGetShaderi(shader, GL20C.GL_COMPILE_STATUS);
GL20C.glDeleteShader(shader);
return result == GL20C.GL_TRUE;
}
public long getFreeDedicatedGpuMemory() {
if (!this.canQueryGpuMemory) {
throw new IllegalStateException("Cannot query gpu memory, missing extension");
}
return glGetInteger64(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX)*1024;//Since its in Kb
}
//TODO: add gpu eviction tracking
}

View File

@@ -1,33 +1,95 @@
package me.cortex.voxy.client.core.gl;
import me.cortex.voxy.common.util.TrackedObject;
import org.lwjgl.opengl.GL11;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.ARBSparseBuffer.GL_SPARSE_STORAGE_BIT_ARB;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL15.glDeleteBuffers;
import static org.lwjgl.opengl.GL44C.glBufferStorage;
import static org.lwjgl.opengl.GL45C.glCreateBuffers;
import static org.lwjgl.opengl.GL45C.glNamedBufferStorage;
import static org.lwjgl.opengl.GL45C.*;
public class GlBuffer extends TrackedObject {
public final int id;
private final long size;
private final int flags;
private static int COUNT;
private static long TOTAL_SIZE;
public GlBuffer(long size) {
this(size, 0);
}
public GlBuffer(long size, boolean zero) {
this(size, 0, zero);
}
public GlBuffer(long size, int flags) {
this(size, flags, true);
}
public GlBuffer(long size, int flags, boolean zero) {
this.flags = flags;
this.id = glCreateBuffers();
this.size = size;
glNamedBufferStorage(this.id, size, flags);
if ((flags&GL_SPARSE_STORAGE_BIT_ARB)==0 && zero) {
this.zero();
}
COUNT++;
TOTAL_SIZE += size;
}
@Override
public void free() {
this.free0();
glDeleteBuffers(this.id);
COUNT--;
TOTAL_SIZE -= this.size;
}
public boolean isSparse() {
return (this.flags&GL_SPARSE_STORAGE_BIT_ARB)!=0;
}
public long size() {
return this.size;
}
public GlBuffer zero() {
nglClearNamedBufferData(this.id, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, 0);
return this;
}
public GlBuffer zeroRange(long offset, long size) {
nglClearNamedBufferSubData(this.id, GL_R8UI, offset, size, GL_RED_INTEGER, GL_UNSIGNED_BYTE, 0);
return this;
}
public GlBuffer fill(int data) {
//Clear unpack values
//Fixed in mesa commit a5c3c452
glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
MemoryUtil.memPutInt(SCRATCH, data);
nglClearNamedBufferData(this.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, SCRATCH);
return this;
}
public static int getCount() {
return COUNT;
}
public static long getTotalSize() {
return TOTAL_SIZE;
}
public GlBuffer name(String name) {
return GlDebug.name(name, this);
}
private static final long SCRATCH = MemoryUtil.nmemAlloc(4);
}

View File

@@ -0,0 +1,49 @@
package me.cortex.voxy.client.core.gl;
import me.cortex.voxy.client.core.gl.shader.Shader;
import static org.lwjgl.opengl.GL43C.*;
public class GlDebug {
public static final boolean GL_DEBUG = System.getProperty("voxy.glDebug", "false").equals("true");
public static void push() {
//glPushDebugGroup()
}
public static GlBuffer name(String name, GlBuffer buffer) {
if (GL_DEBUG) {
glObjectLabel(GL_BUFFER, buffer.id, name);
}
return buffer;
}
public static <T extends Shader> T name(String name, T shader) {
if (GL_DEBUG) {
glObjectLabel(GL_PROGRAM, shader.id(), name);
}
return shader;
}
public static GlFramebuffer name(String name, GlFramebuffer framebuffer) {
if (GL_DEBUG) {
glObjectLabel(GL_FRAMEBUFFER, framebuffer.id, name);
}
return framebuffer;
}
public static GlTexture name(String name, GlTexture texture) {
if (GL_DEBUG) {
glObjectLabel(GL_TEXTURE, texture.id, name);
}
return texture;
}
public static GlPersistentMappedBuffer name(String name, GlPersistentMappedBuffer buffer) {
if (GL_DEBUG) {
glObjectLabel(GL_BUFFER, buffer.id, name);
}
return buffer;
}
}

View File

@@ -1,6 +1,7 @@
package me.cortex.voxy.client.core.gl;
import me.cortex.voxy.common.util.TrackedObject;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.GL32.*;
@@ -12,13 +13,24 @@ public class GlFence extends TrackedObject {
this.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
private static final long SCRATCH = MemoryUtil.nmemCalloc(1,4);
public boolean signaled() {
if (!this.signaled) {
/*
int ret = glClientWaitSync(this.fence, 0, 0);
if (ret == GL_ALREADY_SIGNALED || ret == GL_CONDITION_SATISFIED) {
this.signaled = true;
} else if (ret != GL_TIMEOUT_EXPIRED) {
throw new IllegalStateException("Poll for fence failed, glError: " + glGetError());
}*/
MemoryUtil.memPutInt(SCRATCH, -1);
nglGetSynciv(this.fence, GL_SYNC_STATUS, 1, 0, SCRATCH);
int val = MemoryUtil.memGetInt(SCRATCH);
if (val == GL_SIGNALED) {
this.signaled = true;
} else if (val != GL_UNSIGNALED) {
throw new IllegalStateException("Unknown data from glGetSync: "+val);
}
}
return this.signaled;

View File

@@ -3,6 +3,7 @@ package me.cortex.voxy.client.core.gl;
import me.cortex.voxy.common.util.TrackedObject;
import static org.lwjgl.opengl.GL45C.*;
import static org.lwjgl.opengl.GL45C.glNamedFramebufferDrawBuffers;
public class GlFramebuffer extends TrackedObject {
public final int id;
@@ -19,6 +20,16 @@ public class GlFramebuffer extends TrackedObject {
return this;
}
public GlFramebuffer bind(int attachment, GlRenderBuffer buffer) {
glNamedFramebufferRenderbuffer(this.id, attachment, GL_RENDERBUFFER, buffer.id);
return this;
}
public GlFramebuffer setDrawBuffers(int... buffers) {
glNamedFramebufferDrawBuffers(this.id, buffers);
return this;
}
@Override
public void free() {
super.free0();
@@ -32,4 +43,9 @@ public class GlFramebuffer extends TrackedObject {
}
return this;
}
public GlFramebuffer name(String name) {
return GlDebug.name(name, this);
}
}

View File

@@ -13,14 +13,14 @@ public class GlPersistentMappedBuffer extends TrackedObject {
public GlPersistentMappedBuffer(long size, int flags) {
this.id = glCreateBuffers();
this.size = size;
glNamedBufferStorage(this.id, size, GL_CLIENT_STORAGE_BIT|GL_MAP_PERSISTENT_BIT|(flags&(GL_MAP_COHERENT_BIT|GL_MAP_WRITE_BIT|GL_MAP_READ_BIT)));
glNamedBufferStorage(this.id, size, GL_MAP_PERSISTENT_BIT|(flags&(GL_MAP_COHERENT_BIT|GL_MAP_WRITE_BIT|GL_MAP_READ_BIT|GL_CLIENT_STORAGE_BIT)));
this.addr = nglMapNamedBufferRange(this.id, 0, size, (flags&(GL_MAP_WRITE_BIT|GL_MAP_READ_BIT|GL_MAP_UNSYNCHRONIZED_BIT|GL_MAP_FLUSH_EXPLICIT_BIT))|GL_MAP_PERSISTENT_BIT);
}
@Override
public void free() {
this.free0();
glUnmapBuffer(this.id);
glUnmapNamedBuffer(this.id);
glDeleteBuffers(this.id);
}
@@ -31,4 +31,8 @@ public class GlPersistentMappedBuffer extends TrackedObject {
public long addr() {
return this.addr;
}
public GlPersistentMappedBuffer name(String name) {
return GlDebug.name(name, this);
}
}

View File

@@ -0,0 +1,20 @@
package me.cortex.voxy.client.core.gl;
import me.cortex.voxy.common.util.TrackedObject;
import static org.lwjgl.opengl.GL45C.*;
public class GlRenderBuffer extends TrackedObject {
public final int id;
public GlRenderBuffer(int format, int width, int height) {
this.id = glCreateRenderbuffers();
glNamedRenderbufferStorage(this.id, format, width, height);
}
@Override
public void free() {
super.free0();
glDeleteRenderbuffers(this.id);
}
}

View File

@@ -2,15 +2,23 @@ package me.cortex.voxy.client.core.gl;
import me.cortex.voxy.common.util.TrackedObject;
import static org.lwjgl.opengl.ARBFramebufferObject.glDeleteFramebuffers;
import static org.lwjgl.opengl.ARBFramebufferObject.glGenFramebuffers;
import static org.lwjgl.opengl.GL11.GL_RGBA8;
import static org.lwjgl.opengl.GL11C.*;
import static org.lwjgl.opengl.GL30C.glGetIntegeri;
import static org.lwjgl.opengl.GL30.GL_DEPTH24_STENCIL8;
import static org.lwjgl.opengl.GL45C.*;
public class GlTexture extends TrackedObject {
public final int id;
private final int type;
private int format;
private int width;
private int height;
private int levels;
private boolean hasAllocated;
private static int COUNT;
private static long ESTIMATED_TOTAL_SIZE;
public GlTexture() {
this(GL_TEXTURE_2D);
}
@@ -18,32 +26,111 @@ public class GlTexture extends TrackedObject {
public GlTexture(int type) {
this.id = glCreateTextures(type);
this.type = type;
COUNT++;
}
private GlTexture(int type, boolean useGenTypes) {
if (useGenTypes) {
this.id = glGenTextures();
} else {
this.id = glCreateTextures(type);
}
this.type = type;
COUNT++;
}
public GlTexture store(int format, int levels, int width, int height) {
if (this.hasAllocated) {
throw new IllegalStateException("Texture already allocated");
}
this.hasAllocated = true;
this.format = format;
if (this.type == GL_TEXTURE_2D) {
glTextureStorage2D(this.id, levels, format, width, height);
this.width = width;
this.height = height;
this.levels = levels;
} else {
throw new IllegalStateException("Unknown texture type");
}
ESTIMATED_TOTAL_SIZE += this.getEstimatedSize();
return this;
}
public GlTexture createView() {
this.assertAllocated();
var view = new GlTexture(this.type, true);
glTextureView(view.id, this.type, this.id, this.format, 0, 1, 0, 1);
return view;
}
@Override
public void free() {
if (this.hasAllocated) {
ESTIMATED_TOTAL_SIZE -= this.getEstimatedSize();
}
COUNT--;
this.hasAllocated = false;
super.free0();
glDeleteTextures(this.id);
}
//TODO: FIXME, glGetTextureParameteri doesnt work
public static int getRawTextureType(int texture) {
if (!glIsTexture(texture)) {
throw new IllegalStateException("Not texture");
public GlTexture name(String name) {
this.assertAllocated();
return GlDebug.name(name, this);
}
public int getWidth() {
this.assertAllocated();
return this.width;
}
public int getHeight() {
this.assertAllocated();
return this.height;
}
public int getLevels() {
this.assertAllocated();
return this.levels;
}
public int getFormat() {
this.assertAllocated();
return this.format;
}
private long getEstimatedSize() {
this.assertAllocated();
long elemSize = switch (this.format) {
case GL_R32UI, GL_RGBA8, GL_DEPTH24_STENCIL8, GL_R32F -> 4;
case GL_DEPTH_COMPONENT24 -> 4;//TODO: check this is right????
case GL_DEPTH_COMPONENT32F -> 4;
case GL_DEPTH_COMPONENT32 -> 4;
default -> throw new IllegalStateException("Unknown element size");
};
long size = 0;
for (int lvl = 0; lvl < this.levels; lvl++) {
size += Math.max((((long)this.width)>>lvl), 1) * Math.max((((long)this.height)>>lvl), 1) * elemSize;
}
int immFormat = glGetTextureParameteri(texture, GL_TEXTURE_IMMUTABLE_FORMAT);
if (immFormat == 0) {
throw new IllegalStateException("Texture: " + texture + " is not immutable");
return size;
}
public void assertAllocated() {
if (!this.hasAllocated) {
throw new IllegalStateException("Texture not yet allocated");
}
return immFormat;
}
public static int getCount() {
return COUNT;
}
public static long getEstimatedTotalSize() {
return ESTIMATED_TOTAL_SIZE;
}
}

View File

@@ -0,0 +1,75 @@
package me.cortex.voxy.client.core.gl;
import me.cortex.voxy.common.util.TrackedObject;
import java.util.Arrays;
import static org.lwjgl.opengl.GL30.glGenVertexArrays;
import static org.lwjgl.opengl.GL45C.*;
public class GlVertexArray extends TrackedObject {
public static final int STATIC_VAO = glGenVertexArrays();
public final int id;
private int[] indices = new int[0];
private int stride;
public GlVertexArray() {
this.id = glCreateVertexArrays();
}
@Override
public void free() {
this.free0();
glDeleteVertexArrays(this.id);
}
public void bind() {
glBindVertexArray(this.id);
}
public GlVertexArray bindBuffer(int buffer) {
//TODO: optimization, use glVertexArrayVertexBuffers
for (int index : this.indices) {
glVertexArrayVertexBuffer(this.id, index, buffer, 0, this.stride);
}
return this;
}
public GlVertexArray bindElementBuffer(int buffer) {
glVertexArrayElementBuffer(this.id, buffer);
return this;
}
public GlVertexArray setStride(int stride) {
this.stride = stride;
return this;
}
public GlVertexArray setI(int index, int type, int count, int offset) {
this.addIndex(index);
glEnableVertexArrayAttrib(this.id, index);
glVertexArrayAttribIFormat(this.id, index, count, type, offset);
return this;
}
public GlVertexArray setF(int index, int type, int count, int offset) {
return this.setF(index, type, count, false, offset);
}
public GlVertexArray setF(int index, int type, int count, boolean normalize, int offset) {
this.addIndex(index);
glEnableVertexArrayAttrib(this.id, index);
glVertexArrayAttribFormat(this.id, index, count, type, normalize, offset);
return this;
}
private void addIndex(int index) {
for (int i : this.indices) {
if (i == index) {
return;
}
}
this.indices = Arrays.copyOf(this.indices, this.indices.length+1);
this.indices[this.indices.length-1] = index;
}
}

View File

@@ -0,0 +1,145 @@
package me.cortex.voxy.client.core.gl.shader;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlDebug;
import me.cortex.voxy.client.core.gl.GlTexture;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.lwjgl.opengl.ARBDirectStateAccess.glBindTextureUnit;
import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL30.glBindBufferRange;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
//TODO: rewrite the entire shader builder system
public class AutoBindingShader extends Shader {
private record BufferBinding(int target, int index, GlBuffer buffer, long offset, long size) {}
private record TextureBinding(int unit, int sampler, GlTexture texture) {}
private final Map<String, String> defines;
private final List<BufferBinding> bindings = new ArrayList<>();
private final List<TextureBinding> textureBindings = new ArrayList<>();
private boolean rebuild = true;
AutoBindingShader(Shader.Builder<AutoBindingShader> builder, int program) {
super(program);
this.defines = builder.defines;
}
public AutoBindingShader name(String name) {
return GlDebug.name(name, this);
}
public AutoBindingShader ssboIf(String define, GlBuffer buffer) {
if (this.defines.containsKey(define)) {
return this.ssbo(define, buffer);
}
return this;
}
public AutoBindingShader ssbo(int index, GlBuffer binding) {
return this.ssbo(index, binding, 0);
}
public AutoBindingShader ssbo(String define, GlBuffer binding) {
return this.ssbo(Integer.parseInt(this.defines.get(define)), binding, 0);
}
public AutoBindingShader ssbo(int index, GlBuffer buffer, long offset) {
this.insertOrReplaceBinding(new BufferBinding(GL_SHADER_STORAGE_BUFFER, index, buffer, offset, -1));
return this;
}
public AutoBindingShader ubo(String define, GlBuffer buffer) {
return this.ubo(Integer.parseInt(this.defines.get(define)), buffer);
}
public AutoBindingShader ubo(int index, GlBuffer buffer) {
return this.ubo(index, buffer, 0);
}
public AutoBindingShader ubo(int index, GlBuffer buffer, long offset) {
this.insertOrReplaceBinding(new BufferBinding(GL_UNIFORM_BUFFER, index, buffer, offset, -1));
return this;
}
private void insertOrReplaceBinding(BufferBinding binding) {
this.rebuild = true;
//Check if there is already a binding at the index with the target, if so, replace it
for (int i = 0; i < this.bindings.size(); i++) {
var entry = this.bindings.get(i);
if (entry.target == binding.target && entry.index == binding.index) {
this.bindings.set(i, binding);
return;
}
}
//Else add the new binding
this.bindings.add(binding);
}
public AutoBindingShader texture(String define, GlTexture texture) {
return this.texture(define, -1, texture);
}
public AutoBindingShader texture(String define, int sampler, GlTexture texture) {
return this.texture(Integer.parseInt(this.defines.get(define)), sampler, texture);
}
public AutoBindingShader texture(int unit, int sampler, GlTexture texture) {
this.rebuild = true;
for (int i = 0; i < this.textureBindings.size(); i++) {
var entry = this.textureBindings.get(i);
if (entry.unit == unit) {
this.textureBindings.set(i, new TextureBinding(unit, sampler, texture));
return this;
}
}
this.textureBindings.add(new TextureBinding(unit, sampler, texture));
return this;
}
@Override
public void bind() {
super.bind();
//TODO: replace with multibind and use the invalidate flag
/*
glBindSamplers();
glBindTextures();
glBindBuffersBase();
glBindBuffersRange();
*/
if (!this.bindings.isEmpty()) {
for (var binding : this.bindings) {
binding.buffer.assertNotFreed();
if (binding.offset == 0 && binding.size == -1) {
glBindBufferBase(binding.target, binding.index, binding.buffer.id);
} else {
glBindBufferRange(binding.target, binding.index, binding.buffer.id, binding.offset, binding.size);
}
}
}
if (!this.textureBindings.isEmpty()) {
for (var binding : this.textureBindings) {
if (binding.texture != null) {
binding.texture.assertNotFreed();
glBindTextureUnit(binding.unit, binding.texture.id);
}
if (binding.sampler != -1) {
glBindSampler(binding.unit, binding.sampler);
}
}
}
}
}

View File

@@ -1,12 +0,0 @@
package me.cortex.voxy.client.core.gl.shader;
import java.util.regex.Pattern;
public class GenericsProcessor implements IShaderProcessor {
private static final Pattern GENERIC_DEFINE = Pattern.compile("#defineGen (?<name>[A-Za-z0-9]+)<(?<generic>[A-Za-z0-9]*)>");
private static final Pattern GENERIC_USE = Pattern.compile("(?<type>[A-Za-z0-9]+)<(?<generic>[A-Za-z0-9]*)>");
@Override
public String process(ShaderType type, String source) {
return null;
}
}

View File

@@ -0,0 +1,241 @@
package me.cortex.voxy.client.core.gl.shader;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import org.lwjgl.system.MemoryUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
import static org.lwjgl.opengl.ARBDirectStateAccess.nglClearNamedBufferData;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
import static org.lwjgl.opengl.GL30.GL_R32UI;
import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL45.nglClearNamedBufferSubData;
public class PrintfInjector implements IShaderProcessor {
private final GlBuffer textBuffer;
private final HashMap<String, Integer> printfStringMap = new HashMap<>();
private final HashMap<Integer, String> idToPrintfStringMap = new HashMap<>();
private final int bindingIndex;
private final Consumer<String> callback;
private final Runnable preRun;
public PrintfInjector(int bufferSize, int bufferBindingIndex, Consumer<String> callback) {
this(bufferSize, bufferBindingIndex, callback, null);
}
public PrintfInjector(int bufferSize, int bufferBindingIndex, Consumer<String> callback, Runnable pre) {
this.textBuffer = new GlBuffer(bufferSize*4L+4);
nglClearNamedBufferData(this.textBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
this.bindingIndex = bufferBindingIndex;
this.callback = callback;
this.preRun = pre;
}
private static int findNextCall(String src, int after) {
while (true) {
int idx = src.indexOf("printf", after);
if (idx == -1) {
return -1;
}
boolean lineComment = false;
boolean multiLineComment = false;
//Check for comments
for (int i = 0; i < idx; i++) {
if (src.charAt(i) == '/' && src.charAt(i + 1) == '/') {
lineComment = true;
}
if (src.charAt(i) == '\n') {
lineComment = false;
}
if ((!lineComment) && src.charAt(i) == '/' && src.charAt(i + 1) == '*') {
multiLineComment = true;
}
if ((!lineComment) && src.charAt(i) == '*' && src.charAt(i + 1) == '/') {
multiLineComment = false;
}
}
if (lineComment || multiLineComment) {
after = idx+1;
continue;
}
return idx;
}
}
private static void parsePrintfTypes(String fmtStr, List<Character> types) {
for (int i = 0; i < fmtStr.length()-1; i++) {
if (fmtStr.charAt(i)=='%' && (i==0||fmtStr.charAt(i-1)!='%')) {
types.add(fmtStr.charAt(i+1));
}
}
}
public String transformInject(String src) {
String original = src;
//Quick exit
if (!src.contains("printf")) {
return src;
}
int pos = 0;
StringBuilder result = new StringBuilder();
List<String> argVals = new ArrayList<>();
List<Character> types = new ArrayList<>();
{
int bufferInjection = Math.max(src.lastIndexOf("#version"), src.lastIndexOf("#extension"));
bufferInjection = src.indexOf("\n", bufferInjection);
result.append(src, 0, bufferInjection+1);
result.append(String.format("""
layout(binding = %s, std430) restrict buffer PrintfOutputStream {
uint index;
uint stream[];
} printfOutputStruct;
""", this.bindingIndex));
src = src.substring(bufferInjection+1);
}
boolean usedPrintf = false;
while (true) {
int nextCall = findNextCall(src, pos);
if (nextCall == -1) {
result.append(src, pos, src.length());
break;
}
result.append(src, pos, nextCall);
//Parse the printf() call
String sub = src.substring(nextCall);
sub = sub.substring(sub.indexOf('"')+1);
sub = sub.substring(0, sub.indexOf(';'));
String fmtStr = sub.substring(0, sub.indexOf('"'));
String args = sub.substring(sub.indexOf('"'));
//Parse the commas in the args
int prev = 0;
int brace = 0;
argVals.clear();
for (int i = 0; i < args.length(); i++) {
if (args.charAt(i) == '(' || args.charAt(i) == '[') brace++;
if (args.charAt(i) == ')' || args.charAt(i) == ']') brace--;
if ((args.charAt(i) == ',' && brace==0) || brace==-1) {
if (prev == 0) {
prev = i;
continue;
}
String arg = args.substring(prev+1, i);
prev = i;
argVals.add(arg);
if (brace==-1) {
break;
}
}
}
//Parse the format string
types.clear();
parsePrintfTypes(fmtStr, types);
if (types.size() != argVals.size()) {
throw new IllegalStateException("Printf obj count dont match arg size");
}
//Inject the printf code
StringBuilder subCode = new StringBuilder();
subCode.append(String.format("{" +
"uint printfWriteIndex = atomicAdd(printfOutputStruct.index,%s);" +
"printfOutputStruct.stream[printfWriteIndex]=%s;", types.size()+1,
this.printfStringMap.computeIfAbsent(fmtStr, a->{int id = this.printfStringMap.size();
this.idToPrintfStringMap.put(id, a);
return id;})));
for (int i = 0; i < types.size(); i++) {
subCode.append("printfOutputStruct.stream[printfWriteIndex+").append(i+1).append("]=");
if (types.get(i) == 'd') {
subCode.append("uint(").append(argVals.get(i)).append(")");
} else if (types.get(i) == 'f') {
subCode.append("floatBitsToUint(").append(argVals.get(i)).append(")");
} else {
throw new IllegalStateException("Unknown type " + types.get(i));
}
subCode.append(";");
}
subCode.append("}");
result.append(subCode);
usedPrintf = true;
pos = src.indexOf(';', nextCall)+1;
}
if (!usedPrintf) {
return original;
}
return result.toString();
}
public void bind() {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, this.bindingIndex, this.textBuffer.id);
}
private void processResult(long ptr, long size) {
int total = MemoryUtil.memGetInt(ptr); ptr += 4;
if (total == 0) {
return;
}
if (this.preRun != null) {
this.preRun.run();
}
int cnt = 0;
List<Character> types = new ArrayList<>();
while (cnt < total) {
int id = MemoryUtil.memGetInt(ptr);
ptr += 4;
cnt++;
String fmt = this.idToPrintfStringMap.get(id);
if (fmt == null) {
throw new IllegalStateException("Unknown id: "+ id);
}
types.clear();
parsePrintfTypes(fmt, types);
Object[] args = new Object[types.size()];
for (int i = 0; i < types.size(); i++) {
if (types.get(i) == 'd') {
args[i] = MemoryUtil.memGetInt(ptr);
ptr += 4;
cnt++;
}
if (types.get(i) == 'f') {
args[i] = Float.intBitsToFloat(MemoryUtil.memGetInt(ptr));
ptr += 4;
cnt++;
}
}
this.callback.accept(String.format(fmt, args));
}
}
public void download() {
DownloadStream.INSTANCE.download(this.textBuffer, this::processResult);
nglClearNamedBufferSubData(this.textBuffer.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
}
public void free() {
this.textBuffer.free();
}
@Override
public String process(ShaderType type, String source) {
return this.transformInject(source);
}
}

View File

@@ -1,8 +1,17 @@
package me.cortex.voxy.client.core.gl.shader;
import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.gl.GlDebug;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.ThreadUtils;
import me.cortex.voxy.common.util.TrackedObject;
import org.lwjgl.opengl.GL20C;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
@@ -11,25 +20,10 @@ import static org.lwjgl.opengl.GL20.glUseProgram;
public class Shader extends TrackedObject {
private final int id;
private Shader(int program) {
Shader(int program) {
id = program;
}
public static Builder make(IShaderProcessor... processors) {
List<IShaderProcessor> aa = new ArrayList<>(List.of(processors));
Collections.reverse(aa);
IShaderProcessor applicator = (type,source)->source;
for (IShaderProcessor processor : processors) {
IShaderProcessor finalApplicator = applicator;
applicator = (type, source) -> finalApplicator.process(type, processor.process(type, source));
}
return new Builder(applicator);
}
public static Builder make() {
return new Builder((aa,source)->source);
}
public int id() {
return this.id;
}
@@ -43,35 +37,106 @@ public class Shader extends TrackedObject {
glDeleteProgram(this.id);
}
public static class Builder {
private final Map<String, String> defines = new HashMap<>();
public Shader name(String name) {
return GlDebug.name(name, this);
}
public static Builder<Shader> make(IShaderProcessor... processor) {
return makeInternal((a,b)->new Shader(b), processor);
}
public static Builder<AutoBindingShader> makeAuto(IShaderProcessor... processor) {
return makeInternal(AutoBindingShader::new, processor);
}
static <T extends Shader> Builder<T> makeInternal(Builder.IShaderObjectConstructor<T> constructor, IShaderProcessor[] processors) {
List<IShaderProcessor> aa = new ArrayList<>(List.of(processors));
Collections.reverse(aa);
IShaderProcessor applicator = (type,source)->source;
for (IShaderProcessor processor : processors) {
IShaderProcessor finalApplicator = applicator;
applicator = (type, source) -> finalApplicator.process(type, processor.process(type, source));
}
return new Builder<>(constructor, applicator);
}
public static class Builder <T extends Shader> {
protected interface IShaderObjectConstructor <J extends Shader> {
J make(Builder<J> builder, int program);
}
final Map<String, String> defines = new HashMap<>();
final Map<String, String> replacements = new LinkedHashMap<>();
private final Map<ShaderType, String> sources = new HashMap<>();
private final IShaderProcessor processor;
private Builder(IShaderProcessor processor) {
private final IShaderObjectConstructor<T> constructor;
private Builder(IShaderObjectConstructor<T> constructor, IShaderProcessor processor) {
this.constructor = constructor;
this.processor = processor;
}
public Builder define(String name) {
public Builder<T> clone() {
var clone = new Builder<>(this.constructor, this.processor);
clone.defines.putAll(this.defines);
clone.sources.putAll(this.sources);
return clone;
}
public Builder<T> define(String name) {
this.defines.put(name, "");
return this;
}
public Builder define(String name, int value) {
//Useful for inline setting (such as debug)
public Builder<T> defineIf(String name, boolean condition) {
if (condition) {
this.defines.put(name, "");
}
return this;
}
public Builder<T> defineIf(String name, boolean condition, int value) {
if (condition) {
this.defines.put(name, Integer.toString(value));
}
return this;
}
public Builder<T> define(String name, int value) {
this.defines.put(name, Integer.toString(value));
return this;
}
public Builder add(ShaderType type, String id) {
public Builder<T> define(String name, float value) {
this.defines.put(name, Float.toString(value)+"f");
return this;
}
public Builder<T> define(String name, String value) {
this.defines.put(name, value);
return this;
}
public Builder<T> replace(String value, String replacement) {
this.defines.put(value, replacement);
return this;
}
public Builder<T> add(ShaderType type, String id) {
this.addSource(type, ShaderLoader.parse(id));
return this;
}
public Builder addSource(ShaderType type, String source) {
public Builder<T> addSource(ShaderType type, String source) {
this.sources.put(type, this.processor.process(type, source));
return this;
}
public Shader compile() {
private int compileToProgram() {
int program = GL20C.glCreateProgram();
int[] shaders = new int[this.sources.size()];
{
@@ -85,6 +150,10 @@ public class Shader extends TrackedObject {
defs
+ src.substring(src.indexOf('\n')+1);
for (var replacement : this.replacements.entrySet()) {
src = src.replace(replacement.getKey(), replacement.getValue());
}
shaders[i++] = createShader(entry.getKey(), src);
}
}
@@ -99,15 +168,20 @@ public class Shader extends TrackedObject {
}
printProgramLinkLog(program);
verifyProgramLinked(program);
return new Shader(program);
return program;
}
public T compile() {
this.defineIf("IS_INTEL", Capabilities.INSTANCE.isIntel);
this.defineIf("IS_WINDOWS", ThreadUtils.isWindows);
return this.constructor.make(this, this.compileToProgram());
}
private static void printProgramLinkLog(int program) {
String log = GL20C.glGetProgramInfoLog(program);
if (!log.isEmpty()) {
System.err.println(log);
Logger.error(log);
}
}
@@ -121,24 +195,33 @@ public class Shader extends TrackedObject {
private static int createShader(ShaderType type, String src) {
int shader = GL20C.glCreateShader(type.gl);
GL20C.glShaderSource(shader, src);
{//https://github.com/CaffeineMC/sodium/blob/fc42a7b19836c98a35df46e63303608de0587ab6/src/main/java/me/jellysquid/mods/sodium/client/gl/shader/ShaderWorkarounds.java
long ptr = MemoryUtil.memAddress(MemoryUtil.memUTF8(src, true));
try (var stack = MemoryStack.stackPush()) {
GL20C.nglShaderSource(shader, 1, stack.pointers(ptr).address0(), 0);
}
MemoryUtil.nmemFree(ptr);
}
GL20C.glCompileShader(shader);
String log = GL20C.glGetShaderInfoLog(shader);
if (!log.isEmpty()) {
System.err.println(log);
Logger.warn(log);
}
int result = GL20C.glGetShaderi(shader, GL20C.GL_COMPILE_STATUS);
if (result != GL20C.GL_TRUE) {
GL20C.glDeleteShader(shader);
throw new RuntimeException("Shader compilation failed of type " + type.name() + ", see log for details");
try {
Files.writeString(Path.of("SHADER_DUMP.txt"), src);
} catch (IOException e) {
throw new RuntimeException(e);
}
throw new RuntimeException("Shader compilation failed of type " + type.name() + ", see log for details, dumped shader");
}
return shader;
}
}
}

View File

@@ -1,11 +1,12 @@
package me.cortex.voxy.client.core.gl.shader;
import me.jellysquid.mods.sodium.client.gl.shader.ShaderConstants;
import me.jellysquid.mods.sodium.client.gl.shader.ShaderParser;
import net.caffeinemc.mods.sodium.client.gl.shader.ShaderConstants;
import net.caffeinemc.mods.sodium.client.gl.shader.ShaderParser;
public class ShaderLoader {
public static String parse(String id) {
return ShaderParser.parseShader("#import <" + id + ">", ShaderConstants.builder().build());
return "#version 460 core\n"+ShaderParser.parseShader("\n#import <" + id + ">\n//beans", ShaderConstants.builder().build()).src().replaceAll("\r\n", "\n").replaceFirst("\n#version .+\n", "\n");
//return me.jellysquid.mods.sodium.client.gl.shader.ShaderLoader.getShaderSource(new Identifier(id));
}
}

View File

@@ -1,139 +0,0 @@
package me.cortex.voxy.client.core.model;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.*;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.glBindTexture;
public class BakedBlockEntityModel {
private static final class BakedVertices implements VertexConsumer {
private boolean makingVertex = false;
public final RenderLayer layer;
private float cX, cY, cZ;
private int cR, cG, cB, cA;
private float cU, cV;
private final List<int[]> vertices = new ArrayList<>();
private BakedVertices(RenderLayer layer) {
this.layer = layer;
}
@Override
public VertexConsumer vertex(float x, float y, float z) {
this.next();
this.cX = x;
this.cY = y;
this.cZ = z;
this.makingVertex = true;
return this;
}
@Override
public VertexConsumer color(int red, int green, int blue, int alpha) {
this.cR = 0;//red;
this.cG = 0;//green;
this.cB = 0;//blue;
this.cA = alpha;
return this;
}
@Override
public VertexConsumer texture(float u, float v) {
this.cU = u;
this.cV = v;
return this;
}
@Override
public VertexConsumer overlay(int u, int v) {
return this;
}
@Override
public VertexConsumer light(int u, int v) {
return this;
}
@Override
public VertexConsumer normal(float x, float y, float z) {
return this;
}
private void next() {
if (this.makingVertex) {
this.makingVertex = false;
this.vertices.add(new int[]{
Float.floatToIntBits(this.cX), Float.floatToIntBits(this.cY), Float.floatToIntBits(this.cZ),
this.cR, this.cG, this.cB, this.cA,
Float.floatToIntBits(this.cU), Float.floatToIntBits(this.cV)});
}
}
public void putInto(VertexConsumer vc) {
this.next();
for (var vert : this.vertices) {
vc.vertex(Float.intBitsToFloat(vert[0]), Float.intBitsToFloat(vert[1]), Float.intBitsToFloat(vert[2]))
.color(vert[3], vert[4], vert[5], vert[6])
.texture(Float.intBitsToFloat(vert[7]), Float.intBitsToFloat(vert[8]));
}
}
}
private final List<BakedVertices> layers;
private BakedBlockEntityModel(List<BakedVertices> layers) {
this.layers = layers;
}
public void renderOut() {
var vc = Tessellator.getInstance();
for (var layer : this.layers) {
var bb = vc.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR);
if (layer.layer instanceof RenderLayer.MultiPhase mp) {
Identifier textureId = mp.phases.texture.getId().orElse(null);
if (textureId == null) {
System.err.println("ERROR: Empty texture id for layer: " + layer);
} else {
var texture = MinecraftClient.getInstance().getTextureManager().getTexture(textureId);
glBindTexture(GL_TEXTURE_2D, texture.getGlId());
}
}
layer.putInto(bb);
BufferRenderer.draw(bb.end());
}
}
public static BakedBlockEntityModel bake(BlockState state) {
Map<RenderLayer, BakedVertices> map = new HashMap<>();
var entity = ((BlockEntityProvider)state.getBlock()).createBlockEntity(BlockPos.ORIGIN, state);
if (entity == null) {
return null;
}
var renderer = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(entity);
if (renderer != null) {
entity.setWorld(MinecraftClient.getInstance().world);
try {
renderer.render(entity, 0.0f, new MatrixStack(), layer->map.computeIfAbsent(layer, BakedVertices::new), 0, 0);
} catch (Exception e) {
System.err.println("Unable to bake block entity: " + entity);
e.printStackTrace();
}
}
entity.markRemoved();
if (map.isEmpty()) {
return null;
}
return new BakedBlockEntityModel(map.values().stream().toList());
}
}

View File

@@ -2,21 +2,25 @@ package me.cortex.voxy.client.core.model;
import java.util.Arrays;
public record ColourDepthTextureData(int[] colour, int[] depth, int width, int height) {
public record ColourDepthTextureData(int[] colour, int[] depth, int width, int height, int hash) {
public ColourDepthTextureData(int[] colour, int[] depth, int width, int height) {
this(colour, depth, width, height, width * 312337173 * (Arrays.hashCode(colour) ^ Arrays.hashCode(depth)) ^ height);
}
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
var other = ((ColourDepthTextureData)obj);
return Arrays.equals(other.colour, this.colour) && Arrays.equals(other.depth, this.depth);
return this.hash == other.hash && Arrays.equals(other.colour, this.colour) && Arrays.equals(other.depth, this.depth);
}
@Override
public int hashCode() {
return (this.width * 312337173 * (Arrays.hashCode(this.colour) ^ Arrays.hashCode(this.depth))) ^ this.height;
return this.hash;
}
@Override
public ColourDepthTextureData clone() {
return new ColourDepthTextureData(Arrays.copyOf(this.colour, this.colour.length), Arrays.copyOf(this.depth, this.depth.length), this.width, this.height);
return new ColourDepthTextureData(Arrays.copyOf(this.colour, this.colour.length), Arrays.copyOf(this.depth, this.depth.length), this.width, this.height, this.hash);
}
}

View File

@@ -1,7 +1,13 @@
package me.cortex.voxy.client.core.model;
public class IdNotYetComputedException extends RuntimeException {
public IdNotYetComputedException(int id) {
super("Id not yet computed: " + id);
public final int id;
public final boolean isIdBlockId;
public int auxBitMsk;
public long[] auxData;
public IdNotYetComputedException(int id, boolean isIdBlockId) {
super(null, null, false, false);
this.id = id;
this.isIdBlockId = isIdBlockId;
}
}

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

@@ -0,0 +1,120 @@
package me.cortex.voxy.client.core.model;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.world.other.Mapper;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import static org.lwjgl.opengl.GL11.glGetInteger;
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER;
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_BINDING;
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
public class ModelBakerySubsystem {
//Redo to just make it request the block faces with the async texture download stream which
// basicly solves all the render stutter due to the baking
private final ModelStore storage = new ModelStore();
public final ModelFactory factory;
private final Mapper mapper;
private final AtomicInteger blockIdCount = new AtomicInteger();
private final ConcurrentLinkedDeque<Integer> blockIdQueue = new ConcurrentLinkedDeque<>();//TODO: replace with custom DS
private final Thread processingThread;
private volatile boolean isRunning = true;
public ModelBakerySubsystem(Mapper mapper) {
this.mapper = mapper;
this.factory = new ModelFactory(mapper, this.storage);
this.processingThread = new Thread(()->{//TODO replace this with something good/integrate it into the async processor so that we just have less threads overall
while (this.isRunning) {
this.factory.processAllThings();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "Model factory processor");
this.processingThread.start();
}
public void tick(long totalBudget) {
long start = System.nanoTime();
this.factory.tickAndProcessUploads();
//Always do 1 iteration minimum
Integer i = this.blockIdQueue.poll();
if (i != null) {
int j = 0;
if (i != null) {
int fbBinding = glGetInteger(GL_FRAMEBUFFER_BINDING);
do {
this.factory.addEntry(i);
j++;
if (4<j&&(totalBudget<(System.nanoTime() - start)+50_000))//20<j||
break;
i = this.blockIdQueue.poll();
} while (i != null);
glBindFramebuffer(GL_FRAMEBUFFER, fbBinding);//This is done here as stops needing to set then unset the fb in the thing 1000x
}
this.blockIdCount.addAndGet(-j);
}
//TimingStatistics.modelProcess.stop();
}
public void shutdown() {
this.isRunning = false;
try {
this.processingThread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.factory.free();
this.storage.free();
}
//This is on this side only and done like this as only worker threads call this code
private final ReentrantLock seenIdsLock = new ReentrantLock();
private final IntOpenHashSet seenIds = new IntOpenHashSet(6000);//TODO: move to a lock free concurrent hashmap
public void requestBlockBake(int blockId) {
if (this.mapper.getBlockStateCount() < blockId) {
Logger.error("Error, got bakeing request for out of range state id. StateId: " + blockId + " max id: " + this.mapper.getBlockStateCount(), new Exception());
return;
}
this.seenIdsLock.lock();
if (!this.seenIds.add(blockId)) {
this.seenIdsLock.unlock();
return;
}
this.seenIdsLock.unlock();
this.blockIdQueue.add(blockId);
this.blockIdCount.incrementAndGet();
}
public void addBiome(Mapper.BiomeEntry biomeEntry) {
this.factory.addBiome(biomeEntry);
}
public void addDebugData(List<String> debug) {
debug.add(String.format("MQ/IF/MC: %04d, %03d, %04d", this.blockIdCount.get(), this.factory.getInflightCount(), this.factory.getBakedCount()));//Model bake queue/in flight/model baked count
}
public ModelStore getStore() {
return this.storage;
}
public boolean areQueuesEmpty() {
return this.blockIdCount.get()==0 && this.factory.getInflightCount() == 0;
}
public int getProcessingCount() {
return this.blockIdCount.get() + this.factory.getInflightCount();
}
}

View File

@@ -0,0 +1,923 @@
package me.cortex.voxy.client.core.model;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.model.bakery.ModelTextureBakery;
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.util.Pair;
import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.color.block.BlockColor;
import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.ColorResolver;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.FluidState;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import java.lang.invoke.VarHandle;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.locks.ReentrantLock;
import static me.cortex.voxy.client.core.model.ModelStore.MODEL_SIZE;
import static org.lwjgl.opengl.ARBDirectStateAccess.nglTextureSubImage2D;
import static org.lwjgl.opengl.GL11.*;
//Manages the storage and updating of model states, textures and colours
//Also has a fast long[] based metadata lookup for when the terrain mesher needs to look up the face occlusion data
//TODO: support more than 65535 states, what should actually happen is a blockstate is registered, the model data is generated, then compared
// to all other models already loaded, if it is a duplicate, create a mapping from the id to the already loaded id, this will help with meshing aswell
// as leaves and such will be able to be merged
//TODO: NOTE!!! is it worth even uploading as a 16x16 texture, since automatic lod selection... doing 8x8 textures might be perfectly ok!!!
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
public class ModelFactory {
public static final int MODEL_TEXTURE_SIZE = 16;
public static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE);
//TODO: replace the fluid BlockState with a client model id integer of the fluidState, requires looking up
// the fluid state in the mipper
private record ModelEntry(ColourDepthTextureData down, ColourDepthTextureData up, ColourDepthTextureData north, ColourDepthTextureData south, ColourDepthTextureData west, ColourDepthTextureData east, int fluidBlockStateId, int tintingColour) {
public ModelEntry(ColourDepthTextureData[] textures, int fluidBlockStateId, int tintingColour) {
this(textures[0], textures[1], textures[2], textures[3], textures[4], textures[5], fluidBlockStateId, tintingColour);
}
}
private final Biome DEFAULT_BIOME = Minecraft.getInstance().level.registryAccess().lookupOrThrow(Registries.BIOME).getValue(Biomes.PLAINS);
public final ModelTextureBakery bakery;
//Model data might also contain a constant colour if the colour resolver produces a constant colour, this saves space in the
// section buffer reverse indexing
//model data also contains if a face should be randomly rotated,flipped etc to get rid of moire effect
// this would be done in the fragment shader
//The Meta-cache contains critical information needed for meshing, colour provider bit, per-face = is empty, has alpha, is solid, full width, full height
// alpha means that some pixels have alpha values and belong in the translucent rendering layer,
// is empty means that the face is air/shouldent be rendered as there is nothing there
// is solid means that every pixel is fully opaque
// full width, height, is if the blockmodel dimentions occupy a full block, e.g. comparator, some faces do some dont and some only in a specific axis
//FIXME: the issue is e.g. leaves are translucent but the alpha value is used to colour the leaves, so a block can have alpha but still be only made up of transparent or opaque pixels
// will need to find a way to send this info to the shader via the material, if it is in the opaque phase render as transparent with blending shiz
//TODO: ADD an occlusion mask that can be queried (16x16 pixels takes up 4 longs) this mask shows what pixels are exactly occluded at the edge of the block
// so that full block occlusion can work nicely
//TODO: what might work maybe, is that all the transparent pixels should be set to the average of the other pixels
// that way the block is always "fully occluding" (if the block model doesnt cover the entire thing), maybe
// this has some issues with quad merging
//TODO: ACTUALLY, full out all the transparent pixels that are _within_ the bounding box of the model
// this will mean that when quad merging and rendering, the transparent pixels of the block where there shouldent be
// might still work???
// this has an issue with scaffolding i believe tho, so maybe make it a probability to render??? idk
private final long[] metadataCache;
private final int[] fluidStateLUT;
//Provides a map from id -> model id as multiple ids might have the same internal model id
private final int[] idMappings;
private final Object2IntOpenHashMap<ModelEntry> modelTexture2id = new Object2IntOpenHashMap<>();
//Contains the set of all block ids that are currently inflight/being baked
// this is required due to "async" nature of gpu feedback
private final IntOpenHashSet blockStatesInFlight = new IntOpenHashSet();
private final ReentrantLock blockStatesInFlightLock = new ReentrantLock();
private final List<Biome> biomes = new ArrayList<>();
private final List<Pair<Integer, BlockState>> modelsRequiringBiomeColours = new ArrayList<>();
private static final ObjectSet<BlockState> LOGGED_SELF_CULLING_WARNING = new ObjectOpenHashSet<>();
private final Mapper mapper;
private final ModelStore storage;
private final RawDownloadStream downstream = new RawDownloadStream(8*1024*1024);//8mb downstream
private final ConcurrentLinkedDeque<RawBakeResult> rawBakeResults = new ConcurrentLinkedDeque<>();
private final ConcurrentLinkedDeque<ResultUploader> uploadResults = new ConcurrentLinkedDeque<>();
private Object2IntMap<BlockState> customBlockStateIdMapping;
//TODO: NOTE!!! is it worth even uploading as a 16x16 texture, since automatic lod selection... doing 8x8 textures might be perfectly ok!!!
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
public ModelFactory(Mapper mapper, ModelStore storage) {
this.mapper = mapper;
this.storage = storage;
this.bakery = new ModelTextureBakery(MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
this.metadataCache = new long[1<<16];
this.fluidStateLUT = new int[1<<16];
this.idMappings = new int[1<<20];//Max of 1 million blockstates mapping to 65k model states
Arrays.fill(this.idMappings, -1);
Arrays.fill(this.fluidStateLUT, -1);
this.modelTexture2id.defaultReturnValue(-1);
this.addEntry(0);//Add air as the first entry
}
public void setCustomBlockStateMapping(Object2IntMap<BlockState> mapping) {
this.customBlockStateIdMapping = mapping;
}
private static final class RawBakeResult {
private final int blockId;
private final BlockState blockState;
private final MemoryBuffer rawData;
public boolean isShaded;
public boolean hasDarkenedTextures;
public RawBakeResult(int blockId, BlockState blockState, MemoryBuffer rawData) {
this.blockId = blockId;
this.blockState = blockState;
this.rawData = rawData;
}
public RawBakeResult(int blockId, BlockState blockState) {
this(blockId, blockState, new MemoryBuffer(MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6));
}
public RawBakeResult cpyBuf(long ptr) {
this.rawData.cpyFrom(ptr);
return this;
}
}
public boolean addEntry(int blockId) {
if (this.idMappings[blockId] != -1) {
return false;
}
//We are (probably) going to be baking the block id
// check that it is currently not inflight, if it is, return as its already being baked
// else add it to the flight as it is going to be baked
this.blockStatesInFlightLock.lock();
if (!this.blockStatesInFlight.add(blockId)) {
this.blockStatesInFlightLock.unlock();
//Block baking is already in-flight
return false;
}
this.blockStatesInFlightLock.unlock();
VarHandle.loadLoadFence();
//We need to get it twice cause of threading
if (this.idMappings[blockId] != -1) {
return false;
}
var blockState = this.mapper.getBlockStateFromBlockId(blockId);
//Before we enqueue the baking of this blockstate, we must check if it has a fluid state associated with it
// if it does, we must ensure that it is (effectivly) baked BEFORE we bake this blockstate
boolean isFluid = blockState.getBlock() instanceof LiquidBlock;
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
//Insert into the fluid LUT
var fluidState = blockState.getFluidState().createLegacyBlock();
int fluidStateId = this.mapper.getIdForBlockState(fluidState);
if (this.idMappings[fluidStateId] == -1) {
//Dont have to check for inflight as that is done recursively :p
//This is a hack but does work :tm: due to how the download stream is setup
// it should enforce that the fluid state is processed before our blockstate
addEntry(fluidStateId);
}
}
RawBakeResult result = new RawBakeResult(blockId, blockState);
int allocation = this.downstream.download(MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6, ptr -> this.rawBakeResults.add(result.cpyBuf(ptr)));
int flags = this.bakery.renderToStream(blockState, this.downstream.getBufferId(), allocation);
result.hasDarkenedTextures = (flags&2)!=0;
result.isShaded = (flags&1)!=0;
return true;
}
private boolean processModelResult() {
var result = this.rawBakeResults.poll();
if (result == null) return false;
ColourDepthTextureData[] textureData = new ColourDepthTextureData[6];
{//Create texture data
long ptr = result.rawData.address;
final int FACE_SIZE = MODEL_TEXTURE_SIZE * MODEL_TEXTURE_SIZE;
for (int face = 0; face < 6; face++) {
long faceDataPtr = ptr + (FACE_SIZE * 4) * face * 2;
int[] colour = new int[FACE_SIZE];
int[] depth = new int[FACE_SIZE];
//Copy out colour
for (int i = 0; i < FACE_SIZE; i++) {
//De-interpolate results
colour[i] = MemoryUtil.memGetInt(faceDataPtr + (i * 4 * 2));
depth[i] = MemoryUtil.memGetInt(faceDataPtr + (i * 4 * 2) + 4);
}
textureData[face] = new ColourDepthTextureData(colour, depth, MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
}
}
result.rawData.free();
var bakeResult = this.processTextureBakeResult(result.blockId, result.blockState, textureData, result.isShaded, result.hasDarkenedTextures);
if (bakeResult!=null) {
this.uploadResults.add(bakeResult);
}
return !this.rawBakeResults.isEmpty();
}
private final ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeQueue = new ConcurrentLinkedDeque<>();
public void addBiome(Mapper.BiomeEntry biome) {
this.biomeQueue.add(biome);
}
public void processAllThings() {
var biomeEntry = this.biomeQueue.poll();
while (biomeEntry != null) {
var biomeRegistry = Minecraft.getInstance().level.registryAccess().lookupOrThrow(Registries.BIOME);
var res = this.addBiome0(biomeEntry.id, biomeRegistry.getValue(Identifier.parse(biomeEntry.biome)));
if (res != null) {
this.uploadResults.add(res);
}
biomeEntry = this.biomeQueue.poll();
}
while (this.processModelResult());
}
public void tickAndProcessUploads() {
this.downstream.tick();
var upload = this.uploadResults.poll();
if (upload==null) return;
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
do {
upload.upload(this.storage);
upload.free();
upload = this.uploadResults.poll();
} while (upload != null);
UploadStream.INSTANCE.commit();
}
private interface ResultUploader {
void upload(ModelStore store);
void free();
}
private static final class ModelBakeResultUpload implements ResultUploader {
private final MemoryBuffer model = new MemoryBuffer(MODEL_SIZE).zero();
private final MemoryBuffer texture = new MemoryBuffer((2L*3*computeSizeWithMips(MODEL_TEXTURE_SIZE))*4);
public int modelId = -1;
public int biomeUploadIndex = -1;
public @Nullable MemoryBuffer biomeUpload;
public void upload(ModelStore store) {//Uploads and resets for reuse
this.upload(store.modelBuffer, store.modelColourBuffer, store.textures);
}
public void upload(GlBuffer modelBuffer, GlBuffer colourBuffer, GlTexture atlas) {//Uploads and resets for reuse
this.model.cpyTo(UploadStream.INSTANCE.upload(modelBuffer, (long) this.modelId * MODEL_SIZE, MODEL_SIZE));
if (this.biomeUploadIndex != -1) {
this.biomeUpload.cpyTo(UploadStream.INSTANCE.upload(colourBuffer, this.biomeUploadIndex * 4L, this.biomeUpload.size));
this.biomeUploadIndex = -1;
this.biomeUpload.free();
this.biomeUpload = null;
}
int X = (this.modelId&0xFF) * MODEL_TEXTURE_SIZE*3;
int Y = ((this.modelId>>8)&0xFF) * MODEL_TEXTURE_SIZE*2;
long cAddr = this.texture.address;
for (int lvl = 0; lvl < LAYERS; lvl++) {
nglTextureSubImage2D(atlas.id, lvl, X >> lvl, Y >> lvl, (MODEL_TEXTURE_SIZE*3) >> lvl, (MODEL_TEXTURE_SIZE*2) >> lvl, GL_RGBA, GL_UNSIGNED_BYTE, cAddr);
cAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(lvl<<1);
}
this.modelId = -1;
}
public void free() {
this.model.free();
this.texture.free();
if (this.biomeUpload != null) {
this.biomeUpload.free();
}
}
}
private ModelBakeResultUpload processTextureBakeResult(int blockId, BlockState blockState, ColourDepthTextureData[] textureData, boolean isShaded, boolean darkenedTinting) {
if (this.idMappings[blockId] != -1) {
//This should be impossible to reach as it means that multiple bakes for the same blockId happened and where inflight at the same time!
throw new IllegalStateException("Block id already added: " + blockId + " for state: " + blockState);
}
this.blockStatesInFlightLock.lock();
if (!this.blockStatesInFlight.contains(blockId)) {
this.blockStatesInFlightLock.unlock();
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
}
this.blockStatesInFlightLock.unlock();
//TODO: add thing for `blockState.hasEmissiveLighting()` and `blockState.getLuminance()`
boolean isFluid = blockState.getBlock() instanceof LiquidBlock;
int modelId = -1;
int clientFluidStateId = -1;
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
//Insert into the fluid LUT
var fluidState = blockState.getFluidState().createLegacyBlock();
int fluidStateId = this.mapper.getIdForBlockState(fluidState);
clientFluidStateId = this.idMappings[fluidStateId];
if (clientFluidStateId == -1) {
throw new IllegalStateException("Block has a fluid state but fluid state is not already baked!!!");
}
}
var colourProvider = getColourProvider(blockState.getBlock());
boolean isBiomeColourDependent = false;
if (colourProvider != null) {
isBiomeColourDependent = isBiomeDependentColour(colourProvider, blockState);
}
ModelEntry entry;
{//Deduplicate same entries
entry = new ModelEntry(textureData, clientFluidStateId, isBiomeColourDependent||colourProvider==null?-1:captureColourConstant(colourProvider, blockState, DEFAULT_BIOME)|0xFF000000);
int possibleDuplicate = this.modelTexture2id.getInt(entry);
if (possibleDuplicate != -1) {//Duplicate found
this.idMappings[blockId] = possibleDuplicate;
modelId = possibleDuplicate;
//Remove from flight
this.blockStatesInFlightLock.lock();
if (!this.blockStatesInFlight.remove(blockId)) {
this.blockStatesInFlightLock.unlock();
throw new IllegalStateException();
}
this.blockStatesInFlightLock.unlock();
return null;
} else {//Not a duplicate so create a new entry
modelId = this.modelTexture2id.size();
//NOTE: we set the mapping at the very end so that race conditions with this and getMetadata dont occur
//this.idMappings[blockId] = modelId;
this.modelTexture2id.put(entry, modelId);
}
}
if (isFluid) {
this.fluidStateLUT[modelId] = modelId;
} else if (clientFluidStateId != -1) {
this.fluidStateLUT[modelId] = clientFluidStateId;
}
ChunkSectionLayer blockRenderLayer = null;
if (blockState.getBlock() instanceof LiquidBlock) {
blockRenderLayer = ItemBlockRenderTypes.getRenderLayer(blockState.getFluidState());
} else {
if (blockState.getBlock() instanceof LeavesBlock) {
blockRenderLayer = ChunkSectionLayer.SOLID;
} else {
blockRenderLayer = ItemBlockRenderTypes.getChunkRenderType(blockState);
}
}
int checkMode = blockRenderLayer==ChunkSectionLayer.SOLID?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
ModelBakeResultUpload uploadResult = new ModelBakeResultUpload();
uploadResult.modelId = modelId;
long uploadPtr = uploadResult.model.address;
//TODO: implement;
// TODO: if it has a constant colour instead... idk why (apparently for things like spruce leaves)?? but premultiply the texture data by the constant colour
//If it contains fluid but isnt a fluid
if ((!isFluid) && (!blockState.getFluidState().isEmpty()) && clientFluidStateId != -1) {
//Or it with the fluid state biome dependency
isBiomeColourDependent |= ModelQueries.isBiomeColoured(this.getModelMetadataFromClientId(clientFluidStateId));
}
//TODO: special case stuff like vines and glow lichen, where it can be represented by a single double sided quad
// since that would help alot with perf of lots of vines, can be done by having one of the faces just not exist and the other be in no occlusion mode
var depths = this.computeModelDepth(textureData, checkMode);
//TODO: THIS, note this can be tested for in 2 ways, re render the model with quad culling disabled and see if the result
// is the same, (if yes then needs double sided quads)
// another way to test it is if e.g. up and down havent got anything rendered but the sides do (e.g. all plants etc)
boolean needsDoubleSidedQuads = (depths[0] < -0.1 && depths[1] < -0.1) || (depths[2] < -0.1 && depths[3] < -0.1) || (depths[4] < -0.1 && depths[5] < -0.1);
boolean cullsSame = false;
{
//TODO: Could also move this into the RenderDataFactory and do it on the actual blockstates instead of a guestimation
boolean allTrue = true;
boolean allFalse = true;
//Guestimation test for if the block culls itself
for (var dir : Direction.values()) {
if (blockState.skipRendering(blockState, dir)) {
allFalse = false;
} else {
allTrue = false;
}
}
if (allFalse == allTrue) {//If only some sides where self culled then abort
cullsSame = false;
//if (LOGGED_SELF_CULLING_WARNING.add(blockState))
// Logger.info("Warning! blockstate: " + blockState + " only culled against its self some of the time");
}
if (allTrue) {
cullsSame = true;
}
}
//Each face gets 1 byte, with the top 2 bytes being for whatever
long metadata = 0;
metadata |= isBiomeColourDependent?1:0;
metadata |= blockRenderLayer == ChunkSectionLayer.TRANSLUCENT?2:0;
metadata |= needsDoubleSidedQuads?4:0;
metadata |= ((!isFluid) && !blockState.getFluidState().isEmpty())?8:0;//Has a fluid state accosiacted with it and is not itself a fluid
metadata |= isFluid?16:0;//Is a fluid
metadata |= cullsSame?32:0;
boolean fullyOpaque = true;
//TODO: FIXME faces that have the same "alignment depth" e.g. (sizes[0]+sizes[1])~=1 can be merged into a double faced single quad
//TODO: add a bunch of control config options for overriding/setting options of metadata for each face of each type
for (int face = 5; face != -1; face--) {//In reverse order to make indexing into the metadata long easier
long faceUploadPtr = uploadPtr + 4L * face;//Each face gets 4 bytes worth of data
metadata <<= 8;
float offset = depths[face];
if (offset < -0.1) {//Face is empty, so ignore
metadata |= 0xFF;//Mark the face as non-existent
//Set to -1 as safepoint
MemoryUtil.memPutInt(faceUploadPtr, -1);
fullyOpaque = false;
continue;
}
var faceSize = TextureUtils.computeBounds(textureData[face], checkMode);
int writeCount = TextureUtils.getWrittenPixelCount(textureData[face], checkMode);
boolean faceCoversFullBlock = faceSize[0] == 0 && faceSize[2] == 0 &&
faceSize[1] == (MODEL_TEXTURE_SIZE-1) && faceSize[3] == (MODEL_TEXTURE_SIZE-1);
//TODO: use faceSize and the depths to compute if mesh can be correctly rendered
metadata |= faceCoversFullBlock?2:0;
//TODO: add alot of config options for the following
boolean occludesFace = true;
occludesFace &= blockRenderLayer != ChunkSectionLayer.TRANSLUCENT;//If its translucent, it doesnt occlude
//TODO: make this an option, basicly if the face is really close, it occludes otherwise it doesnt
occludesFace &= offset < 0.1;//If the face is rendered far away from the other face, then it doesnt occlude
if (occludesFace) {
occludesFace &= ((float)writeCount)/(MODEL_TEXTURE_SIZE * MODEL_TEXTURE_SIZE) > 0.9;// only occlude if the face covers more than 90% of the face
}
metadata |= occludesFace?1:0;
fullyOpaque &= occludesFace;
boolean canBeOccluded = true;
//TODO: make this an option on how far/close
canBeOccluded &= offset < 0.3;//If the face is rendered far away from the other face, then it cant be occluded
metadata |= canBeOccluded?4:0;
//Face uses its own lighting if its not flat against the adjacent block & isnt traslucent
metadata |= (offset > 0.01 || blockRenderLayer == ChunkSectionLayer.TRANSLUCENT)?0b1000:0;
if (MODEL_TEXTURE_SIZE-1 != 15) {
//Scale face size from 0->this.modelTextureSize-1 to 0->15
for (int i = 0; i < 4; i++) {
faceSize[i] = Math.round((((float) faceSize[i]) / (MODEL_TEXTURE_SIZE - 1)) * 15);
}
}
int faceModelData = 0;
faceModelData |= faceSize[0] | (faceSize[1]<<4) | (faceSize[2]<<8) | (faceSize[3]<<12);
//Change the scale from 0->1 (ends inclusive)
// this is cursed also warning stuff at 63 (i.e half a pixel from the end will be clamped to the end)
int enc = Math.round(offset*64);
faceModelData |= Math.min(enc,62)<<16;
//Still have 11 bits free
//Stuff like fences are solid, however they have extra side piece that mean it needs to have discard on
int area = (faceSize[1]-faceSize[0]+1) * (faceSize[3]-faceSize[2]+1);
boolean needsAlphaDiscard = ((float)writeCount)/area<0.9;//If the amount of area covered by written pixels is less than a threashold, disable discard as its not needed
needsAlphaDiscard |= blockRenderLayer != ChunkSectionLayer.SOLID;
needsAlphaDiscard &= blockRenderLayer != ChunkSectionLayer.TRANSLUCENT;//Translucent doesnt have alpha discard
faceModelData |= needsAlphaDiscard?1<<22:0;
faceModelData |= ((!faceCoversFullBlock)&&blockRenderLayer != ChunkSectionLayer.TRANSLUCENT)?1<<23:0;//Alpha discard override, translucency doesnt have alpha discard
//Bits 24,25 are tint metadata
if (colourProvider!=null) {//We have a tint
int tintState = TextureUtils.computeFaceTint(textureData[face], checkMode);
if (tintState == 2) {//Partial tint
faceModelData |= 1<<24;
} else if (tintState == 3) {//Full tint
faceModelData |= 2<<24;
}
}
MemoryUtil.memPutInt(faceUploadPtr, faceModelData);
}
metadata |= fullyOpaque?(1L<<(48+6)):0;
boolean canBeCorrectlyRendered = true;//This represents if a model can be correctly (perfectly) represented
// i.e. no gaps
this.metadataCache[modelId] = metadata;
uploadPtr += 4*6;
//Have 40 bytes free for remaining model data
// todo: put in like the render layer type ig? along with colour resolver info
int modelFlags = 0;
modelFlags |= colourProvider != null?1:0;
modelFlags |= isBiomeColourDependent?2:0;//Basicly whether to use the next int as a colour or as a base index/id into a colour buffer for biome dependent colours
modelFlags |= blockRenderLayer == ChunkSectionLayer.TRANSLUCENT?4:0;//Is translucent
//TODO: THIS
modelFlags |= isShaded?8:0;//model has AO and shade
//modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha
MemoryUtil.memPutInt(uploadPtr, modelFlags); uploadPtr += 4;
//Temporary override to always be non biome specific
if (colourProvider == null) {
MemoryUtil.memPutInt(uploadPtr, -1);//Set the default to nothing so that its faster on the gpu
} else if (!isBiomeColourDependent) {
MemoryUtil.memPutInt(uploadPtr, entry.tintingColour);
} else if (!this.biomes.isEmpty()) {
//Populate the list of biomes for the model state
int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size();
MemoryUtil.memPutInt(uploadPtr, biomeIndex);
this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState));
uploadResult.biomeUploadIndex = biomeIndex;
long clrUploadPtr = (uploadResult.biomeUpload = new MemoryBuffer(4L * this.biomes.size())).address;
for (var biome : this.biomes) {
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, blockState, biome)|0xFF000000); clrUploadPtr += 4;
}
}
uploadPtr += 4;
//have 32 bytes of free space after here
//install the custom mapping id if it exists
if (this.customBlockStateIdMapping != null && this.customBlockStateIdMapping.containsKey(blockState)) {
MemoryUtil.memPutInt(uploadPtr, this.customBlockStateIdMapping.getInt(blockState));
} else {
MemoryUtil.memPutInt(uploadPtr, 0);
} uploadPtr += 4;
//Note: if the layer isSolid then need to fill all the points in the texture where alpha == 0 with the average colour
// of the surrounding blocks but only within the computed face size bounds
//TODO callback to inject extra data into the model data
MipGen.putTextures(darkenedTinting, textureData, uploadResult.texture);
//glGenerateTextureMipmap(this.textures.id);
//Set the mapping at the very end
this.idMappings[blockId] = modelId;
this.blockStatesInFlightLock.lock();
if (!this.blockStatesInFlight.remove(blockId)) {
this.blockStatesInFlightLock.unlock();
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
}
this.blockStatesInFlightLock.unlock();
return uploadResult;
}
private static final class BiomeUploadResult implements ResultUploader {
private final MemoryBuffer biomeColourBuffer;
private final MemoryBuffer modelBiomeIndexPairs;
private BiomeUploadResult(int biomes, int models) {
this.biomeColourBuffer = new MemoryBuffer(biomes*models*4);
this.modelBiomeIndexPairs = new MemoryBuffer(models*8);
}
public void upload(ModelStore store) {
this.upload(store.modelBuffer, store.modelColourBuffer);
}
public void upload(GlBuffer modelBuffer, GlBuffer modelColourBuffer) {
this.biomeColourBuffer.cpyTo(UploadStream.INSTANCE.upload(modelColourBuffer, 0, this.biomeColourBuffer.size));
//TODO: optimize this to like a compute scatter update or something
long ptr = this.modelBiomeIndexPairs.address;
for (long offset = 0; offset < this.modelBiomeIndexPairs.size; offset += 8) {
long v = MemoryUtil.memGetLong(ptr);ptr += 8;
MemoryUtil.memPutInt(UploadStream.INSTANCE.upload(modelBuffer, (MODEL_SIZE*(v&((1L<<32)-1)))+ 4*6 + 4, 4), (int) (v>>>32));
}
this.biomeColourBuffer.free();
this.modelBiomeIndexPairs.free();
}
public void free() {
if (!this.biomeColourBuffer.isFreed()) {
this.biomeColourBuffer.free();
this.modelBiomeIndexPairs.free();
}
}
}
private BiomeUploadResult addBiome0(int id, Biome biome) {
for (int i = this.biomes.size(); i <= id; i++) {
this.biomes.add(null);
}
var oldBiome = this.biomes.set(id, biome);
if (oldBiome != null && oldBiome != biome) {
throw new IllegalStateException("Biome was put in an id that was not null");
}
if (oldBiome == biome) {
Logger.error("Biome added was a duplicate");
}
if (this.modelsRequiringBiomeColours.isEmpty()) return null;
var result = new BiomeUploadResult(this.biomes.size(), this.modelsRequiringBiomeColours.size());
int i = 0;
long modelUpPtr = result.modelBiomeIndexPairs.address;
for (var entry : this.modelsRequiringBiomeColours) {
var colourProvider = getColourProvider(entry.right().getBlock());
if (colourProvider == null) {
throw new IllegalStateException();
}
//Populate the list of biomes for the model state
int biomeIndex = (i++) * this.biomes.size();
MemoryUtil.memPutLong(modelUpPtr, Integer.toUnsignedLong(entry.left())|(Integer.toUnsignedLong(biomeIndex)<<32));modelUpPtr+=8;
long clrUploadPtr = result.biomeColourBuffer.address + biomeIndex * 4L;
for (var biomeE : this.biomes) {
if (biomeE == null) {
continue;//If null, ignore
}
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, entry.right(), biomeE)|0xFF000000); clrUploadPtr += 4;
}
}
return result;
}
private static BlockColor getColourProvider(Block block) {
return Minecraft.getInstance().getBlockColors().blockColors.byId(BuiltInRegistries.BLOCK.getId(block));
}
//TODO: add a method to detect biome dependent colours (can do by detecting if getColor is ever called)
// if it is, need to add it to a list and mark it as biome colour dependent or something then the shader
// will either use the uint as an index or a direct colour multiplier
private static int captureColourConstant(BlockColor colorProvider, BlockState state, Biome biome) {
var getter = new BlockAndTintGetter() {
@Override
public float getShade(Direction direction, boolean shaded) {
return 0;
}
@Override
public int getBrightness(LightLayer type, BlockPos pos) {
return 0;
}
@Override
public LevelLightEngine getLightEngine() {
return null;
}
@Override
public int getBlockTint(BlockPos pos, ColorResolver colorResolver) {
return colorResolver.getColor(biome, 0, 0);
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
return null;
}
@Override
public BlockState getBlockState(BlockPos pos) {
return state;
}
@Override
public FluidState getFluidState(BlockPos pos) {
return state.getFluidState();
}
@Override
public int getHeight() {
return 0;
}
@Override
public int getMinY() {
return 0;
}
};
//Multiple layer bs to do with flower beds
int c = colorProvider.getColor(state, getter, BlockPos.ZERO, 0);
if (c!=-1) return c;
return colorProvider.getColor(state, getter, BlockPos.ZERO, 1);
}
private static boolean isBiomeDependentColour(BlockColor colorProvider, BlockState state) {
boolean[] biomeDependent = new boolean[1];
var getter = new BlockAndTintGetter() {
@Override
public float getShade(Direction direction, boolean shaded) {
return 0;
}
@Override
public int getBrightness(LightLayer type, BlockPos pos) {
return 0;
}
@Override
public LevelLightEngine getLightEngine() {
return null;
}
@Override
public int getBlockTint(BlockPos pos, ColorResolver colorResolver) {
biomeDependent[0] = true;
return 0;
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
return null;
}
@Override
public BlockState getBlockState(BlockPos pos) {
return state;
}
@Override
public FluidState getFluidState(BlockPos pos) {
return state.getFluidState();
}
@Override
public int getHeight() {
return 0;
}
@Override
public int getMinY() {
return 0;
}
};
colorProvider.getColor(state, getter, BlockPos.ZERO, 0);
colorProvider.getColor(state, getter, BlockPos.ZERO, 1);
return biomeDependent[0];
}
private static float[] computeModelDepth(ColourDepthTextureData[] textures, int checkMode) {
float[] res = new float[6];
for (var dir : Direction.values()) {
var data = textures[dir.get3DDataValue()];
float fd = TextureUtils.computeDepth(data, TextureUtils.DEPTH_MODE_AVG, checkMode);//Compute the min float depth, smaller means closer to the camera, range 0-1
//int depth = Math.round(fd * MODEL_TEXTURE_SIZE);
//If fd is -1, it means that there was nothing rendered on that face and it should be discarded
if (fd < -0.1) {
res[dir.ordinal()] = -1;
} else {
res[dir.ordinal()] = fd;//((float) depth)/MODEL_TEXTURE_SIZE;
}
}
return res;
}
public int[] _unsafeRawAccess() {
return this.idMappings;
}
public int getModelId(int blockId) {
int map = this.idMappings[blockId];
if (map == -1) {
throw new IdNotYetComputedException(blockId, true);
}
return map;
}
public boolean hasModelForBlockId(int blockId) {
return this.idMappings[blockId] != -1;
}
public int getFluidClientStateId(int clientBlockStateId) {
int map = this.fluidStateLUT[clientBlockStateId];
if (map == -1) {
throw new IdNotYetComputedException(clientBlockStateId, false);
}
return map;
}
public long getModelMetadataFromClientId(int clientId) {
return this.metadataCache[clientId];
}
public void free() {
this.bakery.free();
this.downstream.free();
while (!this.rawBakeResults.isEmpty()) {
this.rawBakeResults.poll().rawData.free();
}
while (!this.uploadResults.isEmpty()) {
this.uploadResults.poll().free();
}
}
public int getBakedCount() {
return this.modelTexture2id.size();
}
public int getInflightCount() {
//TODO replace all of this with an atomic?
int size = this.blockStatesInFlight.size();
size += this.uploadResults.size();
size += this.biomeQueue.size();
return size;
}
private static int computeSizeWithMips(int size) {
int total = 0;
for (;size!=0;size>>=1) total += size*size;
return total;
}
}

View File

@@ -1,686 +0,0 @@
package me.cortex.voxy.client.core.model;
import com.mojang.blaze3d.platform.GlConst;
import com.mojang.blaze3d.platform.GlStateManager;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import me.cortex.voxy.client.core.IGetVoxelCore;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.FluidBlock;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.color.block.BlockColorProvider;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.RenderLayers;
import net.minecraft.fluid.FluidState;
import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.util.Pair;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.LightType;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeKeys;
import net.minecraft.world.biome.ColorResolver;
import net.minecraft.world.chunk.light.LightingProvider;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import java.util.*;
import java.util.stream.Stream;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
import static org.lwjgl.opengl.GL11C.GL_NEAREST_MIPMAP_LINEAR;
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MAX_LOD;
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MIN_LOD;
import static org.lwjgl.opengl.GL33.glDeleteSamplers;
import static org.lwjgl.opengl.GL33.glGenSamplers;
import static org.lwjgl.opengl.GL33C.glSamplerParameteri;
import static org.lwjgl.opengl.GL45C.glTextureSubImage2D;
//Manages the storage and updating of model states, textures and colours
//Also has a fast long[] based metadata lookup for when the terrain mesher needs to look up the face occlusion data
//TODO: support more than 65535 states, what should actually happen is a blockstate is registered, the model data is generated, then compared
// to all other models already loaded, if it is a duplicate, create a mapping from the id to the already loaded id, this will help with meshing aswell
// as leaves and such will be able to be merged
public class ModelManager {
//TODO: replace the fluid BlockState with a client model id integer of the fluidState, requires looking up
// the fluid state in the mipper
private record ModelEntry(List<ColourDepthTextureData> textures, int fluidBlockStateId){
private ModelEntry(ColourDepthTextureData[] textures, int fluidBlockStateId) {
this(Stream.of(textures).map(ColourDepthTextureData::clone).toList(), fluidBlockStateId);
}
}
public static final int MODEL_SIZE = 64;
public final ModelTextureBakery bakery;
private final GlBuffer modelBuffer;
private final GlBuffer modelColourBuffer;
private final GlTexture textures;
private final int blockSampler = glGenSamplers();
private final int modelTextureSize;
//Model data might also contain a constant colour if the colour resolver produces a constant colour, this saves space in the
// section buffer reverse indexing
//model data also contains if a face should be randomly rotated,flipped etc to get rid of moire effect
// this would be done in the fragment shader
//The Meta-cache contains critical information needed for meshing, colour provider bit, per-face = is empty, has alpha, is solid, full width, full height
// alpha means that some pixels have alpha values and belong in the translucent rendering layer,
// is empty means that the face is air/shouldent be rendered as there is nothing there
// is solid means that every pixel is fully opaque
// full width, height, is if the blockmodel dimentions occupy a full block, e.g. comparator, some faces do some dont and some only in a specific axis
//FIXME: the issue is e.g. leaves are translucent but the alpha value is used to colour the leaves, so a block can have alpha but still be only made up of transparent or opaque pixels
// will need to find a way to send this info to the shader via the material, if it is in the opaque phase render as transparent with blending shiz
//TODO: ADD an occlusion mask that can be queried (16x16 pixels takes up 4 longs) this mask shows what pixels are exactly occluded at the edge of the block
// so that full block occlusion can work nicely
//TODO: what might work maybe, is that all the transparent pixels should be set to the average of the other pixels
// that way the block is always "fully occluding" (if the block model doesnt cover the entire thing), maybe
// this has some issues with quad merging
//TODO: ACTUALLY, full out all the transparent pixels that are _within_ the bounding box of the model
// this will mean that when quad merging and rendering, the transparent pixels of the block where there shouldent be
// might still work???
// this has an issue with scaffolding i believe tho, so maybe make it a probability to render??? idk
private final long[] metadataCache;
private final int[] fluidStateLUT;
//Provides a map from id -> model id as multiple ids might have the same internal model id
private final int[] idMappings;
private final Object2IntOpenHashMap<ModelEntry> modelTexture2id = new Object2IntOpenHashMap<>();
private final List<Biome> biomes = new ArrayList<>();
private final List<Pair<Integer, BlockState>> modelsRequiringBiomeColours = new ArrayList<>();
private static final ObjectSet<BlockState> LOGGED_SELF_CULLING_WARNING = new ObjectOpenHashSet<>();
public ModelManager(int modelTextureSize) {
this.modelTextureSize = modelTextureSize;
this.bakery = new ModelTextureBakery(modelTextureSize, modelTextureSize);
this.modelBuffer = new GlBuffer(MODEL_SIZE * (1<<16));
this.modelColourBuffer = new GlBuffer(4 * (1<<16));
//TODO: figure out how to do mipping :blobfox_pineapple:
this.textures = new GlTexture().store(GL_RGBA8, 4, modelTextureSize*3*256,modelTextureSize*2*256);
this.metadataCache = new long[1<<16];
this.fluidStateLUT = new int[1<<16];
this.idMappings = new int[1<<20];//Max of 1 million blockstates mapping to 65k model states
Arrays.fill(this.idMappings, -1);
Arrays.fill(this.fluidStateLUT, -1);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_LOD, 0);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, 4);
this.modelTexture2id.defaultReturnValue(-1);
}
//TODO: what i need to do is seperate out fluid states from blockStates
//TODO: so need a few things, per face sizes and offsets, the sizes should be computed from the pixels and find the minimum bounding pixel
// while the depth is computed from the depth buffer data
public int addEntry(int blockId, BlockState blockState) {
if (this.idMappings[blockId] != -1) {
System.err.println("Block id already added: " + blockId + " for state: " + blockState);
return this.idMappings[blockId];
}
boolean isFluid = blockState.getBlock() instanceof FluidBlock;
int modelId = -1;
var textureData = this.bakery.renderFaces(blockState, 123456, isFluid);
int clientFluidStateId = -1;
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
//Insert into the fluid LUT
var fluidState = blockState.getFluidState().getBlockState();
//TODO:FIXME: PASS IN THE Mapper instead of grabbing it!!! THIS IS CRTICIAL TO FIX
int fluidStateId = ((IGetVoxelCore)MinecraftClient.getInstance().worldRenderer).getVoxelCore().getWorldEngine().getMapper().getIdForBlockState(fluidState);
clientFluidStateId = this.idMappings[fluidStateId];
if (clientFluidStateId == -1) {
clientFluidStateId = this.addEntry(fluidStateId, fluidState);
}
}
{//Deduplicate same entries
var entry = new ModelEntry(textureData, clientFluidStateId);
int possibleDuplicate = this.modelTexture2id.getInt(entry);
if (possibleDuplicate != -1) {//Duplicate found
this.idMappings[blockId] = possibleDuplicate;
modelId = possibleDuplicate;
return possibleDuplicate;
} else {//Not a duplicate so create a new entry
modelId = this.modelTexture2id.size();
this.idMappings[blockId] = modelId;
this.modelTexture2id.put(entry, modelId);
}
}
if (isFluid) {
this.fluidStateLUT[modelId] = modelId;
} else if (clientFluidStateId != -1) {
this.fluidStateLUT[modelId] = clientFluidStateId;
}
var colourProvider = MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(blockState.getBlock()));
RenderLayer blockRenderLayer = null;
if (blockState.getBlock() instanceof FluidBlock) {
blockRenderLayer = RenderLayers.getFluidLayer(blockState.getFluidState());
} else {
blockRenderLayer = RenderLayers.getBlockLayer(blockState);
}
int checkMode = blockRenderLayer==RenderLayer.getSolid()?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
long uploadPtr = UploadStream.INSTANCE.upload(this.modelBuffer, (long) modelId * MODEL_SIZE, MODEL_SIZE);
//TODO: implement;
// TODO: if it has a constant colour instead... idk why (apparently for things like spruce leaves)?? but premultiply the texture data by the constant colour
boolean hasBiomeColourResolver = false;
if (colourProvider != null) {
hasBiomeColourResolver = isBiomeDependentColour(colourProvider, blockState);
}
//TODO: special case stuff like vines and glow lichen, where it can be represented by a single double sided quad
// since that would help alot with perf of lots of vines, can be done by having one of the faces just not exist and the other be in no occlusion mode
var sizes = this.computeModelDepth(textureData, checkMode);
//TODO: THIS, note this can be tested for in 2 ways, re render the model with quad culling disabled and see if the result
// is the same, (if yes then needs double sided quads)
// another way to test it is if e.g. up and down havent got anything rendered but the sides do (e.g. all plants etc)
boolean needsDoubleSidedQuads = (sizes[0] < -0.1 && sizes[1] < -0.1) || (sizes[2] < -0.1 && sizes[3] < -0.1) || (sizes[4] < -0.1 && sizes[5] < -0.1);
boolean cullsSame = false;
{
//TODO: Could also move this into the RenderDataFactory and do it on the actual blockstates instead of a guestimation
boolean allTrue = true;
boolean allFalse = true;
//Guestimation test for if the block culls itself
for (var dir : Direction.values()) {
if (blockState.isSideInvisible(blockState, dir)) {
allFalse = false;
} else {
allTrue = false;
}
}
if (allFalse == allTrue) {//If only some sides where self culled then abort
cullsSame = false;
if (LOGGED_SELF_CULLING_WARNING.add(blockState)) System.err.println("Warning! blockstate: " + blockState + " only culled against its self some of the time");
}
if (allTrue) {
cullsSame = true;
}
}
//Each face gets 1 byte, with the top 2 bytes being for whatever
long metadata = 0;
metadata |= hasBiomeColourResolver?1:0;
metadata |= blockRenderLayer == RenderLayer.getTranslucent()?2:0;
metadata |= needsDoubleSidedQuads?4:0;
metadata |= (!blockState.getFluidState().isEmpty())?8:0;//Has a fluid state accosiacted with it
metadata |= isFluid?16:0;//Is a fluid
metadata |= cullsSame?32:0;
//TODO: add a bunch of control config options for overriding/setting options of metadata for each face of each type
for (int face = 5; face != -1; face--) {//In reverse order to make indexing into the metadata long easier
long faceUploadPtr = uploadPtr + 4L * face;//Each face gets 4 bytes worth of data
metadata <<= 8;
float offset = sizes[face];
if (offset < -0.1) {//Face is empty, so ignore
metadata |= 0xFF;//Mark the face as non-existent
//Set to -1 as safepoint
MemoryUtil.memPutInt(faceUploadPtr, -1);
continue;
}
var faceSize = TextureUtils.computeBounds(textureData[face], checkMode);
int writeCount = TextureUtils.getWrittenPixelCount(textureData[face], checkMode);
boolean faceCoversFullBlock = faceSize[0] == 0 && faceSize[2] == 0 &&
faceSize[1] == (this.modelTextureSize-1) && faceSize[3] == (this.modelTextureSize-1);
metadata |= faceCoversFullBlock?2:0;
//TODO: add alot of config options for the following
boolean occludesFace = true;
occludesFace &= blockRenderLayer != RenderLayer.getTranslucent();//If its translucent, it doesnt occlude
//TODO: make this an option, basicly if the face is really close, it occludes otherwise it doesnt
occludesFace &= offset < 0.1;//If the face is rendered far away from the other face, then it doesnt occlude
if (occludesFace) {
occludesFace &= ((float)writeCount)/(this.modelTextureSize * this.modelTextureSize) > 0.9;// only occlude if the face covers more than 90% of the face
}
metadata |= occludesFace?1:0;
boolean canBeOccluded = true;
//TODO: make this an option on how far/close
canBeOccluded &= offset < 0.3;//If the face is rendered far away from the other face, then it cant be occluded
metadata |= canBeOccluded?4:0;
//Face uses its own lighting if its not flat against the adjacent block & isnt traslucent
metadata |= (offset != 0 || blockRenderLayer == RenderLayer.getTranslucent())?0b1000:0;
//Scale face size from 0->this.modelTextureSize-1 to 0->15
for (int i = 0; i < 4; i++) {
faceSize[i] = Math.round((((float)faceSize[i])/(this.modelTextureSize-1))*15);
}
int faceModelData = 0;
faceModelData |= faceSize[0] | (faceSize[1]<<4) | (faceSize[2]<<8) | (faceSize[3]<<12);
faceModelData |= Math.round(offset*63)<<16;//Change the scale from 0->1 (ends inclusive) float to 0->63 (6 bits) NOTE! that 63 == 1.0f meaning its shifted all the way to the other side of the model
//Still have 11 bits free
//Stuff like fences are solid, however they have extra side piece that mean it needs to have discard on
int area = (faceSize[1]-faceSize[0]+1) * (faceSize[3]-faceSize[2]+1);
boolean needsAlphaDiscard = ((float)writeCount)/area<0.9;//If the amount of area covered by written pixels is less than a threashold, disable discard as its not needed
needsAlphaDiscard |= blockRenderLayer != RenderLayer.getSolid();
needsAlphaDiscard &= blockRenderLayer != RenderLayer.getTranslucent();//Translucent doesnt have alpha discard
faceModelData |= needsAlphaDiscard?1<<22:0;
faceModelData |= ((!faceCoversFullBlock)&&blockRenderLayer != RenderLayer.getTranslucent())?1<<23:0;//Alpha discard override, translucency doesnt have alpha discard
MemoryUtil.memPutInt(faceUploadPtr, faceModelData);
}
this.metadataCache[modelId] = metadata;
uploadPtr += 4*6;
//Have 40 bytes free for remaining model data
// todo: put in like the render layer type ig? along with colour resolver info
int modelFlags = 0;
modelFlags |= colourProvider != null?1:0;
modelFlags |= hasBiomeColourResolver?2:0;//Basicly whether to use the next int as a colour or as a base index/id into a colour buffer for biome dependent colours
modelFlags |= blockRenderLayer == RenderLayer.getTranslucent()?4:0;
modelFlags |= blockRenderLayer == RenderLayer.getCutout()?0:8;
//modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha
MemoryUtil.memPutInt(uploadPtr, modelFlags);
//Temporary override to always be non biome specific
if (colourProvider == null) {
MemoryUtil.memPutInt(uploadPtr + 4, -1);//Set the default to nothing so that its faster on the gpu
} else if (!hasBiomeColourResolver) {
Biome defaultBiome = MinecraftClient.getInstance().world.getRegistryManager().get(RegistryKeys.BIOME).get(BiomeKeys.PLAINS);
MemoryUtil.memPutInt(uploadPtr + 4, captureColourConstant(colourProvider, blockState, defaultBiome)|0xFF000000);
} else if (!this.biomes.isEmpty()) {
//Populate the list of biomes for the model state
int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size();
MemoryUtil.memPutInt(uploadPtr + 4, biomeIndex);
this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState));
long clrUploadPtr = UploadStream.INSTANCE.upload(this.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size());
for (var biome : this.biomes) {
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, blockState, biome)|0xFF000000); clrUploadPtr += 4;
}
}
//Note: if the layer isSolid then need to fill all the points in the texture where alpha == 0 with the average colour
// of the surrounding blocks but only within the computed face size bounds
//TODO
this.putTextures(modelId, textureData);
//glGenerateTextureMipmap(this.textures.id);
return modelId;
}
public void addBiome(int id, Biome biome) {
this.biomes.add(biome);
if (this.biomes.size()-1 != id) {
throw new IllegalStateException("Biome ordering not consistent with biome id for biome " + biome + " expected id: " + (this.biomes.size()-1) + " got id: " + id);
}
int i = 0;
for (var entry : this.modelsRequiringBiomeColours) {
var colourProvider = MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(entry.getRight().getBlock()));
if (colourProvider == null) {
throw new IllegalStateException();
}
//Populate the list of biomes for the model state
int biomeIndex = (i++) * this.biomes.size();
MemoryUtil.memPutInt( UploadStream.INSTANCE.upload(this.modelBuffer, (entry.getLeft()*MODEL_SIZE)+ 4*6 + 4, 4), biomeIndex);
long clrUploadPtr = UploadStream.INSTANCE.upload(this.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size());
for (var biomeE : this.biomes) {
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, entry.getRight(), biomeE)|0xFF000000); clrUploadPtr += 4;
}
}
}
//TODO: add a method to detect biome dependent colours (can do by detecting if getColor is ever called)
// if it is, need to add it to a list and mark it as biome colour dependent or something then the shader
// will either use the uint as an index or a direct colour multiplier
private static int captureColourConstant(BlockColorProvider colorProvider, BlockState state, Biome biome) {
return colorProvider.getColor(state, new BlockRenderView() {
@Override
public float getBrightness(Direction direction, boolean shaded) {
return 0;
}
@Override
public int getLightLevel(LightType type, BlockPos pos) {
return 0;
}
@Override
public LightingProvider getLightingProvider() {
return null;
}
@Override
public int getColor(BlockPos pos, ColorResolver colorResolver) {
return colorResolver.getColor(biome, 0, 0);
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
return null;
}
@Override
public BlockState getBlockState(BlockPos pos) {
return state;
}
@Override
public FluidState getFluidState(BlockPos pos) {
return state.getFluidState();
}
@Override
public int getHeight() {
return 0;
}
@Override
public int getBottomY() {
return 0;
}
}, BlockPos.ORIGIN, 0);
}
private static boolean isBiomeDependentColour(BlockColorProvider colorProvider, BlockState state) {
boolean[] biomeDependent = new boolean[1];
colorProvider.getColor(state, new BlockRenderView() {
@Override
public float getBrightness(Direction direction, boolean shaded) {
return 0;
}
@Override
public int getLightLevel(LightType type, BlockPos pos) {
return 0;
}
@Override
public LightingProvider getLightingProvider() {
return null;
}
@Override
public int getColor(BlockPos pos, ColorResolver colorResolver) {
biomeDependent[0] = true;
return 0;
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
return null;
}
@Override
public BlockState getBlockState(BlockPos pos) {
return state;
}
@Override
public FluidState getFluidState(BlockPos pos) {
return state.getFluidState();
}
@Override
public int getHeight() {
return 0;
}
@Override
public int getBottomY() {
return 0;
}
}, BlockPos.ORIGIN, 0);
return biomeDependent[0];
}
public static boolean faceExists(long metadata, int face) {
return ((metadata>>(8*face))&0xFF)!=0xFF;
}
public static boolean faceCanBeOccluded(long metadata, int face) {
return ((metadata>>(8*face))&0b100)==0b100;
}
public static boolean faceOccludes(long metadata, int face) {
return faceExists(metadata, face) && ((metadata>>(8*face))&0b1)==0b1;
}
public static boolean faceUsesSelfLighting(long metadata, int face) {
return ((metadata>>(8*face))&0b1000) != 0;
}
public static boolean isDoubleSided(long metadata) {
return ((metadata>>(8*6))&4) != 0;
}
public static boolean isTranslucent(long metadata) {
return ((metadata>>(8*6))&2) != 0;
}
public static boolean containsFluid(long metadata) {
return ((metadata>>(8*6))&8) != 0;
}
public static boolean isFluid(long metadata) {
return ((metadata>>(8*6))&16) != 0;
}
public static boolean isBiomeColoured(long metadata) {
return ((metadata>>(8*6))&1) != 0;
}
//NOTE: this might need to be moved to per face
public static boolean cullsSame(long metadata) {
return ((metadata>>(8*6))&32) != 0;
}
private float[] computeModelDepth(ColourDepthTextureData[] textures, int checkMode) {
float[] res = new float[6];
for (var dir : Direction.values()) {
var data = textures[dir.getId()];
float fd = TextureUtils.computeDepth(data, TextureUtils.DEPTH_MODE_AVG, checkMode);//Compute the min float depth, smaller means closer to the camera, range 0-1
int depth = Math.round(fd * this.modelTextureSize);
//If fd is -1, it means that there was nothing rendered on that face and it should be discarded
if (fd < -0.1) {
res[dir.ordinal()] = -1;
} else {
res[dir.ordinal()] = ((float) depth)/this.modelTextureSize;
}
}
return res;
}
//TODO:FIXME: DONT DO SPIN LOCKS :WAA:
public long getModelMetadata(int blockId) {
int map = this.idMappings[blockId];
if (map == -1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
map = this.idMappings[blockId];
}
if (map == -1) {
throw new IdNotYetComputedException(blockId);
}
return this.metadataCache[map];
//int map = 0;
//int i = 10;
//while ((map = this.idMappings[blockId]) == -1) {
// Thread.onSpinWait();
//}
//long meta = 0;
//while ((meta = this.metadataCache[map]) == 0) {
// Thread.onSpinWait();
//}
}
public long getModelMetadataFromClientId(int clientId) {
return this.metadataCache[clientId];
}
public int getModelId(int blockId) {
int map = this.idMappings[blockId];
if (map == -1) {
throw new IdNotYetComputedException(blockId);
}
return map;
}
public int getFluidClientStateId(int clientBlockStateId) {
int map = this.fluidStateLUT[clientBlockStateId];
if (map == -1) {
throw new IdNotYetComputedException(clientBlockStateId);
}
return map;
}
private void putTextures(int id, ColourDepthTextureData[] textures) {
int X = (id&0xFF) * this.modelTextureSize*3;
int Y = ((id>>8)&0xFF) * this.modelTextureSize*2;
for (int subTex = 0; subTex < 6; subTex++) {
int x = X + (subTex>>1)*this.modelTextureSize;
int y = Y + (subTex&1)*this.modelTextureSize;
GlStateManager._pixelStore(GlConst.GL_UNPACK_ROW_LENGTH, 0);
GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_PIXELS, 0);
GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_ROWS, 0);
GlStateManager._pixelStore(GlConst.GL_UNPACK_ALIGNMENT, 4);
var current = textures[subTex].colour();
var next = new int[current.length>>1];
for (int i = 0; i < 4; i++) {
glTextureSubImage2D(this.textures.id, i, x>>i, y>>i, this.modelTextureSize>>i, this.modelTextureSize>>i, GL_RGBA, GL_UNSIGNED_BYTE, current);
int size = this.modelTextureSize>>(i+1);
for (int pX = 0; pX < size; pX++) {
for (int pY = 0; pY < size; pY++) {
int C00 = current[(pY*2)*size+pX*2];
int C01 = current[(pY*2+1)*size+pX*2];
int C10 = current[(pY*2)*size+pX*2+1];
int C11 = current[(pY*2+1)*size+pX*2+1];
next[pY*size+pX] = TextureUtils.mipColours(C00, C01, C10, C11);
}
}
current = next;
next = new int[current.length>>1];
}
}
}
public int getBufferId() {
return this.modelBuffer.id;
}
public int getTextureId() {
return this.textures.id;
}
public int getSamplerId() {
return this.blockSampler;
}
public int getColourBufferId() {
return this.modelColourBuffer.id;
}
public void free() {
this.bakery.free();
this.modelBuffer.free();
this.modelColourBuffer.free();
this.textures.free();
glDeleteSamplers(this.blockSampler);
}
public void addDebugInfo(List<String> info) {
info.add("BlockModels registered: " + this.modelTexture2id.size() + "/" + (1<<16));
}
}

View File

@@ -0,0 +1,48 @@
package me.cortex.voxy.client.core.model;
public abstract class ModelQueries {
public static boolean faceExists(long metadata, int face) {
return ((metadata>>(8*face))&0xFF)!=0xFF;
}
public static boolean faceCanBeOccluded(long metadata, int face) {
return ((metadata>>(8*face))&0b100)==0b100;
}
public static boolean faceOccludes(long metadata, int face) {
return faceExists(metadata, face) && ((metadata>>(8*face))&0b1)==0b1;
}
public static boolean faceUsesSelfLighting(long metadata, int face) {
return ((metadata>>(8*face))&0b1000) != 0;
}
public static boolean isDoubleSided(long metadata) {
return ((metadata>>(8*6))&4) != 0;
}
public static boolean isTranslucent(long metadata) {
return ((metadata>>(8*6))&2) != 0;
}
public static boolean containsFluid(long metadata) {
return ((metadata>>(8*6))&8) != 0;
}
public static boolean isFluid(long metadata) {
return ((metadata>>(8*6))&16) != 0;
}
public static boolean isBiomeColoured(long metadata) {
return ((metadata>>(8*6))&1) != 0;
}
//NOTE: this might need to be moved to per face
public static boolean cullsSame(long metadata) {
return ((metadata>>(8*6))&32) != 0;
}
public static boolean isFullyOpaque(long metadata) {
return ((metadata>>(8*6))&64) != 0;
}
}

View File

@@ -0,0 +1,59 @@
package me.cortex.voxy.client.core.model;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.resources.Identifier;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
import static org.lwjgl.opengl.GL11C.GL_NEAREST_MIPMAP_LINEAR;
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MAX_LOD;
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MIN_LOD;
import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL33.*;
import static org.lwjgl.opengl.GL33C.glSamplerParameteri;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
public class ModelStore {
public static final int MODEL_SIZE = 64;
final GlBuffer modelBuffer;
final GlBuffer modelColourBuffer;
final GlTexture textures;
public final int blockSampler = glGenSamplers();
public ModelStore() {
this.modelBuffer = new GlBuffer(MODEL_SIZE * (1<<16)).name("ModelData");
this.modelColourBuffer = new GlBuffer(4 * (1<<16)).name("ModelColour");
this.textures = new GlTexture().store(GL_RGBA8, Integer.numberOfTrailingZeros(ModelFactory.MODEL_TEXTURE_SIZE), ModelFactory.MODEL_TEXTURE_SIZE*3*256,ModelFactory.MODEL_TEXTURE_SIZE*2*256).name("ModelTextures");
//Limit the mips of the texture to match that of the terrain atlas
int mipLvl = ((TextureAtlas) Minecraft.getInstance().getTextureManager()
.getTexture(Identifier.fromNamespaceAndPath("minecraft", "textures/atlas/blocks.png")))
.maxMipLevel;
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_LOD, 0);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, mipLvl);//Integer.numberOfTrailingZeros(ModelFactory.MODEL_TEXTURE_SIZE)
}
public void free() {
this.modelBuffer.free();
this.modelColourBuffer.free();
this.textures.free();
glDeleteSamplers(this.blockSampler);
}
public void bind(int modelBindingIndex, int colourBindingIndex, int textureBindingIndex) {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, modelBindingIndex, this.modelBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, colourBindingIndex, this.modelColourBuffer.id);
glBindTextureUnit(textureBindingIndex, this.textures.id);
glBindSampler(textureBindingIndex, this.blockSampler);
}
}

View File

@@ -1,315 +0,0 @@
package me.cortex.voxy.client.core.model;
import com.mojang.blaze3d.platform.GlConst;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.systems.VertexSorter;
import me.cortex.voxy.client.core.gl.GlFramebuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.util.GlStateCapture;
import net.minecraft.block.*;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gl.GlUniform;
import net.minecraft.client.render.*;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.fluid.FluidState;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.RotationAxis;
import net.minecraft.util.math.random.LocalRandom;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.LightType;
import net.minecraft.world.biome.ColorResolver;
import net.minecraft.world.chunk.light.LightingProvider;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11C;
import java.util.ArrayList;
import java.util.List;
import static org.lwjgl.opengl.ARBFramebufferObject.*;
import static org.lwjgl.opengl.ARBImaging.GL_FUNC_ADD;
import static org.lwjgl.opengl.ARBImaging.glBlendEquation;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_FRAMEBUFFER_BARRIER_BIT;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glMemoryBarrier;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
import static org.lwjgl.opengl.GL20C.glUniformMatrix4fv;
import static org.lwjgl.opengl.GL45C.glBlitNamedFramebuffer;
import static org.lwjgl.opengl.GL45C.glGetTextureImage;
//Builds a texture for each face of a model
public class ModelTextureBakery {
private final int width;
private final int height;
private final GlTexture colourTex;
private final GlTexture depthTex;
private final GlFramebuffer framebuffer;
private final GlStateCapture glState = GlStateCapture.make()
.addCapability(GL_DEPTH_TEST)
.addCapability(GL_STENCIL_TEST)
.addCapability(GL_BLEND)
.addCapability(GL_CULL_FACE)
.addTexture(GL_TEXTURE0)
.addTexture(GL_TEXTURE1)
.build()
;
private final Shader rasterShader = Shader.make()
.add(ShaderType.VERTEX, "voxy:bakery/position_tex.vsh")
.add(ShaderType.FRAGMENT, "voxy:bakery/position_tex.fsh")
.compile();
private static final List<MatrixStack> FACE_VIEWS = new ArrayList<>();
public ModelTextureBakery(int width, int height) {
//TODO: Make this run in a seperate opengl context so that it can run in a seperate thread
this.width = width;
this.height = height;
this.colourTex = new GlTexture().store(GL_RGBA8, 1, width, height);
this.depthTex = new GlTexture().store(GL_DEPTH24_STENCIL8, 1, width, height);
this.framebuffer = new GlFramebuffer().bind(GL_COLOR_ATTACHMENT0, this.colourTex).bind(GL_DEPTH_STENCIL_ATTACHMENT, this.depthTex).verify();
//This is done to help make debugging easier
FACE_VIEWS.clear();
AddViews();
}
private static void AddViews() {
addView(-90,0, 0, false);//Direction.DOWN
addView(90,0, 0, false);//Direction.UP
addView(0,180, 0, true);//Direction.NORTH
addView(0,0, 0, false);//Direction.SOUTH
//TODO: check these arnt the wrong way round
addView(0,90, 270, false);//Direction.EAST
addView(0,270, 270, false);//Direction.WEST
}
private static void addView(float pitch, float yaw, float rotation, boolean flipX) {
var stack = new MatrixStack();
stack.translate(0.5f,0.5f,0.5f);
stack.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotation));
stack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(pitch));
stack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
stack.translate(-0.5f,-0.5f,-0.5f);
FACE_VIEWS.add(stack);
}
//TODO: For block entities, also somehow attempt to render the default block entity, e.g. chests and stuff
// cause that will result in ok looking micro details in the terrain
public ColourDepthTextureData[] renderFaces(BlockState state, long randomValue, boolean renderFluid) {
this.glState.capture();
var model = MinecraftClient.getInstance()
.getBakedModelManager()
.getBlockModels()
.getModel(state);
var entityModel = state.hasBlockEntity()?BakedBlockEntityModel.bake(state):null;
int oldFB = GlStateManager.getBoundFramebuffer();
var oldProjection = new Matrix4f(RenderSystem.getProjectionMatrix());
GL11C.glViewport(0, 0, this.width, this.height);
RenderSystem.setProjectionMatrix(new Matrix4f().identity().set(new float[]{
2,0,0,0,
0, 2,0,0,
0,0, -1f,0,
-1,-1,0,1,
}), VertexSorter.BY_Z);
RenderLayer renderLayer = null;
if (!renderFluid) {
renderLayer = RenderLayers.getBlockLayer(state);
} else {
renderLayer = RenderLayers.getFluidLayer(state.getFluidState());
}
//TODO: figure out why calling this makes minecraft render black
//renderLayer.startDrawing();
glClearColor(0,0,0,0);
glClearDepth(1);
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer.id);
glEnable(GL_STENCIL_TEST);
glDepthRange(0, 1);
glDepthMask(true);
glEnable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
//glDepthFunc(GL_LESS);
//TODO: Find a better solution
if (renderLayer == RenderLayer.getTranslucent()) {
//Very hacky blend function to retain the effect of the applied alpha since we dont really want to apply alpha
// this is because we apply the alpha again when rendering the terrain meaning the alpha is being double applied
glBlendFuncSeparate(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
} else {
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
//glBlendFunc(GL_ONE, GL_ONE);
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
this.rasterShader.bind();
glActiveTexture(GL_TEXTURE0);
int texId = MinecraftClient.getInstance().getTextureManager().getTexture(Identifier.of("minecraft", "textures/atlas/blocks.png")).getGlId();
GlUniform.uniform1(0, 0);
var faces = new ColourDepthTextureData[FACE_VIEWS.size()];
for (int i = 0; i < faces.length; i++) {
faces[i] = captureView(state, model, entityModel, FACE_VIEWS.get(i), randomValue, i, renderFluid, texId);
//glBlitNamedFramebuffer(this.framebuffer.id, oldFB, 0,0,16,16,300*(i>>1),300*(i&1),300*(i>>1)+256,300*(i&1)+256, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
renderLayer.endDrawing();
glDisable(GL_STENCIL_TEST);
glDisable(GL_BLEND);
RenderSystem.setProjectionMatrix(oldProjection, VertexSorter.BY_DISTANCE);
glBindFramebuffer(GL_FRAMEBUFFER, oldFB);
GL11C.glViewport(GlStateManager.Viewport.getX(), GlStateManager.Viewport.getY(), GlStateManager.Viewport.getWidth(), GlStateManager.Viewport.getHeight());
//TODO: FIXME: fully revert the state of opengl
this.glState.restore();
return faces;
}
private ColourDepthTextureData captureView(BlockState state, BakedModel model, BakedBlockEntityModel blockEntityModel, MatrixStack stack, long randomValue, int face, boolean renderFluid, int textureId) {
var vc = Tessellator.getInstance();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
float[] mat = new float[4*4];
new Matrix4f(RenderSystem.getProjectionMatrix()).mul(stack.peek().getPositionMatrix()).get(mat);
glUniformMatrix4fv(1, false, mat);
if (blockEntityModel != null && !renderFluid) {
blockEntityModel.renderOut();
}
var bb = vc.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR);
if (!renderFluid) {
renderQuads(bb, state, model, new MatrixStack(), randomValue);
} else {
MinecraftClient.getInstance().getBlockRenderManager().renderFluid(BlockPos.ORIGIN, new BlockRenderView() {
@Override
public float getBrightness(Direction direction, boolean shaded) {
return 0;
}
@Override
public LightingProvider getLightingProvider() {
return null;
}
@Override
public int getLightLevel(LightType type, BlockPos pos) {
return 0;
}
@Override
public int getColor(BlockPos pos, ColorResolver colorResolver) {
return 0;
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
return null;
}
@Override
public BlockState getBlockState(BlockPos pos) {
if (pos.equals(Direction.byId(face).getVector())) {
return Blocks.AIR.getDefaultState();
}
//Fixme:
// This makes it so that the top face of water is always air, if this is commented out
// the up block will be a liquid state which makes the sides full
// if this is uncommented, that issue is fixed but e.g. stacking water layers ontop of eachother
// doesnt fill the side of the block
//if (pos.getY() == 1) {
// return Blocks.AIR.getDefaultState();
//}
return state;
}
@Override
public FluidState getFluidState(BlockPos pos) {
if (pos.equals(Direction.byId(face).getVector())) {
return Blocks.AIR.getDefaultState().getFluidState();
}
//if (pos.getY() == 1) {
// return Blocks.AIR.getDefaultState().getFluidState();
//}
return state.getFluidState();
}
@Override
public int getHeight() {
return 0;
}
@Override
public int getBottomY() {
return 0;
}
}, bb, state, state.getFluidState());
}
glBindTexture(GL_TEXTURE_2D, textureId);
try {
BufferRenderer.draw(bb.end());
} catch (IllegalStateException e) {
System.err.println("Got empty buffer builder! for block " + state);
}
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
int[] colourData = new int[this.width*this.height];
int[] depthData = new int[this.width*this.height];
glGetTextureImage(this.colourTex.id, 0, GL_RGBA, GL_UNSIGNED_BYTE, colourData);
glGetTextureImage(this.depthTex.id, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, depthData);
return new ColourDepthTextureData(colourData, depthData, this.width, this.height);
}
private static void renderQuads(BufferBuilder builder, BlockState state, BakedModel model, MatrixStack stack, long randomValue) {
for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) {
var quads = model.getQuads(state, direction, new LocalRandom(randomValue));
for (var quad : quads) {
//TODO: mark pixels that have
int meta = quad.hasColor()?1:0;
builder.quad(stack.peek(), quad, 255f/((meta>>16)&0xff), 255f/((meta>>8)&0xff), 255f/(meta&0xff), 1.0f, 0, 0);
}
}
}
public void free() {
this.framebuffer.free();
this.colourTex.free();
this.depthTex.free();
this.rasterShader.free();
}
}

View File

@@ -1,7 +1,8 @@
package me.cortex.voxy.client.core.model;
import me.jellysquid.mods.sodium.client.util.color.ColorSRGB;
import net.minecraft.util.math.ColorHelper;
import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB;
import net.minecraft.client.renderer.texture.MipmapGenerator;
import net.minecraft.util.ARGB;
//Texturing utils to manipulate data from the model bakery
public class TextureUtils {
@@ -9,14 +10,14 @@ public class TextureUtils {
public static int getWrittenPixelCount(ColourDepthTextureData texture, int checkMode) {
int count = 0;
for (int i = 0; i < texture.colour().length; i++) {
count += wasPixelWritten(texture, checkMode, i)?1:0;
count += wasPixelWritten(texture, checkMode, i) ? 1 : 0;
}
return count;
}
public static boolean isSolid(ColourDepthTextureData texture) {
for (int pixel : texture.colour()) {
if (((pixel>>24)&0xFF) != 255) {
if (((pixel >> 24) & 0xFF) != 255) {
return false;
}
}
@@ -26,18 +27,50 @@ public class TextureUtils {
public static final int WRITE_CHECK_STENCIL = 1;
public static final int WRITE_CHECK_DEPTH = 2;
public static final int WRITE_CHECK_ALPHA = 3;
private static boolean wasPixelWritten(ColourDepthTextureData data, int mode, int index) {
if (mode == WRITE_CHECK_STENCIL) {
return (data.depth()[index]&0xFF)!=0;
return (data.depth()[index] & 0xFF) != 0;
} else if (mode == WRITE_CHECK_DEPTH) {
return (data.depth()[index]>>>8)!=((1<<24)-1);
return (data.depth()[index] >>> 8) != ((1 << 24) - 1);
} else if (mode == WRITE_CHECK_ALPHA) {
//TODO:FIXME: for some reason it has an alpha of 1 even if its ment to be 0
return ((data.colour()[index]>>>24)&0xff)>1;
return ((data.colour()[index] >>> 24) & 0xff) > 1;
}
throw new IllegalArgumentException();
}
//0: nothing written
//1: none tinted
//2: some tinted
//3: all tinted
public static int computeFaceTint(ColourDepthTextureData texture, int checkMode) {
boolean allTinted = true;
boolean someTinted = false;
boolean wasWriten = false;
final var colourData = texture.colour();
final var depthData = texture.depth();
for (int i = 0; i < colourData.length; i++) {
if (!wasPixelWritten(texture, checkMode, i)) {
continue;
}
if ((colourData[i] & 0xFFFFFF) == 0 || (colourData[i] >>> 24) == 0) {//If the pixel is fully black (or translucent)
continue;
}
boolean pixelTinited = (depthData[i] & (1 << 7)) != 0;
wasWriten |= true;
allTinted &= pixelTinited;
someTinted |= pixelTinited;
}
if (!wasWriten) {
return 0;
}
return someTinted ? (allTinted ? 3 : 2) : 1;
}
public static final int DEPTH_MODE_AVG = 1;
public static final int DEPTH_MODE_MAX = 2;
public static final int DEPTH_MODE_MIN = 3;
@@ -59,7 +92,7 @@ public class TextureUtils {
if (!wasPixelWritten(texture, checkMode, i)) {
continue;
}
int depth = depthData[i]>>>8;
int depth = depthData[i] >>> 8;
if (mode == DEPTH_MODE_AVG) {
a++;
b += depth;
@@ -74,7 +107,7 @@ public class TextureUtils {
if (a == 0) {
return -1;
}
return u2fdepth((int) (b/a));
return u2fdepth((int) (b / a));
} else if (mode == DEPTH_MODE_MAX) {
if (a == Long.MIN_VALUE) {
return -1;
@@ -90,12 +123,14 @@ public class TextureUtils {
}
private static float u2fdepth(int depth) {
float depthF = (float) ((double)depth/((1<<24)-1));
float depthF = (float) ((double) depth / ((1 << 24) - 1));
//https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDepthRange.xhtml
// due to this and the unsigned bullshit, i believe the depth value needs to get multiplied by 2
// due to this and the unsigned bullshit, believe the depth value needs to get multiplied by 2
//Shouldent be needed due to the compute bake copy
depthF *= 2;
if (depthF > 1.00001f) {
System.err.println("Warning: Depth greater than 1");
if (depthF > 1.00001f) {//Basicly only happens when a model goes out of bounds (thing)
//System.err.println("Warning: Depth greater than 1");
depthF = 1.0f;
}
return depthF;
@@ -104,7 +139,6 @@ public class TextureUtils {
//NOTE: data goes from bottom left to top right (x first then y)
public static int[] computeBounds(ColourDepthTextureData data, int checkMode) {
final var depth = data.depth();
//Compute x bounds first
int minX = 0;
minXCheck:
@@ -118,10 +152,10 @@ public class TextureUtils {
minX++;
} while (minX != data.width());
int maxX = data.width()-1;
int maxX = data.width() - 1;
maxXCheck:
do {
for (int y = data.height()-1; y!=-1; y--) {
for (int y = data.height() - 1; y != -1; y--) {
int idx = maxX + (y * data.width());
if (wasPixelWritten(data, checkMode, idx)) {
break maxXCheck;//pixel was written too so break from loop
@@ -146,10 +180,10 @@ public class TextureUtils {
} while (minY != data.height());
int maxY = data.height()-1;
int maxY = data.height() - 1;
maxYCheck:
do {
for (int x = data.width()-1; x!=-1; x--) {
for (int x = data.width() - 1; x != -1; x--) {
int idx = (maxY * data.height()) + x;
if (wasPixelWritten(data, checkMode, idx)) {
break maxYCheck;//pixel was written too so break from loop
@@ -163,56 +197,42 @@ public class TextureUtils {
}
public static int mipColours(int one, int two, int three, int four) {
return weightedAverageColor(weightedAverageColor(one, two), weightedAverageColor(three, four));
}
private static int weightedAverageColor(int one, int two) {
int alphaOne = ColorHelper.Abgr.getAlpha(one);
int alphaTwo = ColorHelper.Abgr.getAlpha(two);
if (alphaOne == alphaTwo) {
return averageRgb(one, two, alphaOne);
} else if (alphaOne == 0) {
return two & 16777215 | alphaTwo >> 2 << 24;
} else if (alphaTwo == 0) {
return one & 16777215 | alphaOne >> 2 << 24;
} else {
float scale = 1.0F / (float)(alphaOne + alphaTwo);
float relativeWeightOne = (float)alphaOne * scale;
float relativeWeightTwo = (float)alphaTwo * scale;
float oneR = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getRed(one)) * relativeWeightOne;
float oneG = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getGreen(one)) * relativeWeightOne;
float oneB = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getBlue(one)) * relativeWeightOne;
float twoR = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getRed(two)) * relativeWeightTwo;
float twoG = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getGreen(two)) * relativeWeightTwo;
float twoB = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getBlue(two)) * relativeWeightTwo;
float linearR = oneR + twoR;
float linearG = oneG + twoG;
float linearB = oneB + twoB;
int averageAlpha = alphaOne + alphaTwo >> 1;
return ColorSRGB.linearToSrgb(linearR, linearG, linearB, averageAlpha);
public static int mipColours(boolean darkend, int C00, int C01, int C10, int C11) {
darkend = !darkend;//Invert to make it easier
float r = 0.0f;
float g = 0.0f;
float b = 0.0f;
float a = 0.0f;
if (darkend || (C00 >>> 24) != 0) {
r += ColorSRGB.srgbToLinear((C00 >> 0) & 0xFF);
g += ColorSRGB.srgbToLinear((C00 >> 8) & 0xFF);
b += ColorSRGB.srgbToLinear((C00 >> 16) & 0xFF);
a += darkend ? (C00 >>> 24) : ColorSRGB.srgbToLinear(C00 >>> 24);
}
if (darkend || (C01 >>> 24) != 0) {
r += ColorSRGB.srgbToLinear((C01 >> 0) & 0xFF);
g += ColorSRGB.srgbToLinear((C01 >> 8) & 0xFF);
b += ColorSRGB.srgbToLinear((C01 >> 16) & 0xFF);
a += darkend ? (C01 >>> 24) : ColorSRGB.srgbToLinear(C01 >>> 24);
}
if (darkend || (C10 >>> 24) != 0) {
r += ColorSRGB.srgbToLinear((C10 >> 0) & 0xFF);
g += ColorSRGB.srgbToLinear((C10 >> 8) & 0xFF);
b += ColorSRGB.srgbToLinear((C10 >> 16) & 0xFF);
a += darkend ? (C10 >>> 24) : ColorSRGB.srgbToLinear(C10 >>> 24);
}
if (darkend || (C11 >>> 24) != 0) {
r += ColorSRGB.srgbToLinear((C11 >> 0) & 0xFF);
g += ColorSRGB.srgbToLinear((C11 >> 8) & 0xFF);
b += ColorSRGB.srgbToLinear((C11 >> 16) & 0xFF);
a += darkend ? (C11 >>> 24) : ColorSRGB.srgbToLinear(C11 >>> 24);
}
}
private static int averageRgb(int a, int b, int alpha) {
float ar = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getRed(a));
float ag = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getGreen(a));
float ab = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getBlue(a));
float br = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getRed(b));
float bg = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getGreen(b));
float bb = ColorSRGB.srgbToLinear(ColorHelper.Abgr.getBlue(b));
return ColorSRGB.linearToSrgb((ar + br) * 0.5F, (ag + bg) * 0.5F, (ab + bb) * 0.5F, alpha);
return ColorSRGB.linearToSrgb(
r / 4,
g / 4,
b / 4,
darkend ? ((int) a) / 4 : ARGB.linearToSrgbChannel(a / 4)
);
}
}
}

View File

@@ -0,0 +1,105 @@
/*
package me.cortex.voxy.client.core.model.bakery;
import me.cortex.voxy.common.Logger;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.Model;
import net.minecraft.client.renderer.SubmitNodeStorage;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BakedBlockEntityModel {
private record LayerConsumer(RenderType layer, ReuseVertexConsumer consumer) {}
private final List<LayerConsumer> layers;
private BakedBlockEntityModel(List<LayerConsumer> layers) {
this.layers = layers;
}
public void render(Matrix4f matrix, int texId) {
for (var layer : this.layers) {
if (layer.consumer.isEmpty()) continue;
if (layer.layer instanceof RenderType.CompositeRenderType mp) {
Identifier textureId = mp.state.textureState.cutoutTexture().orElse(null);
if (textureId == null) {
Logger.error("ERROR: Empty texture id for layer: " + layer);
} else {
texId = ((com.mojang.blaze3d.opengl.GlTexture)Minecraft.getInstance().getTextureManager().getTexture(textureId).getTexture()).glId();
}
}
if (texId == 0) continue;
BudgetBufferRenderer.setup(layer.consumer.getAddress(), layer.consumer.quadCount(), texId);
BudgetBufferRenderer.render(matrix);
}
}
public void release() {
this.layers.forEach(layer->layer.consumer.free());
}
private static int getMetaFromLayer(RenderType layer) {
boolean hasDiscard = layer == RenderType.cutout() ||
layer == RenderType.cutoutMipped() ||
layer == RenderType.tripwire();
boolean isMipped = layer == RenderType.cutoutMipped() ||
layer == RenderType.solid() ||
layer.sortOnUpload() ||
layer == RenderType.tripwire();
int meta = hasDiscard?1:0;
meta |= isMipped?2:0;
return meta;
}
public static BakedBlockEntityModel bake(BlockState state) {
Map<RenderType, LayerConsumer> map = new HashMap<>();
var entity = ((EntityBlock)state.getBlock()).newBlockEntity(BlockPos.ZERO, state);
if (entity == null) {
return null;
}
var renderer = Minecraft.getInstance().getBlockEntityRenderDispatcher().getRenderer(entity);
entity.setLevel(Minecraft.getInstance().level);
if (renderer != null) {
try {
var rt = renderer.createRenderState();
renderer.extractRenderState(entity, rt, 0.0f, new Vec3d(0,0,0), null);
//TODO: FIXME: FINISH
var cstate = new CameraRenderState();
var queue = new SubmitNodeStorage();
renderer.submit(rt, new MatrixStack(), queue, cstate);
var qq = queue.order(0);
qq.
//renderer.render(entity, 0.0f, new MatrixStack(), layer->map.computeIfAbsent(layer, rl -> new LayerConsumer(rl, new ReuseVertexConsumer().setDefaultMeta(getMetaFromLayer(rl)))).consumer, 0, 0, new Vec3d(0,0,0));
} catch (Exception e) {
Logger.error("Unable to bake block entity: " + entity, e);
}
}
entity.setRemoved();
if (map.isEmpty()) {
return null;
}
for (var i : new ArrayList<>(map.values())) {
if (i.consumer.isEmpty()) {
map.remove(i.layer);
i.consumer.free();
}
}
if (map.isEmpty()) {
return null;
}
return new BakedBlockEntityModel(new ArrayList<>(map.values()));
}
}
*/

View File

@@ -0,0 +1,96 @@
package me.cortex.voxy.client.core.model.bakery;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.GpuTexture;
import com.mojang.blaze3d.vertex.MeshData;
import com.mojang.blaze3d.vertex.VertexFormat;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlVertexArray;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL45.*;
public class BudgetBufferRenderer {
public static final int VERTEX_FORMAT_SIZE = 24;
private static final Shader bakeryShader = Shader.make()
.add(ShaderType.VERTEX, "voxy:bakery/position_tex.vsh")
.add(ShaderType.FRAGMENT, "voxy:bakery/position_tex.fsh")
.compile();
public static void init(){}
private static final GlBuffer indexBuffer;
static {
var i = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
int id = ((com.mojang.blaze3d.opengl.GlBuffer) i.getBuffer(4096*3*2)).handle;
if (i.type() != VertexFormat.IndexType.SHORT) {
throw new IllegalStateException();
}
indexBuffer = new GlBuffer(3*2*2*4096);
glCopyNamedBufferSubData(id, indexBuffer.id, 0, 0, 3*2*2*4096);
}
private static final int STRIDE = 24;
private static final GlVertexArray VA = new GlVertexArray()
.setStride(STRIDE)
.setF(0, GL_FLOAT, 4, 0)//pos, metadata
.setF(1, GL_FLOAT, 2, 4 * 4)//UV
.bindElementBuffer(indexBuffer.id);
private static GlBuffer immediateBuffer;
private static int quadCount;
public static void drawFast(MeshData buffer, GpuTexture tex, Matrix4f matrix) {
if (buffer.drawState().mode() != VertexFormat.Mode.QUADS) {
throw new IllegalStateException("Fast only supports quads");
}
var buff = buffer.vertexBuffer();
int size = buff.remaining();
if (size%STRIDE != 0) throw new IllegalStateException();
size /= STRIDE;
if (size%4 != 0) throw new IllegalStateException();
size /= 4;
setup(MemoryUtil.memAddress(buff), size, ((com.mojang.blaze3d.opengl.GlTexture)tex).glId());
buffer.close();
render(matrix);
}
public static void setup(long dataPtr, int quads, int texId) {
if (quads == 0) {
throw new IllegalStateException();
}
quadCount = quads;
long size = quads * 4L * STRIDE;
if (immediateBuffer == null || immediateBuffer.size()<size) {
if (immediateBuffer != null) {
immediateBuffer.free();
}
immediateBuffer = new GlBuffer(size*2L);//This also accounts for when immediateBuffer == null
VA.bindBuffer(immediateBuffer.id);
}
long ptr = UploadStream.INSTANCE.upload(immediateBuffer, 0, size);
MemoryUtil.memCopy(dataPtr, ptr, size);
UploadStream.INSTANCE.commit();
bakeryShader.bind();
VA.bind();
glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT);
glBindSampler(0, 0);
glBindTextureUnit(0, texId);
}
public static void render(Matrix4f matrix) {
glUniformMatrix4fv(1, false, matrix.get(new float[16]));
glDrawElements(GL_TRIANGLES, quadCount * 2 * 3, GL_UNSIGNED_SHORT, 0);
}
}

View File

@@ -0,0 +1,101 @@
package me.cortex.voxy.client.core.model.bakery;
import me.cortex.voxy.client.core.gl.GlFramebuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.ARBDirectStateAccess.*;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_FRAMEBUFFER_BARRIER_BIT;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_PIXEL_BUFFER_BARRIER_BIT;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_TEXTURE_UPDATE_BARRIER_BIT;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glMemoryBarrier;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL43.*;
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi;
public class GlViewCapture {
private final int width;
private final int height;
private final GlTexture colourTex;
private final GlTexture depthTex;
private final GlTexture stencilTex;
private final GlTexture metaTex;
final GlFramebuffer framebuffer;
private final Shader copyOutShader;
public GlViewCapture(int width, int height) {
this.width = width;
this.height = height;
this.metaTex = new GlTexture().store(GL_R32UI, 1, width*3, height*2).name("ModelBakeryMetadata");
this.colourTex = new GlTexture().store(GL_RGBA8, 1, width*3, height*2).name("ModelBakeryColour");
this.depthTex = new GlTexture().store(GL_DEPTH24_STENCIL8, 1, width*3, height*2).name("ModelBakeryDepth");
//TODO: FIXME: Mesa is broken when trying to read from a sampler of GL_STENCIL_INDEX
// it seems to just ignore the value set in GL_DEPTH_STENCIL_TEXTURE_MODE
glTextureParameteri(this.depthTex.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
this.stencilTex = this.depthTex.createView();
glTextureParameteri(this.depthTex.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
this.framebuffer = new GlFramebuffer().bind(GL_COLOR_ATTACHMENT0, this.colourTex).bind(GL_COLOR_ATTACHMENT1, this.metaTex).setDrawBuffers(GL_COLOR_ATTACHMENT0,GL_COLOR_ATTACHMENT1).bind(GL_DEPTH_STENCIL_ATTACHMENT, this.depthTex).verify().name("ModelFramebuffer");
glTextureParameteri(this.stencilTex.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
glTextureParameteri(this.stencilTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(this.stencilTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameteri(this.metaTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(this.metaTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameteri(this.depthTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(this.depthTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
this.copyOutShader = Shader.makeAuto()
.define("WIDTH", width)
.define("HEIGHT", height)
.define("COLOUR_IN_BINDING", 0)
.define("DEPTH_IN_BINDING", 1)
.define("STENCIL_IN_BINDING", 2)
.define("META_IN_BINDING", 3)
.define("BUFFER_OUT_BINDING", 4)
.add(ShaderType.COMPUTE, "voxy:bakery/bufferreorder.comp")
.compile()
.name("ModelBakeryOut")
.texture("META_IN_BINDING", 0, this.metaTex)
.texture("COLOUR_IN_BINDING", 0, this.colourTex)
.texture("DEPTH_IN_BINDING", 0, this.depthTex)
.texture("STENCIL_IN_BINDING", 0, this.stencilTex);
}
public void emitToStream(int buffer, int offset) {
this.copyOutShader.bind();
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 4, buffer, offset, (this.width*3L)*(this.height*2L)*4L*2);//its 2*4 because colour + depth stencil
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_TEXTURE_UPDATE_BARRIER_BIT|GL_PIXEL_BUFFER_BARRIER_BIT|GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);//Am not sure if barriers are right
glDispatchCompute(3, 2, 1);
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 4, 0, 0, 4);//WHY DOES THIS FIX FUCKING BINDING ISSUES HERE WHEN DOING THIS IN THE RENDER SYSTEM DOESNT
}
public void clear() {
try (var stack = MemoryStack.stackPush()) {
long ptr = stack.nmalloc(4*4);
MemoryUtil.memPutLong(ptr, 0);
MemoryUtil.memPutLong(ptr+8, 0);
nglClearNamedFramebufferfv(this.framebuffer.id, GL_COLOR, 0, ptr);
nglClearNamedFramebufferuiv(this.framebuffer.id, GL_COLOR, 1, ptr);
//TODO: fix the draw buffer thing maybe? it might need todo multiple clears
//nglClearNamedFramebufferfv(this.framebuffer.id, GL_COLOR, 0, ptr);
}
glClearNamedFramebufferfi(this.framebuffer.id, GL_DEPTH_STENCIL, 0, 1.0f, 0);
}
public void free() {
this.framebuffer.free();
this.colourTex.free();
this.stencilTex.free();
this.depthTex.free();
this.metaTex.free();
this.copyOutShader.free();
}
}

View File

@@ -0,0 +1,379 @@
package me.cortex.voxy.client.core.model.bakery;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.ColorResolver;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.SingleThreadedRandomSource;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.FluidState;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.lwjgl.opengl.ARBDrawBuffersBlend;
import org.lwjgl.opengl.GL14;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL40.glBlendFuncSeparatei;
import static org.lwjgl.opengl.GL45.glTextureBarrier;
import com.mojang.blaze3d.vertex.PoseStack;
public class ModelTextureBakery {
//Note: the first bit of metadata is if alpha discard is enabled
private static final Matrix4f[] VIEWS = new Matrix4f[6];
private final GlViewCapture capture;
private final ReuseVertexConsumer vc = new ReuseVertexConsumer();
private final int width;
private final int height;
public ModelTextureBakery(int width, int height) {
this.capture = new GlViewCapture(width, height);
this.width = width;
this.height = height;
}
public static int getMetaFromLayer(ChunkSectionLayer layer) {
boolean hasDiscard = layer == ChunkSectionLayer.CUTOUT ||
layer == ChunkSectionLayer.TRANSLUCENT||
layer == ChunkSectionLayer.TRIPWIRE;
boolean isMipped = layer == ChunkSectionLayer.SOLID ||
layer == ChunkSectionLayer.TRANSLUCENT ||
layer == ChunkSectionLayer.TRIPWIRE;
int meta = hasDiscard?1:0;
meta |= true?2:0;
return meta;
}
private void bakeBlockModel(BlockState state, ChunkSectionLayer layer) {
if (state.getRenderShape() == RenderShape.INVISIBLE) {
return;//Dont bake if invisible
}
var model = Minecraft.getInstance()
.getModelManager()
.getBlockModelShaper()
.getBlockModel(state);
int meta = getMetaFromLayer(layer);
for (var part : model.collectParts(new SingleThreadedRandomSource(42L))) {
for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) {
var quads = part.getQuads(direction);
for (var quad : quads) {
this.vc.quad(quad, meta|(quad.isTinted()?4:0));
}
}
}
}
private void bakeFluidState(BlockState state, ChunkSectionLayer layer, int face) {
{
//TODO: somehow set the tint flag per quad or something?
int metadata = getMetaFromLayer(layer);
//Just assume all fluids are tinted, if they arnt it should be implicitly culled in the model baking phase
// since it wont have the colour provider
metadata |= 4;//Has tint
this.vc.setDefaultMeta(metadata);//Set the meta while baking
}
Minecraft.getInstance().getBlockRenderer().renderLiquid(BlockPos.ZERO, new BlockAndTintGetter() {
@Override
public float getShade(Direction direction, boolean shaded) {
return 0;
}
@Override
public LevelLightEngine getLightEngine() {
return null;
}
@Override
public int getBrightness(LightLayer type, BlockPos pos) {
return 0;
}
@Override
public int getBlockTint(BlockPos pos, ColorResolver colorResolver) {
return 0;
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
return null;
}
@Override
public BlockState getBlockState(BlockPos pos) {
if (shouldReturnAirForFluid(pos, face)) {
return Blocks.AIR.defaultBlockState();
}
//Fixme:
// This makes it so that the top face of water is always air, if this is commented out
// the up block will be a liquid state which makes the sides full
// if this is uncommented, that issue is fixed but e.g. stacking water layers ontop of eachother
// doesnt fill the side of the block
//if (pos.getY() == 1) {
// return Blocks.AIR.getDefaultState();
//}
return state;
}
@Override
public FluidState getFluidState(BlockPos pos) {
if (shouldReturnAirForFluid(pos, face)) {
return Blocks.AIR.defaultBlockState().getFluidState();
}
return state.getFluidState();
}
@Override
public int getHeight() {
return 0;
}
@Override
public int getMinY() {
return 0;
}
}, this.vc, state, state.getFluidState());
this.vc.setDefaultMeta(0);//Reset default meta
}
private static boolean shouldReturnAirForFluid(BlockPos pos, int face) {
var fv = Direction.from3DDataValue(face).getUnitVec3i();
int dot = fv.getX()*pos.getX() + fv.getY()*pos.getY() + fv.getZ()*pos.getZ();
return dot >= 1;
}
public void free() {
this.capture.free();
this.vc.free();
}
public int renderToStream(BlockState state, int streamBuffer, int streamOffset) {
this.capture.clear();
boolean isBlock = true;
ChunkSectionLayer layer;
if (state.getBlock() instanceof LiquidBlock) {
layer = ItemBlockRenderTypes.getRenderLayer(state.getFluidState());
isBlock = false;
} else {
if (state.getBlock() instanceof LeavesBlock) {
layer = ChunkSectionLayer.SOLID;
} else {
layer = ItemBlockRenderTypes.getChunkRenderType(state);
}
}
//TODO: support block model entities
//BakedBlockEntityModel bbem = null;
if (state.hasBlockEntity()) {
//bbem = BakedBlockEntityModel.bake(state);
}
//Setup GL state
int[] viewdat = new int[4];
int blockTextureId;
{
glEnable(GL_STENCIL_TEST);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
if (layer == ChunkSectionLayer.TRANSLUCENT) {
glEnablei(GL_BLEND, 0);
glDisablei(GL_BLEND, 1);
ARBDrawBuffersBlend.glBlendFuncSeparateiARB(0, GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
} else {
glDisable(GL_BLEND);//FUCK YOU INTEL (screams), for _some reason_ discard or something... JUST DOESNT WORK??
//glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ONE);
}
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
glGetIntegerv(GL_VIEWPORT, viewdat);//TODO: faster way todo this, or just use main framebuffer resolution
//Bind the capture framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
var tex = Minecraft.getInstance().getTextureManager().getTexture(Identifier.fromNamespaceAndPath("minecraft", "textures/atlas/blocks.png")).getTexture();
blockTextureId = ((com.mojang.blaze3d.opengl.GlTexture)tex).glId();
}
boolean isAnyShaded = false;
boolean isAnyDarkend = false;
if (isBlock) {
this.vc.reset();
this.bakeBlockModel(state, layer);
isAnyShaded |= this.vc.anyShaded;
isAnyDarkend |= this.vc.anyDarkendTex;
if (!this.vc.isEmpty()) {//only render if there... is shit to render
//Setup for continual emission
BudgetBufferRenderer.setup(this.vc.getAddress(), this.vc.quadCount(), blockTextureId);//note: this.vc.buffer.address NOT this.vc.ptr
var mat = new Matrix4f();
for (int i = 0; i < VIEWS.length; i++) {
if (i==1||i==2||i==4) {
glCullFace(GL_FRONT);
} else {
glCullFace(GL_BACK);
}
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
//The projection matrix
mat.set(2, 0, 0, 0,
0, 2, 0, 0,
0, 0, -1f, 0,
-1, -1, 0, 1)
.mul(VIEWS[i]);
BudgetBufferRenderer.render(mat);
}
}
glBindVertexArray(0);
} else {//Is fluid, slow path :(
if (!(state.getBlock() instanceof LiquidBlock)) throw new IllegalStateException();
var mat = new Matrix4f();
for (int i = 0; i < VIEWS.length; i++) {
if (i==1||i==2||i==4) {
glCullFace(GL_FRONT);
} else {
glCullFace(GL_BACK);
}
this.vc.reset();
this.bakeFluidState(state, layer, i);
if (this.vc.isEmpty()) continue;
isAnyShaded |= this.vc.anyShaded;
isAnyDarkend |= this.vc.anyDarkendTex;
BudgetBufferRenderer.setup(this.vc.getAddress(), this.vc.quadCount(), blockTextureId);
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
//The projection matrix
mat.set(2, 0, 0, 0,
0, 2, 0, 0,
0, 0, -1f, 0,
-1, -1, 0, 1)
.mul(VIEWS[i]);
BudgetBufferRenderer.render(mat);
}
glBindVertexArray(0);
}
//Render block model entity data if it exists
/*
if (bbem != null) {
//Rerender everything again ;-; but is ok (is not)
var mat = new Matrix4f();
for (int i = 0; i < VIEWS.length; i++) {
if (i==1||i==2||i==4) {
glCullFace(GL_FRONT);
} else {
glCullFace(GL_BACK);
}
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
//The projection matrix
mat.set(2, 0, 0, 0,
0, 2, 0, 0,
0, 0, -1f, 0,
-1, -1, 0, 1)
.mul(VIEWS[i]);
bbem.render(mat, blockTextureId);
}
glBindVertexArray(0);
bbem.release();
}*/
//"Restore" gl state
glViewport(viewdat[0], viewdat[1], viewdat[2], viewdat[3]);
glDisable(GL_STENCIL_TEST);
glDisable(GL_BLEND);
//Finish and download
glTextureBarrier();
this.capture.emitToStream(streamBuffer, streamOffset);
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
glClearDepth(1);
glClear(GL_DEPTH_BUFFER_BIT);
if (layer == ChunkSectionLayer.TRANSLUCENT) {
//reset the blend func
GL14.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
return (isAnyShaded?1:0)|(isAnyDarkend?2:0);
}
static {
//the face/direction is the face (e.g. down is the down face)
addView(0, -90,0, 0, 0);//Direction.DOWN
addView(1, 90,0, 0, 0b100);//Direction.UP
addView(2, 0,180, 0, 0b001);//Direction.NORTH
addView(3, 0,0, 0, 0);//Direction.SOUTH
addView(4, 0,90, 270, 0b100);//Direction.WEST
addView(5, 0,270, 270, 0);//Direction.EAST
}
private static void addView(int i, float pitch, float yaw, float rotation, int flip) {
var stack = new PoseStack();
stack.translate(0.5f,0.5f,0.5f);
stack.mulPose(makeQuatFromAxisExact(new Vector3f(0,0,1), rotation));
stack.mulPose(makeQuatFromAxisExact(new Vector3f(1,0,0), pitch));
stack.mulPose(makeQuatFromAxisExact(new Vector3f(0,1,0), yaw));
stack.mulPose(new Matrix4f().scale(1-2*(flip&1), 1-(flip&2), 1-((flip>>1)&2)));
stack.translate(-0.5f,-0.5f,-0.5f);
VIEWS[i] = new Matrix4f(stack.last().pose());
}
private static Quaternionf makeQuatFromAxisExact(Vector3f vec, float angle) {
angle = (float) Math.toRadians(angle);
float hangle = angle / 2.0f;
float sinAngle = (float) Math.sin(hangle);
float invVLength = (float) (1/Math.sqrt(vec.lengthSquared()));
return new Quaternionf(vec.x * invVLength * sinAngle,
vec.y * invVLength * sinAngle,
vec.z * invVLength * sinAngle,
Math.cos(hangle));
}
}

View File

@@ -0,0 +1,141 @@
package me.cortex.voxy.client.core.model.bakery;
import me.cortex.voxy.common.util.MemoryBuffer;
import net.minecraft.client.model.geom.builders.UVPair;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.MipmapStrategy;
import org.lwjgl.system.MemoryUtil;
import static me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer.VERTEX_FORMAT_SIZE;
import com.mojang.blaze3d.vertex.VertexConsumer;
public final class ReuseVertexConsumer implements VertexConsumer {
private MemoryBuffer buffer = new MemoryBuffer(8192);
private long ptr;
private int count;
private int defaultMeta;
public boolean anyShaded;
public boolean anyDarkendTex;
public ReuseVertexConsumer() {
this.reset();
}
public ReuseVertexConsumer setDefaultMeta(int meta) {
this.defaultMeta = meta;
return this;
}
@Override
public ReuseVertexConsumer addVertex(float x, float y, float z) {
this.ensureCanPut();
this.ptr += VERTEX_FORMAT_SIZE; this.count++; //Goto next vertex
this.meta(this.defaultMeta);
MemoryUtil.memPutFloat(this.ptr, x);
MemoryUtil.memPutFloat(this.ptr + 4, y);
MemoryUtil.memPutFloat(this.ptr + 8, z);
return this;
}
public ReuseVertexConsumer meta(int metadata) {
MemoryUtil.memPutInt(this.ptr + 12, metadata);
return this;
}
@Override
public ReuseVertexConsumer setColor(int red, int green, int blue, int alpha) {
return this;
}
@Override
public VertexConsumer setColor(int i) {
return this;
}
@Override
public ReuseVertexConsumer setUv(float u, float v) {
MemoryUtil.memPutFloat(this.ptr + 16, u);
MemoryUtil.memPutFloat(this.ptr + 20, v);
return this;
}
@Override
public ReuseVertexConsumer setUv1(int u, int v) {
return this;
}
@Override
public ReuseVertexConsumer setUv2(int u, int v) {
return this;
}
@Override
public ReuseVertexConsumer setNormal(float x, float y, float z) {
return this;
}
@Override
public VertexConsumer setLineWidth(float f) {
return null;
}
public ReuseVertexConsumer quad(BakedQuad quad, int metadata) {
this.anyShaded |= quad.shade();
this.anyDarkendTex |= quad.sprite().contents().mipmapStrategy == MipmapStrategy.DARK_CUTOUT;
this.ensureCanPut();
for (int i = 0; i < 4; i++) {
var pos = quad.position(i);
this.addVertex(pos.x(), pos.y(), pos.z());
long puv = quad.packedUV(i);
this.setUv(UVPair.unpackU(puv),UVPair.unpackV(puv));
this.meta(metadata);
}
return this;
}
private void ensureCanPut() {
if ((long) (this.count + 5) * VERTEX_FORMAT_SIZE < this.buffer.size) {
return;
}
long offset = this.ptr-this.buffer.address;
//1.5x the size
var newBuffer = new MemoryBuffer((((int)(this.buffer.size*2)+VERTEX_FORMAT_SIZE-1)/VERTEX_FORMAT_SIZE)*VERTEX_FORMAT_SIZE);
this.buffer.cpyTo(newBuffer.address);
this.buffer.free();
this.buffer = newBuffer;
this.ptr = offset + newBuffer.address;
}
public ReuseVertexConsumer reset() {
this.anyShaded = false;
this.anyDarkendTex = false;
this.defaultMeta = 0;//RESET THE DEFAULT META
this.count = 0;
this.ptr = this.buffer.address - VERTEX_FORMAT_SIZE;//the thing is first time this gets incremented by FORMAT_STRIDE
return this;
}
public void free() {
this.ptr = 0;
this.count = 0;
this.buffer.free();
this.buffer = null;
}
public boolean isEmpty() {
return this.count == 0;
}
public int quadCount() {
if (this.count%4 != 0) throw new IllegalStateException();
return this.count/4;
}
public long getAddress() {
return this.buffer.address;
}
}

View File

@@ -1,181 +0,0 @@
package me.cortex.voxy.client.core.rendering;
//NOTE: an idea on how to do it is so that any render section, we _keep_ aquired (yes this will be very memory intensive)
// could maybe tosomething else
import com.mojang.blaze3d.systems.RenderSystem;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.model.ModelManager;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Frustum;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.util.Identifier;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeKeys;
import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import static org.lwjgl.opengl.ARBMultiDrawIndirect.glMultiDrawElementsIndirect;
import static org.lwjgl.opengl.GL30.*;
//can make it so that register the key of the sections we have rendered, then when a section changes and is registered,
// dispatch an update to the render section data builder which then gets consumed by the render system and updates
// the rendered data
//Contains all the logic to render the world and manage gpu memory
// processes section load,unload,update render data and renders the world each frame
//Todo: tinker with having the compute shader where each thread is a position to render? maybe idk
public abstract class AbstractFarWorldRenderer <T extends Viewport, J extends AbstractGeometryManager> {
public static final int STATIC_VAO = glGenVertexArrays();
protected final GlBuffer uniformBuffer;
protected final J geometry;
protected final ModelManager models;
protected final GlBuffer lightDataBuffer;
protected final int maxSections;
//Current camera base level section position
protected int sx;
protected int sy;
protected int sz;
protected FrustumIntersection frustum;
private final List<T> viewports = new ArrayList<>();
protected IntArrayList updatedSectionIds;
private final ConcurrentLinkedDeque<Mapper.StateEntry> blockStateUpdates = new ConcurrentLinkedDeque<>();
private final ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeUpdates = new ConcurrentLinkedDeque<>();
public AbstractFarWorldRenderer(J geometry) {
this.maxSections = geometry.getMaxSections();
this.uniformBuffer = new GlBuffer(1024);
this.lightDataBuffer = new GlBuffer(256*4);//256 of uint
this.geometry = geometry;
this.models = new ModelManager(16);
}
public void setupRender(Frustum frustum, Camera camera) {
this.frustum = frustum.frustumIntersection;
this.sx = camera.getBlockPos().getX() >> 5;
this.sy = camera.getBlockPos().getY() >> 5;
this.sz = camera.getBlockPos().getZ() >> 5;
//TODO: move this to a render function that is only called
// once per frame when using multi viewport mods
//it shouldent matter if its called multiple times a frame however, as its synced with fences
UploadStream.INSTANCE.tick();
DownloadStream.INSTANCE.tick();
//Update the lightmap
{
long upload = UploadStream.INSTANCE.upload(this.lightDataBuffer, 0, 256*4);
var lmt = MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().texture.getImage();
for (int light = 0; light < 256; light++) {
int x = light&0xF;
int y = ((light>>4)&0xF);
int sample = lmt.getColor(x,y);
sample = ((sample&0xFF0000)>>16)|(sample&0xFF00)|((sample&0xFF)<<16);
MemoryUtil.memPutInt(upload + (((x<<4)|(15-y))*4), sample|(0xFF<<28));//Skylight is inverted
}
}
//Upload any new geometry
this.updatedSectionIds = this.geometry.uploadResults();
{
boolean didHaveBiomeChange = false;
//Do any BiomeChanges
while (!this.biomeUpdates.isEmpty()) {
var update = this.biomeUpdates.pop();
var biomeReg = MinecraftClient.getInstance().world.getRegistryManager().get(RegistryKeys.BIOME);
this.models.addBiome(update.id, biomeReg.get(Identifier.of(update.biome)));
didHaveBiomeChange = true;
}
if (didHaveBiomeChange) {
UploadStream.INSTANCE.commit();
}
int maxUpdatesPerFrame = 40;
//Do any BlockChanges
while ((!this.blockStateUpdates.isEmpty()) && (maxUpdatesPerFrame-- > 0)) {
var update = this.blockStateUpdates.pop();
this.models.addEntry(update.id, update.state);
}
//this.models.bakery.renderFaces(Blocks.ROSE_BUSH.getDefaultState(), 1234, false);
}
//TODO: fix this in a better way than this ungodly hacky stuff, causes clouds to dissapear
//RenderSystem.setShaderFogColor(1f, 1f, 1f, 0f);
RenderSystem.setShaderFogEnd(99999999);
RenderSystem.setShaderFogStart(9999999);
}
public abstract void renderFarAwayOpaque(T viewport);
public abstract void renderFarAwayTranslucent(T viewport);
public void enqueueResult(BuiltSection result) {
this.geometry.enqueueResult(result);
}
public void addBlockState(Mapper.StateEntry entry) {
this.blockStateUpdates.add(entry);
}
public void addBiome(Mapper.BiomeEntry entry) {
this.biomeUpdates.add(entry);
}
public void addDebugData(List<String> debug) {
this.models.addDebugInfo(debug);
debug.add("Geometry buffer usage: " + ((float)Math.round((this.geometry.getGeometryBufferUsage()*100000))/1000) + "%");
debug.add("Render Sections: " + this.geometry.getSectionCount());
}
public void shutdown() {
this.models.free();
this.geometry.free();
this.uniformBuffer.free();
this.lightDataBuffer.free();
}
public ModelManager getModelManager() {
return this.models;
}
public final T createViewport() {
var viewport = createViewport0();
this.viewports.add(viewport);
return viewport;
}
final void removeViewport(T viewport) {
this.viewports.remove(viewport);
}
protected abstract T createViewport0();
public boolean usesMeshlets() {
return false;
}
}

View File

@@ -1,35 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import java.util.concurrent.ConcurrentLinkedDeque;
public abstract class AbstractGeometryManager {
protected int sectionCount = 0;
protected final int maxSections;
protected final ConcurrentLinkedDeque<BuiltSection> buildResults = new ConcurrentLinkedDeque<>();
protected AbstractGeometryManager(int maxSections) {
this.maxSections = maxSections;
}
abstract IntArrayList uploadResults();
int getMaxSections() {
return this.maxSections;
}
public void enqueueResult(BuiltSection sectionGeometry) {
this.buildResults.add(sectionGeometry);
}
public abstract float getGeometryBufferUsage();
public int getSectionCount() {
return this.sectionCount;
}
public abstract void free();
}

View File

@@ -0,0 +1,232 @@
package me.cortex.voxy.client.core.rendering;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import me.cortex.voxy.client.core.AbstractRenderPipeline;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlVertexArray;
import me.cortex.voxy.client.core.gl.shader.AutoBindingShader;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderLoader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.Logger;
import net.minecraft.client.Minecraft;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector3i;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.ARBDirectStateAccess.glCopyNamedBufferSubData;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30C.*;
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
import static org.lwjgl.opengl.GL42.glDrawElementsInstancedBaseInstance;
//This is a render subsystem, its very simple in what it does
// it renders an AABB around loaded chunks, thats it
public class ChunkBoundRenderer {
private static final int INIT_MAX_CHUNK_COUNT = 1<<12;
private GlBuffer chunkPosBuffer = new GlBuffer(INIT_MAX_CHUNK_COUNT*8);//Stored as ivec2
private final GlBuffer uniformBuffer = new GlBuffer(128);
private final Long2IntOpenHashMap chunk2idx = new Long2IntOpenHashMap(INIT_MAX_CHUNK_COUNT);
private long[] idx2chunk = new long[INIT_MAX_CHUNK_COUNT];
private final Shader rasterShader;
private final LongOpenHashSet addQueue = new LongOpenHashSet();
private final LongOpenHashSet remQueue = new LongOpenHashSet();
private final AbstractRenderPipeline pipeline;
public ChunkBoundRenderer(AbstractRenderPipeline pipeline) {
this.chunk2idx.defaultReturnValue(-1);
this.pipeline = pipeline;
String vert = ShaderLoader.parse("voxy:chunkoutline/outline.vsh");
String taa = pipeline.taaFunction("getTAA");
if (taa != null) {
vert = vert+"\n\n\n"+taa;
}
this.rasterShader = Shader.makeAuto()
.addSource(ShaderType.VERTEX, vert)
.defineIf("TAA", taa != null)
.add(ShaderType.FRAGMENT, "voxy:chunkoutline/outline.fsh")
.compile()
.ubo(0, this.uniformBuffer)
.ssbo(1, this.chunkPosBuffer);
}
public void addSection(long pos) {
if (!this.remQueue.remove(pos)) {
this.addQueue.add(pos);
}
}
public void removeSection(long pos) {
if (!this.addQueue.remove(pos)) {
this.remQueue.add(pos);
}
}
//Bind and render, changing as little gl state as possible so that the caller may configure how it wants to render
public void render(Viewport<?> viewport) {
if (!this.remQueue.isEmpty()) {
boolean wasEmpty = this.chunk2idx.isEmpty();
this.remQueue.forEach(this::_remPos);//TODO: REPLACE WITH SCATTER COMPUTE
this.remQueue.clear();
if (this.chunk2idx.isEmpty()&&!wasEmpty) {//When going from stuff to nothing need to clear the depth buffer
viewport.depthBoundingBuffer.clear(0);
}
}
if (this.chunk2idx.isEmpty() && this.addQueue.isEmpty()) return;
viewport.depthBoundingBuffer.clear(0);
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 128);
long matPtr = ptr; ptr += 4*4*4;
final float renderDistance = Minecraft.getInstance().options.getEffectiveRenderDistance()*16;//In blocks
{//This is recomputed to be in chunk section space not worldsection
int sx = (int)(viewport.cameraX);
int sy = (int)(viewport.cameraY);
int sz = (int)(viewport.cameraZ);
new Vector3i(sx, sy, sz).getToAddress(ptr); ptr += 4*4;
var negInnerSec = new Vector3f(
(float) (viewport.cameraX - sx),
(float) (viewport.cameraY - sy),
(float) (viewport.cameraZ - sz));
negInnerSec.getToAddress(ptr); ptr += 4*3;
viewport.MVP.translate(negInnerSec.negate(), new Matrix4f()).getToAddress(matPtr);
MemoryUtil.memPutFloat(ptr, renderDistance); ptr += 4;
}
UploadStream.INSTANCE.commit();
{
//need to reverse the winding order since we want the back faces of the AABB, not the front
glFrontFace(GL_CW);//Reverse winding order
//"reverse depth buffer" it goes from 0->1 where 1 is far away
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_GREATER);
}
glBindVertexArray(GlVertexArray.STATIC_VAO);
viewport.depthBoundingBuffer.bind();
this.rasterShader.bind();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BB_BYTE.id());
this.pipeline.bindUniforms();
//Batch the draws into groups of size 32
int count = this.chunk2idx.size();
if (count >= 32) {
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3 * 32, GL_UNSIGNED_BYTE, 0, count/32);
}
if (count%32 != 0) {
glDrawElementsInstancedBaseInstance(GL_TRIANGLES, 6 * 2 * 3 * (count%32), GL_UNSIGNED_BYTE, 0, 1, (count/32)*32);
}
{
glFrontFace(GL_CCW);//Restore winding order
glDepthFunc(GL_LEQUAL);
//TODO: check this is correct
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
}
if (!this.addQueue.isEmpty()) {
this.addQueue.forEach(this::_addPos);//TODO: REPLACE WITH SCATTER COMPUTE
this.addQueue.clear();
UploadStream.INSTANCE.commit();
}
}
private void _remPos(long pos) {
int idx = this.chunk2idx.remove(pos);
if (idx == -1) {
Logger.warn("Chunk not in map: " + pos);
return;
}
if (idx == this.chunk2idx.size()) {
//Dont need to do anything as heap is already compact
return;
}
if (this.idx2chunk[idx] != pos) {
throw new IllegalStateException();
}
//Move last entry on heap to this index
long ePos = this.idx2chunk[this.chunk2idx.size()];// since is already removed size is correct end idx
if (this.chunk2idx.put(ePos, idx) == -1) {
throw new IllegalStateException();
}
this.idx2chunk[idx] = ePos;
//Put the end pos into the new idx
this.put(idx, ePos);
}
private void _addPos(long pos) {
if (this.chunk2idx.containsKey(pos)) {
Logger.warn("Chunk already in map: " + pos);
return;
}
this.ensureSize1();//Resize if needed
int idx = this.chunk2idx.size();
this.chunk2idx.put(pos, idx);
this.idx2chunk[idx] = pos;
this.put(idx, pos);
}
private void ensureSize1() {
if (this.chunk2idx.size() < this.idx2chunk.length) return;
//Commit any copies, ensures is synced to new buffer
UploadStream.INSTANCE.commit();
int size = (int) (this.idx2chunk.length*1.5);
Logger.info("Resizing chunk position buffer to: " + size);
//Need to resize
var old = this.chunkPosBuffer;
this.chunkPosBuffer = new GlBuffer(size * 8L);
glCopyNamedBufferSubData(old.id, this.chunkPosBuffer.id, 0, 0, old.size());
old.free();
var old2 = this.idx2chunk;
this.idx2chunk = new long[size];
System.arraycopy(old2, 0, this.idx2chunk, 0, old2.length);
//Replace the old buffer with the new one
((AutoBindingShader)this.rasterShader).ssbo(1, this.chunkPosBuffer);
}
private void put(int idx, long pos) {
long ptr2 = UploadStream.INSTANCE.upload(this.chunkPosBuffer, 8L*idx, 8);
//Need to do it in 2 parts because ivec2 is 2 parts
MemoryUtil.memPutInt(ptr2, (int)(pos&0xFFFFFFFFL)); ptr2 += 4;
MemoryUtil.memPutInt(ptr2, (int)((pos>>>32)&0xFFFFFFFFL));
}
public void reset() {
this.chunk2idx.clear();
}
public void free() {
this.rasterShader.free();
this.uniformBuffer.free();
this.chunkPosBuffer.free();
}
}

View File

@@ -1,187 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.util.BufferArena;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
import org.lwjgl.system.MemoryUtil;
import java.util.concurrent.ConcurrentLinkedDeque;
public class DefaultGeometryManager extends AbstractGeometryManager {
private static final int SECTION_METADATA_SIZE = 32;
private final Long2IntOpenHashMap pos2id = new Long2IntOpenHashMap();
private final LongArrayList id2pos = new LongArrayList();
private final ObjectArrayList<SectionMeta> sectionMetadata = new ObjectArrayList<>();
private final IntArrayList markSectionIds = new IntArrayList();//Section ids to mark as visible (either due to being new, or swapping)
private final GlBuffer sectionMetaBuffer;
private final BufferArena geometryBuffer;
private final int geometryElementSize;
public DefaultGeometryManager(long geometryBufferSize, int maxSections) {
this(geometryBufferSize, maxSections, 8);//8 is default quad size
}
public DefaultGeometryManager(long geometryBufferSize, int maxSections, int elementSize) {
super(maxSections);
this.sectionMetaBuffer = new GlBuffer(((long) maxSections) * SECTION_METADATA_SIZE);
this.geometryBuffer = new BufferArena(geometryBufferSize, elementSize);
this.pos2id.defaultReturnValue(-1);
this.geometryElementSize = elementSize;
}
IntArrayList uploadResults() {
this.markSectionIds.clear();
while (!this.buildResults.isEmpty()) {
var result = this.buildResults.pop();
boolean isDelete = result.isEmpty();
if (isDelete) {
int id = -1;
if ((id = this.pos2id.remove(result.position)) != -1) {
if (this.id2pos.getLong(id) != result.position) {
throw new IllegalStateException("Removed position id not the same requested");
}
var meta = this.sectionMetadata.get(id);
this.freeMeta(meta);
this.sectionCount--;
if (id == this.sectionCount) {
//if we are at the end of the array dont have to do anything (maybe just upload a blank data, just to be sure)
//Remove the last element
this.sectionMetadata.remove(id);
this.id2pos.removeLong(id);
} else {
long swapLodPos = this.id2pos.getLong(this.sectionCount);
this.pos2id.put(swapLodPos, id);
this.id2pos.set(id, swapLodPos);
//Remove from the lists
this.id2pos.removeLong(this.sectionCount);
var swapMeta = this.sectionMetadata.remove(this.sectionCount);
this.sectionMetadata.set(id, swapMeta);
if (swapMeta.position != swapLodPos) {
throw new IllegalStateException();
}
long ptr = UploadStream.INSTANCE.upload(this.sectionMetaBuffer, (long) SECTION_METADATA_SIZE * id, SECTION_METADATA_SIZE);
swapMeta.writeMetadata(ptr);
this.markSectionIds.add(id);
}
}
} else {
int id = -1;
if ((id = this.pos2id.get(result.position)) != -1) {
//Update the existing data
var meta = this.sectionMetadata.get(id);
if (meta.position != result.position) {
throw new IllegalStateException("Meta position != result position");
}
//Delete the old data
this.freeMeta(meta);
//Create the new meta
meta = this.createMeta(result);
if (meta == null) {
continue;
}
this.sectionMetadata.set(id, meta);
long ptr = UploadStream.INSTANCE.upload(this.sectionMetaBuffer, (long)SECTION_METADATA_SIZE * id, SECTION_METADATA_SIZE);
meta.writeMetadata(ptr);
} else {
//Create the new meta
var meta = this.createMeta(result);
if (meta == null) {
continue;
}
//Add to the end of the array
id = this.sectionCount++;
this.pos2id.put(result.position, id);
this.id2pos.add(result.position);
this.sectionMetadata.add(meta);
long ptr = UploadStream.INSTANCE.upload(this.sectionMetaBuffer, (long)SECTION_METADATA_SIZE * id, SECTION_METADATA_SIZE);
meta.writeMetadata(ptr);
this.markSectionIds.add(id);
}
}
//Assert some invarients
if (this.id2pos.size() != this.sectionCount || this.sectionCount != this.pos2id.size()) {
throw new IllegalStateException("Invariants broken");
}
result.free();
}
return this.markSectionIds;
}
public void free() {
while (!this.buildResults.isEmpty()) {
this.buildResults.pop().free();
}
this.sectionMetaBuffer.free();
this.geometryBuffer.free();
}
public int geometryId() {
return this.geometryBuffer.id();
}
public int metaId() {
return this.sectionMetaBuffer.id;
}
public float getGeometryBufferUsage() {
return this.geometryBuffer.usage();
}
//=========================================================================================================================================================================================
//=========================================================================================================================================================================================
//=========================================================================================================================================================================================
//TODO: pack the offsets of each axis so that implicit face culling can work
//Note! the opaquePreDataCount and translucentPreDataCount are never writen to the meta buffer, as they are indexed in reverse relative to the base opaque and translucent geometry
protected record SectionMeta(long position, int aabb, int geometryPtr, int size, int[] offsets) {
public void writeMetadata(long ptr) {
//THIS IS DUE TO ENDIANNESS and that we are splitting a long into 2 ints
MemoryUtil.memPutInt(ptr, (int) (this.position>>32)); ptr += 4;
MemoryUtil.memPutInt(ptr, (int) this.position); ptr += 4;
MemoryUtil.memPutInt(ptr, (int) this.aabb); ptr += 4;
MemoryUtil.memPutInt(ptr, this.geometryPtr + this.offsets[0]); ptr += 4;
MemoryUtil.memPutInt(ptr, (this.offsets[1]-this.offsets[0])|((this.offsets[2]-this.offsets[1])<<16)); ptr += 4;
MemoryUtil.memPutInt(ptr, (this.offsets[3]-this.offsets[2])|((this.offsets[4]-this.offsets[3])<<16)); ptr += 4;
MemoryUtil.memPutInt(ptr, (this.offsets[5]-this.offsets[4])|((this.offsets[6]-this.offsets[5])<<16)); ptr += 4;
MemoryUtil.memPutInt(ptr, (this.offsets[7]-this.offsets[6])|((this.size -this.offsets[7])<<16)); ptr += 4;
}
}
protected SectionMeta createMeta(BuiltSection geometry) {
int geometryPtr = (int) this.geometryBuffer.upload(geometry.geometryBuffer);
if (geometryPtr == -1) {
String msg = "Buffer arena out of memory, please increase it in settings or decrease LoD quality";
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.literal(msg));
System.err.println(msg);
return null;
}
return new SectionMeta(geometry.position, geometry.aabb, geometryPtr, (int) (geometry.geometryBuffer.size/this.geometryElementSize), geometry.offsets);
}
protected void freeMeta(SectionMeta meta) {
if (meta.geometryPtr != -1) {
this.geometryBuffer.free(meta.geometryPtr);
}
}
}

View File

@@ -0,0 +1,64 @@
package me.cortex.voxy.client.core.rendering;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import java.util.concurrent.locks.ReentrantLock;
//CPU side cache for section geometry, not thread safe
public class GeometryCache {
private final ReentrantLock lock = new ReentrantLock();
private long maxCombinedSize;
private long currentSize;
private final Long2ObjectLinkedOpenHashMap<BuiltSection> cache = new Long2ObjectLinkedOpenHashMap<>();
public GeometryCache(long maxSize) {
this.setMaxTotalSize(maxSize);
}
public void setMaxTotalSize(long size) {
this.maxCombinedSize = size;
}
//Puts the section into the cache
public void put(BuiltSection section) {
this.lock.lock();
var prev = this.cache.put(section.position, section);
this.currentSize += section.geometryBuffer.size;
if (prev != null) {
this.currentSize -= prev.geometryBuffer.size;
}
while (this.maxCombinedSize <= this.currentSize) {
var entry = this.cache.removeFirst();
this.currentSize -= entry.geometryBuffer.size;
entry.free();
}
this.lock.unlock();
if (prev != null) {
prev.free();
}
}
public BuiltSection remove(long position) {
this.lock.lock();
var section = this.cache.remove(position);
if (section != null) {
this.currentSize -= section.geometryBuffer.size;
}
this.lock.unlock();
return section;
}
public void clear(long position) {
var sec = this.remove(position);
if (sec != null) {
sec.free();
}
}
public void free() {
this.lock.lock();
this.cache.values().forEach(BuiltSection::free);
this.cache.clear();
this.lock.unlock();
}
}

View File

@@ -1,232 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
import net.minecraft.block.Blocks;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.system.MemoryUtil;
import java.util.List;
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_SHORT;
import static org.lwjgl.opengl.GL11.glGetInteger;
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30C.GL_R8UI;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL42.*;
import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT;
import static org.lwjgl.opengl.GL43.*;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
import static org.lwjgl.opengl.GL45.glClearNamedBufferData;
import static org.lwjgl.opengl.GL45C.nglClearNamedBufferData;
public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer<Gl46Viewport, DefaultGeometryManager> {
private final Shader commandGen = Shader.make()
.add(ShaderType.COMPUTE, "voxy:lod/gl46/cmdgen.comp")
.compile();
private final Shader lodShader = Shader.make()
.add(ShaderType.VERTEX, "voxy:lod/gl46/quads2.vert")
.add(ShaderType.FRAGMENT, "voxy:lod/gl46/quads.frag")
.compile();
//TODO: Note the cull shader needs a different element array since its rastering cubes not quads
private final Shader cullShader = Shader.make()
.add(ShaderType.VERTEX, "voxy:lod/gl46/cull/raster.vert")
.add(ShaderType.FRAGMENT, "voxy:lod/gl46/cull/raster.frag")
.compile();
private final GlBuffer glCommandBuffer;
private final GlBuffer glCommandCountBuffer;
public Gl46FarWorldRenderer(int geometryBuffer, int maxSections) {
super(new DefaultGeometryManager(geometryBuffer*8L, maxSections));
this.glCommandBuffer = new GlBuffer(maxSections*5L*4 * 6);
this.glCommandCountBuffer = new GlBuffer(4*2);
nglClearNamedBufferData(this.glCommandBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
}
protected void bindResources(Gl46Viewport viewport) {
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometry.geometryId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.glCommandBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.glCommandCountBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometry.metaId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, viewport.visibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.models.getBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, this.models.getColourBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 8, this.lightDataBuffer.id);//Lighting LUT
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.glCommandBuffer.id);
glBindBuffer(GL_PARAMETER_BUFFER_ARB, this.glCommandCountBuffer.id);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
//Bind the texture atlas
glBindSampler(0, this.models.getSamplerId());
glBindTextureUnit(0, this.models.getTextureId());
}
//FIXME: dont do something like this as it breaks multiviewport mods
// the issue is that voxy expects the counter to be incremented by one each frame (the compute shader generating the commands)
// checks `frameId - 1`, this means in a multiviewport, effectivly only the stuff that was marked visible by the last viewport
// would be visible in the current viewport (if there are 2 viewports)
//To fix the issue, need to make a viewport api, that independently tracks the frameId and has its own glVisibilityBuffer buffer
private void updateUniformBuffer(Gl46Viewport viewport) {
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, this.uniformBuffer.size());
var mat = new Matrix4f(viewport.projection).mul(viewport.modelView);
var innerTranslation = new Vector3f((float) (viewport.cameraX-(this.sx<<5)), (float) (viewport.cameraY-(this.sy<<5)), (float) (viewport.cameraZ-(this.sz<<5)));
mat.translate(-innerTranslation.x, -innerTranslation.y, -innerTranslation.z);
mat.getToAddress(ptr); ptr += 4*4*4;
MemoryUtil.memPutInt(ptr, this.sx); ptr += 4;
MemoryUtil.memPutInt(ptr, this.sy); ptr += 4;
MemoryUtil.memPutInt(ptr, this.sz); ptr += 4;
MemoryUtil.memPutInt(ptr, this.geometry.getSectionCount()); ptr += 4;
var planes = ((AccessFrustumIntersection)this.frustum).getPlanes();
for (var plane : planes) {
plane.getToAddress(ptr); ptr += 4*4;
}
innerTranslation.getToAddress(ptr); ptr += 4*3;
MemoryUtil.memPutInt(ptr, viewport.frameId++); ptr += 4;
}
public void renderFarAwayOpaque(Gl46Viewport viewport) {
if (this.geometry.getSectionCount() == 0) {
return;
}
{//Mark all of the updated sections as being visible from last frame
for (int id : this.updatedSectionIds) {
long ptr = UploadStream.INSTANCE.upload(viewport.visibilityBuffer, id * 4L, 4);
MemoryUtil.memPutInt(ptr, viewport.frameId - 1);//(visible from last frame)
}
}
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
//this.models.bakery.renderFaces(Blocks.WATER.getDefaultState().with(FluidBlock.LEVEL, 1), 1234, true);
//this.models.bakery.renderFaces(Blocks.CHEST.getDefaultState(), 1234, false);
RenderLayer.getCutoutMipped().startDrawing();
//RenderSystem.enableBlend();
//RenderSystem.defaultBlendFunc();
this.updateUniformBuffer(viewport);
UploadStream.INSTANCE.commit();
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
nglClearNamedBufferData(this.glCommandCountBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
this.commandGen.bind();
this.bindResources(viewport);
glDispatchCompute((this.geometry.getSectionCount()+127)/128, 1, 1);
glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_UNIFORM_BARRIER_BIT);
this.lodShader.bind();
this.bindResources(viewport);
glDisable(GL_CULL_FACE);
//glPointSize(10);
//TODO: replace glMultiDrawElementsIndirectCountARB with glMultiDrawElementsIndirect on intel gpus, since it performs so much better
//glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, drawCnt, 0);
//glLineWidth(2);
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, 0, (int) (this.geometry.getSectionCount()*4.4), 0);
glEnable(GL_CULL_FACE);
/*
DownloadStream.INSTANCE.download(this.glCommandCountBuffer, 0, 4, (ptr, siz) -> {
int cnt = MemoryUtil.memGetInt(ptr);
drawCnt = cnt;
});
DownloadStream.INSTANCE.commit();
*/
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
this.cullShader.bind();
this.bindResources(viewport);
glColorMask(false, false, false, false);
glDepthMask(false);
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3, GL_UNSIGNED_BYTE, (1 << 16) * 6 * 2, this.geometry.getSectionCount());
glDepthMask(true);
glColorMask(true, true, true, true);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
//TODO: need to do temporal rasterization here
glBindVertexArray(0);
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
RenderLayer.getCutoutMipped().endDrawing();
}
@Override
public void renderFarAwayTranslucent(Gl46Viewport viewport) {
RenderLayer.getTranslucent().startDrawing();
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
//TODO: maybe change this so the alpha isnt applied in the same way or something?? since atm the texture bakery uses a very hacky
// blend equation to make it avoid double applying translucency
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindSampler(0, this.models.getSamplerId());
glBindTextureUnit(0, this.models.getTextureId());
//RenderSystem.blendFunc(GlStateManager.SrcFactor.ONE, GlStateManager.DstFactor.ONE);
this.lodShader.bind();
this.bindResources(viewport);
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 400_000 * 4 * 5, 4, this.geometry.getSectionCount(), 0);
glEnable(GL_CULL_FACE);
glBindVertexArray(0);
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
glDisable(GL_BLEND);
RenderLayer.getTranslucent().endDrawing();
}
protected Gl46Viewport createViewport0() {
return new Gl46Viewport(this);
}
@Override
public void shutdown() {
super.shutdown();
this.commandGen.free();
this.lodShader.free();
this.cullShader.free();
this.glCommandBuffer.free();
this.glCommandCountBuffer.free();
}
}

View File

@@ -1,21 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
import static org.lwjgl.opengl.GL30C.GL_R8UI;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL42.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL45C.nglClearNamedBufferData;
public class Gl46MeshletViewport extends Viewport<Gl46MeshletViewport> {
GlBuffer visibilityBuffer;
public Gl46MeshletViewport(Gl46MeshletsFarWorldRenderer renderer) {
super(renderer);
this.visibilityBuffer = new GlBuffer(renderer.maxSections*4L);
nglClearNamedBufferData(this.visibilityBuffer.id, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, 0);
}
protected void delete0() {
this.visibilityBuffer.free();
}
}

View File

@@ -1,235 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.building.RenderDataFactory;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderLayer;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.ARBDirectStateAccess.glGetNamedFramebufferAttachmentParameteriv;
import static org.lwjgl.opengl.ARBDirectStateAccess.glTextureParameteri;
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL40.glDrawElementsIndirect;
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT;
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.*;
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
import static org.lwjgl.opengl.GL45.nglClearNamedBufferSubData;
import static org.lwjgl.opengl.GL45C.nglClearNamedBufferData;
import static org.lwjgl.opengl.NVMeshShader.glDrawMeshTasksNV;
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
//TODO: NOTE
// can do "meshlet compaction"
// that is, meshlets are refered not by the meshlet id but by an 8 byte alligned index, (the quad index)
// this means that non full meshlets can have the tailing data be truncated and used in the next meshlet
// as long as the number of quads in the meshlet is stored in the header
// the shader can cull the verticies of any quad that has its index over the expected quuad count
// this could potentially result in a fair bit of memory savings (especially if used in normal mc terrain rendering)
public class Gl46MeshletsFarWorldRenderer extends AbstractFarWorldRenderer<Gl46MeshletViewport, DefaultGeometryManager> {
private final Shader lodShader = Shader.make()
.define("QUADS_PER_MESHLET", RenderDataFactory.QUADS_PER_MESHLET)
.add(ShaderType.VERTEX, "voxy:lod/gl46mesh/quads.vert")
.add(ShaderType.FRAGMENT, "voxy:lod/gl46mesh/quads.frag")
.compile();
private final Shader cullShader = Shader.make()
.define("QUADS_PER_MESHLET", RenderDataFactory.QUADS_PER_MESHLET)
.add(ShaderType.VERTEX, "voxy:lod/gl46mesh/cull.vert")
.add(ShaderType.FRAGMENT, "voxy:lod/gl46mesh/cull.frag")
.compile();
private final Shader meshletGenerator = Shader.make()
.define("QUADS_PER_MESHLET", RenderDataFactory.QUADS_PER_MESHLET)
.add(ShaderType.COMPUTE, "voxy:lod/gl46mesh/cmdgen.comp")
.compile();
private final Shader meshletCuller = Shader.make()
.define("QUADS_PER_MESHLET", RenderDataFactory.QUADS_PER_MESHLET)
.add(ShaderType.COMPUTE, "voxy:lod/gl46mesh/meshletculler.comp")
.compile();
private final GlBuffer glDrawIndirect;
private final GlBuffer meshletBuffer;
private final HiZBuffer hiZBuffer = new HiZBuffer();
private final int hizSampler = glGenSamplers();
public Gl46MeshletsFarWorldRenderer(int geometrySize, int maxSections) {
super(new DefaultGeometryManager(alignUp(geometrySize*8L, 8* (RenderDataFactory.QUADS_PER_MESHLET+2)), maxSections, 8*(RenderDataFactory.QUADS_PER_MESHLET+2)));
this.glDrawIndirect = new GlBuffer(4*(4+5));
this.meshletBuffer = new GlBuffer(4*1000000);//TODO: Make max meshlet count configurable, not just 1 million (even tho thats a max of 126 million quads per frame)
glSamplerParameteri(this.hizSampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);//This is so that using the shadow sampler works correctly
glTextureParameteri(this.hizSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(this.hizSampler, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTextureParameteri(this.hizSampler, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
glTextureParameteri(this.hizSampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTextureParameteri(this.hizSampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
nglClearNamedBufferData(this.meshletBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
nglClearNamedBufferData(this.glDrawIndirect.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
}
protected void bindResources(Gl46MeshletViewport viewport, boolean bindToDrawIndirect, boolean bindToDispatchIndirect, boolean bindHiz) {
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometry.geometryId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, bindToDrawIndirect?0:this.glDrawIndirect.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.meshletBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometry.metaId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, viewport.visibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.models.getBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, this.models.getColourBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 8, this.lightDataBuffer.id);//Lighting LUT
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, bindToDrawIndirect?this.glDrawIndirect.id:0);
glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, bindToDispatchIndirect?this.glDrawIndirect.id:0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BYTE.id());
if (!bindHiz) {
//Bind the texture atlas
glBindSampler(0, this.models.getSamplerId());
glBindTextureUnit(0, this.models.getTextureId());
} else {
//Bind the hiz buffer
glBindSampler(0, this.hizSampler);
glBindTextureUnit(0, this.hiZBuffer.getHizTextureId());
}
}
private void updateUniformBuffer(Gl46MeshletViewport viewport) {
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, this.uniformBuffer.size());
//TODO:FIXME remove this.sx/sy/sz and make it based on the viewport!!!
var mat = new Matrix4f(viewport.projection).mul(viewport.modelView);
var innerTranslation = new Vector3f((float) (viewport.cameraX-(this.sx<<5)), (float) (viewport.cameraY-(this.sy<<5)), (float) (viewport.cameraZ-(this.sz<<5)));
mat.translate(-innerTranslation.x, -innerTranslation.y, -innerTranslation.z);
mat.getToAddress(ptr); ptr += 4*4*4;
MemoryUtil.memPutInt(ptr, this.sx); ptr += 4;
MemoryUtil.memPutInt(ptr, this.sy); ptr += 4;
MemoryUtil.memPutInt(ptr, this.sz); ptr += 4;
MemoryUtil.memPutInt(ptr, this.geometry.getSectionCount()); ptr += 4;
var planes = ((AccessFrustumIntersection)this.frustum).getPlanes();
for (var plane : planes) {
plane.getToAddress(ptr); ptr += 4*4;
}
innerTranslation.getToAddress(ptr); ptr += 4*3;
MemoryUtil.memPutInt(ptr, viewport.frameId++); ptr += 4;
MemoryUtil.memPutInt(ptr, viewport.width); ptr += 4;
MemoryUtil.memPutInt(ptr, viewport.height); ptr += 4;
}
@Override
public void renderFarAwayOpaque(Gl46MeshletViewport viewport) {
if (this.geometry.getSectionCount() == 0) {
return;
}
{//Mark all of the updated sections as being visible from last frame
for (int id : this.updatedSectionIds) {
long ptr = UploadStream.INSTANCE.upload(viewport.visibilityBuffer, id * 4L, 4);
MemoryUtil.memPutInt(ptr, viewport.frameId - 1);//(visible from last frame)
}
}
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
RenderLayer.getCutoutMipped().startDrawing();
this.updateUniformBuffer(viewport);
UploadStream.INSTANCE.commit();
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
nglClearNamedBufferSubData(this.glDrawIndirect.id, GL_R32UI, 0, 4*4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
this.lodShader.bind();
this.bindResources(viewport, true, false, false);
glDisable(GL_CULL_FACE);
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_BYTE, 4*4);
glEnable(GL_CULL_FACE);
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
this.cullShader.bind();
this.bindResources(viewport, false, false, false);
glDepthMask(false);
glColorMask(false, false, false, false);
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3, GL_UNSIGNED_BYTE, (1 << 8) * 6, this.geometry.getSectionCount());
glColorMask(true, true, true, true);
glDepthMask(true);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
this.meshletGenerator.bind();
this.bindResources(viewport, false, false, false);
glDispatchCompute((this.geometry.getSectionCount()+63)/64, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT);
var i = new int[1];
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, i);
this.hiZBuffer.buildMipChain(i[0], MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight);
this.meshletCuller.bind();
this.bindResources(viewport, false, true, true);
glDispatchComputeIndirect(0);
glBindVertexArray(0);
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
RenderLayer.getCutoutMipped().endDrawing();
}
@Override
public void renderFarAwayTranslucent(Gl46MeshletViewport viewport) {
}
@Override
protected Gl46MeshletViewport createViewport0() {
return new Gl46MeshletViewport(this);
}
@Override
public void shutdown() {
super.shutdown();
this.lodShader.free();
this.cullShader.free();
this.meshletGenerator.free();
this.meshletCuller.free();
this.glDrawIndirect.free();
this.meshletBuffer.free();
this.hiZBuffer.free();
glDeleteSamplers(this.hizSampler);
}
public static long alignUp(long n, long alignment) {
return (n + alignment - 1) & -alignment;
}
@Override
public boolean usesMeshlets() {
return true;
}
}

View File

@@ -1,23 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
import static org.lwjgl.opengl.GL30C.GL_R8UI;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL42.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL45C.glClearNamedBufferData;
import static org.lwjgl.opengl.GL45C.nglClearNamedBufferData;
public class Gl46Viewport extends Viewport<Gl46Viewport> {
GlBuffer visibilityBuffer;
public Gl46Viewport(Gl46FarWorldRenderer renderer) {
super(renderer);
this.visibilityBuffer = new GlBuffer(renderer.maxSections*4L);
nglClearNamedBufferData(this.visibilityBuffer.id, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, 0);
}
protected void delete0() {
this.visibilityBuffer.free();
}
}

View File

@@ -1,50 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.hierarchical.NodeManager;
import me.cortex.voxy.common.util.HierarchicalBitSet;
import static org.lwjgl.opengl.GL42C.*;
import static org.lwjgl.opengl.GL43C.GL_SHADER_STORAGE_BARRIER_BIT;
import static org.lwjgl.opengl.GL43C.glDispatchCompute;
import static org.lwjgl.opengl.GL44.GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT;
public class HierarchicalOcclusionRenderer {
private final int workgroup_dispatch_size_x;//The number of workgroups required to saturate the gpu efficiently
private final NodeManager nodeManager = new NodeManager(null);
private final HiZBuffer hiz = new HiZBuffer();
private Shader hiercarchialShader = Shader.make()
.add(ShaderType.COMPUTE, "voxy:lod/hierarchical/selector.comp")
.compile();
public HierarchicalOcclusionRenderer(int workgroup_size) {
this.workgroup_dispatch_size_x = workgroup_size;
}
private void bind() {
}
public void render(int depthBuffer, int width, int height) {
//Make hiz
this.hiz.buildMipChain(depthBuffer, width, height);
//Node upload phase
this.nodeManager.uploadPhase();
//Node download phase (pulls from previous frame (should maybe result in lower latency)) also clears and resets the queues
this.nodeManager.downloadPhase();
//Bind all the resources
this.bind();
//run hierachial selection shader
this.hiercarchialShader.bind();
//barrier
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT|GL_UNIFORM_BARRIER_BIT|GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT|GL_FRAMEBUFFER_BARRIER_BIT);
//Emit enough work to fully populate the gpu
glDispatchCompute(this.workgroup_dispatch_size_x, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT|GL_COMMAND_BARRIER_BIT|GL_UNIFORM_BARRIER_BIT);
}
}

View File

@@ -0,0 +1,23 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.common.world.WorldEngine;
public interface ISectionWatcher {
default boolean watch(int lvl, int x, int y, int z, int types) {
return this.watch(WorldEngine.getWorldSectionId(lvl, x, y, z), types);
}
boolean watch(long position, int types);
default boolean unwatch(int lvl, int x, int y, int z, int types) {
return this.unwatch(WorldEngine.getWorldSectionId(lvl, x, y, z), types);
}
boolean unwatch(long position, int types);
default int get(int lvl, int x, int y, int z) {
return this.get(WorldEngine.getWorldSectionId(lvl, x, y, z));
}
int get(long position);
}

View File

@@ -1,188 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Frustum;
import net.minecraft.client.render.RenderLayer;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.system.MemoryUtil;
import java.util.List;
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT;
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BARRIER_BIT;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
import static org.lwjgl.opengl.NVMeshShader.glDrawMeshTasksNV;
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
public class NvMeshFarWorldRenderer extends AbstractFarWorldRenderer<NvMeshViewport, DefaultGeometryManager> {
private final Shader terrain = Shader.make()
.add(ShaderType.TASK, "voxy:lod/nvmesh/primary.task")
.add(ShaderType.MESH, "voxy:lod/nvmesh/primary.mesh")
.add(ShaderType.FRAGMENT, "voxy:lod/nvmesh/primary.frag")
.compile();
private final Shader translucent = Shader.make()
.add(ShaderType.TASK, "voxy:lod/nvmesh/translucent.task")
.add(ShaderType.MESH, "voxy:lod/nvmesh/translucent.mesh")
.add(ShaderType.FRAGMENT, "voxy:lod/nvmesh/primary.frag")
.compile();
private final Shader cull = Shader.make()
.add(ShaderType.VERTEX, "voxy:lod/nvmesh/cull.vert")
.add(ShaderType.FRAGMENT, "voxy:lod/nvmesh/cull.frag")
.compile();
public NvMeshFarWorldRenderer(int geometrySize, int maxSections) {
super(new DefaultGeometryManager(geometrySize*8L, maxSections));
}
private void updateUniform(NvMeshViewport viewport) {
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, this.uniformBuffer.size());
var mat = new Matrix4f(viewport.projection).mul(viewport.modelView);
mat.getToAddress(ptr); ptr += 4*4*4;
var innerTranslation = new Vector3f((float) (viewport.cameraX-(this.sx<<5)), (float) (viewport.cameraY-(this.sy<<5)), (float) (viewport.cameraZ-(this.sz<<5)));
MemoryUtil.memPutInt(ptr, this.sx); ptr += 4;
MemoryUtil.memPutInt(ptr, this.sy); ptr += 4;
MemoryUtil.memPutInt(ptr, this.sz); ptr += 4;
MemoryUtil.memPutInt(ptr, this.geometry.getSectionCount()); ptr += 4;
innerTranslation.getToAddress(ptr); ptr += 4*3;
MemoryUtil.memPutInt(ptr, viewport.frameId++); ptr += 4;
}
private void bindResources(NvMeshViewport viewport) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometry.geometryId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.geometry.metaId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.visibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.models.getBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, this.models.getColourBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.lightDataBuffer.id);//Lighting LUT
//Bind the texture atlas
glBindSampler(0, this.models.getSamplerId());
glBindTextureUnit(0, this.models.getTextureId());
}
@Override
public void renderFarAwayOpaque(NvMeshViewport viewport) {
{//TODO: move all this code into a common super method renderFarAwayTranslucent and make the current method renderFarAwayTranslucent0
if (this.geometry.getSectionCount() == 0) {
return;
}
{//Mark all of the updated sections as being visible from last frame
for (int id : this.updatedSectionIds) {
long ptr = UploadStream.INSTANCE.upload(viewport.visibilityBuffer, id * 4L, 4);
MemoryUtil.memPutInt(ptr, viewport.frameId - 1);//(visible from last frame)
}
}
}
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
//Update and upload data
this.updateUniform(viewport);
UploadStream.INSTANCE.commit();
this.terrain.bind();
RenderLayer.getCutoutMipped().startDrawing();
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
this.bindResources(viewport);
glDisable(GL_CULL_FACE);
glDrawMeshTasksNV(0, this.geometry.getSectionCount());
glEnable(GL_CULL_FACE);
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);
this.cull.bind();
this.bindResources(viewport);
glColorMask(false, false, false, false);
glDepthMask(false);
glEnable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3, GL_UNSIGNED_BYTE, (1 << 16) * 6 * 2, this.geometry.getSectionCount());
glDisable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
glDepthMask(true);
glColorMask(true, true, true, true);
glBindVertexArray(0);
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
RenderLayer.getCutoutMipped().endDrawing();
}
@Override
public void renderFarAwayTranslucent(NvMeshViewport viewport) {
if (this.geometry.getSectionCount()==0) {
return;
}
RenderLayer.getTranslucent().startDrawing();
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
//TODO: maybe change this so the alpha isnt applied in the same way or something?? since atm the texture bakery uses a very hacky
// blend equation to make it avoid double applying translucency
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindSampler(0, this.models.getSamplerId());
glBindTextureUnit(0, this.models.getTextureId());
this.translucent.bind();
this.bindResources(viewport);
glDrawMeshTasksNV(0, this.geometry.getSectionCount());
glEnable(GL_CULL_FACE);
glBindVertexArray(0);
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
glDisable(GL_BLEND);
RenderLayer.getTranslucent().endDrawing();
}
@Override
protected NvMeshViewport createViewport0() {
return new NvMeshViewport(this);
}
@Override
public void shutdown() {
super.shutdown();
this.terrain.free();
this.translucent.free();
this.cull.free();
}
}

View File

@@ -1,21 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
import static org.lwjgl.opengl.GL30C.GL_R8UI;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL42.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL45C.glClearNamedBufferData;
public class NvMeshViewport extends Viewport<NvMeshViewport> {
GlBuffer visibilityBuffer;
public NvMeshViewport(NvMeshFarWorldRenderer renderer) {
super(renderer);
this.visibilityBuffer = new GlBuffer(renderer.maxSections*4L);
glClearNamedBufferData(this.visibilityBuffer.id, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, new int[1]);
}
protected void delete0() {
this.visibilityBuffer.free();
}
}

View File

@@ -0,0 +1,60 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.util.RingTracker;
import me.cortex.voxy.common.world.WorldEngine;
import java.util.function.LongConsumer;
public class RenderDistanceTracker {
private static final int CHECK_DISTANCE_BLOCKS = 128;
private final LongConsumer addTopLevelNode;
private final LongConsumer removeTopLevelNode;
private final int processRate;
private final int minSec;
private final int maxSec;
private RingTracker tracker;
private int renderDistance;
private double posX;
private double posZ;
public RenderDistanceTracker(int rate, int minSec, int maxSec, LongConsumer addTopLevelNode, LongConsumer removeTopLevelNode) {
this.addTopLevelNode = addTopLevelNode;
this.removeTopLevelNode = removeTopLevelNode;
this.renderDistance = 2;
this.tracker = new RingTracker(this.renderDistance, 0, 0, true);
this.processRate = rate;
this.minSec = minSec;
this.maxSec = maxSec;
}
public void setRenderDistance(int renderDistance) {
if (renderDistance == this.renderDistance) {
return;
}
this.renderDistance = renderDistance;
this.tracker.unload();//Mark all as unload
this.tracker = new RingTracker(this.tracker, renderDistance, ((int)this.posX)>>9, ((int)this.posZ)>>9, true);//Steal from previous tracker
}
public boolean setCenterAndProcess(double x, double z) {
double dx = this.posX-x;
double dz = this.posZ-z;
if (CHECK_DISTANCE_BLOCKS*CHECK_DISTANCE_BLOCKS<dx*dx+dz*dz) {
this.posX = x;
this.posZ = z;
this.tracker.moveCenter(((int)x)>>9, ((int)z)>>9);
}
return this.tracker.process(this.processRate, this::add, this::rem)!=0;
}
private void add(int x, int z) {
for (int y = this.minSec; y <= this.maxSec; y++) {
this.addTopLevelNode.accept(WorldEngine.getWorldSectionId(4, x, y, z));
}
}
private void rem(int x, int z) {
for (int y = this.minSec; y <= this.maxSec; y++) {
this.removeTopLevelNode.accept(WorldEngine.getWorldSectionId(4, x, y, z));
}
}
}

View File

@@ -1,206 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.math.Direction;
import java.util.concurrent.ConcurrentHashMap;
//Tracks active sections, dispatches updates to the build system, everything related to rendering flows through here
public class RenderTracker {
private final WorldEngine world;
private RenderGenerationService renderGen;
private final AbstractFarWorldRenderer renderer;
private final LongSet[] sets;
public RenderTracker(WorldEngine world, AbstractFarWorldRenderer renderer) {
this.world = world;
this.renderer = renderer;
this.sets = new LongSet[1<<4];
for (int i = 0; i < this.sets.length; i++) {
this.sets[i] = new LongOpenHashSet();
}
}
public void setRenderGen(RenderGenerationService renderGen) {
this.renderGen = renderGen;
}
public static long mixStafford13(long seed) {
seed = (seed ^ seed >>> 30) * -4658895280553007687L;
seed = (seed ^ seed >>> 27) * -7723592293110705685L;
return seed ^ seed >>> 31;
}
private LongSet getSet(long key) {
return this.sets[(int) (mixStafford13(key) & (this.sets.length-1))];
}
private void put(long key) {
var set = this.getSet(key);
synchronized (set) {
set.add(key);
}
}
private void remove(long key) {
var set = this.getSet(key);
synchronized (set) {
set.remove(key);
}
}
private boolean contains(long key) {
var set = this.getSet(key);
synchronized (set) {
return set.contains(key);
}
}
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
//Adds a lvl 0 section into the world renderer
public void addLvl0(int x, int y, int z) {
this.put(WorldEngine.getWorldSectionId(0, x, y, z));
this.renderGen.enqueueTask(0, x, y, z, this::shouldStillBuild);
}
//Removes a lvl 0 section from the world renderer
public void remLvl0(int x, int y, int z) {
this.remove(WorldEngine.getWorldSectionId(0, x, y, z));
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(0, x, y, z)));
this.renderGen.removeTask(0, x, y, z);
}
//Increases from lvl-1 to lvl at the coordinates (which are in lvl space)
public void inc(int lvl, int x, int y, int z) {
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1));
this.put(WorldEngine.getWorldSectionId(lvl, x, y, z));
//TODO: make a seperate object to hold the build data and link it with the location in a
// concurrent hashmap or something, this is so that e.g. the build data position
// can be updated
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
this.renderGen.enqueueTask(lvl, x, y, z, this::shouldStillBuild);
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1))));
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1)));
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1))));
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1)));
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1))));
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1)));
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1))));
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1)));
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1), (z<<1));
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1), (z<<1)+1);
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1)+1, (z<<1));
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1);
this.renderGen.removeTask(lvl-1, (x<<1)+1, (y<<1), (z<<1));
this.renderGen.removeTask(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1);
this.renderGen.removeTask(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1));
this.renderGen.removeTask(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1);
}
//Decreases from lvl to lvl-1 at the coordinates (which are in lvl space)
public void dec(int lvl, int x, int y, int z) {
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1));
this.remove(WorldEngine.getWorldSectionId(lvl, x, y, z));
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl, x, y, z)));
this.renderGen.removeTask(lvl, x, y, z);
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1), (z<<1), this::shouldStillBuild);
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1), (z<<1)+1, this::shouldStillBuild);
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1)+1, (z<<1), this::shouldStillBuild);
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1)+1, (z<<1)+1, this::shouldStillBuild);
this.renderGen.enqueueTask(lvl - 1, (x<<1)+1, (y<<1), (z<<1), this::shouldStillBuild);
this.renderGen.enqueueTask(lvl - 1, (x<<1)+1, (y<<1), (z<<1)+1, this::shouldStillBuild);
this.renderGen.enqueueTask(lvl - 1, (x<<1)+1, (y<<1)+1, (z<<1), this::shouldStillBuild);
this.renderGen.enqueueTask(lvl - 1, (x<<1)+1, (y<<1)+1, (z<<1)+1, this::shouldStillBuild);
}
//Enqueues a renderTask for a section to cache the result
public void addCache(int lvl, int x, int y, int z) {
this.renderGen.markCache(lvl, x, y, z);
this.renderGen.enqueueTask(lvl, x, y, z, ((lvl1, x1, y1, z1) -> true));//TODO: replace the true identity lambda with a callback check to the render cache
}
//Removes the position from the cache
public void removeCache(int lvl, int x, int y, int z) {
this.renderGen.unmarkCache(lvl, x, y, z);
}
public void remove(int lvl, int x, int y, int z) {
this.remove(WorldEngine.getWorldSectionId(lvl, x, y, z));
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl, x, y, z)));
}
public void add(int lvl, int x, int y, int z) {
this.put(WorldEngine.getWorldSectionId(lvl, x, y, z));
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
this.renderGen.enqueueTask(lvl, x, y, z, this::shouldStillBuild);
}
//Called by the world engine when a section gets dirtied
public void sectionUpdated(WorldSection section) {
if (this.contains(section.key)) {
//TODO:FIXME: if the section gets updated, that means that its neighbors might need to be updated aswell
// (due to block occlusion)
//TODO: FIXME: REBUILDING THE ENTIRE NEIGHBORS when probably only the internal layout changed is NOT SMART
this.renderGen.clearCache(section.lvl, section.x, section.y, section.z);
this.renderGen.clearCache(section.lvl, section.x-1, section.y, section.z);
this.renderGen.clearCache(section.lvl, section.x+1, section.y, section.z);
this.renderGen.clearCache(section.lvl, section.x, section.y, section.z-1);
this.renderGen.clearCache(section.lvl, section.x, section.y, section.z+1);
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z, this::shouldStillBuild);
this.renderGen.enqueueTask(section.lvl, section.x-1, section.y, section.z, this::shouldStillBuild);
this.renderGen.enqueueTask(section.lvl, section.x+1, section.y, section.z, this::shouldStillBuild);
this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z-1, this::shouldStillBuild);
this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z+1, this::shouldStillBuild);
}
//this.renderGen.enqueueTask(section);
}
//called by the RenderGenerationService about built geometry, the RenderTracker checks if it can use the result (e.g. the LoD hasnt changed/still correct etc)
// and dispatches it to the renderer
// it also batch collects the geometry sections until all the geometry for an operation is collected, then it executes the operation, its removes flickering
public void processBuildResult(BuiltSection section) {
//Check that we still want the section
if (this.contains(section.position)) {
this.renderer.enqueueResult(section);
} else {
section.free();
}
}
public boolean shouldStillBuild(int lvl, int x, int y, int z) {
return this.contains(WorldEngine.getWorldSectionId(lvl, x, y, z));
}
}

View File

@@ -0,0 +1,172 @@
package me.cortex.voxy.client.core.rendering;
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection;
import java.util.concurrent.locks.StampedLock;
import java.util.function.LongConsumer;
import static me.cortex.voxy.common.world.WorldEngine.UPDATE_TYPE_BLOCK_BIT;
public class SectionUpdateRouter implements ISectionWatcher {
private static final int SLICES = 1<<4;
public interface IChildUpdate {void accept(WorldSection section);}
private final Long2ByteOpenHashMap[] slices = new Long2ByteOpenHashMap[SLICES];
private final StampedLock[] locks = new StampedLock[SLICES];
{
for (int i = 0; i < this.slices.length; i++) {
this.slices[i] = new Long2ByteOpenHashMap();
this.locks[i] = new StampedLock();
}
}
private LongConsumer initialRenderMeshGen;
private LongConsumer renderMeshGen;
private IChildUpdate childUpdateCallback;
public void setCallbacks(LongConsumer initialRenderMeshGen, LongConsumer renderMeshGen, IChildUpdate childUpdateCallback) {
if (this.renderMeshGen != null) {
throw new IllegalStateException();
}
this.initialRenderMeshGen = initialRenderMeshGen;
this.renderMeshGen = renderMeshGen;
this.childUpdateCallback = childUpdateCallback;
}
public boolean watch(int lvl, int x, int y, int z, int types) {
return this.watch(WorldEngine.getWorldSectionId(lvl, x, y, z), types);
}
public boolean watch(long position, int types) {
int idx = getSliceIndex(position);
var set = this.slices[idx];
var lock = this.locks[idx];
byte delta = 0;
{
long stamp = lock.readLock();
byte current = set.getOrDefault(position, (byte) 0);
delta = (byte) (current&types);
current |= (byte) types;
delta ^= (byte) (current&types);
if (delta != 0) {//Was change
long ws = lock.tryConvertToWriteLock(stamp);
if (ws == 0) {
lock.unlockRead(stamp);
stamp = lock.writeLock();
//We need to recompute as we failed to acquire an immediate write lock
current = set.getOrDefault(position, (byte) 0);
delta = (byte) (current&types);
current |= (byte) types;
delta ^= (byte) (current&types);
if (delta != 0)
set.put(position, current);
} else {
stamp = ws;
set.put(position, current);
}
}
lock.unlock(stamp);
}
if (((delta&types)&UPDATE_TYPE_BLOCK_BIT)!=0) {
//If we added it, immediately invoke for an update
this.initialRenderMeshGen.accept(position);
}
return delta!=0;
}
public boolean unwatch(int lvl, int x, int y, int z, int types) {
return this.unwatch(WorldEngine.getWorldSectionId(lvl, x, y, z), types);
}
public boolean unwatch(long position, int types) {//Types is types to unwatch
int idx = getSliceIndex(position);
var set = this.slices[idx];
var lock = this.locks[idx];
long stamp = lock.readLock();
byte current = set.getOrDefault(position, (byte)0);
if (current == 0) {
throw new IllegalStateException("Section pos not in map " + WorldEngine.pprintPos(position));
}
boolean removed = false;
if ((current&types) != 0) {//Was change
long ws = lock.tryConvertToWriteLock(stamp);
if (ws == 0) {//failed to get write lock, need to unlock, get write, then redo
lock.unlockRead(stamp);
stamp = lock.writeLock();
current = set.getOrDefault(position, (byte)0);
if (current == 0) {
throw new IllegalStateException("Section pos not in map " + WorldEngine.pprintPos(position));
}
} else {
stamp = ws;
}
if ((current&types) != 0) {
current &= (byte) ~types;
if (current == 0) {
set.remove(position);
removed = true;
} else {
set.put(position, current);
}
}
}
lock.unlock(stamp);
return removed;
}
public int get(long position) {
int idx = getSliceIndex(position);
var set = this.slices[idx];
var lock = this.locks[idx];
long stamp = lock.readLock();
int ret = set.getOrDefault(position, (byte) 0);
lock.unlockRead(stamp);
return ret;
}
public void forwardEvent(WorldSection section, int type) {
final long position = section.key;
int idx = getSliceIndex(position);
var set = this.slices[idx];
var lock = this.locks[idx];
long stamp = lock.readLock();
byte types = (byte) (set.getOrDefault(position, (byte) 0)&type);
lock.unlockRead(stamp);
if (types!=0) {
if ((types&WorldEngine.UPDATE_TYPE_CHILD_EXISTENCE_BIT)!=0) {
this.childUpdateCallback.accept(section);
}
if ((types&UPDATE_TYPE_BLOCK_BIT)!=0) {
this.renderMeshGen.accept(section.key);
}
}
}
public void triggerRemesh(long position) {
int idx = getSliceIndex(position);
var set = this.slices[idx];
var lock = this.locks[idx];
long stamp = lock.readLock();
byte types = set.getOrDefault(position, (byte) 0);
lock.unlockRead(stamp);
if ((types&UPDATE_TYPE_BLOCK_BIT)!=0) {
this.renderMeshGen.accept(position);
}
}
private static int getSliceIndex(long value) {
value = (value ^ value >>> 30) * -4658895280553007687L;
value = (value ^ value >>> 27) * -7723592293110705685L;
return (int) ((value ^ value >>> 31)&(SLICES-1));
}
}

View File

@@ -1,102 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.core.util.IndexUtil;
import me.cortex.voxy.common.util.MemoryBuffer;
import org.lwjgl.system.MemoryUtil;
//Has a base index buffer of 16380 quads, and also a 1 cube byte index buffer at the end
public class SharedIndexBuffer {
public static final SharedIndexBuffer INSTANCE = new SharedIndexBuffer();
public static final SharedIndexBuffer INSTANCE_BYTE = new SharedIndexBuffer(true);
private final GlBuffer indexBuffer;
public SharedIndexBuffer() {
this.indexBuffer = new GlBuffer((1<<16)*6*2 + 6*2*3);
var quadIndexBuff = IndexUtil.generateQuadIndicesShort(16380);
var cubeBuff = generateCubeIndexBuffer();
long ptr = UploadStream.INSTANCE.upload(this.indexBuffer, 0, this.indexBuffer.size());
quadIndexBuff.cpyTo(ptr);
cubeBuff.cpyTo((1<<16)*2*6 + ptr);
quadIndexBuff.free();
cubeBuff.free();
}
private SharedIndexBuffer(boolean type2) {
this.indexBuffer = new GlBuffer((1<<8)*6 + 6*2*3);
var quadIndexBuff = IndexUtil.generateQuadIndicesByte(63);
var cubeBuff = generateCubeIndexBuffer();
long ptr = UploadStream.INSTANCE.upload(this.indexBuffer, 0, this.indexBuffer.size());
quadIndexBuff.cpyTo(ptr);
cubeBuff.cpyTo((1<<8)*6 + ptr);
quadIndexBuff.free();
cubeBuff.free();
}
private static MemoryBuffer generateCubeIndexBuffer() {
var buffer = new MemoryBuffer(6*2*3);
long ptr = buffer.address;
MemoryUtil.memSet(ptr, 0, 6*2*3 );
//Bottom face
MemoryUtil.memPutByte(ptr++, (byte) 0);
MemoryUtil.memPutByte(ptr++, (byte) 1);
MemoryUtil.memPutByte(ptr++, (byte) 2);
MemoryUtil.memPutByte(ptr++, (byte) 3);
MemoryUtil.memPutByte(ptr++, (byte) 2);
MemoryUtil.memPutByte(ptr++, (byte) 1);
//top face
MemoryUtil.memPutByte(ptr++, (byte) 6);
MemoryUtil.memPutByte(ptr++, (byte) 5);
MemoryUtil.memPutByte(ptr++, (byte) 4);
MemoryUtil.memPutByte(ptr++, (byte) 5);
MemoryUtil.memPutByte(ptr++, (byte) 6);
MemoryUtil.memPutByte(ptr++, (byte) 7);
//north face
MemoryUtil.memPutByte(ptr++, (byte) 0);
MemoryUtil.memPutByte(ptr++, (byte) 4);
MemoryUtil.memPutByte(ptr++, (byte) 1);
MemoryUtil.memPutByte(ptr++, (byte) 5);
MemoryUtil.memPutByte(ptr++, (byte) 1);
MemoryUtil.memPutByte(ptr++, (byte) 4);
//south face
MemoryUtil.memPutByte(ptr++, (byte) 3);
MemoryUtil.memPutByte(ptr++, (byte) 6);
MemoryUtil.memPutByte(ptr++, (byte) 2);
MemoryUtil.memPutByte(ptr++, (byte) 6);
MemoryUtil.memPutByte(ptr++, (byte) 3);
MemoryUtil.memPutByte(ptr++, (byte) 7);
//west face
MemoryUtil.memPutByte(ptr++, (byte) 2);
MemoryUtil.memPutByte(ptr++, (byte) 4);
MemoryUtil.memPutByte(ptr++, (byte) 0);
MemoryUtil.memPutByte(ptr++, (byte) 4);
MemoryUtil.memPutByte(ptr++, (byte) 2);
MemoryUtil.memPutByte(ptr++, (byte) 6);
//east face
MemoryUtil.memPutByte(ptr++, (byte) 1);
MemoryUtil.memPutByte(ptr++, (byte) 5);
MemoryUtil.memPutByte(ptr++, (byte) 3);
MemoryUtil.memPutByte(ptr++, (byte) 7);
MemoryUtil.memPutByte(ptr++, (byte) 3);
MemoryUtil.memPutByte(ptr++, (byte) 5);
return buffer;
}
public int id() {
return this.indexBuffer.id;
}
}

View File

@@ -1,28 +1,69 @@
package me.cortex.voxy.client.core.rendering;
import org.joml.Matrix4f;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
import net.caffeinemc.mods.sodium.client.util.FogParameters;
import net.minecraft.util.Mth;
import org.joml.*;
import java.lang.reflect.Field;
public abstract class Viewport <A extends Viewport<A>> {
private final AbstractFarWorldRenderer renderer;
int width;
int height;
int frameId;
Matrix4f projection;
Matrix4f modelView;
double cameraX;
double cameraY;
double cameraZ;
//public final HiZBuffer2 hiZBuffer = new HiZBuffer2();
public final HiZBuffer hiZBuffer = new HiZBuffer();
public final DepthFramebuffer depthBoundingBuffer = new DepthFramebuffer();
protected Viewport(AbstractFarWorldRenderer renderer) {
this.renderer = renderer;
private static final Field planesField;
static {
try {
planesField = FrustumIntersection.class.getDeclaredField("planes");
planesField.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
public int width;
public int height;
public int frameId;
public Matrix4f vanillaProjection = new Matrix4f();
public Matrix4f projection = new Matrix4f();
public Matrix4f modelView = new Matrix4f();
public final FrustumIntersection frustum = new FrustumIntersection();
public final Vector4f[] frustumPlanes;
public double cameraX;
public double cameraY;
public double cameraZ;
public FogParameters fogParameters;
public final Matrix4f MVP = new Matrix4f();
public final Vector3i section = new Vector3i();
public final Vector3f innerTranslation = new Vector3f();
protected Viewport() {
Vector4f[] planes = null;
try {
planes = (Vector4f[]) planesField.get(this.frustum);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
this.frustumPlanes = planes;
}
public final void delete() {
this.delete0();
this.renderer.removeViewport((A) this);
}
protected abstract void delete0();
protected void delete0() {
this.hiZBuffer.free();
this.depthBoundingBuffer.free();
}
public A setVanillaProjection(Matrix4fc projection) {
this.vanillaProjection.set(projection);
return (A) this;
}
public A setProjection(Matrix4f projection) {
this.projection = projection;
@@ -46,4 +87,36 @@ public abstract class Viewport <A extends Viewport<A>> {
this.height = height;
return (A) this;
}
public A setFogParameters(FogParameters fogParameters) {
this.fogParameters = fogParameters;
return (A) this;
}
public A update() {
//MVP
this.projection.mul(this.modelView, this.MVP);
//Update the frustum
this.frustum.set(this.MVP, false);
//Translation vectors
int sx = Mth.floor(this.cameraX)>>5;
int sy = Mth.floor(this.cameraY)>>5;
int sz = Mth.floor(this.cameraZ)>>5;
this.section.set(sx, sy, sz);
this.innerTranslation.set(
(float) (this.cameraX-(sx<<5)),
(float) (this.cameraY-(sy<<5)),
(float) (this.cameraZ-(sz<<5)));
if (this.depthBoundingBuffer.resize(this.width, this.height)) {
this.depthBoundingBuffer.clear(0.0f);
}
return (A) this;
}
public abstract GlBuffer getRenderList();
}

View File

@@ -0,0 +1,59 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.util.IrisUtil;
import net.fabricmc.loader.api.FabricLoader;
import org.vivecraft.api.client.VRRenderingAPI;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import static org.vivecraft.api.client.data.RenderPass.VANILLA;
public class ViewportSelector <T extends Viewport<?>> {
public static final boolean VIVECRAFT_INSTALLED = FabricLoader.getInstance().isModLoaded("vivecraft");
private final Supplier<T> creator;
private final T defaultViewport;
private final Map<Object, T> extraViewports = new HashMap<>();//TODO should maybe be a weak hashmap with value cleanup queue thing?
public ViewportSelector(Supplier<T> viewportCreator) {
this.creator = viewportCreator;
this.defaultViewport = viewportCreator.get();
}
private T getOrCreate(Object holder) {
return this.extraViewports.computeIfAbsent(holder, a->this.creator.get());
}
private T getVivecraftViewport() {
var pass = VRRenderingAPI.instance().getCurrentRenderPass();
if (pass == null || pass == VANILLA) {
return null;
}
return this.getOrCreate(pass);
}
private static final Object IRIS_SHADOW_OBJECT = new Object();
public T getViewport() {
T viewport = null;
if (viewport == null && VIVECRAFT_INSTALLED) {
viewport = getVivecraftViewport();
}
if (viewport == null && IrisUtil.irisShadowActive()) {
viewport = this.getOrCreate(IRIS_SHADOW_OBJECT);
}
if (viewport == null) {
viewport = this.defaultViewport;
}
return viewport;
}
public void free() {
this.defaultViewport.delete();
this.extraViewports.values().forEach(Viewport::delete);
this.extraViewports.clear();
}
}

View File

@@ -1,26 +1,38 @@
package me.cortex.voxy.client.core.rendering.building;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.commonImpl.VoxyCommon;
import java.util.Arrays;
//TODO: also have an AABB size stored
public final class BuiltSection {
public static final boolean VERIFY_BUILT_SECTION_OFFSETS = VoxyCommon.isVerificationFlagOn("verifyBuiltSectionOffsets");
public final long position;
public final byte childExistence;
public final int aabb;
public final MemoryBuffer geometryBuffer;
public final int[] offsets;
public final MemoryBuffer occupancy;
public BuiltSection(long position) {
this(position, -1, null, null);
private BuiltSection(long position, byte children) {
this(position, children, -1, null, null, null);
}
public BuiltSection(long position, int aabb, MemoryBuffer geometryBuffer, int[] offsets) {
public static BuiltSection empty(long position) {
return new BuiltSection(position, (byte) 0);
}
public static BuiltSection emptyWithChildren(long position, byte children) {
return new BuiltSection(position, children);
}
public BuiltSection(long position, byte childExistence, int aabb, MemoryBuffer geometryBuffer, int[] offsets, MemoryBuffer occupancy) {
this.position = position;
this.childExistence = childExistence;
this.aabb = aabb;
this.geometryBuffer = geometryBuffer;
this.offsets = offsets;
if (offsets != null) {
if (offsets != null && VERIFY_BUILT_SECTION_OFFSETS) {
for (int i = 0; i < offsets.length-1; i++) {
int delta = offsets[i+1] - offsets[i];
if (delta<0||delta>=(1<<16)) {
@@ -28,16 +40,20 @@ public final class BuiltSection {
}
}
}
this.occupancy = occupancy;
}
public BuiltSection clone() {
return new BuiltSection(this.position, this.aabb, this.geometryBuffer!=null?this.geometryBuffer.copy():null, this.offsets!=null?Arrays.copyOf(this.offsets, this.offsets.length):null);
return new BuiltSection(this.position, this.childExistence, this.aabb, this.geometryBuffer!=null?this.geometryBuffer.copy():null, this.offsets!=null?Arrays.copyOf(this.offsets, this.offsets.length):null, this.occupancy!=null?this.occupancy.copy():null);
}
public void free() {
if (this.geometryBuffer != null) {
this.geometryBuffer.free();
}
if (this.occupancy != null) {
this.occupancy.free();
}
}
public boolean isEmpty() {

View File

@@ -1,69 +0,0 @@
package me.cortex.voxy.client.core.rendering.building;
import java.util.concurrent.ConcurrentHashMap;
//TODO: Have a second level disk cache
//TODO: instead of storing duplicate render geometry between here and gpu memory
// when a section is unloaded from the gpu, put it into a download stream and recover the BuiltSection
// and put that into the cache, then remove the uploaded mesh from the cache
public class BuiltSectionMeshCache {
private static final BuiltSection HOLDER = new BuiltSection(-1);
private final ConcurrentHashMap<Long, BuiltSection> renderCache = new ConcurrentHashMap<>(1000,0.75f,10);
public BuiltSection getMesh(long key) {
BuiltSection[] res = new BuiltSection[1];
this.renderCache.computeIfPresent(key, (a, value) -> {
if (value == HOLDER) {
return value;
}
res[0] = value.clone();
return value;
});
return res[0];
}
//Returns true if the mesh was used, (this is so the parent method can free mesh object)
public boolean putMesh(BuiltSection mesh) {
var mesh2 = this.renderCache.computeIfPresent(mesh.position, (id, value) -> {
if (value != HOLDER) {
value.free();
}
return mesh;
});
return mesh2 == mesh;
}
public void clearMesh(long key) {
this.renderCache.computeIfPresent(key, (a,val)->{
if (val != HOLDER) {
val.free();
}
return HOLDER;
});
}
public void markCache(long key) {
this.renderCache.putIfAbsent(key, HOLDER);
}
public void unmarkCache(long key) {
var mesh = this.renderCache.remove(key);
if (mesh != null && mesh != HOLDER) {
mesh.free();
}
}
public void free() {
for (var mesh : this.renderCache.values()) {
if (mesh != HOLDER) {
mesh.free();
}
}
}
public int getCount() {
return this.renderCache.size();
}
}

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

@@ -1,54 +0,0 @@
package me.cortex.voxy.client.core.rendering.building;
import me.cortex.voxy.client.core.util.Mesher2D;
public class QuadEncoder {
public static int getX(long data) {
return (int) ((data>>21)&0b11111);
}
public static int getY(long data) {
return (int) ((data>>16)&0b11111);
}
public static int getZ(long data) {
return (int) ((data>>11)&0b11111);
}
public static int getW(long data) {
return (int) ((data>>3)&0b1111)+1;
}
public static int getH(long data) {
return (int) ((data>>7)&0b1111)+1;
}
public static int getFace(long data) {
return (int) (data&0b111);
}
//Note: the encodedMeshedData is from the Mesher2D
public static int encodePosition(int face, int otherAxis, int encodedMeshedData) {
if (false&&(Mesher2D.getW(encodedMeshedData) > 16 || Mesher2D.getH(encodedMeshedData) > 16)) {
throw new IllegalStateException("Width or height > 16");
}
int dat = face;
dat |= ((Mesher2D.getW(encodedMeshedData) - 1) << 7) |
((Mesher2D.getH(encodedMeshedData) - 1) << 3);
if (face>>1 == 0) {
return dat |
(Mesher2D.getX(encodedMeshedData) << 21) |
(otherAxis << 16) |
(Mesher2D.getZ(encodedMeshedData) << 11);
}
if (face>>1 == 1) {
return dat |
(Mesher2D.getX(encodedMeshedData) << 21) |
(Mesher2D.getZ(encodedMeshedData) << 16) |
(otherAxis << 11);
}
return dat |
(otherAxis << 21) |
(Mesher2D.getX(encodedMeshedData) << 16) |
(Mesher2D.getZ(encodedMeshedData) << 11);
}
}

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