744 Commits

Author SHA1 Message Date
mcrcortex
a06fc754fc bring 1.21.8 up to date 2025-10-04 12:38:49 +10:00
mcrcortex
8546a754c7 things 2025-10-04 12:35:18 +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
336 changed files with 25703 additions and 9356 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

4
.gitignore vendored
View File

@@ -1,3 +1,7 @@
# Project exclude paths
/.gradle/
/.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.

View File

@@ -1,11 +1,17 @@
plugins {
id 'fabric-loom' version "1.7.1"
id 'fabric-loom' version "1.11-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,56 @@ repositories {
includeGroup "maven.modrinth"
}
}
maven { url "https://maven.shedaniel.me/" }
maven { url "https://maven.terraformersmc.com/releases/" }
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,43 +81,61 @@ 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"
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"
modRuntimeOnlyMsk "maven.modrinth:sodium:mc1.21.8-0.7.0-fabric"
modCompileOnly "maven.modrinth:sodium:mc1.21.8-0.7.0-fabric"
//modRuntimeOnly "maven.modrinth:nvidium:0.2.6-beta"
modCompileOnly "maven.modrinth:nvidium:0.2.8-beta"
modImplementation("maven.modrinth:lithium:mc1.21.8-0.18.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:15.0.0-beta.3")
modRuntimeOnlyMsk("maven.modrinth:modmenu:15.0.0-beta.3")
modCompileOnly("maven.modrinth:iris:1.9.1+1.21.7-fabric")
//modRuntimeOnlyMsk("maven.modrinth:iris:1.9.1+1.21.7-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")
modRuntimeOnly("maven.modrinth:spark:1.10.73-fabric")
modRuntimeOnly("maven.modrinth:fabric-permissions-api:0.3.1")
modCompileOnly("maven.modrinth:chunky:1.4.40-fabric")
modRuntimeOnlyMsk("maven.modrinth:chunky:1.4.40-fabric")
modRuntimeOnlyMsk("maven.modrinth:spark:1.10.139-fabric")
modRuntimeOnlyMsk("maven.modrinth:fabric-permissions-api:0.3.3")
//modRuntimeOnly("maven.modrinth:nsight-loader:1.2.0")
modImplementation('io.github.douira:glsl-transformer:2.0.1')
//modImplementation('io.github.douira:glsl-transformer:2.0.1')
modCompileOnly("maven.modrinth:vivecraft:1.21.1-1.1.14-b2-fabric")
modCompileOnly("maven.modrinth:flashback:rNCr1Rbs")
}
@@ -91,16 +156,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,6 +227,7 @@ repositories {
mavenCentral()
}
dependencies {
implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
@@ -124,9 +241,33 @@ dependencies {
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$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'))
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.1.0') {
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.8
yarn_mappings=1.21.8+build.1
loader_version=0.16.14
loom_version=1.11-SNAPSHOT
# Fabric API
fabric_version=0.129.0+1.21.8
# Mod Properties
mod_version = 0.1.6-alpha
mod_version = 0.2.5-alpha
maven_group = me.cortex
archives_base_name = voxy
fabric_version=0.100.1+1.21

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-8.14-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,58 @@
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.MinecraftClient;
import net.minecraft.client.gui.hud.ClientBossBar;
import net.minecraft.entity.boss.BossBar;
import net.minecraft.text.Text;
import net.minecraft.util.math.MathHelper;
import java.util.UUID;
public class ClientImportManager extends ImportManager {
protected class ClientImportTask extends ImportTask {
private final UUID bossbarUUID;
private final ClientBossBar bossBar;
protected ClientImportTask(IDataImporter importer) {
super(importer);
this.bossbarUUID = MathHelper.randomUuid();
this.bossBar = new ClientBossBar(this.bossbarUUID, Text.of("Voxy world importer"), 0.0f, BossBar.Color.GREEN, BossBar.Style.PROGRESS, false, false, false);
MinecraftClient.getInstance().execute(()->{
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.put(bossBar.getUuid(), bossBar);
});
}
@Override
protected boolean onUpdate(int completed, int outOf) {
if (!super.onUpdate(completed, outOf)) {
return false;
}
MinecraftClient.getInstance().execute(()->{
this.bossBar.setPercent((float) (((double)completed) / ((double) Math.max(1, outOf))));
this.bossBar.setName(Text.of("Voxy import: " + completed + "/" + outOf + " chunks"));
});
return true;
}
@Override
protected void onCompleted(int total) {
super.onCompleted(total);
MinecraftClient.getInstance().execute(()->{
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.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";
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.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.chunk.WorldChunk;
public interface ICheekyClientChunkManager {
WorldChunk 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,60 @@
package me.cortex.voxy.client;
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 java.util.HashSet;
import java.util.function.Consumer;
import java.util.function.Function;
public class VoxyClient implements ClientModInitializer {
private static final HashSet<String> FREX = new HashSet<>();
public static void initVoxyClient() {
Capabilities.init();//Ensure clinit is called
boolean systemSupported = Capabilities.INSTANCE.compute && Capabilities.INSTANCE.indirectParameters;
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() {
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);
}}));
}
public static boolean isFrexActive() {
return !FREX.isEmpty();
}
}

View File

@@ -0,0 +1,152 @@
package me.cortex.voxy.client;
import me.cortex.voxy.client.compat.FlashbackCompat;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.common.Logger;
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.minecraft.client.MinecraftClient;
import net.minecraft.util.WorldSavePath;
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;
public VoxyClientInstance() {
super(VoxyConfig.CONFIG.serviceThreads);
var path = FlashbackCompat.getReplayStoragePath();
this.noIngestOverride = path != null;
if (path == null) {
path = getBasePath();
}
this.basePath = path;
this.storageConfig = getCreateStorageConfig(path);
}
@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 static SectionStorageConfig getCreateStorageConfig(Path path) {
try {
Files.createDirectories(path);
} catch (Exception e) {
throw new RuntimeException(e);
}
var json = path.resolve("config.json");
Config config = null;
if (Files.exists(json)) {
try {
config = Serialization.GSON.fromJson(Files.readString(json), Config.class);
if (config == null) {
Logger.error("Config deserialization null, reverting to default");
} else {
if (config.sectionStorageConfig == null) {
Logger.error("Config section storage null, reverting to default");
config = null;
}
}
} catch (Exception e) {
Logger.error("Failed to load the storage configuration file, resetting it to default, this will probably break your save if you used a custom storage config", e);
}
}
if (config == null) {
config = DEFAULT_STORAGE_CONFIG;
}
try {
Files.writeString(json, Serialization.GSON.toJson(config));
} catch (Exception e) {
throw new RuntimeException("Failed write the config, aborting!", e);
}
if (config == null) {
throw new IllegalStateException("Config is still null\n");
}
return config.sectionStorageConfig;
}
public Path getStorageBasePath() {
return this.basePath;
}
@Override
public boolean isIngestEnabled(WorldIdentifier worldId) {
return !this.noIngestOverride;
}
private static class Config {
public int version = 1;
public SectionStorageConfig sectionStorageConfig;
}
private static final Config DEFAULT_STORAGE_CONFIG;
static {
var config = new Config();
//Load the default config
var baseDB = new RocksDBStorageBackend.Config();
var compressor = new ZSTDCompressor.Config();
compressor.compressionLevel = 1;
var compression = new CompressionStorageAdaptor.Config();
compression.delegate = baseDB;
compression.compressor = compressor;
var serializer = new SectionSerializationStorage.Config();
serializer.storage = compression;
config.sectionStorageConfig = serializer;
DEFAULT_STORAGE_CONFIG = config;
}
private static Path getBasePath() {
Path basePath = MinecraftClient.getInstance().runDirectory.toPath().resolve(".voxy").resolve("saves");
var iserver = MinecraftClient.getInstance().getServer();
if (iserver != null) {
basePath = iserver.getSavePath(WorldSavePath.ROOT).resolve("voxy");
} else {
var netHandle = MinecraftClient.getInstance().interactionManager;
if (netHandle == null) {
Logger.error("Network handle null");
basePath = basePath.resolve("UNKNOWN");
} else {
var info = netHandle.networkHandler.getServerInfo();
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.address.replace(":", "_"));
}
}
}
}
return basePath.toAbsolutePath();
}
}

View File

@@ -0,0 +1,247 @@
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.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.MinecraftClient;
import net.minecraft.command.CommandSource;
import net.minecraft.text.PlainTextContent;
import net.minecraft.text.Text;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
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(Text.translatable("Voxy must be enabled in settings to use this"));
return 1;
}
var wr = MinecraftClient.getInstance().worldRenderer;
if (wr!=null) {
((IGetVoxyRenderSystem)wr).shutdownRenderer();
}
VoxyCommon.shutdownInstance();
VoxyCommon.createInstance();
if (wr!=null) {
((IGetVoxyRenderSystem)wr).createRenderer();
}
return 0;
}
private static int importDistantHorizons(CommandContext<FabricClientCommandSource> ctx) {
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
if (instance == null) {
ctx.getSource().sendError(Text.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(MinecraftClient.getInstance().world);
if (engine==null)return 1;
return instance.getImportManager().makeAndRunIfNone(engine, ()->
new DHImporter(dbFile_, engine, MinecraftClient.getInstance().world, instance.getThreadPool(), instance.savingServiceRateLimiter))?0:1;
}
private static boolean fileBasedImporter(File directory) {
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
if (instance == null) {
return false;
}
var engine = WorldIdentifier.ofEngine(MinecraftClient.getInstance().world);
if (engine==null) return false;
return instance.getImportManager().makeAndRunIfNone(engine, ()->{
var importer = new WorldImporter(engine, MinecraftClient.getInstance().world, instance.getThreadPool(), instance.savingServiceRateLimiter);
importer.importRegionDirectoryAsync(directory);
return importer;
});
}
private static int importRaw(CommandContext<FabricClientCommandSource> ctx) {
if (VoxyCommon.getInstance() == null) {
ctx.getSource().sendError(Text.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(Text.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(MinecraftClient.getInstance().runDirectory.toPath().resolve("saves"), sb);
}
private static CompletableFuture<Suggestions> importBobbySuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
return fileDirectorySuggester(MinecraftClient.getInstance().runDirectory.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 (CommandSource.shouldSuggest(remaining, wn) || CommandSource.shouldSuggest(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(Text.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();
if (name.endsWith("/")) {
name = name.substring(0, name.length()-1);
}
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(Text.translatable("Voxy must be enabled in settings to use this"));
return 1;
}
String finalInnerDir = innerDir;
var engine = WorldIdentifier.ofEngine(MinecraftClient.getInstance().world);
if (engine != null) {
return instance.getImportManager().makeAndRunIfNone(engine, () -> {
var importer = new WorldImporter(engine, MinecraftClient.getInstance().world, instance.getThreadPool(), 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(Text.translatable("Voxy must be enabled in settings to use this"));
return 1;
}
var world = WorldIdentifier.ofEngineNullable(MinecraftClient.getInstance().world);
if (world != null) {
return instance.getImportManager().cancelImport(world)?0:1;
}
return 1;
}
}

View File

@@ -0,0 +1,42 @@
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 org.apache.commons.logging.Log;
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

@@ -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,32 @@
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.gui.SodiumOptionsGUI;
public class ModMenuIntegration implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return parent -> {
if (VoxyCommon.isAvailable()) {
var screen = (SodiumOptionsGUI) SodiumOptionsGUI.createScreen(parent);
//Sorry jelly and douira, please dont hurt me
try {
//We cant use .setPage() as that invokes rebuildGui, however the screen hasnt been initalized yet
// causing things to crash
var field = SodiumOptionsGUI.class.getDeclaredField("currentPage");
field.setAccessible(true);
field.set(screen, VoxyConfigScreenPages.voxyOptionPage);
field.setAccessible(false);
} catch (Exception e) {
Logger.error("Failed to set the current page to voxy", e);
}
return screen;
} else {
return null;
}
};
}
}

View File

@@ -3,10 +3,11 @@ 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 me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.cpu.CpuLayout;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.caffeinemc.mods.sodium.client.gui.options.storage.OptionStorage;
import net.fabricmc.loader.api.FabricLoader;
import org.lwjgl.opengl.GL;
import java.io.FileReader;
import java.io.IOException;
@@ -14,53 +15,57 @@ import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
public class VoxyConfig {
public class VoxyConfig implements OptionStorage<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 enableRendering = 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 int sectionRenderDistance = 16;
public int serviceThreads = (int) Math.max(CpuLayout.CORES.length/1.5, 1);
public float subDivisionSize = 64;
public boolean renderVanillaFog = false;
public boolean useEnvironmentalFog = false;
public boolean renderStatistics = false;
public static VoxyConfig loadOrCreate() {
private static VoxyConfig loadOrCreate() {
if (VoxyCommon.isAvailable()) {
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;
var conf = GSON.fromJson(reader, VoxyConfig.class);
if (conf != null) {
conf.save();
return conf;
} else {
Logger.error("Failed to load voxy config, resetting");
}
return cfg;
} catch (IOException e) {
System.err.println("Could not parse config");
e.printStackTrace();
Logger.error("Could not parse config", e);
}
}
var config = new VoxyConfig();
config.defaultSaveConfig = ContextSelectionSystem.DEFAULT_STORAGE_CONFIG;
config.save();
return config;
} else {
var config = new VoxyConfig();
config.enabled = false;
config.enableRendering = false;
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();
Logger.error("Failed to write config file", e);
}
}
@@ -70,7 +75,12 @@ public class VoxyConfig {
.resolve("voxy-config.json");
}
public boolean useMeshShaders() {
return this.useMeshShaderIfPossible && Capabilities.INSTANCE.meshShaders;
@Override
public VoxyConfig getData() {
return this;
}
public boolean isRenderingEnabled() {
return VoxyCommon.isAvailable() && this.enabled && this.enableRendering;
}
}

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

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

View File

@@ -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,237 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.RenderStatistics;
import me.cortex.voxy.client.TimingStatistics;
import me.cortex.voxy.client.core.gl.Capabilities;
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.AbstractSectionRenderer;
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_NEAREST;
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_FRAMEBUFFER;
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
import static org.lwjgl.opengl.GL42.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL42.GL_LEQUAL;
import static org.lwjgl.opengl.GL42.GL_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.GL45C.glBindTextureUnit;
import static org.lwjgl.opengl.GL45C.glBlitNamedFramebuffer;
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");
protected AbstractRenderPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
this.frexStillHasWork = frexSupplier;
this.nodeManager = nodeManager;
this.nodeCleaner = nodeCleaner;
this.traversal = traversal;
}
//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);
this.innerPrimaryWork(viewport, depthTexture);
rs.buildDrawCalls(viewport);
rs.renderTemporal(viewport);
this.postOpaquePreTranslucent(viewport);
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);
glBlitNamedFramebuffer(sourceFrameBuffer, targetFb, 0,0, srcWidth, srcHeight, 0,0, width, height, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL30.GL_FRAMEBUFFER, targetFb);
/*
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
glColorMask(false,false,false,false);
//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) {
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);
//We keep the stencil test on with the emitting, only to where non terrain is rendered
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.I.start();
this.traversal.doTraversal(viewport);
TimingStatistics.I.stop();
} while (this.frexStillHasWork.getAsBoolean());
}
@Override
protected void free0() {
this.sectionRenderer.free();
this.depthMaskBlit.delete();
this.depthSetBlit.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,7 @@
package me.cortex.voxy.client.core;
public interface IGetVoxyRenderSystem {
VoxyRenderSystem getVoxyRenderSystem();
void shutdownRenderer();
void createRenderer();
}

View File

@@ -0,0 +1,262 @@
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.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 fb = new DepthFramebuffer(GL_DEPTH24_STENCIL8);
public final DepthFramebuffer fbTranslucent = new DepthFramebuffer(GL_DEPTH24_STENCIL8);
private final GlBuffer shaderUniforms;
public IrisVoxyRenderPipeline(IrisVoxyRenderPipelineData data, AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
super(nodeManager, nodeCleaner, traversal, frexSupplier);
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.fb.free();
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);
}
}
@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,150 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.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.MinecraftClient;
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 DepthFramebuffer fb = new DepthFramebuffer(GL_DEPTH24_STENCIL8);
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);
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(GL_DEPTH_STENCIL_ATTACHMENT, 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();
float invEndFogDelta = 1f/(end-start);
float endDistance = MinecraftClient.getInstance().gameRenderer.getViewDistanceBlocks()*1.5f;
glUniform3f(4, endDistance, invEndFogDelta, Math.abs(start)*invEndFogDelta);
glUniform3f(5, viewport.fogParameters.red(), viewport.fogParameters.green(), viewport.fogParameters.blue());
}
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.fb.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,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,472 @@
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.client.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.AbstractSectionRenderer;
import me.cortex.voxy.client.core.rendering.section.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.ServiceThreadPool;
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.MinecraftClient;
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;
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<?,?> createSectionRenderer(AbstractRenderPipeline pipeline, ModelStore modelStore, IGeometryData geometryData) {
//TODO: need todo a thing where selects optimal section render based on if supports the pipeline and geometry data type
return new MDICSectionRenderer(pipeline, modelStore, (BasicSectionGeometryData) geometryData);//We only have MDIC backend... for now
}
public VoxyRenderSystem(WorldEngine world, ServiceThreadPool threadPool) {
//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();
//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();
{
this.modelService = new ModelBakerySubsystem(world.getMapper());
this.renderGen = new RenderGenerationService(world, this.modelService, threadPool, false, () -> true);
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 = createSectionRenderer(this.pipeline, this.modelService.getStore(), this.geometryData);
this.pipeline.setSectionRenderer(sectionRenderer);
this.viewportSelector = new ViewportSelector<>(sectionRenderer::createViewport);
{
int minSec = MinecraftClient.getInstance().world.getBottomSectionCoord() >> 5;
int maxSec = (MinecraftClient.getInstance().world.getTopSectionCoord() - 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.renderDistanceTracker.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]);
}
}
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();
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 (!IrisUtil.irisShadowActive()) {
this.chunkBoundRenderer.render(viewport);
} else {
viewport.depthBoundingBuffer.clear(0);
}
TimingStatistics.E.stop();
//The entire rendering pipeline (excluding the chunkbound thing)
this.pipeline.runPipeline(viewport, boundFB, dims[2], dims[3]);
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
//Done here as is allows less gl state resetup
this.modelService.tick(Math.max(3_000_000-(System.nanoTime()-startTime), 500_000));
}
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.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 (MinecraftClient.getInstance().getCurrentFps() < MIN_FPS) {
VoxyConfig.CONFIG.subDivisionSize = Math.min(VoxyConfig.CONFIG.subDivisionSize + INCREASE_PER_SECOND / Math.max(1f, MinecraftClient.getInstance().getCurrentFps()), 256);
}
if (MAX_FPS < MinecraftClient.getInstance().getCurrentFps() && canDecreaseSize) {
VoxyConfig.CONFIG.subDivisionSize = Math.max(VoxyConfig.CONFIG.subDivisionSize - DECREASE_PER_SECOND / Math.max(1f, MinecraftClient.getInstance().getCurrentFps()), 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 = MinecraftClient.getInstance();
var gameRenderer = client.gameRenderer;//tickCounter.getTickDelta(true);
float fov = gameRenderer.getFov(gameRenderer.getCamera(), client.getRenderTickCounter().getTickProgress(true), true);
projection.setPerspective(fov * 0.01745329238474369f,
(float) client.getWindow().getFramebufferWidth() / (float)client.getWindow().getFramebufferHeight(),
near, far);
return projection;
}
//TODO: Make a reverse z buffer
private static Matrix4f computeProjectionMat(Matrix4fc base) {
return base.mulLocal(
makeProjectionMatrix(0.05f, MinecraftClient.getInstance().gameRenderer.getFarPlaneDistance()).invert(),
new Matrix4f()
).mulLocal(makeProjectionMatrix(16, 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());
}
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,99 @@
package me.cortex.voxy.client.core.gl;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL20C;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL32.glGetInteger64;
import static org.lwjgl.opengl.GL43C.GL_MAX_SHADER_STORAGE_BLOCK_SIZE;
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 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().contains("mesa");
this.isIntel = glGetString(GL_VENDOR).toLowerCase().contains("intel");
this.isNvidia = glGetString(GL_VENDOR).toLowerCase().contains("nvidia");
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;
}
}
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;
}
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,106 @@ public class GlTexture extends TrackedObject {
public GlTexture(int type) {
this.id = glCreateTextures(type);
this.type = type;
COUNT++;
}
private GlTexture(int type, boolean useGenTypes) {
if (useGenTypes) {
this.id = glGenTextures();
} else {
this.id = glCreateTextures(type);
}
this.type = type;
COUNT++;
}
public GlTexture store(int format, int levels, int width, int height) {
if (this.hasAllocated) {
throw new IllegalStateException("Texture already allocated");
}
this.hasAllocated = true;
this.format = format;
if (this.type == GL_TEXTURE_2D) {
glTextureStorage2D(this.id, levels, format, width, height);
this.width = width;
this.height = height;
this.levels = levels;
} else {
throw new IllegalStateException("Unknown texture type");
}
ESTIMATED_TOTAL_SIZE += this.getEstimatedSize();
return this;
}
public GlTexture createView() {
this.assertAllocated();
var view = new GlTexture(this.type, true);
glTextureView(view.id, this.type, this.id, this.format, 0, 1, 0, 1);
return view;
}
@Override
public void free() {
if (this.hasAllocated) {
ESTIMATED_TOTAL_SIZE -= this.getEstimatedSize();
}
COUNT--;
this.hasAllocated = false;
super.free0();
glDeleteTextures(this.id);
}
//TODO: FIXME, glGetTextureParameteri doesnt work
public static int getRawTextureType(int texture) {
if (!glIsTexture(texture)) {
throw new IllegalStateException("Not texture");
public GlTexture name(String name) {
this.assertAllocated();
return GlDebug.name(name, this);
}
int immFormat = glGetTextureParameteri(texture, GL_TEXTURE_IMMUTABLE_FORMAT);
if (immFormat == 0) {
throw new IllegalStateException("Texture: " + texture + " is not immutable");
public int getWidth() {
this.assertAllocated();
return this.width;
}
return immFormat;
public int getHeight() {
this.assertAllocated();
return this.height;
}
public int getLevels() {
this.assertAllocated();
return this.levels;
}
private long getEstimatedSize() {
this.assertAllocated();
long elemSize = switch (this.format) {
case GL_R32UI, GL_RGBA8, GL_DEPTH24_STENCIL8, GL_R32F -> 4;
case GL_DEPTH_COMPONENT24 -> 4;//TODO: check this is right????
case GL_DEPTH_COMPONENT32F -> 4;
case GL_DEPTH_COMPONENT32 -> 4;
default -> throw new IllegalStateException("Unknown element size");
};
long size = 0;
for (int lvl = 0; lvl < this.levels; lvl++) {
size += Math.max((((long)this.width)>>lvl), 1) * Math.max((((long)this.height)>>lvl), 1) * elemSize;
}
return size;
}
public void assertAllocated() {
if (!this.hasAllocated) {
throw new IllegalStateException("Texture not yet allocated");
}
}
public static int getCount() {
return COUNT;
}
public static long getEstimatedTotalSize() {
return ESTIMATED_TOTAL_SIZE;
}
}

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,95 @@ 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<>();
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, String value) {
this.defines.put(name, value);
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()];
{
@@ -99,15 +153,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 +180,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,143 @@
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 net.minecraft.client.MinecraftClient;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.util.Identifier;
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 ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeQueue = new ConcurrentLinkedDeque<>();
public ModelBakerySubsystem(Mapper mapper) {
this.mapper = mapper;
this.factory = new ModelFactory(mapper, this.storage);
}
public void tick(long totalBudget) {
//Upload all biomes
while (!this.biomeQueue.isEmpty()) {
var biome = this.biomeQueue.poll();
var biomeReg = MinecraftClient.getInstance().world.getRegistryManager().getOrThrow(RegistryKeys.BIOME);
this.factory.addBiome(biome.id, biomeReg.get(Identifier.of(biome.biome)));
}
/*
//There should be a method to access the frame time IIRC, if the user framecap is unlimited lock it to like 60 fps for computation
int BUDGET = 16;//TODO: make this computed based on the remaining free time in a frame (and like div by 2 to reduce overhead) (with a min of 1)
if (!this.blockIdQueue.isEmpty()) {
int[] est = new int[Math.min(this.blockIdQueue.size(), BUDGET)];
int i = 0;
synchronized (this.blockIdQueue) {
for (;i < est.length && !this.blockIdQueue.isEmpty(); i++) {
int blockId = this.blockIdQueue.removeFirstInt();
if (blockId == -1) {
i--;
continue;
}
est[i] = blockId;
}
}
for (int j = 0; j < i; j++) {
this.factory.addEntry(est[j]);
}
}*/
//TimingStatistics.modelProcess.start();
if (this.blockIdCount.get() != 0) {
long budget = Math.min(totalBudget-150_000, totalBudget-(this.factory.resultJobs.size()*10_000L))-150_000;
//Always do 1 iteration minimum
Integer i = this.blockIdQueue.poll();
int j = 0;
if (i != null) {
int fbBinding = glGetInteger(GL_FRAMEBUFFER_BINDING);
do {
this.factory.addEntry(i);
j++;
if (24<j)//budget<(System.nanoTime() - start)+1000
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);
}
this.factory.tick();
long start = System.nanoTime();
while (!this.factory.resultJobs.isEmpty()) {
this.factory.resultJobs.poll().run();
if (totalBudget<(System.nanoTime()-start))
break;
}
//TimingStatistics.modelProcess.stop();
}
public void shutdown() {
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.biomeQueue.add(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 && this.biomeQueue.isEmpty();
}
public int getProcessingCount() {
return this.blockIdCount.get() + this.factory.getInflightCount();
}
}

View File

@@ -1,24 +1,25 @@
package me.cortex.voxy.client.core.model;
import com.mojang.blaze3d.platform.GlConst;
import com.mojang.blaze3d.platform.GlStateManager;
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.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.gl.Capabilities;
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.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.LeavesBlock;
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.BlockRenderLayer;
import net.minecraft.client.render.RenderLayers;
import net.minecraft.fluid.FluidState;
import net.minecraft.registry.Registries;
@@ -36,17 +37,10 @@ import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import java.util.*;
import java.util.stream.Stream;
import static me.cortex.voxy.client.core.model.ModelStore.MODEL_SIZE;
import static org.lwjgl.opengl.ARBDirectStateAccess.nglTextureSubImage2D;
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
@@ -55,24 +49,26 @@ import static org.lwjgl.opengl.GL45C.glTextureSubImage2D;
//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: 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;
//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);
private record ModelEntry(ColourDepthTextureData down, ColourDepthTextureData up, ColourDepthTextureData north, ColourDepthTextureData south, ColourDepthTextureData west, ColourDepthTextureData east, int fluidBlockStateId) {
public ModelEntry(ColourDepthTextureData[] textures, int fluidBlockStateId) {
this(textures[0], textures[1], textures[2], textures[3], textures[4], textures[5], fluidBlockStateId);
}
}
public static final int MODEL_SIZE = 64;
private final Biome DEFAULT_BIOME = MinecraftClient.getInstance().world.getRegistryManager().getOrThrow(RegistryKeys.BIOME).get(BiomeKeys.PLAINS);
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
@@ -108,53 +104,121 @@ public class ModelManager {
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 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));
private final Mapper mapper;
private final ModelStore storage;
private final RawDownloadStream downstream = new RawDownloadStream(8*1024*1024);//8mb downstream
this.modelColourBuffer = new GlBuffer(4 * (1<<16));
public final Deque<Runnable> resultJobs = new ArrayDeque<>();
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);
//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);
this.addEntry(0);//Add air as the first entry
}
public void tick() {
this.downstream.tick();
}
public void setCustomBlockStateMapping(Object2IntMap<BlockState> mapping) {
this.customBlockStateIdMapping = mapping;
}
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
if (!this.blockStatesInFlight.add(blockId)) {
//Block baking is already in-flight
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 FluidBlock;
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
//Insert into the fluid LUT
var fluidState = blockState.getFluidState().getBlockState();
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);
}
}
int TOTAL_FACES_TEXTURE_SIZE = MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6;// since both depth and colour are packed together, 6 faces, 4 bytes per pixel
int allocation = this.downstream.download(TOTAL_FACES_TEXTURE_SIZE, ptr -> {
ColourDepthTextureData[] textureData = new ColourDepthTextureData[6];
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);
}
this.resultJobs.add(()->processTextureBakeResult(blockId, blockState, textureData));
});
this.bakery.renderToStream(blockState, this.downstream.getBufferId(), allocation);
return true;
}
//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) {
//This is
private void processTextureBakeResult(int blockId, BlockState blockState, ColourDepthTextureData[] textureData) {
if (this.idMappings[blockId] != -1) {
System.err.println("Block id already added: " + blockId + " for state: " + blockState);
return this.idMappings[blockId];
//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);
}
if (!this.blockStatesInFlight.contains(blockId)) {
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
}
boolean isFluid = blockState.getBlock() instanceof FluidBlock;
int modelId = -1;
var textureData = this.bakery.renderFaces(blockState, 123456, isFluid);
int clientFluidStateId = -1;
@@ -162,13 +226,11 @@ public class ModelManager {
//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);
int fluidStateId = this.mapper.getIdForBlockState(fluidState);
clientFluidStateId = this.idMappings[fluidStateId];
if (clientFluidStateId == -1) {
clientFluidStateId = this.addEntry(fluidStateId, fluidState);
throw new IllegalStateException("Block has a fluid state but fluid state is not already baked!!!");
}
}
@@ -178,10 +240,15 @@ public class ModelManager {
if (possibleDuplicate != -1) {//Duplicate found
this.idMappings[blockId] = possibleDuplicate;
modelId = possibleDuplicate;
return possibleDuplicate;
//Remove from flight
if (!this.blockStatesInFlight.remove(blockId)) {
throw new IllegalStateException();
}
return;
} else {//Not a duplicate so create a new entry
modelId = this.modelTexture2id.size();
this.idMappings[blockId] = modelId;
//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);
}
}
@@ -192,30 +259,38 @@ public class ModelManager {
this.fluidStateLUT[modelId] = clientFluidStateId;
}
var colourProvider = MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(blockState.getBlock()));
RenderLayer blockRenderLayer = null;
BlockRenderLayer blockRenderLayer = null;
if (blockState.getBlock() instanceof FluidBlock) {
blockRenderLayer = RenderLayers.getFluidLayer(blockState.getFluidState());
} else {
if (blockState.getBlock() instanceof LeavesBlock) {
blockRenderLayer = BlockRenderLayer.SOLID;
} else {
blockRenderLayer = RenderLayers.getBlockLayer(blockState);
}
}
int checkMode = blockRenderLayer==RenderLayer.getSolid()?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
int checkMode = blockRenderLayer==BlockRenderLayer.SOLID?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
var colourProvider = getColourProvider(blockState.getBlock());
long uploadPtr = UploadStream.INSTANCE.upload(this.modelBuffer, (long) modelId * MODEL_SIZE, MODEL_SIZE);
long uploadPtr = UploadStream.INSTANCE.upload(this.storage.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;
boolean isBiomeColourDependent = false;
if (colourProvider != null) {
hasBiomeColourResolver = isBiomeDependentColour(colourProvider, blockState);
isBiomeColourDependent = isBiomeDependentColour(colourProvider, blockState);
}
//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));
}
@@ -248,7 +323,8 @@ public class ModelManager {
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 (LOGGED_SELF_CULLING_WARNING.add(blockState))
// Logger.info("Warning! blockstate: " + blockState + " only culled against its self some of the time");
}
if (allTrue) {
@@ -259,14 +335,18 @@ public class ModelManager {
//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 |= isBiomeColourDependent?1:0;
metadata |= blockRenderLayer == BlockRenderLayer.TRANSLUCENT?2:0;
metadata |= needsDoubleSidedQuads?4:0;
metadata |= (!blockState.getFluidState().isEmpty())?8:0;//Has a fluid state accosiacted with it
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
@@ -276,27 +356,32 @@ public class ModelManager {
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] == (this.modelTextureSize-1) && faceSize[3] == (this.modelTextureSize-1);
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 != RenderLayer.getTranslucent();//If its translucent, it doesnt occlude
occludesFace &= blockRenderLayer != BlockRenderLayer.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)/(this.modelTextureSize * this.modelTextureSize) > 0.9;// only occlude if the face covers more than 90% of the face
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;
@@ -307,34 +392,53 @@ public class ModelManager {
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;
metadata |= (offset > 0.01 || blockRenderLayer == BlockRenderLayer.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])/(this.modelTextureSize-1))*15);
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);
faceModelData |= Math.round(offset*63)<<16;//Change the scale from 0->1 (ends inclusive) float to 0->63 (6 bits) NOTE! that 63 == 1.0f meaning its shifted all the way to the other side of the model
//Change the scale from 0->1 (ends inclusive)
// this is cursed also warning stuff at 63 (i.e half a pixel from the end will be clamped to the end)
int enc = Math.round(offset*64);
faceModelData |= Math.min(enc,63)<<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 != RenderLayer.getSolid();
needsAlphaDiscard &= blockRenderLayer != RenderLayer.getTranslucent();//Translucent doesnt have alpha discard
needsAlphaDiscard |= blockRenderLayer != BlockRenderLayer.SOLID;
needsAlphaDiscard &= blockRenderLayer != BlockRenderLayer.TRANSLUCENT;//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
faceModelData |= ((!faceCoversFullBlock)&&blockRenderLayer != BlockRenderLayer.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;
@@ -342,63 +446,102 @@ public class ModelManager {
// 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 |= isBiomeColourDependent?2:0;//Basicly whether to use the next int as a colour or as a base index/id into a colour buffer for biome dependent colours
modelFlags |= blockRenderLayer == BlockRenderLayer.TRANSLUCENT?4:0;//Is translucent
modelFlags |= blockRenderLayer == BlockRenderLayer.CUTOUT?0:8;//Dont use mipmaps (AND ALSO FKING SPECIFIES IF IT HAS AO, WHY??? GREAT QUESTION, TODO FIXE THIS)
//modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha
MemoryUtil.memPutInt(uploadPtr, modelFlags);
MemoryUtil.memPutInt(uploadPtr, modelFlags); uploadPtr += 4;
//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);
MemoryUtil.memPutInt(uploadPtr, -1);//Set the default to nothing so that its faster on the gpu
} else if (!isBiomeColourDependent) {
MemoryUtil.memPutInt(uploadPtr, captureColourConstant(colourProvider, blockState, DEFAULT_BIOME)|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);
MemoryUtil.memPutInt(uploadPtr, biomeIndex);
this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState));
long clrUploadPtr = UploadStream.INSTANCE.upload(this.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size());
//NOTE: UploadStream.INSTANCE is called _after_ uploadPtr is finished being used, this is cause the upload pointer
// may be invalidated as soon as another upload stream is invoked
long clrUploadPtr = UploadStream.INSTANCE.upload(this.storage.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size());
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
//TODO callback to inject extra data into the model data
this.putTextures(modelId, textureData);
//glGenerateTextureMipmap(this.textures.id);
return modelId;
//Set the mapping at the very end
this.idMappings[blockId] = modelId;
if (!this.blockStatesInFlight.remove(blockId)) {
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
}
//Upload/commit stream
//TODO maybe dont do it for every uploaded block?? try to batch it
UploadStream.INSTANCE.commit();
}
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);
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");
}
int i = 0;
for (var entry : this.modelsRequiringBiomeColours) {
var colourProvider = MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(entry.getRight().getBlock()));
var colourProvider = getColourProvider(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());
MemoryUtil.memPutInt(UploadStream.INSTANCE.upload(this.storage.modelBuffer, (entry.getLeft()* MODEL_SIZE)+ 4*6 + 4, 4), biomeIndex);
long clrUploadPtr = UploadStream.INSTANCE.upload(this.storage.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size());
for (var biomeE : this.biomes) {
if (biomeE == null) {
continue;//If null, ignore
}
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, entry.getRight(), biomeE)|0xFF000000); clrUploadPtr += 4;
}
}
UploadStream.INSTANCE.commit();
}
private static BlockColorProvider getColourProvider(Block block) {
return MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(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
@@ -506,181 +649,125 @@ public class ModelManager {
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()];
var data = textures[dir.getIndex()];
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);
//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()] = ((float) depth)/this.modelTextureSize;
res[dir.ordinal()] = fd;//((float) depth)/MODEL_TEXTURE_SIZE;
}
}
return res;
}
//TODO:FIXME: DONT DO SPIN LOCKS :WAA:
public long getModelMetadata(int blockId) {
public int[] _unsafeRawAccess() {
return this.idMappings;
}
public int getModelId(int blockId) {
int map = this.idMappings[blockId];
if (map == -1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
throw new IdNotYetComputedException(blockId, true);
}
map = this.idMappings[blockId];
return map;
}
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 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 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);
private static int computeSizeWithMips(int size) {
int total = 0;
for (;size!=0;size>>=1) total += size*size;
return total;
}
return map;
}
private static final MemoryBuffer SCRATCH_TEX = new MemoryBuffer((2L*3*computeSizeWithMips(MODEL_TEXTURE_SIZE))*4);
private static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE);
//TODO: redo to batch blit, instead of 6 seperate blits, and also fix mipping
private void putTextures(int id, ColourDepthTextureData[] textures) {
int X = (id&0xFF) * this.modelTextureSize*3;
int Y = ((id>>8)&0xFF) * this.modelTextureSize*2;
//if (MODEL_TEXTURE_SIZE != 16) {throw new IllegalStateException("THIS METHOD MUST BE REDONE IF THIS CONST CHANGES");}
for (int subTex = 0; subTex < 6; subTex++) {
int x = X + (subTex>>1)*this.modelTextureSize;
int y = Y + (subTex&1)*this.modelTextureSize;
//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
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);
//Copy all textures into scratch
final long addr = SCRATCH_TEX.address;
final int LENGTH_B = MODEL_TEXTURE_SIZE*3;
for (int i = 0; i < 6; i++) {
int x = (i>>1)*MODEL_TEXTURE_SIZE;
int y = (i&1)*MODEL_TEXTURE_SIZE;
int j = 0;
for (int t : textures[i].colour()) {
int o = ((y+(j>>LAYERS))*LENGTH_B + ((j&(MODEL_TEXTURE_SIZE-1))+x))*4; j++;//LAYERS here is just cause faster
MemoryUtil.memPutInt(addr+o, t);
}
}
current = next;
next = new int[current.length>>1];
//Mip the scratch
long dAddr = addr;
for (int i = 0; i < LAYERS-1; i++) {
long sAddr = dAddr;
dAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(i<<1);//is.. i*2 because shrink both MODEL_TEXTURE_SIZE by >>i so is 2*i total shift
int width = (MODEL_TEXTURE_SIZE*3)>>(i+1);
int sWidth = (MODEL_TEXTURE_SIZE*3)>>i;
int height = (MODEL_TEXTURE_SIZE*2)>>(i+1);
//TODO: OPTIMZIE THIS
for (int px = 0; px < width; px++) {
for (int py = 0; py < height; py++) {
long bp = sAddr + (px*2 + py*2*sWidth)*4;
int C00 = MemoryUtil.memGetInt(bp);
int C01 = MemoryUtil.memGetInt(bp+sWidth*4);
int C10 = MemoryUtil.memGetInt(bp+4);
int C11 = MemoryUtil.memGetInt(bp+sWidth*4+4);
MemoryUtil.memPutInt(dAddr + (px+py*width) * 4L, TextureUtils.mipColours(C00, C01, C10, C11));
}
}
}
public int getBufferId() {
return this.modelBuffer.id;
}
public int getTextureId() {
return this.textures.id;
}
int X = (id&0xFF) * MODEL_TEXTURE_SIZE*3;
int Y = ((id>>8)&0xFF) * MODEL_TEXTURE_SIZE*2;
public int getSamplerId() {
return this.blockSampler;
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
public int getColourBufferId() {
return this.modelColourBuffer.id;
long cAddr = addr;
for (int lvl = 0; lvl < LAYERS; lvl++) {
nglTextureSubImage2D(this.storage.textures.id, lvl, X >> lvl, Y >> lvl, (MODEL_TEXTURE_SIZE*3) >> lvl, (MODEL_TEXTURE_SIZE*2) >> lvl, GL_RGBA, GL_UNSIGNED_BYTE, cAddr);
cAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(lvl<<1);
}
}
public void free() {
this.downstream.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));
public int getBakedCount() {
return this.modelTexture2id.size();
}
public int getInflightCount() {
return this.blockStatesInFlight.size();
}
}

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.MinecraftClient;
import net.minecraft.client.texture.SpriteAtlasTexture;
import net.minecraft.util.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 = ((SpriteAtlasTexture) MinecraftClient.getInstance().getTextureManager()
.getTexture(Identifier.of("minecraft", "textures/atlas/blocks.png")))
.mipLevel;
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,7 @@
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.texture.MipmapHelper;
//Texturing utils to manipulate data from the model bakery
public class TextureUtils {
@@ -38,6 +38,37 @@ public class TextureUtils {
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;
@@ -92,10 +123,12 @@ public class TextureUtils {
private static float u2fdepth(int depth) {
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;
@@ -176,43 +209,50 @@ public class TextureUtils {
public static int mipColours(int one, int two, int three, int four) {
if (true) {
return MipmapHelper.blend(one, two, three, four, false);
} else {
return weightedAverageColor(weightedAverageColor(one, two), weightedAverageColor(three, four));
}
}
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);
//TODO: FIXME!!! ITS READING IT AS ABGR??? isnt the format RGBA??
private static int weightedAverageColor(int a, int b) {
//We specifically want the entire other component if the alpha is zero
// this prevents black mips from generating due to A) non filled colours, and B) when the sampler samples everything it doesnt detonate
if ((a&0xFF000000) == 0) {
return b;
}
if ((b&0xFF000000) == 0) {
return a;
}
if (((a^b)&0xFF000000)==0) {
return ColorSRGB.linearToSrgb(
addHalfLinear(0, a,b),
addHalfLinear(8, a,b),
addHalfLinear(16, a,b),
a>>>24);
}
{
int A = (a>>>24);
int B = (a>>>24);
float mul = 1.0F / (float)(A+B);
float wA = A * mul;
float wB = B * mul;
return ColorSRGB.linearToSrgb(
addMulLinear(0, a,b,wA,wB),
addMulLinear(8, a,b,wA,wB),
addMulLinear(16, a,b,wA,wB)
, (A + B)/2);
}
}
private static 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);
private static float addHalfLinear(int shift, int a, int b) {
return addMulLinear(shift, a, b, 0.5f, 0.5f);
}
private static float addMulLinear(int shift, int a, int b, float mulA, float mulB) {
return Math.fma(ColorSRGB.srgbToLinear((a>>shift)&0xFF),mulA, ColorSRGB.srgbToLinear((b>>shift)&0xFF)*mulB);
}
}

View File

@@ -0,0 +1,92 @@
package me.cortex.voxy.client.core.model.bakery;
import me.cortex.voxy.common.Logger;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import org.joml.Matrix4f;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BakedBlockEntityModel {
private record LayerConsumer(RenderLayer 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 RenderLayer.MultiPhase mp) {
Identifier textureId = mp.phases.texture.getId().orElse(null);
if (textureId == null) {
Logger.error("ERROR: Empty texture id for layer: " + layer);
} else {
texId = ((net.minecraft.client.texture.GlTexture)MinecraftClient.getInstance().getTextureManager().getTexture(textureId).getGlTexture()).getGlId();
}
}
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(RenderLayer layer) {
boolean hasDiscard = layer == RenderLayer.getCutout() ||
layer == RenderLayer.getCutoutMipped() ||
layer == RenderLayer.getTripwire();
boolean isMipped = layer == RenderLayer.getCutoutMipped() ||
layer == RenderLayer.getSolid() ||
layer.isTranslucent() ||
layer == RenderLayer.getTripwire();
int meta = hasDiscard?1:0;
meta |= isMipped?2:0;
return meta;
}
public static BakedBlockEntityModel bake(BlockState state) {
Map<RenderLayer, LayerConsumer> map = new HashMap<>();
var entity = ((BlockEntityProvider)state.getBlock()).createBlockEntity(BlockPos.ORIGIN, state);
if (entity == null) {
return null;
}
var renderer = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(entity);
entity.setWorld(MinecraftClient.getInstance().world);
if (renderer != null) {
try {
renderer.render(entity, 0.0f, new MatrixStack(), layer->map.computeIfAbsent(layer, rl -> new LayerConsumer(rl, new ReuseVertexConsumer().setDefaultMeta(getMetaFromLayer(rl)))).consumer, 0, 0, new Vec3d(0,0,0));
} catch (Exception e) {
Logger.error("Unable to bake block entity: " + entity, e);
}
}
entity.markRemoved();
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,97 @@
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.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 net.minecraft.client.gl.GlGpuBuffer;
import net.minecraft.client.render.BuiltBuffer;
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.DrawMode.QUADS);
int id = ((GlGpuBuffer) i.getIndexBuffer(4096*3*2)).id;
if (i.getIndexType() != 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(BuiltBuffer buffer, GpuTexture tex, Matrix4f matrix) {
if (buffer.getDrawParameters().mode() != VertexFormat.DrawMode.QUADS) {
throw new IllegalStateException("Fast only supports quads");
}
var buff = buffer.getBuffer();
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, ((net.minecraft.client.texture.GlTexture)tex).getGlId());
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,98 @@
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);
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,364 @@
package me.cortex.voxy.client.core.model.bakery;
import net.minecraft.block.*;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BlockRenderLayer;
import net.minecraft.client.render.RenderLayers;
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.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.joml.Quaternionf;
import org.joml.Vector3f;
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.GL45.glTextureBarrier;
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(BlockRenderLayer layer) {
boolean hasDiscard = layer == BlockRenderLayer.CUTOUT ||
layer == BlockRenderLayer.CUTOUT_MIPPED ||
layer == BlockRenderLayer.TRIPWIRE;
boolean isMipped = layer == BlockRenderLayer.CUTOUT_MIPPED ||
layer == BlockRenderLayer.SOLID ||
layer == BlockRenderLayer.TRANSLUCENT ||
layer == BlockRenderLayer.TRIPWIRE;
int meta = hasDiscard?1:0;
meta |= isMipped?2:0;
return meta;
}
private void bakeBlockModel(BlockState state, BlockRenderLayer layer) {
if (state.getRenderType() == BlockRenderType.INVISIBLE) {
return;//Dont bake if invisible
}
var model = MinecraftClient.getInstance()
.getBakedModelManager()
.getBlockModels()
.getModel(state);
int meta = getMetaFromLayer(layer);
for (var part : model.getParts(new LocalRandom(42L))) {
for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) {
var quads = part.getQuads(direction);
for (var quad : quads) {
this.vc.quad(quad, meta|(quad.hasTint()?4:0));
}
}
}
}
private void bakeFluidState(BlockState state, BlockRenderLayer 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
}
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 (shouldReturnAirForFluid(pos, face)) {
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 (shouldReturnAirForFluid(pos, face)) {
return Blocks.AIR.getDefaultState().getFluidState();
}
return state.getFluidState();
}
@Override
public int getHeight() {
return 0;
}
@Override
public int getBottomY() {
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.byIndex(face).getVector();
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 void renderToStream(BlockState state, int streamBuffer, int streamOffset) {
this.capture.clear();
boolean isBlock = true;
BlockRenderLayer layer;
if (state.getBlock() instanceof FluidBlock) {
layer = RenderLayers.getFluidLayer(state.getFluidState());
isBlock = false;
} else {
if (state.getBlock() instanceof LeavesBlock) {
layer = BlockRenderLayer.SOLID;
} else {
layer = RenderLayers.getBlockLayer(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 == BlockRenderLayer.TRANSLUCENT) {
glEnable(GL_BLEND);
glBlendFuncSeparate(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 = MinecraftClient.getInstance().getTextureManager().getTexture(Identifier.of("minecraft", "textures/atlas/blocks.png")).getGlTexture();
blockTextureId = ((net.minecraft.client.texture.GlTexture)tex).getGlId();
}
//TODO: fastpath for blocks
if (isBlock) {
this.vc.reset();
this.bakeBlockModel(state, layer);
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 FluidBlock)) 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;
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 == BlockRenderLayer.TRANSLUCENT) {
//reset the blend func
GL14.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
}
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 MatrixStack();
stack.translate(0.5f,0.5f,0.5f);
stack.multiply(makeQuatFromAxisExact(new Vector3f(0,0,1), rotation));
stack.multiply(makeQuatFromAxisExact(new Vector3f(1,0,0), pitch));
stack.multiply(makeQuatFromAxisExact(new Vector3f(0,1,0), yaw));
stack.multiplyPositionMatrix(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.peek().getPositionMatrix());
}
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,125 @@
package me.cortex.voxy.client.core.model.bakery;
import me.cortex.voxy.common.util.MemoryBuffer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.model.BakedQuad;
import org.lwjgl.system.MemoryUtil;
import static me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer.VERTEX_FORMAT_SIZE;
public final class ReuseVertexConsumer implements VertexConsumer {
private MemoryBuffer buffer = new MemoryBuffer(8192);
private long ptr;
private int count;
private int defaultMeta;
public ReuseVertexConsumer() {
this.reset();
}
public ReuseVertexConsumer setDefaultMeta(int meta) {
this.defaultMeta = meta;
return this;
}
@Override
public ReuseVertexConsumer vertex(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 color(int red, int green, int blue, int alpha) {
return this;
}
@Override
public ReuseVertexConsumer texture(float u, float v) {
MemoryUtil.memPutFloat(this.ptr + 16, u);
MemoryUtil.memPutFloat(this.ptr + 20, v);
return this;
}
@Override
public ReuseVertexConsumer overlay(int u, int v) {
return this;
}
@Override
public ReuseVertexConsumer light(int u, int v) {
return this;
}
@Override
public ReuseVertexConsumer normal(float x, float y, float z) {
return this;
}
public ReuseVertexConsumer quad(BakedQuad quad, int metadata) {
this.ensureCanPut();
int[] data = quad.vertexData();
for (int i = 0; i < 4; i++) {
float x = Float.intBitsToFloat(data[i * 8]);
float y = Float.intBitsToFloat(data[i * 8 + 1]);
float z = Float.intBitsToFloat(data[i * 8 + 2]);
this.vertex(x,y,z);
float u = Float.intBitsToFloat(data[i * 8 + 4]);
float v = Float.intBitsToFloat(data[i * 8 + 5]);
this.texture(u,v);
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.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,233 @@
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.MinecraftClient;
import net.minecraft.util.math.MathHelper;
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 = MinecraftClient.getInstance().options.getClampedViewDistance()*16;//In blocks
{//This is recomputed to be in chunk section space not worldsection
int sx = MathHelper.floor(viewport.cameraX) >> 4;
int sy = MathHelper.floor(viewport.cameraY) >> 4;
int sz = MathHelper.floor(viewport.cameraZ) >> 4;
new Vector3i(sx, sy, sz).getToAddress(ptr); ptr += 4*4;
var negInnerSec = new Vector3f(
-(float) (viewport.cameraX - (sx << 4)),
-(float) (viewport.cameraY - (sy << 4)),
-(float) (viewport.cameraZ - (sz << 4)));
viewport.MVP.translate(negInnerSec, new Matrix4f()).getToAddress(matPtr);
negInnerSec.getToAddress(ptr); ptr += 4*3;
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.math.MathHelper;
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 = MathHelper.floor(this.cameraX)>>5;
int sy = MathHelper.floor(this.cameraY)>>5;
int sz = MathHelper.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

@@ -1,7 +1,6 @@
package me.cortex.voxy.client.core;
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.rendering.AbstractFarWorldRenderer;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.util.IrisUtil;
import net.fabricmc.loader.api.FabricLoader;
import org.vivecraft.client_vr.ClientDataHolderVR;
@@ -9,7 +8,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class ViewportSelector <T extends Viewport> {
public class ViewportSelector <T extends Viewport<?>> {
public static final boolean VIVECRAFT_INSTALLED = FabricLoader.getInstance().isModLoaded("vivecraft");
private final Supplier<T> creator;
@@ -21,19 +20,28 @@ public class ViewportSelector <T extends Viewport> {
this.defaultViewport = viewportCreator.get();
}
private T getOrCreate(Object holder) {
return this.extraViewports.computeIfAbsent(holder, a->this.creator.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());
return this.getOrCreate(pass);
}
private static final Object IRIS_SHADOW_OBJECT = new Object();
public T getViewport() {
if (VIVECRAFT_INSTALLED) {
return getVivecraftViewport();
}
if (IrisUtil.irisShadowActive()) {
return this.getOrCreate(IRIS_SHADOW_OBJECT);
}
return this.defaultViewport;
}

View File

@@ -1,26 +1,37 @@
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 BuiltSection(long position) {
this(position, -1, null, null);
private BuiltSection(long position, byte children) {
this(position, children, -1, 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) {
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)) {
@@ -31,7 +42,7 @@ public final class BuiltSection {
}
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);
}
public void free() {

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

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

View File

@@ -1,199 +1,371 @@
package me.cortex.voxy.client.core.rendering.building;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import me.cortex.voxy.client.core.model.IdNotYetComputedException;
import me.cortex.voxy.client.core.model.ModelManager;
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
import me.cortex.voxy.common.thread.ServiceSlice;
import me.cortex.voxy.common.thread.ServiceThreadPool;
import me.cortex.voxy.common.util.Pair;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
import me.cortex.voxy.common.world.other.Mapper;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.List;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.StampedLock;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.function.ToIntFunction;
//TODO: Add a render cache
//TODO: to add remove functionallity add a "defunked" variable to the build task and set it to true on remove
// and process accordingly
public class RenderGenerationService {
private static final int MAX_HOLDING_SECTION_COUNT = 1000;
public interface TaskChecker {boolean check(int lvl, int x, int y, int z);}
private record BuildTask(Supplier<WorldSection> sectionSupplier) {}
public static final AtomicInteger MESH_FAILED_COUNTER = new AtomicInteger();
private static final AtomicInteger COUNTER = new AtomicInteger();
private static final class BuildTask {
WorldSection section;
final long position;
boolean hasDoneModelRequestInner;
boolean hasDoneModelRequestOuter;
int attempts;
int addin;
long priority = Long.MIN_VALUE;
private BuildTask(long position) {
this.position = position;
}
private void updatePriority() {
int unique = COUNTER.incrementAndGet();
int lvl = WorldEngine.MAX_LOD_LAYER-WorldEngine.getLevel(this.position);
lvl = Math.min(lvl, 3);//Make the 2 highest quality have equal priority
this.priority = (((lvl*3L + Math.min(this.attempts, 3))*2 + this.addin) <<32) + Integer.toUnsignedLong(unique);
this.addin = 0;
}
}
private volatile boolean running = true;
private final Thread[] workers;
private final AtomicInteger holdingSectionCount = new AtomicInteger();//Used to limit section holding
private final Long2ObjectLinkedOpenHashMap<BuildTask> taskQueue = new Long2ObjectLinkedOpenHashMap<>();
private final AtomicInteger taskQueueCount = new AtomicInteger();
private final PriorityBlockingQueue<BuildTask> taskQueue = new PriorityBlockingQueue<>(5000, (a,b)-> Long.compareUnsigned(a.priority, b.priority));
private final StampedLock taskMapLock = new StampedLock();
private final Long2ObjectOpenHashMap<BuildTask> taskMap = new Long2ObjectOpenHashMap<>(5000);
private final Semaphore taskCounter = new Semaphore(0);
private final WorldEngine world;
private final ModelManager modelManager;
private final Consumer<BuiltSection> resultConsumer;
private final BuiltSectionMeshCache meshCache = new BuiltSectionMeshCache();
private final ModelBakerySubsystem modelBakery;
private Consumer<BuiltSection> resultConsumer;
private final boolean emitMeshlets;
public RenderGenerationService(WorldEngine world, ModelManager modelManager, int workers, Consumer<BuiltSection> consumer, boolean emitMeshlets) {
private final ServiceSlice threads;
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, boolean emitMeshlets) {
this(world, modelBakery, serviceThreadPool, emitMeshlets, ()->true);
}
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, boolean emitMeshlets, BooleanSupplier taskLimiter) {
this.emitMeshlets = emitMeshlets;
this.world = world;
this.modelManager = modelManager;
this.resultConsumer = consumer;
this.workers = new Thread[workers];
for (int i = 0; i < workers; i++) {
this.workers[i] = new Thread(this::renderWorker);
this.workers[i].setDaemon(true);
this.workers[i].setName("Render generation service #" + i);
this.workers[i].start();
this.modelBakery = modelBakery;
this.threads = serviceThreadPool.createService("Section mesh generation service", 100, ()->{
//Thread local instance of the factory
var factory = new RenderDataFactory(this.world, this.modelBakery.factory, this.emitMeshlets);
IntOpenHashSet seenMissed = new IntOpenHashSet(128);
return new Pair<>(() -> {
this.processJob(factory, seenMissed);
}, factory::free);
}, taskLimiter);
}
public void setResultConsumer(Consumer<BuiltSection> consumer) {
this.resultConsumer = consumer;
}
//NOTE: the biomes are always fully populated/kept up to date
//Asks the Model system to bake all blocks that currently dont have a model
private void computeAndRequestRequiredModels(IntOpenHashSet seenMissedIds, int bitMsk, long[] auxData) {
final var factory = this.modelBakery.factory;
for (int i = 0; i < 6; i++) {
if ((bitMsk&(1<<i))==0) continue;
for (int j = 0; j < 32*32; j++) {
int block = Mapper.getBlockId(auxData[j+(i*32*32)]);
if (block != 0 && !factory.hasModelForBlockId(block)) {
if (seenMissedIds.add(block)) {
this.modelBakery.requestBlockBake(block);
}
}
}
}
}
private void computeAndRequestRequiredModels(IntOpenHashSet seenMissedIds, WorldSection section) {
//Know this is... very much not safe, however it reduces allocation rates and other garbage, am sure its "fine"
final var factory = this.modelBakery.factory;
for (long state : section._unsafeGetRawDataArray()) {
int block = Mapper.getBlockId(state);
if (block != 0 && !factory.hasModelForBlockId(block)) {
if (seenMissedIds.add(block)) {
this.modelBakery.requestBlockBake(block);
}
}
}
}
private WorldSection acquireSection(long pos) {
return this.world.acquireIfExists(pos);
}
private static boolean putTaskFirst(long pos) {
//Level 3 or 4
return WorldEngine.getLevel(pos) > 2;
}
//TODO: add a generated render data cache
private void renderWorker() {
//Thread local instance of the factory
var factory = new RenderDataFactory(this.world, this.modelManager, this.emitMeshlets);
while (this.running) {
this.taskCounter.acquireUninterruptibly();
if (!this.running) break;
try {
BuildTask task;
synchronized (this.taskQueue) {
task = this.taskQueue.removeFirst();
private void processJob(RenderDataFactory factory, IntOpenHashSet seenMissedIds) {
BuildTask task = this.taskQueue.poll();
this.taskQueueCount.decrementAndGet();
//long time = BuiltSection.getTime();
boolean shouldFreeSection = true;
WorldSection section;
if (task.section == null) {
section = this.acquireSection(task.position);
} else {
section = task.section;
}
var section = task.sectionSupplier.get();
{//Remove the task from the map, this is done before we check for null sections as well the task map needs to be correct
long stamp = this.taskMapLock.writeLock();
var rtask = this.taskMap.remove(task.position);
if (rtask != task) {
this.taskMapLock.unlockWrite(stamp);
throw new IllegalStateException();
}
this.taskMapLock.unlockWrite(stamp);
}
if (section == null) {
continue;
if (this.resultConsumer != null) {
this.resultConsumer.accept(BuiltSection.empty(task.position));
}
return;
}
section.assertNotFree();
BuiltSection mesh = null;
try {
mesh = factory.generateMesh(section);
} catch (IdNotYetComputedException e) {
{
long stamp = this.taskMapLock.writeLock();
BuildTask other = this.taskMap.putIfAbsent(task.position, task);
this.taskMapLock.unlockWrite(stamp);
if (other != null) {//Weve been replaced
//Request the block
if (e.isIdBlockId) {
//TODO: maybe move this to _after_ task as been readded to queue??
if (!this.modelBakery.factory.hasModelForBlockId(e.id)) {
if (seenMissedIds.add(e.id)) {
this.modelBakery.requestBlockBake(e.id);
}
}
}
//Exchange info
if (task.hasDoneModelRequestInner) {
other.hasDoneModelRequestInner = true;
}
if (task.hasDoneModelRequestOuter) {
other.hasDoneModelRequestOuter = true;
}
if (task.section != null) {
this.holdingSectionCount.decrementAndGet();
}
task.section = null;
shouldFreeSection = true;
task = null;
}
}
if (task != null) {
//This is our task
//Request the block
if (e.isIdBlockId) {
//TODO: maybe move this to _after_ task as been readded to queue??
if (!this.modelBakery.factory.hasModelForBlockId(e.id)) {
if (seenMissedIds.add(e.id)) {
this.modelBakery.requestBlockBake(e.id);
}
}
}
if (task.hasDoneModelRequestOuter || task.hasDoneModelRequestInner) {
MESH_FAILED_COUNTER.incrementAndGet();
}
if (task.hasDoneModelRequestInner && task.hasDoneModelRequestOuter) {
task.attempts++;
try {
Thread.sleep(100);
Thread.sleep(1);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
//We need to reinsert the build task into the queue
//System.err.println("Render task failed to complete due to un-computed client id");
synchronized (this.taskQueue) {
this.taskQueue.computeIfAbsent(section.key, key->{this.taskCounter.release(); return task;});
} else {
if (task.hasDoneModelRequestInner) {
task.attempts++;//This is because it can be baking and just model thing isnt keeping up
}
if (!task.hasDoneModelRequestInner) {
//The reason for the extra id parameter is that we explicitly add/check against the exception id due to e.g. requesting accross a chunk boarder wont be captured in the request
if (e.auxData == null)//the null check this is because for it to be, the inner must already be computed
this.computeAndRequestRequiredModels(seenMissedIds, section);
task.hasDoneModelRequestInner = true;
}
//If this happens... aahaha painnnn
if (task.hasDoneModelRequestOuter) {
task.attempts++;
}
if ((!task.hasDoneModelRequestOuter) && e.auxData != null) {
this.computeAndRequestRequiredModels(seenMissedIds, e.auxBitMsk, e.auxData);
task.hasDoneModelRequestOuter = true;
}
task.addin = WorldEngine.getLevel(task.position)>2?1:0;//Single time addin which gives the models time to bake before the task executes
}
//Keep the lock on the section, and attach it to the task, this prevents needing to re-aquire it later
if (task.section == null) {
if (this.holdingSectionCount.get() < MAX_HOLDING_SECTION_COUNT) {
this.holdingSectionCount.incrementAndGet();
task.section = section;
shouldFreeSection = false;
}
} else {
shouldFreeSection = false;
}
task.updatePriority();
this.taskQueue.add(task);
this.taskQueueCount.incrementAndGet();
if (this.threads.isAlive()) {//Only execute if were not dead
this.threads.execute();//Since we put in queue, release permit
}
}
}
if (shouldFreeSection) {
if (task != null && task.section != null) {
this.holdingSectionCount.decrementAndGet();
}
section.release();
if (mesh != null) {
//TODO: if the mesh is null, need to clear the cache at that point
this.resultConsumer.accept(mesh.clone());
if (!this.meshCache.putMesh(mesh)) {
}
if (mesh != null) {//If the mesh is null it means it didnt finish, so dont submit
if (this.resultConsumer != null) {
this.resultConsumer.accept(mesh);
} else {
mesh.free();
}
}
} catch (Exception e) {
System.err.println(e);
MinecraftClient.getInstance().executeSync(()->MinecraftClient.getInstance().player.sendMessage(Text.literal("Voxy render service had an exception while executing please check logs and report error")));
}
}
}
public int getMeshCacheCount() {
return this.meshCache.getCount();
}
//TODO: Add a priority system, higher detail sections must always be updated before lower detail
// e.g. priorities NONE->lvl0 and lvl1 -> lvl0 over lvl0 -> lvl1
//TODO: make it pass either a world section, _or_ coodinates so that the render thread has to do the loading of the sections
// not the calling method
//TODO: maybe make it so that it pulls from the world to stop the inital loading absolutly butt spamming the queue
// and thus running out of memory
//TODO: REDO THIS ENTIRE THING
// render tasks should not be bound to a WorldSection, instead it should be bound to either a WorldSection or
// an LoD position, the issue is that if we bound to a LoD position we loose all the info of the WorldSection
// like if its in the render queue and if we should abort building the render data
//1 proposal fix is a Long2ObjectLinkedOpenHashMap<WorldSection> which means we can abort if needed,
// also gets rid of dependency on a WorldSection (kinda)
public void enqueueTask(int lvl, int x, int y, int z) {
this.enqueueTask(lvl, x, y, z, (l,x1,y1,z1)->true);
}
public void enqueueTask(int lvl, int x, int y, int z, TaskChecker checker) {
long ikey = WorldEngine.getWorldSectionId(lvl, x, y, z);
{
var cache = this.meshCache.getMesh(ikey);
if (cache != null) {
this.resultConsumer.accept(cache);
public void enqueueTask(long pos) {
if (!this.threads.isAlive()) {
return;
}
}
synchronized (this.taskQueue) {
this.taskQueue.computeIfAbsent(ikey, key->{
this.taskCounter.release();
return new BuildTask(()->{
if (checker.check(lvl, x, y, z)) {
return this.world.acquireIfExists(lvl, x, y, z);
} else {
return null;
}
});
boolean[] isOurs = new boolean[1];
long stamp = this.taskMapLock.writeLock();
BuildTask task = this.taskMap.computeIfAbsent(pos, p->{
isOurs[0] = true;
return new BuildTask(p);
});
this.taskMapLock.unlockWrite(stamp);
if (isOurs[0]) {//If its not ours we dont care about it
//Set priority and insert into queue and execute
task.updatePriority();
this.taskQueue.add(task);
this.taskQueueCount.incrementAndGet();
this.threads.execute();
}
}
//Tells the render cache that the mesh at the specified position should be cached
public void markCache(int lvl, int x, int y, int z) {
this.meshCache.markCache(WorldEngine.getWorldSectionId(lvl, x, y, z));
}
//Tells the render cache that the mesh at the specified position should not be cached/any previous cache result, freed
public void unmarkCache(int lvl, int x, int y, int z) {
this.meshCache.unmarkCache(WorldEngine.getWorldSectionId(lvl, x, y, z));
}
//Resets a chunks cache mesh
public void clearCache(int lvl, int x, int y, int z) {
this.meshCache.clearMesh(WorldEngine.getWorldSectionId(lvl, x, y, z));
}
public void removeTask(int lvl, int x, int y, int z) {
synchronized (this.taskQueue) {
if (this.taskQueue.remove(WorldEngine.getWorldSectionId(lvl, x, y, z)) != null) {
this.taskCounter.acquireUninterruptibly();
}
}
}
public int getTaskCount() {
return this.taskCounter.availablePermits();
/*
public void enqueueTask(int lvl, int x, int y, int z) {
this.enqueueTask(WorldEngine.getWorldSectionId(lvl, x, y, z));
}
*/
public void shutdown() {
boolean anyAlive = false;
for (var worker : this.workers) {
anyAlive |= worker.isAlive();
//Steal and free as much work as possible
while (this.threads.hasJobs()) {
int i = this.threads.drain();
if (i == 0) break;
{
long stamp = this.taskMapLock.writeLock();
for (int j = 0; j < i; j++) {
var task = this.taskQueue.remove();
if (task.section != null) {
task.section.release();
this.holdingSectionCount.decrementAndGet();
}
if (this.taskMap.remove(task.position) != task) {
throw new IllegalStateException();
}
}
this.taskMapLock.unlockWrite(stamp);
this.taskQueueCount.addAndGet(-i);
}
}
if (!anyAlive) {
System.err.println("Render gen workers already dead on shutdown! this is very very bad, check log for errors from this thread");
return;
}
//Since this is just render data, dont care about any tasks needing to finish
this.running = false;
this.taskCounter.release(1000);
//Wait for thread to join
try {
for (var worker : this.workers) {
worker.join();
}
} catch (InterruptedException e) {throw new RuntimeException(e);}
//Shutdown the threads
this.threads.shutdown();
//Cleanup any remaining data
while (!this.taskQueue.isEmpty()) {
this.taskQueue.removeFirst();
var task = this.taskQueue.remove();
this.taskQueueCount.decrementAndGet();
if (task.section != null) {
task.section.release();
this.holdingSectionCount.decrementAndGet();
}
this.meshCache.free();
long stamp = this.taskMapLock.writeLock();
if (this.taskMap.remove(task.position) != task) {
throw new IllegalStateException();
}
this.taskMapLock.unlockWrite(stamp);
}
if (this.taskQueueCount.get() != 0) {
throw new IllegalStateException();
}
}
private long lastChangedTime = 0;
private int failedCounter = 0;
public void addDebugData(List<String> debug) {
if (System.currentTimeMillis()-this.lastChangedTime > 1000) {
this.failedCounter = 0;
this.lastChangedTime = System.currentTimeMillis();
}
this.failedCounter += MESH_FAILED_COUNTER.getAndSet(0);
debug.add("RSSQ/TFC: " + this.taskQueueCount.get() + "/" + this.failedCounter);//render section service queue, Task Fail Counter
}
public int getTaskCount() {
return this.taskQueueCount.get();
}
}

View File

@@ -0,0 +1,85 @@
package me.cortex.voxy.client.core.rendering.hierachical;
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.Viewport;
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import net.minecraft.util.math.MathHelper;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL15;
import org.lwjgl.system.MemoryUtil;
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.GL15C.glBindBuffer;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL40.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL40.glDrawElementsIndirect;
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.*;
public class DebugRenderer {
private final Shader debugShader = Shader.make()
.add(ShaderType.VERTEX, "voxy:lod/hierarchical/debug/node_outline.vert")
.add(ShaderType.FRAGMENT, "voxy:lod/hierarchical/debug/frag.frag")
.compile();
private final Shader setupShader = Shader.make()
.add(ShaderType.COMPUTE, "voxy:lod/hierarchical/debug/setup.comp")
.compile();
private final GlBuffer uniformBuffer = new GlBuffer(1024).zero();
private final GlBuffer drawBuffer = new GlBuffer(1024).zero();
private void uploadUniform(Viewport<?> viewport) {
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 1024);
int sx = MathHelper.floor(viewport.cameraX)>>5;
int sy = MathHelper.floor(viewport.cameraY)>>5;
int sz = MathHelper.floor(viewport.cameraZ)>>5;
new Matrix4f(viewport.projection).mul(viewport.modelView).getToAddress(ptr); ptr += 4*4*4;
MemoryUtil.memPutInt(ptr, sx); ptr += 4;
MemoryUtil.memPutInt(ptr, sy); ptr += 4;
MemoryUtil.memPutInt(ptr, sz); ptr += 4;
MemoryUtil.memPutInt(ptr, viewport.width); ptr += 4;
var innerTranslation = new Vector3f((float) (viewport.cameraX-(sx<<5)), (float) (viewport.cameraY-(sy<<5)), (float) (viewport.cameraZ-(sz<<5)));
innerTranslation.getToAddress(ptr); ptr += 4*3;
MemoryUtil.memPutInt(ptr, viewport.height); ptr += 4;
}
public void render(Viewport<?> viewport, GlBuffer nodeData, GlBuffer nodeList) {
this.uploadUniform(viewport);
UploadStream.INSTANCE.commit();
this.setupShader.bind();
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, this.drawBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, nodeList.id);
glDispatchCompute(1,1,1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT|GL_COMMAND_BARRIER_BIT);
glEnable(GL_DEPTH_TEST);
this.debugShader.bind();
glBindVertexArray(GlVertexArray.STATIC_VAO);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.drawBuffer.id);
GL15.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BYTE.id());
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, nodeData.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, nodeList.id);
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_BYTE, 0);
}
public void free() {
this.drawBuffer.free();
this.uniformBuffer.free();
this.debugShader.free();
this.setupShader.free();
}
}

View File

@@ -0,0 +1,368 @@
package me.cortex.voxy.client.core.rendering.hierachical;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import me.cortex.voxy.client.RenderStatistics;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.AutoBindingShader;
import me.cortex.voxy.client.core.gl.shader.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.building.RenderGenerationService;
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.common.Logger;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.world.WorldEngine;
import org.lwjgl.system.MemoryUtil;
import static me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil.PRINTF_processor;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.GL_UNPACK_IMAGE_HEIGHT;
import static org.lwjgl.opengl.GL12.GL_UNPACK_SKIP_IMAGES;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BARRIER_BIT;
import static org.lwjgl.opengl.GL45.*;
// TODO: swap to persistent gpu threads instead of dispatching MAX_ITERATIONS of compute layers
public class HierarchicalOcclusionTraverser {
public static final boolean HIERARCHICAL_SHADER_DEBUG = System.getProperty("voxy.hierarchicalShaderDebug", "false").equals("true");
public static final int MAX_REQUEST_QUEUE_SIZE = 50;
public static final int MAX_QUEUE_SIZE = 200_000;
private static final int MAX_ITERATIONS = WorldEngine.MAX_LOD_LAYER+1;
private static final int LOCAL_WORK_SIZE_BITS = 5;
private final AsyncNodeManager nodeManager;
private final NodeCleaner nodeCleaner;
private final RenderGenerationService meshGen;
private final GlBuffer requestBuffer;
private final GlBuffer nodeBuffer;
private final GlBuffer uniformBuffer = new GlBuffer(1024).zero();
private final GlBuffer statisticsBuffer = new GlBuffer(1024).zero();
private int topNodeCount;
private final Int2IntOpenHashMap topNode2idxMapping = new Int2IntOpenHashMap();//Used to store mapping from TLN to array index
private final int[] idx2topNodeMapping = new int[MAX_QUEUE_SIZE];//Used to map idx to TLN id
private final GlBuffer topNodeIds = new GlBuffer(MAX_QUEUE_SIZE*4).zero();
private final GlBuffer queueMetaBuffer = new GlBuffer(4*4*MAX_ITERATIONS).zero();
private final GlBuffer scratchQueueA = new GlBuffer(MAX_QUEUE_SIZE*4).zero();
private final GlBuffer scratchQueueB = new GlBuffer(MAX_QUEUE_SIZE*4).zero();
private static int BINDING_COUNTER = 1;
private static final int SCENE_UNIFORM_BINDING = BINDING_COUNTER++;
private static final int REQUEST_QUEUE_BINDING = BINDING_COUNTER++;
private static final int RENDER_QUEUE_BINDING = BINDING_COUNTER++;
private static final int NODE_DATA_BINDING = BINDING_COUNTER++;
private static final int NODE_QUEUE_INDEX_BINDING = BINDING_COUNTER++;
private static final int NODE_QUEUE_META_BINDING = BINDING_COUNTER++;
private static final int NODE_QUEUE_SOURCE_BINDING = BINDING_COUNTER++;
private static final int NODE_QUEUE_SINK_BINDING = BINDING_COUNTER++;
private static final int RENDER_TRACKER_BINDING = BINDING_COUNTER++;
private static final int STATISTICS_BUFFER_BINDING = BINDING_COUNTER++;
private final int hizSampler = glGenSamplers();
private final AutoBindingShader traversal = Shader.makeAuto(PRINTF_processor)
.defineIf("DEBUG", HIERARCHICAL_SHADER_DEBUG)
.define("MAX_ITERATIONS", MAX_ITERATIONS)
.define("LOCAL_SIZE_BITS", LOCAL_WORK_SIZE_BITS)
.define("MAX_REQUEST_QUEUE_SIZE", MAX_REQUEST_QUEUE_SIZE)
.define("HIZ_BINDING", 0)
.define("SCENE_UNIFORM_BINDING", SCENE_UNIFORM_BINDING)
.define("REQUEST_QUEUE_BINDING", REQUEST_QUEUE_BINDING)
.define("RENDER_QUEUE_BINDING", RENDER_QUEUE_BINDING)
.define("NODE_DATA_BINDING", NODE_DATA_BINDING)
.define("NODE_QUEUE_INDEX_BINDING", NODE_QUEUE_INDEX_BINDING)
.define("NODE_QUEUE_META_BINDING", NODE_QUEUE_META_BINDING)
.define("NODE_QUEUE_SOURCE_BINDING", NODE_QUEUE_SOURCE_BINDING)
.define("NODE_QUEUE_SINK_BINDING", NODE_QUEUE_SINK_BINDING)
.define("RENDER_TRACKER_BINDING", RENDER_TRACKER_BINDING)
.defineIf("HAS_STATISTICS", RenderStatistics.enabled)
.defineIf("STATISTICS_BUFFER_BINDING", RenderStatistics.enabled, STATISTICS_BUFFER_BINDING)
.add(ShaderType.COMPUTE, "voxy:lod/hierarchical/traversal_dev.comp")
.compile();
public HierarchicalOcclusionTraverser(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, RenderGenerationService meshGen) {
this.nodeCleaner = nodeCleaner;
this.nodeManager = nodeManager;
this.meshGen = meshGen;
this.requestBuffer = new GlBuffer(MAX_REQUEST_QUEUE_SIZE*8L+8).zero();
this.nodeBuffer = new GlBuffer(nodeManager.maxNodeCount*16L).fill(-1);
glSamplerParameteri(this.hizSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glSamplerParameteri(this.hizSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glSamplerParameteri(this.hizSampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glSamplerParameteri(this.hizSampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
this.traversal
.ubo("SCENE_UNIFORM_BINDING", this.uniformBuffer)
.ssbo("REQUEST_QUEUE_BINDING", this.requestBuffer)
.ssbo("NODE_DATA_BINDING", this.nodeBuffer)
.ssbo("NODE_QUEUE_META_BINDING", this.queueMetaBuffer)
.ssbo("RENDER_TRACKER_BINDING", this.nodeCleaner.visibilityBuffer)
.ssboIf("STATISTICS_BUFFER_BINDING", this.statisticsBuffer);
this.topNode2idxMapping.defaultReturnValue(-1);
this.nodeManager.setTLNAddRemoveCallbacks(this::addTLN, this::remTLN);
}
private void addTLN(int id) {
int aid = this.topNodeCount++;//Increment buffer
if (this.topNodeCount > this.topNodeIds.size()/4) {
throw new IllegalStateException("Top level node count greater than capacity");
}
//Use clear buffer, yes know is a bad idea, TODO: replace
//Add the new top level node to the queue
MemoryUtil.memPutInt(SCRATCH, id);
nglClearNamedBufferSubData(this.topNodeIds.id, GL_R32UI, aid * 4L, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, SCRATCH);
if (this.topNode2idxMapping.put(id, aid) != -1) {
throw new IllegalStateException();
}
this.idx2topNodeMapping[aid] = id;
}
private void remTLN(int id) {
//Remove id
int idx = this.topNode2idxMapping.remove(id);
//Decrement count
this.topNodeCount--;
if (idx == -1) {
throw new IllegalStateException();
}
//Count has already been decremented so is an exact match
//If we are at the end of the array we dont need to do anything
if (idx == this.topNodeCount) {
return;
}
//Move the entry at the end to the current index
int endTLNId = this.idx2topNodeMapping[this.topNodeCount];
this.idx2topNodeMapping[idx] = endTLNId;//Set the old to the new
if (this.topNode2idxMapping.put(endTLNId, idx) == -1)
throw new IllegalStateException();
//Move it server side, from end to new idx
MemoryUtil.memPutInt(SCRATCH, endTLNId);
nglClearNamedBufferSubData(this.topNodeIds.id, GL_R32UI, idx*4L, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, SCRATCH);
}
private static void setFrustum(Viewport<?> viewport, long ptr) {
for (int i = 0; i < 6; i++) {
var plane = viewport.frustumPlanes[i];
plane.getToAddress(ptr); ptr += 4*4;
}
}
private void uploadUniform(Viewport<?> viewport) {
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 1024);
viewport.MVP.getToAddress(ptr); ptr += 4*4*4;
viewport.section.getToAddress(ptr); ptr += 4*3;
//MemoryUtil.memPutFloat(ptr, viewport.width); ptr += 4;
MemoryUtil.memPutInt(ptr, viewport.hiZBuffer.getPackedLevels()); ptr += 4;
viewport.innerTranslation.getToAddress(ptr); ptr += 4*3;
//MemoryUtil.memPutFloat(ptr, viewport.height); ptr += 4;
final float screenspaceAreaDecreasingSize = VoxyConfig.CONFIG.subDivisionSize*VoxyConfig.CONFIG.subDivisionSize;
//Screen space size for descending
MemoryUtil.memPutFloat(ptr, (float) (screenspaceAreaDecreasingSize) /(viewport.width*viewport.height)); ptr += 4;
setFrustum(viewport, ptr); ptr += 4*4*6;
MemoryUtil.memPutInt(ptr, (int) (viewport.getRenderList().size()/4-1)); ptr += 4;
//VisibilityId
MemoryUtil.memPutInt(ptr, this.nodeCleaner.visibilityId); ptr += 4;
{
final double TARGET_COUNT = 4000;//TODO: make this configurable, or at least dynamically computed based on throughput rate of mesh gen
double iFillness = Math.max(0, (TARGET_COUNT - this.meshGen.getTaskCount()) / TARGET_COUNT);
iFillness = Math.pow(iFillness, 2);
final int requestSize = (int) Math.ceil(iFillness * MAX_REQUEST_QUEUE_SIZE);
MemoryUtil.memPutInt(ptr, Math.max(0, Math.min(MAX_REQUEST_QUEUE_SIZE, requestSize)));ptr += 4;
}
}
private void bindings(Viewport<?> viewport) {
glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, this.queueMetaBuffer.id);
//Bind the hiz buffer
glBindTextureUnit(0, viewport.hiZBuffer.getHizTextureId());
glBindSampler(0, this.hizSampler);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, RENDER_QUEUE_BINDING, viewport.getRenderList().id);
}
public void doTraversal(Viewport<?> viewport) {
this.uploadUniform(viewport);
//UploadStream.INSTANCE.commit(); //Done inside traversal
this.traversal.bind();
this.bindings(viewport);
PrintfDebugUtil.bind();
if (RenderStatistics.enabled) {
this.statisticsBuffer.zero();
}
//Clear the render output counter
nglClearNamedBufferSubData(viewport.getRenderList().id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
//Traverse
this.traverseInternal();
this.downloadResetRequestQueue();
if (RenderStatistics.enabled) {
DownloadStream.INSTANCE.download(this.statisticsBuffer, down->{
for (int i = 0; i < MAX_ITERATIONS; i++) {
RenderStatistics.hierarchicalTraversalCounts[i] = MemoryUtil.memGetInt(down.address+i*4L);
}
for (int i = 0; i < MAX_ITERATIONS; i++) {
RenderStatistics.hierarchicalRenderSections[i] = MemoryUtil.memGetInt(down.address+MAX_ITERATIONS*4L+i*4L);
}
});
}
//Bind the hiz buffer
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
}
private void traverseInternal() {
{
//Fix mesa bug
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
}
int firstDispatchSize = (this.topNodeCount+(1<<LOCAL_WORK_SIZE_BITS)-1)>>LOCAL_WORK_SIZE_BITS;
/*
//prime the queue Todo: maybe move after the traversal? cause then it is more efficient work since it doesnt need to wait for this before starting?
glClearNamedBufferData(this.queueMetaBuffer.id, GL_RGBA32UI, GL_RGBA, GL_UNSIGNED_INT, new int[]{0,1,1,0});//Prime the metadata buffer, which also contains
//Set the first entry
glClearNamedBufferSubData(this.queueMetaBuffer.id, GL_RGBA32UI, 0, 16, GL_RGBA, GL_UNSIGNED_INT, new int[]{firstDispatchSize,1,1,initialQueueSize});
*/
{//TODO:FIXME: THIS IS BULLSHIT BY INTEL need to fix the clearing
long ptr = UploadStream.INSTANCE.upload(this.queueMetaBuffer, 0, 16*MAX_ITERATIONS);
MemoryUtil.memPutInt(ptr + 0, firstDispatchSize);
MemoryUtil.memPutInt(ptr + 4, 1);
MemoryUtil.memPutInt(ptr + 8, 1);
MemoryUtil.memPutInt(ptr + 12, this.topNodeCount);
for (int i = 1; i < MAX_ITERATIONS; i++) {
MemoryUtil.memPutInt(ptr + (i*16)+ 0, 0);
MemoryUtil.memPutInt(ptr + (i*16)+ 4, 1);
MemoryUtil.memPutInt(ptr + (i*16)+ 8, 1);
MemoryUtil.memPutInt(ptr + (i*16)+12, 0);
}
UploadStream.INSTANCE.commit();
}
//Execute first iteration
glUniform1ui(NODE_QUEUE_INDEX_BINDING, 0);
//Use the top node id buffer
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, NODE_QUEUE_SOURCE_BINDING, this.topNodeIds.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, NODE_QUEUE_SINK_BINDING, this.scratchQueueB.id);
//Dont need to use indirect to dispatch the first iteration
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT|GL_COMMAND_BARRIER_BIT|GL_BUFFER_UPDATE_BARRIER_BIT);
glDispatchCompute(firstDispatchSize, 1,1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT|GL_COMMAND_BARRIER_BIT);
//Dispatch max iterations
for (int iter = 1; iter < MAX_ITERATIONS; iter++) {
glUniform1ui(NODE_QUEUE_INDEX_BINDING, iter);
//Flipflop buffers
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, NODE_QUEUE_SOURCE_BINDING, ((iter & 1) == 0 ? this.scratchQueueA : this.scratchQueueB).id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, NODE_QUEUE_SINK_BINDING, ((iter & 1) == 0 ? this.scratchQueueB : this.scratchQueueA).id);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT);
//Dispatch and barrier
glDispatchComputeIndirect(iter * 4 * 4);
}
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT);
}
private void downloadResetRequestQueue() {
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
DownloadStream.INSTANCE.download(this.requestBuffer, this::forwardDownloadResult);
nglClearNamedBufferSubData(this.requestBuffer.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
}
private void forwardDownloadResult(long ptr, long size) {
int count = MemoryUtil.memGetInt(ptr);ptr += 8;//its 8 since we need to skip the second value (which is empty)
if (count < 0 || count > 50000) {
Logger.error(new IllegalStateException("Count unexpected extreme value: " + count + " things may get weird"));
return;
}
if (count > (this.requestBuffer.size()>>3)-1) {
//This should not break the synchonization between gpu and cpu as in the traversal shader is
// `if (atomRes < REQUEST_QUEUE_SIZE) {` which forcefully clamps to the request size
//Logger.warn("Count over max buffer size, clamping, got count: " + count + ".");
count = (int) ((this.requestBuffer.size()>>3)-1);
//Write back the clamped count
MemoryUtil.memPutInt(ptr-8, count);
}
//if (count > REQUEST_QUEUE_SIZE) {
// Logger.warn("Count larger than 'maxRequestCount', overflow captured. Overflowed by " + (count-REQUEST_QUEUE_SIZE));
//}
if (count != 0) {
this.nodeManager.submitRequestBatch(new MemoryBuffer(count*8L+8).cpyFrom(ptr-8));// the -8 is because we incremented it by 8
}
}
public GlBuffer getNodeBuffer() {
return this.nodeBuffer;
}
public void free() {
this.traversal.free();
this.requestBuffer.free();
this.nodeBuffer.free();
this.uniformBuffer.free();
this.statisticsBuffer.free();
this.queueMetaBuffer.free();
this.topNodeIds.free();
this.scratchQueueA.free();
this.scratchQueueB.free();
glDeleteSamplers(this.hizSampler);
}
private static final long SCRATCH = MemoryUtil.nmemAlloc(32);//32 bytes of scratch memory
}

View File

@@ -0,0 +1,103 @@
package me.cortex.voxy.client.core.rendering.hierachical;
class NodeChildRequest {
//Child states contain micrometadata in the top bits
// such as isEmpty, and isEmptyButEventuallyHasNonEmptyChild
private final long nodePos;
private final int[] childStates = new int[]{-1,-1,-1,-1,-1,-1,-1,-1};
private final byte[] childChildExistence = new byte[]{(byte) 0,(byte) 0,(byte) 0,(byte) 0,(byte) 0,(byte) 0,(byte) 0,(byte) 0};
private byte results;
private byte mask;
private byte existenceMask = 0;
NodeChildRequest(long nodePos) {
this.nodePos = nodePos;
}
public int getChildMesh(int childIdx) {
if ((this.mask&(1<<childIdx))==0) {
throw new IllegalStateException("Tried getting mesh result of child not in mask");
}
return this.childStates[childIdx];
}
public void setChildChildExistence(int childIdx, byte childExistence) {
if ((this.mask&(1<<childIdx))==0) {
throw new IllegalStateException("Tried setting child child existence in request when child isnt in mask");
}
this.childChildExistence[childIdx] = childExistence;
this.existenceMask |= (byte) (1<<childIdx);
}
public boolean hasChildChildExistence(int childId) {
if ((this.mask&(1<<childId))==0) {
throw new IllegalStateException("Tried getting child child existence set of child not in mask");
}
return (this.existenceMask&(1<<childId))!=0;
}
public byte getChildChildExistence(int childIdx) {
if (!this.hasChildChildExistence(childIdx)) {
throw new IllegalStateException("Tried getting child child existence when child child existence for child was not set");
}
return this.childChildExistence[childIdx];
}
public int setChildMesh(int childIdx, int mesh) {
if ((this.mask&(1<<childIdx))==0) {
throw new IllegalStateException("Tried setting child mesh when child isnt in mask");
}
//Note the mesh can be -ve meaning empty mesh, but we should still mark that node as having a result
boolean isFirstInsert = (this.results&(1<<childIdx))==0;
this.results |= (byte) (1<<childIdx);
int prev = this.childStates[childIdx];
this.childStates[childIdx] = mesh;
if (isFirstInsert) {
return -1;
} else {
return prev;
}
}
public int removeAndUnRequire(int childIdx) {
byte MSK = (byte) (1<<childIdx);
if ((this.mask&MSK)==0) {
throw new IllegalStateException("Tried removing and unmasking child that was never masked");
}
byte prev = this.results;
this.results &= (byte) ~MSK;
this.mask &= (byte) ~MSK;
this.existenceMask &= (byte) ~MSK;
int mesh = this.childStates[childIdx];
this.childStates[childIdx] = -1;
if ((prev&MSK)==0) {
return -1;
} else {
return mesh;
}
}
public void addChildRequirement(int childIdx) {
byte MSK = (byte) (1<<childIdx);
if ((this.mask&MSK)!=0) {
throw new IllegalStateException("Child already required!");
}
this.mask |= MSK;
}
public boolean isSatisfied() {
return (this.results&this.mask)==this.mask;
}
public long getPosition() {
return this.nodePos;
}
public byte getMsk() {
return this.mask;
}
}

View File

@@ -0,0 +1,192 @@
package me.cortex.voxy.client.core.rendering.hierachical;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.AutoBindingShader;
import me.cortex.voxy.client.core.gl.shader.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.PrintfDebugUtil;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import org.lwjgl.opengl.ARBDirectStateAccess;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.GL30C.glBindBufferRange;
import static org.lwjgl.opengl.GL42C.glMemoryBarrier;
import static org.lwjgl.opengl.GL43C.*;
//Uses compute shaders to compute the last 256 rendered section (64x64 workgroup size maybe)
// done via warp level sort, then workgroup sort (shared memory), (/w sorting network)
// then use bubble sort (/w fast path going to middle or 2 subdivisions deep) the bubble it up
// can do incremental sorting pass aswell, so only scan and sort a rolling sector of sections
// (over a few frames to not cause lag, maybe)
//TODO : USE THIS IN HierarchicalOcclusionTraverser instead of other shit
public class NodeCleaner {
//TODO: use batch_visibility_set to clear visibility data when nodes are removed!! (TODO: nodeManager will need to forward info to this)
private static final int SORTING_WORKER_SIZE = 64;
private static final int WORK_PER_THREAD = 8;
static final int OUTPUT_COUNT = 256;
private final AutoBindingShader sorter = Shader.makeAuto(PrintfDebugUtil.PRINTF_processor)
.define("WORK_SIZE", SORTING_WORKER_SIZE)
.define("ELEMS_PER_THREAD", WORK_PER_THREAD)
.define("OUTPUT_SIZE", OUTPUT_COUNT)
.define("VISIBILITY_BUFFER_BINDING", 1)
.define("OUTPUT_BUFFER_BINDING", 2)
.define("NODE_DATA_BINDING", 3)
.add(ShaderType.COMPUTE, "voxy:lod/hierarchical/cleaner/sort_visibility.comp")
.compile();
private final AutoBindingShader resultTransformer = Shader.makeAuto()
.define("OUTPUT_SIZE", OUTPUT_COUNT)
.define("MIN_ID_BUFFER_BINDING", 0)
.define("NODE_BUFFER_BINDING", 1)
.define("OUTPUT_BUFFER_BINDING", 2)
.define("VISIBILITY_BUFFER_BINDING", 3)
.add(ShaderType.COMPUTE, "voxy:lod/hierarchical/cleaner/result_transformer.comp")
.compile();
private final AutoBindingShader batchClear = Shader.makeAuto()
.define("VISIBILITY_BUFFER_BINDING", 0)
.define("LIST_BUFFER_BINDING", 1)
.add(ShaderType.COMPUTE, "voxy:lod/hierarchical/cleaner/batch_visibility_set.comp")
.compile();
final GlBuffer visibilityBuffer;
private final GlBuffer outputBuffer = new GlBuffer(OUTPUT_COUNT*4+OUTPUT_COUNT*8);//Scratch + output
private final AsyncNodeManager nodeManager;
int visibilityId = 0;
public NodeCleaner(AsyncNodeManager nodeManager) {
this.nodeManager = nodeManager;
this.visibilityBuffer = new GlBuffer(nodeManager.maxNodeCount*4L).zero();
this.visibilityBuffer.fill(-1);
this.batchClear
.ssbo("VISIBILITY_BUFFER_BINDING", this.visibilityBuffer);
this.sorter
.ssbo("VISIBILITY_BUFFER_BINDING", this.visibilityBuffer)
.ssbo("OUTPUT_BUFFER_BINDING", this.outputBuffer);
/*
this.nodeManager.setClear(new NodeManager.ICleaner() {
@Override
public void alloc(int id) {
NodeCleaner.this.allocIds.add(id);
NodeCleaner.this.freeIds.remove(id);
}
@Override
public void move(int from, int to) {
NodeCleaner.this.allocIds.remove(to);
glCopyNamedBufferSubData(NodeCleaner.this.visibilityBuffer.id, NodeCleaner.this.visibilityBuffer.id, 4L*from, 4L*to, 4);
}
@Override
public void free(int id) {
NodeCleaner.this.freeIds.add(id);
NodeCleaner.this.allocIds.remove(id);
}
});
*/
}
public void tick(GlBuffer nodeDataBuffer) {
this.visibilityId++;
if (this.shouldCleanGeometry()) {
this.outputBuffer.fill(this.nodeManager.maxNodeCount - 2);//TODO: maybe dont set to zero??
this.sorter.bind();
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, nodeDataBuffer.id);
//TODO: choose whether this is in nodeSpace or section/geometryId space
//
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
//This should (IN THEORY naturally align its self to the pow2 max boarder, if not... well undefined behavior is ok right?)
glDispatchCompute((this.nodeManager.getCurrentMaxNodeId() + (SORTING_WORKER_SIZE*WORK_PER_THREAD) - 1) / (SORTING_WORKER_SIZE*WORK_PER_THREAD), 1, 1);
this.resultTransformer.bind();
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, this.outputBuffer.id, 0, 4 * OUTPUT_COUNT);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, nodeDataBuffer.id);
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 2, this.outputBuffer.id, 4 * OUTPUT_COUNT, 8 * OUTPUT_COUNT);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.visibilityBuffer.id);
glUniform1ui(0, this.visibilityId);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
DownloadStream.INSTANCE.download(this.outputBuffer, 4 * OUTPUT_COUNT, 8 * OUTPUT_COUNT,
buffer -> this.nodeManager.submitRemoveBatch(buffer.copy())//Copy into buffer and emit to node manager
);
}
}
private boolean shouldCleanGeometry() {
if (false) {
//If used more than 75% of geometry buffer
long used = this.nodeManager.getUsedGeometryCapacity();
return 3 < ((double) used) / ((double) (this.nodeManager.getGeometryCapacity() - used));
} else {
long remaining = this.nodeManager.getGeometryCapacity() - this.nodeManager.getUsedGeometryCapacity();
return remaining < 256_000_000;//If less than 256 mb free memory
}
}
public void updateIds(IntOpenHashSet collection) {
if (!collection.isEmpty()) {
int count = collection.size();
long addr = UploadStream.INSTANCE.rawUploadAddress(count * 4 + 16);//TODO ensure alignment, create method todo alignment things
addr = (addr+15)&~15L;//Align to 16 bytes
long ptr = UploadStream.INSTANCE.getBaseAddress() + addr;
var iter = collection.iterator();
while (iter.hasNext()) {
MemoryUtil.memPutInt(ptr, iter.nextInt()); ptr+=4;
}
UploadStream.INSTANCE.commit();
this.batchClear.bind();
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 1, UploadStream.INSTANCE.getRawBufferId(), addr, count*4L);
glUniform1ui(0, count);
glUniform1ui(1, this.visibilityId);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
glDispatchCompute((count+127)/128, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
}
}
private void dumpDebugData() {
int[] outData = new int[OUTPUT_COUNT*3];
ARBDirectStateAccess.glGetNamedBufferSubData(this.outputBuffer.id, 0, outData);
for(int i =0;i < OUTPUT_COUNT; i++) {
System.out.println(outData[i]);
}
/*
System.out.println("---------------\n");
for(int i =0;i < OUTPUT_COUNT; i++) {
System.out.println(data[i*2+OUTPUT_COUNT]+", "+data[i*2+OUTPUT_COUNT+1]);
}*/
int[] visData = new int[(int) (this.visibilityBuffer.size()/4)];
ARBDirectStateAccess.glGetNamedBufferSubData(this.visibilityBuffer.id, 0, visData);
int a = 0;
}
public void free() {
this.sorter.free();
this.visibilityBuffer.free();
this.outputBuffer.free();
this.batchClear.free();
this.resultTransformer.free();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,312 @@
package me.cortex.voxy.client.core.rendering.hierachical;
import me.cortex.voxy.common.util.HierarchicalBitSet;
import org.lwjgl.system.MemoryUtil;
public final class NodeStore {
public static final int EMPTY_GEOMETRY_ID = -1;
public static final int NODE_ID_MSK = ((1<<24)-1);
public static final int REQUEST_ID_MSK = ((1<<16)-1);
public static final int GEOMETRY_ID_MSK = (1<<24)-1;
public static final int MAX_GEOMETRY_ID = (1<<24)-3;
private static final int SENTINEL_NULL_GEOMETRY_ID = (1<<24)-1;
private static final int SENTINEL_EMPTY_GEOMETRY_ID = (1<<24)-2;
private static final int SENTINEL_NULL_NODE_ID = NODE_ID_MSK;
private static final int LONGS_PER_NODE = 4;
private static final int INCREMENT_SIZE = 1<<16;
private final HierarchicalBitSet allocationSet;
private long[] localNodeData;
public NodeStore(int maxNodeCount) {
if (maxNodeCount>=SENTINEL_NULL_NODE_ID) {
throw new IllegalArgumentException("Max count too large");
}
//Initial count is 1024
this.localNodeData = new long[INCREMENT_SIZE*LONGS_PER_NODE];
this.allocationSet = new HierarchicalBitSet(maxNodeCount);
}
private static int id2idx(int idx) {
return idx*LONGS_PER_NODE;
}
public int allocate() {
int id = this.allocationSet.allocateNext();
if (id < 0) {
throw new IllegalStateException("Failed to allocate node slot!");
}
this.ensureSized(id);
this.clear(id);
return id;
}
public int allocate(int count) {
if (count <= 0) {
throw new IllegalArgumentException("Count cannot be <= 0 was " + count);
}
int id = this.allocationSet.allocateNextConsecutiveCounted(count);
if (id < 0) {
throw new IllegalStateException("Failed to allocate " + count + " consecutive nodes!!");
}
this.ensureSized(id + count);
for (int i = 0; i < count; i++) {
this.clear(id + i);
}
return id;
}
//Ensures that index is within the array, if not, resizes to contain it + buffer zone
private void ensureSized(int index) {
if (index*LONGS_PER_NODE >= this.localNodeData.length) {
int newSize = Math.min((index+INCREMENT_SIZE), this.allocationSet.getLimit());
long[] newStore = new long[newSize * LONGS_PER_NODE];
System.arraycopy(this.localNodeData, 0, newStore, 0, this.localNodeData.length);
this.localNodeData = newStore;
}
}
public void free(int nodeId) {
this.free(nodeId, 1);
}
public void free(int baseNodeId, int count) {
for (int i = 0; i < count; i++) {
int nodeId = baseNodeId + i;
if (!this.allocationSet.free(nodeId)) {//TODO: add batch free
throw new IllegalStateException("Node " + nodeId + " was not allocated!");
}
this.clear(nodeId);
}
}
private void clear(int nodeId) {
int idx = id2idx(nodeId);
this.localNodeData[idx] = -1;//Position
this.localNodeData[idx+1] = GEOMETRY_ID_MSK|(((long)NODE_ID_MSK)<<24);
this.localNodeData[idx+2] = REQUEST_ID_MSK;
this.localNodeData[idx+3] = 0;
}
//Copy from allocated index A to allocated index B
public void copyNode(int fromId, int toId) {
if (!(this.allocationSet.isSet(fromId)&&this.allocationSet.isSet(toId))) {
throw new IllegalArgumentException();
}
int f = id2idx(fromId);
int t = id2idx(toId);
this.localNodeData[t ] = this.localNodeData[f ];
this.localNodeData[t+1] = this.localNodeData[f+1];
this.localNodeData[t+2] = this.localNodeData[f+2];
this.localNodeData[t+3] = this.localNodeData[f+3];
}
public void setNodePosition(int node, long position) {
this.localNodeData[id2idx(node)] = position;
}
public long nodePosition(int nodeId) {
return this.localNodeData[id2idx(nodeId)];
}
public boolean nodeExists(int nodeId) {
return this.allocationSet.isSet(nodeId);
}
public int getNodeGeometry(int node) {
long data = this.localNodeData[id2idx(node)+1];
int geometryPtr = (int) (data&GEOMETRY_ID_MSK);
if (geometryPtr == SENTINEL_NULL_GEOMETRY_ID) {
return -1;
}
if (geometryPtr == SENTINEL_EMPTY_GEOMETRY_ID) {
return -2;
}
return geometryPtr;
}
public void setNodeGeometry(int node, int geometryId) {
if (geometryId>MAX_GEOMETRY_ID) {
throw new IllegalArgumentException("Geometry ptr greater than MAX_GEOMETRY_ID: " + geometryId);
}
if (geometryId == -1) {
geometryId = SENTINEL_NULL_GEOMETRY_ID;
}
if (geometryId == -2) {
geometryId = SENTINEL_EMPTY_GEOMETRY_ID;
}
if (geometryId < 0) {
throw new IllegalArgumentException("Geometry ptr less than -1 : " + geometryId);
}
int idx = id2idx(node)+1;
long data = this.localNodeData[idx];
data &= ~GEOMETRY_ID_MSK;
data |= geometryId;
this.localNodeData[idx] = data;
}
public int getChildPtr(int nodeId) {
long data = this.localNodeData[id2idx(nodeId)+1];
int nodePtr = (int) ((data>>24)&NODE_ID_MSK);
if (nodePtr == SENTINEL_NULL_NODE_ID) {
return -1;
}
return nodePtr;
}
public void setChildPtr(int nodeId, int ptr) {
if (ptr>=NODE_ID_MSK || ptr<-1) {
throw new IllegalArgumentException("Node child ptr greater GEQ NODE_ID_MSK or less than -1 : " + ptr);
}
if (ptr == -1) {
ptr = SENTINEL_NULL_NODE_ID;
}
int idx = id2idx(nodeId)+1;
long data = this.localNodeData[idx];
data &= ~(((long)NODE_ID_MSK)<<24);
data |= ((long)ptr)<<24;
this.localNodeData[idx] = data;
}
public void setNodeRequest(int node, int requestId) {
int id = id2idx(node)+2;
long data = this.localNodeData[id];
data &= ~REQUEST_ID_MSK;
data |= requestId;
this.localNodeData[id] = data;
}
public int getNodeRequest(int node) {
return (int) (this.localNodeData[id2idx(node)+2]&REQUEST_ID_MSK);
}
public void markRequestInFlight(int nodeId) {
this.localNodeData[id2idx(nodeId)+1] |= 1L<<63;
}
public void unmarkRequestInFlight(int nodeId) {
this.localNodeData[id2idx(nodeId)+1] &= ~(1L<<63);
}
public boolean isNodeRequestInFlight(int nodeId) {
return ((this.localNodeData[id2idx(nodeId)+1]>>63)&1)!=0;
}
//TODO: Implement this in node manager
public void setAllChildrenAreLeaf(int nodeId, boolean state) {
this.localNodeData[id2idx(nodeId)+2] &= ~(1L<<16);
this.localNodeData[id2idx(nodeId)+2] |= state?1L<<16:0;
}
public boolean getAllChildrenAreLeaf(int nodeId) {
return ((this.localNodeData[id2idx(nodeId)+2]>>16)&1)!=0;
}
public void markNodeGeometryInFlight(int nodeId) {
this.localNodeData[id2idx(nodeId)+1] |= 1L<<59;
}
public void unmarkNodeGeometryInFlight(int nodeId) {
this.localNodeData[id2idx(nodeId)+1] &= ~(1L<<59);
}
public boolean isNodeGeometryInFlight(int nodeId) {
return (this.localNodeData[id2idx(nodeId)+1]&(1L<<59))!=0;
}
public int getNodeType(int nodeId) {
return (int)((this.localNodeData[id2idx(nodeId)+1]>>61)&3)<<30;
}
public void setNodeType(int nodeId, int type) {
type >>>= 30;
int idx = id2idx(nodeId)+1;
long data = this.localNodeData[idx];
data &= ~(3L<<61);
data |= ((long)type)<<61;
this.localNodeData[idx] = data;
}
public byte getNodeChildExistence(int nodeId) {
long data = this.localNodeData[id2idx(nodeId)+1];
return (byte) ((data>>48)&0xFF);
}
public void setNodeChildExistence(int nodeId, byte existence) {
int idx = id2idx(nodeId)+1;
long data = this.localNodeData[idx];
data &= ~(0xFFL<<48);
data |= Byte.toUnsignedLong(existence)<<48;
this.localNodeData[idx] = data;
}
public int getChildPtrCount(int nodeId) {
long data = this.localNodeData[id2idx(nodeId)+1];
return ((int)((data>>56)&0x7))+1;
}
public void setChildPtrCount(int nodeId, int count) {
if (count <= 0 || count>8) throw new IllegalArgumentException("Count: " + count);
int idx = id2idx(nodeId)+1;
long data = this.localNodeData[idx];
data &= ~(7L<<56);
data |= ((long) (count - 1)) <<56;
this.localNodeData[idx] = data;
}
//Writes out a nodes data to the ptr in the compacted/reduced format
public void writeNode(long ptr, int nodeId) {
if (!this.nodeExists(nodeId)) {
MemoryUtil.memPutLong(ptr, -1);
MemoryUtil.memPutLong(ptr + 8, -1);
return;
}
long pos = this.nodePosition(nodeId);
MemoryUtil.memPutInt(ptr, (int) (pos>>32)); ptr += 4;
MemoryUtil.memPutInt(ptr, (int) pos); ptr += 4;
int z = 0;
int w = 0;
short flags = 0;
flags |= (short) (this.isNodeRequestInFlight(nodeId)?1:0);//1 bit
flags |= (short) ((this.getChildPtrCount(nodeId)-1)<<2);//3 bit
boolean isEligibleForCleaning = false;
isEligibleForCleaning |= this.getAllChildrenAreLeaf(nodeId);
//isEligibleForCleaning |= this.getNodeType()
flags |= (short) (isEligibleForCleaning?1<<5:0);//1 bit
{
int geometry = this.getNodeGeometry(nodeId);
if (geometry == -2) {
z |= 0xFFFFFF-1;//This is a special case, which basically says to the renderer that the geometry is empty (not that it doesnt exist)
} else if (geometry == -1) {
z |= 0xFFFFFF;//Special case null
} else {
z |= geometry&0xFFFFFF;//TODO: check and ensure bounds
}
}
int childPtr = this.getChildPtr(nodeId);
//TODO: check and ensure bounds
w |= childPtr&0xFFFFFF;
z |= (flags&0xFF)<<24;
w |= ((flags>>8)&0xFF)<<24;
MemoryUtil.memPutInt(ptr, z); ptr += 4;
MemoryUtil.memPutInt(ptr, w); ptr += 4;
}
public int getEndNodeId() {
return this.allocationSet.getMaxIndex();
}
public int getNodeCount() {
return this.allocationSet.getCount();
}
}

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