Compare commits

...
Author SHA1 Message Date
Jason Mitchell e9257b8739 Fixes (#1251) 2026-01-10 15:20:19 -08:00
Jason Mitchell 3984225faa Better OSX J8 Support (#1248) 2026-01-09 12:58:42 -08:00
Jason Mitchell 52db6b56d6 Fix GLSL 120 support (#1243) 2026-01-08 21:34:40 -08:00
Jason Mitchell 34bfe513f1 Maybe less deadlock-y (#1241) 2026-01-08 20:53:32 -08:00
Jason Mitchell 42d7ed58aa 2.0.0 Alpha1 Fixes (#1240) 2026-01-08 19:26:56 -08:00
Jason Mitchell 9a4f0f9b99 Adapt some Beddium features & add FPS Cap info 2026-01-07 13:22:16 -08:00
Jason Mitchell be90a01725 Angelica 2.0
* GLStateManagerService & RenderSystemService
* Wire up Celeritas
* Iris working
* Dynamic lights working
* Goodbye Sodium 0.2
* Add Multi-Threading for check meshing again
2026-01-07 13:22:16 -08:00
Jason Mitchell e5e8250d44 JVMDG 2026-01-07 13:22:16 -08:00
Wilhelm Schuster 062a03ffc2 Add more menu backgrounds (#1225) 2026-01-07 12:46:33 -08:00
Jason Mitchell 9f5afaaec3 Better Splash support (#1220) 2026-01-05 10:24:12 -08:00
Death Fuel b05f17a477 Disable custom fonts with GraphicsEnvironment.isHeadless() (#1218)
* disable custom fonts with GraphicsEnvironment.isHeadless()
2026-01-04 10:19:17 -08:00
Death Fuel 57caf08212 remove a horizontal shift (#1216) 2026-01-03 13:34:23 -08:00
Jason Mitchell 085ec79c21 Set defaults once per TE pass instead of once per TE (#1213) 2026-01-03 12:46:40 -08:00
Wilhelm Schuster 38641268ae Random menu backgrounds (#1217) 2026-01-03 12:01:48 -08:00
Jason Mitchell fe8e723638 Update 000-report-bug.yml 2025-12-31 22:35:40 -08:00
Jason Mitchell 16debfd518 Move EntityID shader mixin to shaders, and optimize (#1208) 2025-12-29 18:23:48 -08:00
Jason Mitchell 8cfc3e1f4a Why were these bypassing GLSM? (#1207) 2025-12-28 22:42:39 -05:00
Eclipse-Sol 2c808fae43 Update entity.properties formatting (#1197)
* Update entity.properties formatting

1.7.10 doesn't have namespaces like modern does, this code updates this so that Angelica can handle names such as XPOrb or Creeper.

* Allows shaders to target Lightning Bolts

Lightning bolts within 1.7.10 are not registered, this allows shader devs to target the lightning bolt like it does in modern.

* Additional fixes

* Adds back getEntityId as a fallback
2025-12-27 09:04:37 -08:00
Eclipse-Sol 1649c3c0c6 Adds entity_flame ID to Angelic (#1202) 2025-12-26 13:05:08 -08:00
Jason Mitchell b84286ba3b Nvidia test fix (#1201)
confirmed working in #meta-dev (nvidia/windows 11)
2025-12-25 13:36:19 -08:00
Jason Mitchell ee6c79802a Display List Overhaul (#1200) 2025-12-24 20:30:48 -08:00
Jason Mitchell d3366ad31a Update Buildscript (#1199) 2025-12-23 19:56:48 -08:00
Jason Mitchell 92b6dcc010 Texture cache fixes (#1198) 2025-12-23 17:38:52 -08:00
Jason Mitchell 89132f3ef9 Parallel shader transformation and use Transformer instead of Utils (#1191) 2025-12-23 09:11:32 -08:00
Eclipse-Sol f0d45b0d62 Fix metadata parsing by removing space in split (#1196) 2025-12-23 01:53:31 +00:00
Jason Mitchell 173d986a5b Add a test for the server side texture cache fix (#1194) 2025-12-21 12:14:53 -08:00
mist475 473a690054 fix #1190: remove remap = false from GuiIngameForge (#1192) 2025-12-21 10:34:01 -08:00
sisyphussy 2515b3520f Always delete texture parameter cache (#1193) 2025-12-21 10:33:44 -08:00
glowredman a32f9ca244 Only catch Exceptions not Errors (#1186) 2025-12-21 14:14:50 +01:00
Jason Mitchell 941fc88484 Shader boot fix + more DSA (#1184) 2025-12-16 16:54:50 -08:00
Creeper-banner 4106dd3865 Fix broken text rendering after certain characters (#1178)
* Font render fix

* re-fix

* Update fix for consistency
2025-12-14 07:10:12 -08:00
Jason Mitchell 61f6aa19a8 glDrawBuffers Support 2025-12-09 18:46:47 -08:00
Jason Mitchell 77875f7f91 Stop recording normals... we're already baking them into a VBO 2025-12-08 15:28:07 -08:00
Jason Mitchell 241c32def1 Add some transformer exclusions 2025-12-08 14:57:27 -08:00
sisyphussy a8107f97e8 Add VAO's (#1126) 2025-12-07 16:51:32 -08:00
Death FuelandJason Mitchell 8bec433699 GT5 custom glyph handling and visual fixes (#1148)
Co-authored-by: Jason Mitchell <[email protected]>
2025-12-06 09:48:03 -08:00
Kotmatross 2d39fb2397 [BCF] Fix z-fighting on GT ores 2: here we go again edition (#1161) 2025-12-06 08:17:26 -08:00
Jason Mitchell cfd8eb1411 fix compatability profile detection (#1159) 2025-12-05 17:01:20 -08:00
Jason Mitchell 59c270a5b9 glFrontFace, glHint, and glBlendColor (#1156) 2025-12-05 16:11:04 -08:00
Jason Mitchell 87ac03281c Add clear depth in display lists 2025-12-04 09:21:22 -08:00
Jason Mitchell 36b82fa2ce Update README.MD 2025-12-03 16:01:26 -08:00
Jason Mitchell 2fc2e2e38d Fix some small things
* Bump nhlib dep due to crash when Iris is disabled
* Use jabel for tests
* use joml isIdentity
* remove Integer usage
2025-12-03 10:54:20 -08:00
Jason Mitchell 33de1de9c4 Add display list compilation/optimization (#1141) 2025-12-02 21:43:17 -08:00
Jason Mitchell 55811b9d7b Set some defaults for tile entity rendering: (#1144) 2025-12-02 09:54:48 -08:00
Alexdoru a10d92f52e [Memory-opti:fix leak] Fix world leak in MixinRenderBlockFluid (#1131) 2025-11-30 10:26:49 -08:00
Christoffer Hjalmarsson 7d2fe12ce9 Fix PBR atlas failing to load all textures (#1132) 2025-11-30 10:26:09 -08:00
Death Fuel 006ded801e Font config UI improvements (#1128) 2025-11-30 10:25:21 -08:00
Alexdoru 03afd6939e Revert "limit the amount of dropped items rendered exactly to the config value (#973)" (#1125)
This reverts commit 891715b113.
2025-11-19 19:41:29 +01:00
sisyphussy adc5421e00 Optimize CloudRenderer (#1123) 2025-11-18 09:38:27 -08:00
sisyphussy dfe60e4b3e Optimize hud caching (#1103) 2025-11-17 19:52:23 +00:00
MalTeeez 72c6cdfa60 Disable conflicting mixins when DragonAPI is present (#1119)
* Exclude dapi from problematic mixins

* Don't force hodgepodge at runtime
2025-11-15 18:27:44 -05:00
Nikita Manchuk c5daea86f8 Fix: entity/item textures and lighting break with shaders when name tags render (batched font GL state) (#1091) 2025-11-10 08:57:13 -08:00
Kotmatross f8958ee990 [BCF] Fix z-fighting on GT ores (#1113) 2025-11-09 16:19:04 -08:00
sisyphussy 073260060b Fix GLSM display list (#1110) 2025-11-08 19:20:20 -08:00
Jason Mitchell 771fee124d Fall back to display lists if not using VBOs 2025-11-08 13:05:37 -08:00
NealDeal34 680285bf54 Update zh_CN.lang (#1036) 2025-11-08 10:19:48 -08:00
Tomasz Sobczyk df8784536e Use separate queue and workers for chunk renders that can block and wait for the main thread. Increase number of renders done on main thread to 16. (#1084) 2025-11-07 15:11:24 -08:00
KotmatrossandJason Mitchell c8991d810c Port FIX_BLOCK_CRACK from FalseTweaks 2 (#1023)
Co-authored-by: Jason Mitchell <[email protected]>
2025-11-07 15:07:04 -08:00
Alexdoru e3b02642ac split AngelicaRedirector from SodiumBlockTransform (#1018) 2025-11-07 14:49:46 -08:00
ALongStringOfNumbers 2759dd6689 Increase Scroll Speed in Settings GUIs (#1106) 2025-11-07 14:48:09 -08:00
Kotmatross 996dbaef2c [Shaders] Basic compatibility with NTM:Space skybox & more (#1096) 2025-11-07 14:44:38 -08:00
Jason Mitchell 72ca675c93 Uniform Additions and fixes (#1102) 2025-11-07 14:39:10 -08:00
sisyphussy e6e230e270 Add more VAO tests (#1093) 2025-11-07 19:42:05 +00:00
ALongStringOfNumbers 43d7282ba5 Set maxShiftBy to fix some log warnings (#1105) 2025-11-07 17:47:16 +00:00
ah-OOG-ah 4b2d57a32d Update model API (#842)
* Bump GTNHLib

* Change test block's properties

This makes it easier to test the block overlay

* Update VanillaModels.java

# Conflicts:
#	src/main/java/com/gtnewhorizons/angelica/models/VanillaModels.java

* Revert rename

* Rename models

* Sledgehammer out removed abstractions

* Switch to ModelISBRH

* Replace old models

With a single new and interesting model!

* Adapt to GTNHLib

* Remove dead code

* Fix JSON model detection + remove dead code

* Remove direct model renderer

* yet more yote

* Remove ColorABGR

* Remove unused line methods

* Yeet color fully

* publicize nextLong

Technically you don't *need* this on Java 8, but it silences a warning and doesn't break anything so 🤷

* yoten

* Register Angelica for automatic texture loading

* Remove TX-Loader dep

* Make the list mutable

* Remove rebase mistake

* Bump BS + deps
2025-11-06 17:40:45 +00:00
Darren Eberly 06bf71126f Add several missing built-in uniforms (#1101) 2025-11-05 15:38:57 -08:00
Darren Eberly 4c0f2f2685 Disable hardcoded custom uniforms by default 2025-11-05 12:25:18 -08:00
Jason Mitchell 78334717e7 Fix TAA:
* Return an immutable view of the underlying camera position to avoid mutating it
* Update Camera Uniforms to utilize the view instead of mutating in place
2025-11-05 09:59:47 -08:00
MalTeeez a77765a2c9 Allow avoiding other mods spash screen handlers (#1080) 2025-10-31 16:06:28 -07:00
sisyphussy 1a9fe45ee9 Add VAO test (#1089) 2025-10-31 10:10:01 -07:00
Jason Mitchell 21ef5905c8 Roll back some of the hacks (#1085) 2025-10-30 20:50:43 -07:00
ABKQPO 5b67eaa474 Quick fix PBR Resourcepack loader error (#1043) 2025-10-29 08:01:52 -07:00
SuperSoupr c9d59c3b90 Allow overriding of blend function in batching font renderer (#1075) 2025-10-29 07:59:27 -07:00
Death Fuel fa7ff74fec make an exception for the splash screen (#1069) 2025-10-29 07:57:41 -07:00
Kotmatross a34a3ebd39 Fix missing depth buffer writing before GUI rendering (#1073) 2025-10-29 07:56:45 -07:00
Nikita Manchuk 2e7476d287 render: set frustum position before BE culling (#1077) 2025-10-29 07:23:25 -07:00
Death Fuel 3244071012 font shader changes (#1071) 2025-10-24 14:44:32 -07:00
DarkShadow44 7035dbde02 Rework ANGELICA_VERSION into an integer (#873) 2025-10-22 01:19:15 +00:00
sisyphussy 97ae04c2fa Fix CME (#1008) 2025-10-20 14:38:30 -07:00
sisyphussy e7fa7f62d0 Fix framebuffer depth not clearing (#1051) 2025-10-20 14:36:02 -07:00
Wilhelm Schuster ea39712749 Add null check for zoom reset (#1061) 2025-10-20 14:35:29 -07:00
Death Fuel 66dd3eaa94 Custom/Smooth Font follow-up (#1040) 2025-10-20 14:35:06 -07:00
ah-OOG-ah 270697cd10 Refactor vendor detection (#1049)
* Refactor vendor detection

Only one vendor can be active at any given time, so make it an enum.

* lowercase vendor string
2025-10-13 15:48:21 -07:00
sisyphussy 2b53a851ef Fix intel build tests (#1048) 2025-10-13 10:31:48 -07:00
Jason Mitchell 34b6efde7b Maybe fix TAA (#1047) 2025-10-12 17:40:41 -07:00
Jason Mitchell 86edb721da GLSM updates (#1046) 2025-10-12 14:51:56 -07:00
Kotmatross 39c5f4bf7b Fix missing GL20 in cstPoolParser (#1042) 2025-10-09 12:26:55 -04:00
Death Fuel 5dc54bb1da remove implicit float cast (#1037) 2025-10-05 05:03:50 -07:00
Death Fuel 7924769ab1 Custom/Smooth Font (#1017) 2025-10-04 11:20:38 -07:00
sisyphussy 8afe2e2ccf Optimize in-world item rendering (#1034) 2025-10-01 09:05:06 -07:00
Wilhelm Schuster e8dd60e088 Check if module is enabled before resetting zoom (#1032) 2025-09-28 09:01:23 -07:00
Kotmatross 9a621405ba Fix Tileentities not casting shadows (#1024) 2025-09-26 12:04:46 -07:00
slprime 45a677030d Disabled force unicode (#1029) 2025-09-26 09:59:08 -07:00
Wilhelm Schuster 4d531022d2 Improve zoom key handling (reset on GUI enter and compatibility with mouse buttons) (#1016) 2025-09-26 09:41:08 -07:00
Pedro Ortiz Bledow 48ba2160ad Fix fast clouds rendering with setting off after game restart (#1028) 2025-09-26 13:04:47 +02:00
Eldrinn-Elantey 334ec81f9b Font rendering: configurable shadow & glyph fix (#1011) 2025-09-03 08:53:39 -07:00
Alexdoru 86c726e50b Revert "Port FIX_BLOCK_CRACK from FalseTweaks (#999)" (#1005)
This reverts commit dce5013ffb.
2025-08-16 19:09:18 -07:00
AlexdoruandJason Mitchell e6d778dad3 ASM Housekeeping (#1003)
Co-authored-by: Jason Mitchell <[email protected]>
2025-08-16 17:29:50 -07:00
Kotmatross dce5013ffb Port FIX_BLOCK_CRACK from FalseTweaks (#999) 2025-08-16 22:10:55 +00:00
glowredman 6eef63619c Update BatchingFontRenderer.java (#1002) 2025-08-10 22:27:29 +02:00
Alexdoru a8153fa0f7 remove access transformer to invoke EntityRenderer#getNightVisionBrightness (#1001) 2025-08-10 16:31:30 +00:00
Niclas Klugmann 49c04cf776 German translation (#997) 2025-07-27 20:02:26 +07:00
Alexdoru 85b0277a66 use EnumChatFormatting and not Realms enum to avoid crash (#996) 2025-07-21 10:59:04 +02:00
Alexdoru 0525b31262 Cache Tessellator#instance in local var (#995) 2025-07-18 12:44:03 -07:00
boubou19 fdb2053c50 Speed up BlockError event registration (#993) 2025-07-17 20:38:13 +02:00
Alexdoru 31abbcce32 Convert to new IMixins and MixinsBuilder system (#991) 2025-07-15 08:54:06 -07:00
063b2242e8 Initial FluidLogged compat (#985)
* Initial FluidLogged compat

* FluidLogged works(probably mostly)

* Make FluidLogged optional

* Remove a leftover print

* Fix fluidlogged fluids rendering incorrectly with shaders on

* Rmemove unused code

* Cleanup unused imports

---------

Co-authored-by: embeddedt <[email protected]>
Co-authored-by: Jason Mitchell <[email protected]>
2025-07-09 10:31:45 -04:00
Rad 577f905dc5 Update README.MD (#981) 2025-07-06 13:56:20 -07:00
Jason MitchellandAlexdoru 071d2d4a39 Add a guard to only init once (#984)
Co-authored-by: Alexdoru <[email protected]>
2025-07-01 10:33:49 -07:00
Alexdoru 891715b113 limit the amount of dropped items rendered exactly to the config value (#973) 2025-06-16 07:55:28 +02:00
jss2a98aj 30a35ef25e Misc. CTM resource tweaks 2025-06-13 18:04:31 -07:00
jss2a98aj 33453b3b8c Fix crash with OpenAutism
Brandyyn says this fixes the crash but I have not tested it myself.
2025-06-13 18:04:31 -07:00
jss2a98aj 62e699f6d0 Remove unneeded debug print 2025-06-13 18:04:31 -07:00
Jason Mitchell f107d173b7 Stop loading HoloInventory too early in the compat class, which was causing fake fluids to not render properly. 2025-06-09 14:22:12 -07:00
glowredman d0948b36f3 No longer use Reflection for SplashProgress.fontRenderer (#966)
* Access SplashProgress font renderer via mixins

* Use AT
2025-06-07 12:07:14 +02:00
YannickMG ae1211f280 Battlegear 2 for Backhand compat 2025-05-31 08:01:28 -07:00
David Lindström 3d76cb3073 Use instanceof instead of direct cast to protect against scenarios where mods like FMB use the same rendering code for world, inventory and preview, where the two latter happens in passes that are not rendered using a CapturingTesselator 2025-05-27 14:10:07 -07:00
kotmatross28729 45fb120223 Fix COLOR_BUFFER_BIT leak in ShaderPackScreen 2025-05-25 08:25:59 -07:00
Jason Mitchell d7a196eb5c Use invokeExact instead of invoke 2025-05-25 08:25:05 -07:00
Jason Mitchell 4d53e2af9b Holoinventory compat via reflection 2025-05-25 08:25:05 -07:00
Jason Mitchell cb6336a365 Backhand Compat via Reflection 2025-05-25 08:25:05 -07:00
Jason Mitchell 5095021e0e Capture shader outline 2025-05-16 18:08:27 -07:00
Caedis f88ecbf68d Replace Baubles with Baubles Expanded (#949) 2025-05-13 10:26:50 -04:00
Darren Eberly 7da9b9eb1c Compat for security craft (#948) 2025-05-12 23:34:15 -04:00
Pxx500 02c394108f enable ctm, disable everything unnecessary 2025-05-05 12:44:46 -07:00
Fabian Maurer 720aaca1b7 Add functionality to fake blockId for shaders 2025-04-24 14:11:24 -07:00
Midnight145 1e71b1c4d8 liteloader docs update 2025-04-17 17:41:58 -07:00
くぁーりぃ df4c423e93 fix config load (#914) 2025-04-07 19:10:39 +00:00
JuliaandJason Mitchell 78e0074e5a Apply linear filter to unifont glyphs at odd scale factors (#907)
* Apply linear filter to unifont glyphs at odd scale factors

* Prevent glyph texture bleeding by adjusting UV coordinates with a slight offset

* Tiny bit of tuning

---------

Co-authored-by: Jason Mitchell <[email protected]>
2025-04-07 17:37:46 +02:00
くぁーりぃ 0350ea2cb6 fix stencil buffer does not exist (#912) 2025-03-20 07:53:27 -07:00
Darren Eberly bed724ba59 Support for custom uniforms (#902)
* wip backport of custom uniforms

* Functioning custom uniforms maybe?

* Workon separating dynamic/non-dynamic uniforms and making variables available to custom uniforms (currently the JVM dies horribly)

* wip

* It no longer crashes horrifically

* star imports
2025-03-18 10:04:31 -07:00
Jason Mitchell 6fa973dbc8 Adds two new overloads (#906) 2025-03-13 08:24:57 -07:00
RecursivePineapple c4ad460f34 Update (#899) 2025-03-09 23:04:46 +00:00
Glease 2a8404c6c2 add an API to retrieve world instance from WorldSlice (#897) 2025-03-08 23:00:58 -08:00
ah-OOG-ah 108db9031b Add an option to disable terrain fog (#896)
* Add an option to disable terrain fog

* forgot a remap = false
2025-03-09 05:38:30 +00:00
amyandJason Mitchell d6d8a5dad6 Catch CancellationException while building chunks (#889)
Co-authored-by: Jason Mitchell <[email protected]>
2025-03-08 20:45:10 -08:00
RecursivePineappleandJason Mitchell ad2b73edf9 Fix hologlasses crash (#891)
Co-authored-by: Jason Mitchell <[email protected]>
2025-03-08 20:44:36 -08:00
glowredman bb5526598e Various Mixin-related Fixes (#890)
* Update deps

* Remove dead code

* Add missing `@Override` annotations

* Fix generics

* Add `serialVersionUID`s

* Address deprecation warnings

* Use `@WrapWithCondition` v2

* Misc cleanup

* Use AccessTransformers if possible

* Fix mixin conflicts

* Add `@Mutable` annotations

* Fix injection targets

* Update deps
2025-03-08 20:41:47 -08:00
RecursivePineapple 611bbf02c8 Optimize NEI rendering 2025-02-28 11:32:26 -08:00
OBlankenship ca16036604 Update GTNHLib dependency version in README (#885) 2025-02-27 09:22:04 +01:00
Caedis f118211257 Fix IC2 universal cell rendering w/offhand (#876) 2025-02-18 10:49:29 -08:00
RecursivePineapple 81d03ea552 Fix PBR specular & normal textures getting ignored (#874)
* Fix PBR textures not loading

- Stack was being overfilled by MemoryStack
- Added tons of logging for ignored important exceptions
- Added missing interface on mixin (should it be moved to gtnhlib?)

* Move PBR debug to angelica config

* Free malloc'd memory
2025-02-18 18:34:38 +00:00
RecursivePineapple 67c8628aeb Fix a handful of broken animations (#868)
* Fix warning

* Improve animated sprite tracking

* Fix NPE in animation code

* Fix animations for abnormally rendered items

* Swap Stack for ArrayDeque
2025-02-18 18:28:54 +00:00
embeddedt f9b9873637 Avoid checking frustum visibility of infinite bounding boxes 2025-02-18 10:24:09 -08:00
DarkShadow44 c66bf8658e Fix issue with dynamic lights world mixin not respecting opaque blocks (#872) 2025-02-17 10:36:34 -06:00
DarkShadow44 052824017d Fix crash when keycode is negative (#871) 2025-02-16 21:43:15 +00:00
Caedis 6a5d914a43 add offhand support via Backhand (up required version to 1.6.9) (#859) 2025-02-04 12:41:47 -05:00
Calvin Reveraster 208373deaa Fix any dimension without a sky getting flagged as the nether (#857)
* fix crash when connected textures are disabled, but custom colors are enabled.

* Dimension Check Fix

Dimension check set any dimension with no sky as the nether; this includes the end, and several custom dims such as Witchery's mirror dim, etc.
2025-02-01 09:18:29 -08:00
TotallyNotOndre f22a3f612b Fix slider dragging in iris options (#858) 2025-02-01 09:17:26 -08:00
TotallyNotOndre 9ff1015d19 Fix shadow render distance reset to 32 (#856) 2025-01-31 11:18:09 -08:00
Calvin Reveraster 0023fe3da2 fix crash when connected textures are disabled, but custom colors are enabled. (#855) 2025-01-29 08:19:50 -08:00
Raven Szewczyk 7e2399714c Use GTNHLib MemoryUtils instead of the builtin compat ones (#853) 2025-01-26 11:20:43 -08:00
George Paton 9029182353 Fix Hodgepodge animation speedup breaking animated textures (#850) 2025-01-25 15:34:51 -08:00
Caedis e6039691b5 Remove forced even unicode gui scaling (#840) 2025-01-15 14:39:21 -06:00
paulzzh 5db1edba33 fix MC-76356 Bold unicode characters appear doubled (#834) 2025-01-05 15:24:16 +00:00
Martin Robertz d450461726 update bs + deps (#823) 2025-01-04 13:50:46 -08:00
embeddedt 95f8afc0f3 Remove use of empty build tasks, submit empty data directly to result queue (#821) 2024-12-27 21:39:05 -08:00
Caedis 19635e20ec Disable optimizeInWorldItemRendering by default for now (#820) 2024-12-25 23:25:42 -06:00
BucketBrigade ead4e11376 Expose texture updates to API (#805)
* Expose texture updates to API

* Expose for blocks as well

* Fix mistake

* Fixed API
2024-12-19 19:04:24 -08:00
FM Bellomy b0ceb5306a the most boring typo PR of all time (#810) 2024-12-19 13:25:56 -08:00
embeddedt 99a81369f0 fix origin weirdness (#799) 2024-12-17 10:09:22 -08:00
nshepperd e2ee822948 Only clear the translucency sorting render queue when we're not rendering shadows. (#798) 2024-12-16 13:30:20 -08:00
nshepperd 9fe7e800f6 Remove broken reflection in ChunkBufferSorter. (#797) 2024-12-16 21:11:05 +00:00
embeddedt debfcdfda0 Properly prevent block face culling when TS is active (#796) 2024-12-16 13:07:00 -08:00
embeddedt df662188f4 Fix terrain rendering incorrectly when far from origin (#790) 2024-12-13 18:14:00 +00:00
embeddedt 842916405f Fix rebuild check being wrong when shaders are on (#788) 2024-12-13 16:55:58 +00:00
embeddedt 09afe81290 Fix chunk render manager state not swapping, causing lower render distance with shaders on 2024-12-13 08:52:32 -08:00
Caedis 292809d8e8 Actually fix xaero's crosshair (#781) 2024-12-11 21:50:04 +00:00
Caedis 47503ce254 Fix invisible crosshair with Xaero's Minimap (#775) 2024-12-11 16:32:49 -05:00
ah-OOG-ah f738adbdef Frametime exploration (#742)
* Sleep before swap

* Add a config

* I think this is slightly less jittery
2024-12-06 09:09:26 -08:00
Alexdoru 70d15f2383 Set render distance for mob inside mod spawner to 16 blocks (#761)
* set render distance for mob inside mod spawner to 16 blocks

* add config in angelica

* add slider in video setting menu
2024-12-02 15:20:44 -10:00
kurrycatandDarren Eberly d66b5577f4 add missing GL calls in GLStateManager (#767)
Co-authored-by: Darren Eberly <[email protected]>
2024-12-02 09:12:44 -05:00
ah-OOG-ah e293f12fb8 Refresh Notfine options on world load (#760) 2024-12-01 00:03:21 +00:00
kurrycat b69eca6749 add missing Pipeline::syncProgram for glCallList (#762) 2024-11-30 10:38:00 -10:00
Jason Mitchell 03342f08c6 Update README.MD 2024-11-27 09:03:01 -08:00
ah-OOG-ah b85614f983 Null check (#756) 2024-11-26 22:19:05 +00:00
829 changed files with 34159 additions and 21416 deletions

No files matched your search

@@ -39,6 +39,7 @@ body:
- Java 19
- Java 20
- Java 21
- Java 25
- Other (Please Specify)
validations:
required: true
+14 -11
View File
@@ -7,40 +7,41 @@
* It's a cute angle -mitchej123
* It's the angel that pulls you out of OptiFine's hell -Roadhog360
## **Beta Quality - Here there be Dragons**
* Shaders sneak preview - Please don't report issues yet, but please do submit fixes
* Complementary & BSL mostly function, but have some bugs
* Sildurs, Seus, and any other shader probably doesn't work
## **Beta Quality - Here there be Dragons**
* Shaders - Beta
* Complementary + Euphoria Patches is the primary compatibility target
* Sildurs, Seus, and any other shaders might work
# Required Dependencies
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.14
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.4.2
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.9.0
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
# Known (temporary) Incompatibilities
* Forge Relocation - Currently non Thread Safe; safe if not in use
* ProjectRed Frames - Currently non Thread Safe; safe if not in use'
* ProjectRed Frames - Currently non Thread Safe; safe if not in use
# Potential Incompatibilities
* EndlessIDs - Compat commits have been made, might work, might not - not fully tested
# Known Incompatibilities
* FalseTweaks - Not currently planned (Looking for volunteers) [Might work if you effectively disable most of the mod]
* SmoothFont - Not currently planned (Looking for volunteers)
* RPLE - Not currently planned (Looking for volunteers)
* LiteLoader - Install [LiteloaderLoader](https://github.com/Midnight145/LiteloaderLoader)
# Permanent Incompatibilities
* OptiFine [Disabled, won't fix]
* FastCraft [Disabled, won't fix]
* BetterFPS [Disabled, won't fix]
* OptimizationsAndTweaks [Disabled, won't fix]
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - use 1.2.2+
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - Use 1.2.2+
* [LWJGL3ify 1.5.4 and below](https://github.com/GTNewHorizons/lwjgl3ify/releases/) - Use the latest version available, 1.5.7+
* [ArchaicFix 0.6.2 and below](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) - Use 0.7.0 or above
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 0.4.5 or above
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 2.4.5 or above
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
* SmoothFont - Equivalent functionality added
# Credits
* ShadersMod
@@ -64,7 +65,7 @@
* Makamys for [Neodymium](https://github.com/makamys/Neodymium) - Various Tessellator -> Quad functionality taken under the LGPL 3
* MCPatcherForge
* Original [MCPatcher](https://bitbucket.org/prupe/mcpatcher/src/master/): Paul Rupe
* [Forge version](https://bitbucket.org/prupe/mcpatcher/src/master/): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
* [Forge version](https://github.com/mist475/MCPatcherForge): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
* HUDCaching
* Created by [Moulberry](https://github.com/Moulberry/MCHUDCaching), backported from [Patcher](https://github.com/Sk1erLLC/Patcher) mod by Alexdoru under the CC-BY-SA-NC 4.0 license
* Angelica
@@ -82,3 +83,5 @@
* Code from both glsl-transformation-lib and Monocle taken under their respective LGPL 3.0 licenses
* FalseTweaks
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
* Beddium
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
+22 -2
View File
@@ -4,6 +4,10 @@ plugins {
minecraft {
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
}
tasks.test {
@@ -11,17 +15,24 @@ tasks.test {
testLogging {
events("passed", "skipped", "failed")
}
// On macOS ARM64, use an ARM64-compatible JVM for tests
val isMacOsArm64 = System.getProperty("os.name").lowercase().contains("mac") &&
System.getProperty("os.arch") == "aarch64"
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
if (isMacOsArm64) {
vendor = JvmVendorSpec.AZUL
}
}
dependsOn(tasks.extractNatives2)
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
}
tasks.processResources {
inputs.property("version", project.version.toString())
val projectVersion = project.version.toString()
inputs.property("version", projectVersion)
filesMatching("META-INF/rfb-plugin/*") {
expand("version" to project.version.toString())
expand("version" to projectVersion)
}
}
@@ -31,3 +42,12 @@ tasks.register<Copy>("copyDependencies") {
from(configurations.default)
into("testDependencies")
}
val embedOnly: Configuration by configurations
tasks.shadowJar {
from(embedOnly.map(::zipTree))
relocate("com.mitchej123", "com.mitchej123")
relocate("org.embeddedt", "org.embeddedt")
}
+53 -28
View File
@@ -46,45 +46,58 @@ configurations {
// Can be enabled for testing if desired
// runtimeClasspath.extendsFrom(transformedMod)
// testRuntimeClasspath.extendsFrom(transformedMod)
// Embed in JAR without relocation (compile only, shadowed into JAR)
embedOnly
embedOnly.canBeConsumed = false
embedOnly.canBeResolved = true
compileClasspath.extendsFrom(embedOnly)
testCompileClasspath.extendsFrom(embedOnly)
}
// Exclude self-reference from all configurations - prevents transitive Angelica deps from resolving against this project
configurations.all {
exclude group: "com.github.GTNewHorizons", module: "Angelica"
}
dependencies {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.5.78")
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14:dev")
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:0.4.0") { transitive = false }
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.7") { transitive = false }
compileOnly("org.projectlombok:lombok:1.18.22") { transitive = false }
annotationProcessor("org.projectlombok:lombok:1.18.22")
// Use modern models+textures to inject QuadProviders
devOnlyNonPublishable('com.github.GTNewHorizons:TX-Loader:1.7.2:dev')
compileOnly("org.projectlombok:lombok:1.18.42") { transitive = false }
annotationProcessor("org.projectlombok:lombok:1.18.42")
// Iris Shaders
compileOnly('org.jetbrains:annotations:24.0.1')
api("com.github.GTNewHorizons:GTNHLib:0.5.20:dev")
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.9.0:dev")
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-4.g6b42bca") {
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-25.g3a943bb") {
exclude module: "antlr4" // we only want to shadow the runtime module
}
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
compileOnly "org.apache.ant:ant:1.8.2"
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.1-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.1-GTNH") { transitive = false }
// Because who doesn't want NEI
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.6.45-GTNH:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.3.10:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.7:dev')
// Notfine Deps
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles:1.0.4:dev")
compileOnly("com.github.GTNewHorizons:twilightforest:2.6.35:dev") { transitive = false }
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles-Expanded:2.2.2-GTNH:dev")
compileOnly("com.github.GTNewHorizons:twilightforest:2.7.13:dev") { transitive = false }
compileOnly(rfg.deobf('curse.maven:witchery-69673:2234410'))
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.12.14-GTNH:dev") { transitive = false }
compileOnly("com.github.GTNewHorizons:Natura:2.7.5:dev")
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.14.6-GTNH:dev") { transitive = false }
compileOnly("com.github.GTNewHorizons:Natura:2.8.9:dev")
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.6.4:dev")
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.8.4:dev")
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.1:dev") { transitive = false }
compileOnly(rfg.deobf('maven.modrinth:backhand:1.4.1')) { transitive = false }
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.3:dev") { transitive = false }
compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.5.1:api") { transitive = false }
compileOnly("com.falsepattern:endlessids-mc1.7.10:1.5-beta0003:dev") { transitive = false }
@@ -94,15 +107,18 @@ dependencies {
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.6:dev") // Mixin Version
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev") // Mixin Version
compileOnly("com.github.GTNewHorizons:NotEnoughIds-Legacy:1.4.7:dev") // ASM Version
compileOnly(rfg.deobf("curse.maven:ntm-space-1121235:6974420"))
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
compileOnly("com.github.GTNewHorizons:HoloInventory:2.4.13-GTNH:dev")
runtimeOnlyNonPublishable(rfg.deobf("CoreTweaks:CoreTweaks:0.3.3.2"))
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:CoreTweaks:0.3.3.6-GTNH")
// For testing alternative splash screens, can be sourced from https://github.com/MalTeeez/ModernSplash/releases
//runtimeOnly(rfg.deobf(files("dependencies/modernsplash-1.7.10-1.2.2-dev.jar")))
// Hodgepodge
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
@@ -110,9 +126,18 @@ dependencies {
// Better Crashes
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.0-GTNH:dev")
// Tests
testImplementation(platform('org.junit:junit-bom:5.9.2'))
testImplementation('org.junit.jupiter:junit-jupiter')
testImplementation("org.mockito:mockito-core:3.+")
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
afterEvaluate {
dependencies {
testImplementation(platform('org.junit:junit-bom:5.14.1'))
testImplementation('org.junit.jupiter:junit-jupiter')
testImplementation("org.mockito:mockito-core:3.+")
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
testImplementation('org.junit.platform:junit-platform-engine')
testImplementation('org.junit.platform:junit-platform-reporting')
testImplementation('org.junit.platform:junit-platform-launcher')
}
}
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
}
+43 -16
View File
@@ -14,7 +14,7 @@ modId = angelica
modGroup = com.gtnewhorizons.angelica
# Whether to use modGroup as the maven publishing group.
# Due to a history of using JitPack, the default is com.github.GTNewHorizons for all mods.
# When false, com.github.GTNewHorizons is used.
useModGroupForPublishing = false
# Updates your build.gradle and settings.gradle automatically whenever an update is available.
@@ -39,9 +39,33 @@ remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/co
# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE.
developmentEnvironmentUserName = Developer
# Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8.
# See https://github.com/bsideup/jabel for details on how this works.
enableModernJavaSyntax = true
# Enables modern Java syntax support. Valid values:
# - false: No modern syntax, Java 8 only
# - jabel: Jabel syntax-only support, compiles to J8 bytecode
# - jvmDowngrader: Full modern Java via JVM Downgrader (syntax + stdlib APIs)
# - modern: Native modern Java bytecode, no downgrading
enableModernJavaSyntax = jvmDowngrader
# If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues,
# and should *not* be used in most situations. -1 disables this.
# forceToolchainVersion = -1
# Target JVM version for JVM Downgrader bytecode downgrading.
# Only used when enableModernJavaSyntax = jvmDowngrader
# downgradeTargetVersion = 8
# Comma-separated list of Java versions for multi-release jar support (JVM Downgrader only).
# Classes will be available in META-INF/versions/N/ for each version N in this list.
# Default: "21,25" (J25+ gets native classes, J21-24 gets partial downgrade, J8-20 gets full downgrade).
jvmDowngraderMultiReleaseVersions = 17, 21, 25
# Specifies how JVM Downgrader API stubs are provided. Options:
# - shade: Shade minimized stubs into the jar
# - gtnhlib: GTNHLib provides stubs at runtime (adds version constraint)
# - external: Another dependency provides stubs (no constraint, no warning)
# - (empty): Warning reminding you to configure stubs
# Note: 'shade' option requires you to verify license compliance, see: https://github.com/unimined/JvmDowngrader/blob/main/LICENSE.md
jvmDowngraderStubsProvider = gtnhlib
# Enables injecting missing generics into the decompiled source code for a better coding experience.
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
@@ -55,22 +79,13 @@ generateGradleTokenClass = com.gtnewhorizons.angelica.Tags
# Name of the token containing the project's current version to generate/replace.
gradleTokenVersion = VERSION
# [DEPRECATED] Mod ID replacement token.
gradleTokenModId =
# [DEPRECATED] Mod name replacement token.
gradleTokenModName =
# [DEPRECATED] Mod Group replacement token.
gradleTokenGroupName =
# [DEPRECATED]
# Multiple source files can be defined here by providing a comma-separated list: Class1.java,Class2.java,Class3.java
# public static final String VERSION = "GRADLETOKEN_VERSION";
# The string's content will be replaced with your mod's version when compiled. You should use this to specify your mod's
# version in @Mod([...], version = VERSION, [...]).
# Leave these properties empty to skip individual token replacements.
replaceGradleTokenInFile =
# replaceGradleTokenInFile =
# In case your mod provides an API for other mods to implement you may declare its package here. Otherwise, you can
# leave this property empty.
@@ -96,7 +111,9 @@ usesMixinDebug = false
# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
mixinPlugin =
# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail!
# Specify the package that contains all of your Mixins. The package must exist or
# the build will fail. If you have a package property defined in your mixins.<modid>.json,
# it must match with this or the build will fail.
mixinsPackage = mixins
# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin!
@@ -151,7 +168,7 @@ modrinthProjectId = angelica
# type can be one of [project, version],
# and the name is the Modrinth project or version slug/id of the other mod.
# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech
# Note: GTNH Mixins is automatically set as a required dependency if usesMixins = true
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
modrinthRelations = required-project\:gtnhlib
# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens.
@@ -172,6 +189,12 @@ curseForgeRelations = requiredDependency\:gtnhlib
# projects. New projects should not use this parameter.
# customArchiveBaseName =
# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory.
# runClientWorkingDirectory = run/client
# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory.
# runServerWorkingDirectory = run/server
# Optional parameter to have the build automatically fail if an illegal version is used.
# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'.
# The check is ONLY performed if the version is a git tag.
@@ -201,3 +224,7 @@ disableSpotless = true
# This is meant to be set in $HOME/.gradle/gradle.properties.
# ideaCheckSpotlessOnBuild = true
# Non-GTNH properties
org.gradle.configuration-cache = true
org.gradle.jvmargs = -Xmx2g
org.gradle.parallel = true
+12
View File
@@ -0,0 +1,12 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/5ee463876563a46fff651cce1bc53a4c/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/5ee463876563a46fff651cce1bc53a4c/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e1118b4c7c2858d28154d086dbff010e/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/59a9771cad43219260d9aac9a8ec4d6a/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/5ee463876563a46fff651cce1bc53a4c/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/832229f0f6f0be60ed817c5a5e5848eb/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/b96cb7670c095245220e7d01d5927b58/redirect
toolchainVersion=25
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+4 -8
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -115,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -173,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -206,15 +203,14 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+1 -2
View File
@@ -70,11 +70,10 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
+5 -15
View File
@@ -8,21 +8,11 @@ repositories {
includeGroup 'com.falsepattern'
}
}
exclusiveContent {
forRepository {
ivy {
name = 'CoreTweaks releases'
url = 'https://github.com/makamys/CoreTweaks/releases/download/'
patternLayout {
artifact '[revision]/[module]-1.7.10-[revision]+nomixin(-[classifier])(.[ext])'
}
metadataSources {
artifact()
}
}
}
filter {
includeGroup('CoreTweaks')
maven {
name = 'mega'
url = 'https://mvn.falsepattern.com/gtmega_releases/'
content {
includeGroup 'mega'
}
}
maven {
+3 -3
View File
@@ -2,8 +2,8 @@ pluginManagement {
repositories {
maven {
// RetroFuturaGradle
name "GTNH Maven"
url "https://nexus.gtnewhorizons.com/repository/public/"
name = "GTNH Maven"
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
mavenContent {
includeGroup("com.gtnewhorizons")
includeGroupByRegex("com\\.gtnewhorizons\\..+")
@@ -16,5 +16,5 @@ pluginManagement {
}
plugins {
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.29'
id("com.gtnewhorizons.gtnhsettingsconvention") version("2.0.13")
}
@@ -2,21 +2,26 @@ package com.gtnewhorizons.angelica;
import com.gtnewhorizon.gtnhmixins.ILateMixinLoader;
import com.gtnewhorizon.gtnhmixins.LateMixin;
import com.gtnewhorizon.gtnhmixins.builders.IMixins;
import com.gtnewhorizons.angelica.mixins.Mixins;
import java.util.List;
import java.util.Set;
@LateMixin
@SuppressWarnings("unused")
public class AngelicaLateMixins implements ILateMixinLoader {
@Override
public String getMixinConfig() {
int v = Runtime.version().feature();
if (v >= 21) return "mixins.angelica.late.j21.json";
if (v >= 17) return "mixins.angelica.late.j17.json";
return "mixins.angelica.late.json";
}
@Override
public List<String> getMixins(Set<String> loadedMods) {
return Mixins.getLateMixins(loadedMods);
return IMixins.getLateMixins(Mixins.class, loadedMods);
}
}
@@ -1,5 +1,7 @@
package com.gtnewhorizons.angelica;
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
import com.gtnewhorizons.angelica.common.BlockError;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.proxy.CommonProxy;
@@ -7,20 +9,30 @@ import com.gtnewhorizons.angelica.utils.AnimationMode;
import com.gtnewhorizons.angelica.utils.ManagedEnum;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.SidedProxy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
@Mod(
modid = "angelica",
modid = MOD_ID,
name = "Angelica",
version = Tags.VERSION,
dependencies = " before:lwjgl3ify@[1.5.3,);" + " after:hodgepodge@[2.4.4,);" + " after:CodeChickenCore@[1.2.0,);"
+ " after:archaicfix@[0.7.0,);" + " required-after:gtnhlib@[0.4.2,);",
dependencies = """
required-after:gtnhlib@[0.8.21,);\
before:lwjgl3ify@[1.5.3,);\
after:hodgepodge@[2.4.4,);\
after:CodeChickenCore@[1.2.0,);\
after:archaicfix@[0.7.0,);\
""",
acceptedMinecraftVersions = "[1.7.10]",
acceptableRemoteVersions = "*",
guiFactory = "com.gtnewhorizons.angelica.config.AngelicaGuiConfigFactory")
public class AngelicaMod {
public static final String MOD_ID = "angelica";
public static final Logger LOGGER = LogManager.getLogger("Angelica");
@SidedProxy(clientSide = "com.gtnewhorizons.angelica.proxy.ClientProxy", serverSide = "com.gtnewhorizons.angelica.proxy.CommonProxy")
public static CommonProxy proxy;
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.api;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
public interface IBlockAccessExtended extends IBlockAccess {
World getWorld();
}
@@ -0,0 +1,27 @@
package com.gtnewhorizons.angelica.api;
import com.gtnewhorizons.angelica.mixins.interfaces.ITexturesCache;
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.util.IIcon;
/**
* Exposes a method to the API that allow mods to add texture update compat
*/
@SuppressWarnings("unused")
public class TextureServices {
@SuppressWarnings("unused")
public static void updateBlockTextureAnimation(IIcon icon, RenderBlocks renderBlocks) {
if (renderBlocks instanceof ITexturesCache texturesCache) {
texturesCache.getRenderedTextures().add(icon);
}
}
@SuppressWarnings("unused")
public static void updateTextureAnimation(IIcon icon) {
if (icon instanceof SpriteExtension ext) {
ext.celeritas$markActive();
}
}
}
@@ -1,25 +1,32 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
import cpw.mods.fml.client.SplashProgress;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import jss.util.RandomXoshiro256StarStar;
import net.coderbot.iris.gl.program.Program;
import net.coderbot.iris.gl.program.ProgramBuilder;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.BufferUtils;
import org.apache.commons.io.IOUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.Objects;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
/**
* A batching replacement for {@code FontRenderer}
*
@@ -29,8 +36,6 @@ public class BatchingFontRenderer {
/** The underlying FontRenderer object that's being accelerated */
protected FontRenderer underlying;
/** Cached locations for each unicode page atlas */
private final ResourceLocation[] unicodePageLocations;
/** Array of width of all the characters in default.png */
protected int[] charWidth = new int[256];
/** Array of the start/end column (in upper/lower nibble) for every glyph in the /font directory. */
@@ -42,50 +47,59 @@ public class BatchingFontRenderer {
private int[] colorCode;
/** Location of the primary font atlas to bind. */
protected final ResourceLocation locationFontTexture;
/** The RenderEngine used to load and setup glyph textures. */
private final TextureManager renderEngine;
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
/** The full list of characters present in the default Minecraft font, excluding the Unicode font */
@SuppressWarnings("UnnecessaryUnicodeEscape")
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
private final int AAMode;
private final int AAStrength;
private final int texBoundAttrLocation;
private final int fontShaderId;
private static final short[] MCFONT_ASCII_LUT = new short[512];
private static final Char2ShortOpenHashMap MCFONT_UNI_LUT = new Char2ShortOpenHashMap();
final boolean isSGA;
final boolean isSplash;
static {
Arrays.fill(MCFONT_ASCII_LUT, (short) -1);
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
char ch = MCFONT_CHARS.charAt(i);
if (ch < MCFONT_ASCII_LUT.length) {
MCFONT_ASCII_LUT[ch] = i;
} else {
MCFONT_UNI_LUT.put(ch, i);
private static class FontAAShader {
private static Program fontShader = null;
public static Program getProgram() {
if (fontShader == null) {
String vsh, fsh;
try {
fsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.fsh"))), StandardCharsets.UTF_8);
vsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.vsh"))), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}
ProgramBuilder builder = ProgramBuilder.begin("fontFilter", vsh, null, fsh, ImmutableSet.of(0));
fontShader = builder.build();
}
return fontShader;
}
}
public static int lookupMcFontPosition(char ch) {
if (ch < MCFONT_ASCII_LUT.length) {
return MCFONT_ASCII_LUT[ch];
} else {
return MCFONT_UNI_LUT.getOrDefault(ch, (short) -1);
}
}
public BatchingFontRenderer(FontRenderer underlying, ResourceLocation[] unicodePageLocations, int[] charWidth,
byte[] glyphWidth, int[] colorCode, ResourceLocation locationFontTexture, TextureManager renderEngine) {
public BatchingFontRenderer(FontRenderer underlying, int[] charWidth, byte[] glyphWidth,
int[] colorCode, ResourceLocation locationFontTexture) {
this.underlying = underlying;
this.unicodePageLocations = unicodePageLocations;
this.charWidth = charWidth;
this.glyphWidth = glyphWidth;
this.colorCode = colorCode;
this.locationFontTexture = locationFontTexture;
this.renderEngine = renderEngine;
for (int i = 0; i < 64; i++) {
batchCommandPool.add(new FontDrawCmd());
}
this.isSGA = Objects.equals(this.locationFontTexture.getResourcePath(), "textures/font/ascii_sga.png");
this.isSplash = FontStrategist.isSplashFontRendererActive(underlying);
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
FontProviderUnicode.get().glyphWidth = this.glyphWidth;
//noinspection deprecation
fontShaderId = FontAAShader.getProgram().getProgramId();
AAMode = GL20.glGetUniformLocation(fontShaderId, "aaMode");
AAStrength = GL20.glGetUniformLocation(fontShaderId, "strength");
texBoundAttrLocation = GL20.glGetAttribLocation(fontShaderId, "texBounds");
}
// === Batched rendering
@@ -97,24 +111,29 @@ public class BatchingFontRenderer {
private static final int INITIAL_BATCH_SIZE = 256;
private static final ResourceLocation DUMMY_RESOURCE_LOCATION = new ResourceLocation("angelica$dummy",
"this is invalid!");
private FloatBuffer batchVtxPositions = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
private ByteBuffer batchVtxColors = BufferUtils.createByteBuffer(INITIAL_BATCH_SIZE * 4);
private FloatBuffer batchVtxTexCoords = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
private IntBuffer batchIndices = BufferUtils.createIntBuffer(INITIAL_BATCH_SIZE / 2 * 3);
private FloatBuffer batchVtxPositions = memAllocFloat(INITIAL_BATCH_SIZE * 2);
private ByteBuffer batchVtxColors = memAlloc(INITIAL_BATCH_SIZE * 4);
private FloatBuffer batchVtxTexCoords = memAllocFloat(INITIAL_BATCH_SIZE * 2);
private IntBuffer batchIndices = memAllocInt(INITIAL_BATCH_SIZE / 2 * 3);
private FloatBuffer batchVtxTexBounds = memAllocFloat(INITIAL_BATCH_SIZE * 4);
private final ObjectArrayList<FontDrawCmd> batchCommands = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
private final ObjectArrayList<FontDrawCmd> batchCommandPool = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
private int blendSrcRGB = GL11.GL_SRC_ALPHA;
private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
/** */
private void pushVtx(float x, float y, int rgba, float u, float v) {
private void pushVtx(float x, float y, int rgba, float u, float v, float uMin, float uMax, float vMin, float vMax) {
final int oldCap = batchVtxPositions.capacity() / 2;
if (vtxWriterIndex >= oldCap) {
final int newCap = oldCap * 2;
batchVtxPositions = CompatMemoryUtil.memReallocDirect(batchVtxPositions, newCap * 2);
batchVtxColors = CompatMemoryUtil.memReallocDirect(batchVtxColors, newCap * 4);
batchVtxTexCoords = CompatMemoryUtil.memReallocDirect(batchVtxTexCoords, newCap * 2);
batchVtxPositions = memRealloc(batchVtxPositions, newCap * 2);
batchVtxColors = memRealloc(batchVtxColors, newCap * 4);
batchVtxTexCoords = memRealloc(batchVtxTexCoords, newCap * 2);
batchVtxTexBounds = memRealloc(batchVtxTexBounds, newCap * 4);
final int oldIdxCap = batchIndices.capacity();
final int newIdxCap = oldIdxCap * 2;
batchIndices = CompatMemoryUtil.memReallocDirect(batchIndices, newIdxCap);
batchIndices = memRealloc(batchIndices, newIdxCap);
}
final int idx = vtxWriterIndex;
final int idx2 = idx * 2;
@@ -128,15 +147,28 @@ public class BatchingFontRenderer {
batchVtxColors.put(idx4 + 3, (byte) ((rgba >> 24) & 0xFF));
batchVtxTexCoords.put(idx2, u);
batchVtxTexCoords.put(idx2 + 1, v);
batchVtxTexBounds.put(idx4, uMin);
batchVtxTexBounds.put(idx4 + 1, uMax);
batchVtxTexBounds.put(idx4 + 2, vMin);
batchVtxTexBounds.put(idx4 + 3, vMax);
vtxWriterIndex++;
}
private void pushUntexRect(float x, float y, float w, float h, int rgba) {
final int vtxId = vtxWriterIndex;
pushVtx(x, y, rgba, 0, 0);
pushVtx(x, y + h, rgba, 0, 0);
pushVtx(x + w, y, rgba, 0, 0);
pushVtx(x + w, y + h, rgba, 0, 0);
pushVtx(x, y, rgba, 0, 0, 0, 0, 0, 0);
pushVtx(x, y + h, rgba, 0, 0, 0, 0, 0, 0);
pushVtx(x + w, y, rgba, 0, 0, 0, 0, 0, 0);
pushVtx(x + w, y + h, rgba, 0, 0, 0, 0, 0, 0);
pushQuadIdx(vtxId);
}
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz) {
final int vtxId = vtxWriterIndex;
pushVtx(x + itOff, y, rgba, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff, y + h, rgba, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
pushQuadIdx(vtxId);
}
@@ -153,7 +185,7 @@ public class BatchingFontRenderer {
return idx;
}
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture) {
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode) {
if (!batchCommands.isEmpty()) {
final FontDrawCmd lastCmd = batchCommands.get(batchCommands.size() - 1);
final int prevEndVtx = lastCmd.startVtx + lastCmd.idxCount;
@@ -169,7 +201,7 @@ public class BatchingFontRenderer {
}
}
final FontDrawCmd cmd = batchCommandPool.pop();
cmd.reset(startIdx, idxCount, texture);
cmd.reset(startIdx, idxCount, texture, isUnicode);
batchCommands.add(cmd);
}
@@ -177,12 +209,14 @@ public class BatchingFontRenderer {
public int startVtx;
public int idxCount;
public boolean isUnicode;
public ResourceLocation texture;
public void reset(int startVtx, int vtxCount, ResourceLocation texture) {
public void reset(int startVtx, int vtxCount, ResourceLocation texture, boolean isUnicode) {
this.startVtx = startVtx;
this.idxCount = vtxCount;
this.texture = texture;
this.isUnicode = isUnicode;
}
@Override
@@ -241,7 +275,12 @@ public class BatchingFontRenderer {
}
}
int lastActiveProgram;
int fontAAModeLast = -1;
int fontAAStrengthLast = -1;
private void flushBatch() {
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
// Sort&Draw
batchCommands.sort(FontDrawCmd.DRAW_ORDER_COMPARATOR);
@@ -253,9 +292,26 @@ public class BatchingFontRenderer {
GLStateManager.enableTexture();
GLStateManager.enableAlphaTest();
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glShadeModel(GL11.GL_FLAT);
final boolean canUseAA = FontConfig.fontAAMode != 0 && prevProgram == 0;
if (canUseAA) {
GL20.glVertexAttribPointer(texBoundAttrLocation, 4, false, 0, batchVtxTexBounds);
GL20.glEnableVertexAttribArray(texBoundAttrLocation);
lastActiveProgram = prevProgram;
GLStateManager.glUseProgram(fontShaderId);
if (FontConfig.fontAAMode != fontAAModeLast) {
fontAAModeLast = FontConfig.fontAAMode;
GL20.glUniform1i(AAMode, FontConfig.fontAAMode);
}
if (FontConfig.fontAAStrength != fontAAStrengthLast) {
fontAAStrengthLast = FontConfig.fontAAStrength;
GL20.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
}
}
GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
GL11.glTexCoordPointer(2, 0, batchVtxTexCoords);
GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GL11.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 0, batchVtxColors);
@@ -264,6 +320,9 @@ public class BatchingFontRenderer {
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GL11.glDisableClientState(GL11.GL_NORMAL_ARRAY);
GL11.glNormal3f(0.0f, 0.0f, 1.0f);
// Use plain for loop to avoid allocations
final FontDrawCmd[] cmdsData = batchCommands.elements();
final int cmdsSize = batchCommands.size();
@@ -283,8 +342,13 @@ public class BatchingFontRenderer {
}
batchIndices.limit(cmd.startVtx + cmd.idxCount);
batchIndices.position(cmd.startVtx);
GL11.glDrawElements(GL11.GL_TRIANGLES, batchIndices);
}
if (canUseAA) {
GLStateManager.glUseProgram(lastActiveProgram);
GL20.glDisableVertexAttribArray(texBoundAttrLocation);
}
GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
@@ -312,17 +376,28 @@ public class BatchingFontRenderer {
return (what >= fromInclusive) && (what <= toInclusive);
}
private ResourceLocation getUnicodePageLocation(int page) {
final ResourceLocation lookup = unicodePageLocations[page];
if (lookup == null) {
final ResourceLocation rl = new ResourceLocation(String.format(
"textures/font/unicode_page_%02x.png",
page));
unicodePageLocations[page] = rl;
return rl;
} else {
return lookup;
}
public boolean forceDefaults() {
return this.isSGA || this.isSplash;
}
public float getGlyphScaleX() {
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale * Math.pow(2, FontConfig.glyphAspect));
}
public float getGlyphScaleY() {
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale / Math.pow(2, FontConfig.glyphAspect));
}
public float getGlyphSpacing() {
return forceDefaults() ? 0 : FontConfig.glyphSpacing;
}
public float getWhitespaceScale() {
return forceDefaults() ? 1 : FontConfig.whitespaceScale;
}
public float getShadowOffset() {
return forceDefaults() ? 1 : FontConfig.fontShadowOffset;
}
private static final char FORMATTING_CHAR = 167; // §
@@ -335,6 +410,9 @@ public class BatchingFontRenderer {
}
final int shadowColor = (color & 0xfcfcfc) >> 2 | color & 0xff000000;
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
this.beginBatch();
float curX = anchorX;
try {
@@ -354,10 +432,14 @@ public class BatchingFontRenderer {
boolean curStrikethrough = false;
boolean curUnderline = false;
final float underlineY = anchorY + underlying.FONT_HEIGHT - 1.0f;
float glyphScaleY = getGlyphScaleY();
float heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY / 2);
final float underlineY = heightNorth + (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY;
float underlineStartX = 0.0f;
float underlineEndX = 0.0f;
final float strikethroughY = anchorY + (float) (underlying.FONT_HEIGHT / 2) - 1.0F;
final float strikethroughY = heightNorth + ((float) (underlying.FONT_HEIGHT / 2) - 1.0f) * glyphScaleY;
float strikethroughStartX = 0.0f;
float strikethroughEndX = 0.0f;
@@ -369,8 +451,8 @@ public class BatchingFontRenderer {
if (curUnderline && underlineStartX != underlineEndX) {
final int ulIdx = idxWriterIndex;
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, 1.0f, curColor);
pushDrawCmd(ulIdx, 6, null);
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
pushDrawCmd(ulIdx, 6, null, false);
underlineStartX = underlineEndX;
}
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
@@ -379,9 +461,9 @@ public class BatchingFontRenderer {
strikethroughStartX,
strikethroughY,
strikethroughEndX - strikethroughStartX,
1.0f,
glyphScaleY,
curColor);
pushDrawCmd(ulIdx, 6, null);
pushDrawCmd(ulIdx, 6, null, false);
strikethroughStartX = strikethroughEndX;
}
@@ -426,148 +508,83 @@ public class BatchingFontRenderer {
continue;
}
int lutIndex = lookupMcFontPosition(chr);
if (curRandom && lutIndex != -1) {
int randomReplacementIndex;
do {
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
lutIndex = randomReplacementIndex;
chr = MCFONT_CHARS.charAt(lutIndex);
if (FontConfig.enableCustomFont) {
String chrReplacement = GlyphReplacements.customGlyphs.get(String.valueOf(chr));
if (chrReplacement != null) {
chr = chrReplacement.charAt(0);
}
}
final float shadowOffset = unicodeFlag ? 0.5F : 1.0F;
if (curRandom) {
chr = FontProviderMC.get(this.isSGA).getRandomReplacement(chr);
}
FontProvider fontProvider = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, unicodeFlag);
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * fontProvider.getYScaleMultiplier() / 2);
float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY * fontProvider.getYScaleMultiplier();
// Check ASCII space, NBSP, NNBSP
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
curX += 4;
curX += 4 * this.getWhitespaceScale();
continue;
}
final float uStart;
final float vStart;
final float xAdvance;
final float glyphW;
final float uSz;
final float vSz;
final float uStart = fontProvider.getUStart(chr);
final float vStart = fontProvider.getVStart(chr);
final float xAdvance = fontProvider.getXAdvance(chr) * getGlyphScaleX();
final float glyphW = fontProvider.getGlyphW(chr) * getGlyphScaleX();
final float uSz = fontProvider.getUSize(chr);
final float vSz = fontProvider.getVSize(chr);
final float itOff = curItalic ? 1.0F : 0.0F; // italic offset
final ResourceLocation texture;
if (lutIndex == -1 || unicodeFlag) {
if (glyphWidth[chr] == 0) {
continue;
}
// Draw unicode char
final int uniPage = chr / 256;
texture = getUnicodePageLocation(uniPage);
final int startColumn = this.glyphWidth[chr] >>> 4;
final int endColumn = this.glyphWidth[chr] & 15;
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
uStart = ((float) (chr % 16 * 16) + startColumnF) / 256.0f;
vStart = ((float) ((chr & 255) / 16 * 16)) / 256.0f;
final float chrWidth = endColumnF - startColumnF - 0.02F;
glyphW = chrWidth / 2.0f + 1.0f;
xAdvance = (endColumnF - startColumnF) / 2.0F + 1.0F;
uSz = chrWidth / 256.0f;
vSz = 15.98f / 256.0f;
} else {
// Draw "ASCII" char
uStart = ((lutIndex % 16) * 8) / 128.0F;
vStart = (float) ((lutIndex / 16) * 8) / 128.0F;
xAdvance = this.charWidth[lutIndex];
if (xAdvance == 0) {
continue;
}
glyphW = xAdvance - 0.01F;
uSz = (glyphW - 1.0F) / 128.0F;
vSz = 7.99F / 128.0F;
texture = locationFontTexture;
}
final int vtxId = vtxWriterIndex;
final float shadowOffset = fontProvider.getShadowOffset();
final int shadowCopies = FontConfig.shadowCopies;
final int boldCopies = FontConfig.boldCopies;
final ResourceLocation texture = fontProvider.getTexture(chr);
final int idxId = idxWriterIndex;
int vtxCount = 0;
if (enableShadow) {
pushVtx(curX + itOff + shadowOffset, anchorY + shadowOffset, curShadowColor, uStart, vStart);
pushVtx(
curX - itOff + shadowOffset,
anchorY + 7.99F + shadowOffset,
curShadowColor,
uStart,
vStart + vSz);
pushVtx(curX + glyphW - 1.0F + itOff + shadowOffset,
anchorY + shadowOffset,
curShadowColor,
uStart + uSz,
vStart);
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset,
anchorY + 7.99F + shadowOffset,
curShadowColor,
uStart + uSz,
vStart + vSz);
pushQuadIdx(vtxId + vtxCount);
vtxCount += 4;
for (int n = 1; n <= shadowCopies; n++) {
final float shadowOffsetPart = shadowOffset * ((float) n / shadowCopies);
pushTexRect(curX + shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
if (curBold) {
final float shadowOffset2 = 2.0f * shadowOffset;
pushVtx(curX + itOff + shadowOffset2, anchorY + shadowOffset, curShadowColor, uStart, vStart);
pushVtx(
curX - itOff + shadowOffset2,
anchorY + 7.99F + shadowOffset,
curShadowColor,
uStart,
vStart + vSz);
pushVtx(
curX + glyphW - 1.0F + itOff + shadowOffset2,
anchorY + shadowOffset,
curShadowColor,
uStart + uSz,
vStart);
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset2,
anchorY + 7.99F + shadowOffset,
curShadowColor,
uStart + uSz,
vStart + vSz);
pushQuadIdx(vtxId + vtxCount);
vtxCount += 4;
if (curBold) {
pushTexRect(curX + 2.0f * shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
}
}
}
pushVtx(curX + itOff, anchorY, curColor, uStart, vStart);
pushVtx(curX - itOff, anchorY + 7.99F, curColor, uStart, vStart + vSz);
pushVtx(curX + glyphW - 1.0F + itOff, anchorY, curColor, uStart + uSz, vStart);
pushVtx(curX + glyphW - 1.0F - itOff, anchorY + 7.99F, curColor, uStart + uSz, vStart + vSz);
pushQuadIdx(vtxId + vtxCount);
vtxCount += 4;
pushTexRect(curX, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
if (curBold) {
pushVtx(shadowOffset + curX + itOff, anchorY, curColor, uStart, vStart);
pushVtx(shadowOffset + curX - itOff, anchorY + 7.99F, curColor, uStart, vStart + vSz);
pushVtx(shadowOffset + curX + glyphW - 1.0F + itOff, anchorY, curColor, uStart + uSz, vStart);
pushVtx(shadowOffset + curX + glyphW - 1.0F - itOff,
anchorY + 7.99F,
curColor,
uStart + uSz,
vStart + vSz);
pushQuadIdx(vtxId + vtxCount);
vtxCount += 4;
for (int n = 1; n <= boldCopies; n++) {
final float shadowOffsetPart = shadowOffset * ((float) n / boldCopies);
pushTexRect(curX + shadowOffsetPart, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
}
}
pushDrawCmd(idxId, vtxCount / 2 * 3, texture);
curX += xAdvance + (curBold ? shadowOffset : 0.0f);
/*
Vertex-per-char counts for different configurations
default: 4
shadow only: 4(1 + shadowCopies)
bold only: 4(1 + boldCopies)
both: 4(1 + 2 * shadowCopies + boldCopies)
*/
int charCount = 1;
if (enableShadow) { charCount += shadowCopies * (curBold ? 2 : 1); }
if (curBold) { charCount += boldCopies; }
final int vtxCount = 4 * charCount;
pushDrawCmd(idxId, vtxCount / 2 * 3, texture, chr > 255);
curX += (xAdvance + (curBold ? shadowOffset : 0.0f)) + getGlyphSpacing();
underlineEndX = curX;
strikethroughEndX = curX;
}
if (curUnderline && underlineStartX != underlineEndX) {
final int ulIdx = idxWriterIndex;
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, 1.0f, curColor);
pushDrawCmd(ulIdx, 6, null);
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
pushDrawCmd(ulIdx, 6, null, false);
}
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
final int ulIdx = idxWriterIndex;
@@ -575,9 +592,9 @@ public class BatchingFontRenderer {
strikethroughStartX,
strikethroughY,
strikethroughEndX - strikethroughStartX,
1.0f,
glyphScaleY,
curColor);
pushDrawCmd(ulIdx, 6, null);
pushDrawCmd(ulIdx, 6, null, false);
}
} finally {
@@ -586,4 +603,25 @@ public class BatchingFontRenderer {
return curX + (enableShadow ? 1.0f : 0.0f);
}
public float getCharWidthFine(char chr) {
if (chr == FORMATTING_CHAR) { return -1; }
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
return 4 * this.getWhitespaceScale();
}
FontProvider fp = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, underlying.getUnicodeFlag());
return fp.getXAdvance(chr) * this.getGlyphScaleX();
}
public void overrideBlendFunc(int srcRgb, int dstRgb) {
blendSrcRGB = srcRgb;
blendDstRGB = dstRgb;
}
public void resetBlendFunc() {
blendSrcRGB = GL11.GL_SRC_ALPHA;
blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
}
}
@@ -0,0 +1,21 @@
package com.gtnewhorizons.angelica.client.font;
import net.minecraft.util.ResourceLocation;
public interface FontProvider {
/**
* For use with §k. Should fetch a character of the same width as provided.
*/
char getRandomReplacement(char chr);
boolean isGlyphAvailable(char chr);
float getUStart(char chr);
float getVStart(char chr);
float getXAdvance(char chr);
float getGlyphW(char chr);
float getUSize(char chr);
float getVSize(char chr);
float getShadowOffset();
ResourceLocation getTexture(char chr);
float getYScaleMultiplier();
}
@@ -0,0 +1,299 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
import com.gtnewhorizons.angelica.config.FontConfig;
import lombok.Setter;
import lombok.Value;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.util.ResourceLocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.imageio.ImageIO;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Objects;
public final class FontProviderCustom implements FontProvider {
public static final Logger LOGGER = LogManager.getLogger("Angelica");
public static final String FONT_DIR = "fonts/custom/";
static final int ATLAS_SIZE = 128;
static final int ATLAS_COUNT = 512;
private final byte id; // 0 - primary font, 1 - fallback font
private FontAtlas[] fontAtlases = new FontAtlas[ATLAS_COUNT];
private float currentFontQuality = FontConfig.customFontQuality;
@Setter
private Font font;
private FontProviderCustom(byte id) {
this.id = id;
Font[] availableFonts = FontStrategist.getAvailableFonts();
String myFontName = switch (this.id) {
case 0 -> FontConfig.customFontNamePrimary;
case 1 -> FontConfig.customFontNameFallback;
default -> null;
};
if (Objects.equals(myFontName, "(none)")) {
font = null;
return;
}
int fontPos = -1;
for (int i = 0; i < availableFonts.length; i++) {
if (Objects.equals(myFontName, availableFonts[i].getFontName())) {
fontPos = i;
break;
}
}
if (fontPos == -1) {
LOGGER.info("Could not find previously set font \"{}\". ", myFontName);
font = null;
return;
}
font = availableFonts[fontPos].deriveFont(currentFontQuality);
}
private static class InstLoader {
static final FontProviderCustom instance0 = new FontProviderCustom((byte)0);
static final FontProviderCustom instance1 = new FontProviderCustom((byte)1);
}
public static FontProviderCustom getPrimary() { return InstLoader.instance0; }
public static FontProviderCustom getFallback() { return InstLoader.instance1; }
public void reloadFont(int fontID) {
currentFontQuality = FontConfig.customFontQuality;
font = FontStrategist.getAvailableFonts()[fontID].deriveFont(currentFontQuality);
File[] files = new File(getFontDir()).listFiles();
if (files != null) {
for (File f : files) {
if (!Files.isSymbolicLink(f.toPath())) {
f.delete();
}
}
}
TextureManager tm = Minecraft.getMinecraft().getTextureManager();
Map mapTextureObjects = tm.mapTextureObjects;
for (int i = 0; i < ATLAS_COUNT; i++) {
ResourceLocation key = new ResourceLocation(getAtlasResourceName(i));
if (mapTextureObjects.containsKey(key)) {
ITextureObject obj = (ITextureObject) mapTextureObjects.get(key);
TextureUtil.deleteTexture(obj.getGlTextureId());
mapTextureObjects.remove(key);
}
}
fontAtlases = new FontAtlas[ATLAS_COUNT];
}
private String getFontDir() {
return FONT_DIR + "f" + this.id + "/";
}
private String getAtlasFilename(int atlasId) {
return "f" + this.id + "p" + atlasId;
}
String getAtlasResourceName(int atlasId) {
return "minecraft:angelica_c" + getAtlasFilename(atlasId);
}
String getAtlasFullPath(int atlasId) {
return getFontDir() + getAtlasFilename(atlasId) + ".png";
}
@Value
private class GlyphData {
float uStart;
float vStart;
float xAdvance;
float glyphW;
float uSz;
float vSz;
}
private class FontAtlas {
GlyphData[] glyphData = new GlyphData[ATLAS_SIZE];
private ResourceLocation texture;
private final int id;
FontAtlas(int id) {
this.id = id;
}
void construct(Font font) {
float separator = currentFontQuality / 3f;
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
g2d.dispose();
int width = 0;
int actualChars = 0;
for (int i = 0; i < ATLAS_SIZE; i++) {
final char ch = (char)(i + ATLAS_SIZE * this.id);
if (font.canDisplay(ch)) {
width += (int) (separator + fm.charWidth(ch));
actualChars++;
}
}
if (actualChars == 0) {
return;
}
final int atlasTilesX = (int) Math.ceil(Math.sqrt(actualChars) * 1.5f);
final int atlasTilesY = (int) Math.ceil((double) actualChars / atlasTilesX);
LOGGER.info("constructing custom font atlas (ID {}) of nominal size {} chars, actual: {}, {} rows by {} columns",
this.id, ATLAS_SIZE, actualChars, atlasTilesX, atlasTilesY);
width = 0;
actualChars = 0;
int maxRowWidth = 0;
for (int i = 0; i < ATLAS_SIZE; i++) {
if (actualChars % atlasTilesX == 0) {
maxRowWidth = Math.max(maxRowWidth, width);
width = 0;
}
final char ch = (char)(i + ATLAS_SIZE * this.id);
if (font.canDisplay(ch)) {
width += (int) (separator + fm.charWidth(ch));
actualChars++;
}
}
maxRowWidth = Math.max(maxRowWidth, width);
final int lineHeight = fm.getHeight();
final float desc = fm.getDescent();
final int imageWidth = (int) (maxRowWidth + separator);
final int imageHeight = (int) ((separator + lineHeight) * atlasTilesY + separator);
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setFont(font);
fm = g2d.getFontMetrics();
int tileX = 0, tileY = 0; // position in atlas tiles
int imgX = (int) separator; // position in pixels
for (int i = 0; i < ATLAS_SIZE; i++) {
final char ch = (char)(i + ATLAS_SIZE * this.id);
if (!font.canDisplay(ch)) { continue; }
if (tileX >= atlasTilesX) {
tileX = 0;
imgX = (int) separator;
tileY++;
}
final int charWidth = fm.charWidth(ch);
final float charAspectRatio = (float) charWidth / lineHeight;
final float inset = currentFontQuality / 16;
g2d.drawString(Character.toString(ch), imgX, (lineHeight + separator) * (tileY + 1) - desc);
final float uStart = (float) (imgX - inset * charAspectRatio) / imageWidth;
final float vStart = ((lineHeight + separator) * (tileY + 1) - lineHeight - inset) / imageHeight;
final float xAdvance = charAspectRatio * 8 * charWidth / (charWidth + 2 * inset * charAspectRatio);
final float glyphW = charAspectRatio * 8 + 1;
final float uSz = (float) (charWidth + 2 * inset * charAspectRatio) / imageWidth;
final float vSz = (float) (lineHeight + 2 * inset) / imageHeight;
glyphData[i] = new GlyphData(uStart, vStart, xAdvance, glyphW, uSz, vSz);
imgX += (int) (charWidth + separator);
tileX++;
}
g2d.dispose();
try {
LOGGER.info("writing custom font atlas texture ({}x{} px) to {}", imageWidth, imageHeight, getAtlasFullPath(this.id));
Files.createDirectories(Paths.get(getFontDir()));
ImageIO.write(image, "png", new File(getAtlasFullPath(this.id)));
} catch (IOException e) {
e.printStackTrace();
}
this.texture = new ResourceLocation(getAtlasResourceName(this.id));
}
}
private FontAtlas getAtlas(char chr) {
int id = chr / ATLAS_SIZE;
FontAtlas fa = fontAtlases[id];
if (fa == null) {
fa = new FontAtlas(id);
fa.construct(font);
}
fontAtlases[id] = fa;
return fa;
}
@Override
public boolean isGlyphAvailable(char chr) {
if (font == null) { return false; }
return (getAtlas(chr).glyphData[chr % ATLAS_SIZE] != null);
}
@Override
public char getRandomReplacement(char chr) {
return chr;
}
@Override
public float getUStart(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uStart;
}
@Override
public float getVStart(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vStart;
}
@Override
public float getXAdvance(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].xAdvance * FontConfig.customFontScale;
}
@Override
public float getGlyphW(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].glyphW * FontConfig.customFontScale;
}
@Override
public float getUSize(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uSz;
}
@Override
public float getVSize(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vSz;
}
@Override
public float getShadowOffset() {
return FontConfig.fontShadowOffset;
}
@Override
public ResourceLocation getTexture(char chr) {
return getAtlas(chr).texture;
}
@Override
public float getYScaleMultiplier() {
return FontConfig.customFontScale;
}
}
@@ -0,0 +1,111 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
import jss.util.RandomXoshiro256StarStar;
import net.minecraft.util.ResourceLocation;
public final class FontProviderMC implements FontProvider {
/**
* The full list of characters present in the default Minecraft font, excluding the Unicode font
*/
@SuppressWarnings("UnnecessaryUnicodeEscape")
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u2302\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
private FontProviderMC() {}
private static class InstLoader {
static final FontProviderMC instance = new FontProviderMC();
static final FontProviderMC instanceSGA = new FontProviderMC();
}
public static FontProviderMC get(boolean isSGA) {
return (isSGA ? InstLoader.instanceSGA : InstLoader.instance);
}
private static final Char2ShortOpenHashMap MCFONT_ASCII_MAP = new Char2ShortOpenHashMap();
ResourceLocation locationFontTexture = null;
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
public int[] charWidth;
static {
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
MCFONT_ASCII_MAP.put(MCFONT_CHARS.charAt(i), i);
}
}
public int lookupMcFontPosition(char ch) {
if (isGlyphAvailable(ch)) {
return MCFONT_ASCII_MAP.get(ch);
}
return -1;
}
@Override
public boolean isGlyphAvailable(char chr) {
return MCFONT_ASCII_MAP.containsKey(chr);
}
public char getRandomReplacement(char chr){
int lutIndex = lookupMcFontPosition(chr);
if (lutIndex != -1) {
int randomReplacementIndex;
do {
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
lutIndex = randomReplacementIndex;
return MCFONT_CHARS.charAt(lutIndex);
}
return chr;
}
@Override
public float getUStart(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return ((lutIndex % 16) * 8) / 128.0F;
}
@Override
public float getVStart(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return (float) ((lutIndex / 16) * 8) / 128.0F;
}
@Override
public float getXAdvance(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return charWidth[lutIndex];
}
@Override
public float getGlyphW(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return charWidth[lutIndex] - 0.01F;
}
@Override
public float getUSize(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return (charWidth[lutIndex] - 1.01F) / 128.0F;
}
@Override
public float getVSize(char chr) {
return 7.99F / 128.0F;
}
@Override
public float getShadowOffset() {
return FontConfig.fontShadowOffset;
}
@Override
public ResourceLocation getTexture(char chr) {
return locationFontTexture;
}
@Override
public float getYScaleMultiplier() {
return 1;
}
}
@@ -0,0 +1,98 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import net.minecraft.util.ResourceLocation;
public final class FontProviderUnicode implements FontProvider {
private FontProviderUnicode() {}
private static class InstLoader { static final FontProviderUnicode instance = new FontProviderUnicode(); }
public static FontProviderUnicode get() { return FontProviderUnicode.InstLoader.instance; }
private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[256];
public byte[] glyphWidth;
private ResourceLocation getUnicodePageLocation(int page) {
final ResourceLocation lookup = unicodePageLocations[page];
if (lookup == null) {
final ResourceLocation rl = new ResourceLocation(String.format(
"textures/font/unicode_page_%02x.png",
page));
unicodePageLocations[page] = rl;
return rl;
} else {
return lookup;
}
}
@Override
public boolean isGlyphAvailable(char chr) {
return true;
}
@Override
public char getRandomReplacement(char chr) {
return chr;
}
@Override
public float getUStart(char chr) {
final float startColumnF = (float)((this.glyphWidth[chr] >>> 4) & 15);
return ((float) (chr % 16 * 16) + startColumnF + 0.21f) / 256.0f;
}
@Override
public float getVStart(char chr) {
return ((float) ((chr & 255) / 16 * 16) + 0.21f) / 256.0f;
}
@Override
public float getXAdvance(char chr) {
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
final int endColumn = this.glyphWidth[chr] & 15;
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
return (endColumnF - startColumnF) / 2.0F + 1.0F;
}
@Override
public float getGlyphW(char chr) {
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
final int endColumn = this.glyphWidth[chr] & 15;
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
final float chrWidth = endColumnF - startColumnF - 0.02F;
return chrWidth / 2.0f + 1.0f;
}
@Override
public float getUSize(char chr) {
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
final int endColumn = this.glyphWidth[chr] & 15;
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
final float chrWidth = endColumnF - startColumnF - 0.02F;
return (chrWidth - 0.42f) / 256.0f;
}
@Override
public float getVSize(char chr) {
return (16.0f - 0.42f) / 256.0f;
}
@Override
public float getShadowOffset() {
return FontConfig.fontShadowOffset;
}
@Override
public ResourceLocation getTexture(char chr) {
final int uniPage = chr / 256;
return getUnicodePageLocation(uniPage);
}
@Override
public float getYScaleMultiplier() {
return 1;
}
}
@@ -0,0 +1,127 @@
package com.gtnewhorizons.angelica.client.font;
import com.google.common.collect.HashMultiset;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.mixins.interfaces.ResourceAccessor;
import cpw.mods.fml.client.SplashProgress;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.resources.DefaultResourcePack;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.io.File;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
public class FontStrategist {
@Getter
private static final Font[] availableFonts;
public static final Logger LOGGER = LogManager.getLogger("Angelica");
static {
if (GraphicsEnvironment.isHeadless()) {
LOGGER.warn("GraphicsEnvironment.isHeadless() returned true! Custom fonts will be unavailable. This is likely a MacOS issue.");
availableFonts = new Font[0];
} else {
// get available fonts without duplicates (250 copies of dialog.plain need not apply)
Font[] availableFontsDirty = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
HashMap<String, Font> fontSet = new HashMap<>();
HashMultiset<String> duplicates = HashMultiset.create(); // for debugging
for (Font font : availableFontsDirty) {
String fontName = font.getFontName();
if (fontSet.containsKey(fontName)) {
duplicates.add(fontName);
} else {
fontSet.put(fontName, font);
}
}
if (!duplicates.isEmpty()) {
StringBuilder sb = new StringBuilder(duplicates.size() + " duplicate font(s) found in the list reported by Java: ");
for (Iterator<String> iter = duplicates.stream().distinct().iterator(); iter.hasNext(); ) {
String dupe = iter.next();
sb.append(duplicates.count(dupe)).append("x ").append(dupe);
if (iter.hasNext()) {
sb.append(", ");
}
}
sb.append(". Some fonts may be missing from the font selection menu.");
LOGGER.warn(sb.toString());
}
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, availableFonts.length);
}
// create and add the resource pack that provides fonts
HashMap<String, File> packMap = new HashMap<>();
for (int i = 0; i < FontProviderCustom.ATLAS_COUNT; i++) {
packMap.put(FontProviderCustom.getPrimary().getAtlasResourceName(i), new File(FontProviderCustom.getPrimary().getAtlasFullPath(i)));
packMap.put(FontProviderCustom.getFallback().getAtlasResourceName(i), new File(FontProviderCustom.getFallback().getAtlasFullPath(i)));
}
DefaultResourcePack fontResourcePack = new DefaultResourcePack(packMap);
List defaultResourcePacks = ((ResourceAccessor) Minecraft.getMinecraft()).angelica$getDefaultResourcePacks();
defaultResourcePacks.add(fontResourcePack);
Minecraft.getMinecraft().refreshResources();
}
/**
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether or not
SGA is on, if we're in a splash screen, if a font can even display a character in the first place, etc.
*/
public static FontProvider getFontProvider(BatchingFontRenderer me, char chr, boolean customFontEnabled, boolean forceUnicode) {
if (me.isSGA && FontProviderMC.get(true).isGlyphAvailable(chr)) {
return FontProviderMC.get(true);
}
if (customFontEnabled && !me.isSplash) {
FontProvider fp;
fp = FontProviderCustom.getPrimary();
if (fp.isGlyphAvailable(chr)) { return fp; }
fp = FontProviderCustom.getFallback();
if (fp.isGlyphAvailable(chr)) { return fp; }
return FontProviderUnicode.get();
} else {
if (!forceUnicode && FontProviderMC.get(false).isGlyphAvailable(chr)) {
return FontProviderMC.get(false);
} else {
return FontProviderUnicode.get();
}
}
}
public static void reloadCustomFontProviders() {
FontProviderCustom.getPrimary().setFont(null);
FontProviderCustom.getFallback().setFont(null);
for (int i = 0; i < availableFonts.length; i++) {
if (Objects.equals(FontConfig.customFontNamePrimary, availableFonts[i].getFontName())) {
FontProviderCustom.getPrimary().reloadFont(i);
}
if (Objects.equals(FontConfig.customFontNameFallback, availableFonts[i].getFontName())) {
FontProviderCustom.getFallback().reloadFont(i);
}
}
}
public static boolean isSplashFontRendererActive(FontRenderer fontRenderer) {
// noinspection deprecation
boolean active = fontRenderer instanceof SplashProgress.SplashFontRenderer;
try {
Class<?> customSplashClass = Class.forName("gkappa.modernsplash.CustomSplash$SplashFontRenderer");
active = active || customSplashClass.isInstance(fontRenderer);
} catch (ClassNotFoundException ignored) {
}
return active;
}
}
@@ -0,0 +1,561 @@
package com.gtnewhorizons.angelica.client.gui;
import com.gtnewhorizon.gtnhlib.config.ConfigException;
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
import com.gtnewhorizons.angelica.client.font.FontStrategist;
import com.gtnewhorizons.angelica.config.FontConfig;
import net.coderbot.iris.gui.element.widget.IrisButton;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiSlot;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.MathHelper;
import org.lwjgl.input.Keyboard;
import java.awt.Font;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class FontConfigScreen extends GuiScreen {
private static final Font[] availableFonts = FontStrategist.getAvailableFonts();
private final GuiScreen parent;
private final String title;
private final String searchPrompt;
private final String testAreaPrompt;
private List<String> testAreaInfo;
private List<String> noFontsWarning;
private String currentPrimaryFontName;
private String currentFallbackFontName;
private FontList fontList;
private int selectedPrimaryFontListPos = -1;
private int selectedFallbackFontListPos = -1;
private ArrayList<Font> displayedFonts;
private GuiTextField searchBox;
private GuiTextField testArea;
private int sliderPages = 0;
private int displayedSliderPage = 0;
private final int sliderWidth = 160;
private final int sliderHeight = 20;
// need to save these separately
private IrisButton fwdButton;
private IrisButton backButton;
SliderClone.Option optFontQuality = new SliderClone.Option(6, 72, 6);
SliderClone.Option optShadowOffset = new SliderClone.Option(0, 2, 0.05f);
SliderClone.Option optShadowCopies = new SliderClone.Option(1, 8, 1);
SliderClone.Option optBoldCopies = new SliderClone.Option(1, 8, 1);
SliderClone.Option optGlyphAspect = new SliderClone.Option(-1, 1, 0.05f);
SliderClone.Option optGlyphScale = new SliderClone.Option(0.1f, 3, 0.05f);
SliderClone.Option optWhitespaceScale = new SliderClone.Option(0.1f, 3, 0.05f);
SliderClone.Option optGlyphSpacing = new SliderClone.Option(-2f, 2f, 0.05f);
SliderClone.Option optFontAAMode = new SliderClone.Option(0, 2, 1);
SliderClone.Option optFontAAStrength = new SliderClone.Option(1, 24, 1);
SliderClone.Option optCustomFontScale = new SliderClone.Option(0.1f, 3, 0.05f);
public FontConfigScreen(GuiScreen parent) {
this.title = I18n.format("options.angelica.fontconfig.title");
this.searchPrompt = I18n.format("options.angelica.fontconfig.searchprompt");
this.testAreaPrompt = I18n.format("options.angelica.fontconfig.testareaprompt");
this.parent = parent;
this.currentPrimaryFontName = FontConfig.customFontNamePrimary;
this.currentFallbackFontName = FontConfig.customFontNameFallback;
this.displayedFonts = new ArrayList<>(Arrays.asList(availableFonts));
for (int i = 0; i < availableFonts.length; i++) {
if (Objects.equals(this.currentPrimaryFontName, availableFonts[i].getFontName())) {
selectedPrimaryFontListPos = i;
}
if (Objects.equals(this.currentFallbackFontName, availableFonts[i].getFontName())) {
selectedFallbackFontListPos = i;
}
}
}
public void initGui() {
Keyboard.enableRepeatEvents(true);
searchBox = new GuiTextField(this.fontRendererObj, this.width / 2 - 120, 24, 240, 20);
searchBox.setMaxStringLength(64);
fontList = new FontList();
testArea = new GuiTextField(this.fontRendererObj, this.width * 5 / 6 - this.width / 7, 63 - 10, this.width * 2 / 7, 20);
testArea.setMaxStringLength(512);
testArea.setText(I18n.format("options.angelica.fontconfig.testareaexample"));
initButtons();
}
private void initButtons() {
final int halfWidth = this.width / 2;
final int sliderSpacing = 4;
final int x1 = halfWidth - sliderWidth - sliderSpacing;
final int x2 = halfWidth;
final int x3 = halfWidth + sliderWidth + sliderSpacing;
final int y3 = this.height - 65 + 2 * (sliderHeight + sliderSpacing);
this.buttonList.add(new IrisButton(x1 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
FontConfig.enableCustomFont ? I18n.format("options.angelica.fontconfig.disable_custom_font") :
I18n.format("options.angelica.fontconfig.enable_custom_font"), this::toggleCustomFont));
this.buttonList.add(new IrisButton(x2 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
I18n.format("options.angelica.fontconfig.reset_values"), this::resetValues));
this.buttonList.add(new IrisButton(x3 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
I18n.format("gui.done"), button -> this.onClose()));
backButton = new IrisButton(halfWidth - 20 - 224, this.height - 89 - 10, 40, 20,
"<", button -> this.switchPage(-1));
fwdButton = new IrisButton(halfWidth - 20 + 224, this.height - 89 - 10, 40, 20,
">", button -> this.switchPage(1));
this.buttonList.add(fwdButton);
this.buttonList.add(backButton);
initSliders();
}
private void initSliders() {
ArrayList<SliderClone> sliders = new ArrayList<>();
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optFontQuality)
.initialValue(FontConfig.customFontQuality)
.setter(this::setFontQuality)
.langKey("options.angelica.fontconfig.font_quality")
.formatString("%2.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optShadowOffset)
.initialValue(FontConfig.fontShadowOffset)
.setter(value -> FontConfig.fontShadowOffset = value)
.langKey("options.angelica.fontconfig.shadow_offset")
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optGlyphAspect)
.initialValue(FontConfig.glyphAspect)
.setter(value -> FontConfig.glyphAspect = value)
.langKey("options.angelica.fontconfig.glyph_aspect")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optGlyphScale)
.initialValue(FontConfig.glyphScale)
.setter(value -> FontConfig.glyphScale = value)
.langKey("options.angelica.fontconfig.glyph_scale")
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optFontAAStrength)
.initialValue(FontConfig.fontAAStrength)
.setter(value -> FontConfig.fontAAStrength = value.intValue())
.langKey("options.angelica.fontconfig.font_aa_strength")
.formatString("%.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optFontAAMode)
.initialValue(FontConfig.fontAAMode)
.setter(value -> FontConfig.fontAAMode = value.intValue())
.formatter(this::fontAAModeFormat)
.langKey("options.angelica.fontconfig.aamode")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optWhitespaceScale)
.initialValue(FontConfig.whitespaceScale)
.setter(value -> FontConfig.whitespaceScale = value)
.langKey("options.angelica.fontconfig.whitespace_scale")
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optGlyphSpacing)
.initialValue(FontConfig.glyphSpacing)
.setter(value -> FontConfig.glyphSpacing = value)
.langKey("options.angelica.fontconfig.glyph_spacing")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optShadowCopies)
.initialValue(FontConfig.shadowCopies)
.setter(value -> FontConfig.shadowCopies = value.intValue())
.langKey("options.angelica.fontconfig.shadow_copies")
.formatString("%.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optBoldCopies)
.initialValue(FontConfig.boldCopies)
.setter(value -> FontConfig.boldCopies = value.intValue())
.langKey("options.angelica.fontconfig.bold_copies")
.formatString("%.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optCustomFontScale)
.initialValue(FontConfig.customFontScale)
.setter(value -> FontConfig.customFontScale = value)
.langKey("options.angelica.fontconfig.custom_font_scale")
.formatString("x%3.2f")
.build()
);
final int halfWidth = this.width / 2;
final int sliderSpacing = 4;
final int x1 = halfWidth - sliderWidth - sliderSpacing;
final int x2 = halfWidth;
final int x3 = halfWidth + sliderWidth + sliderSpacing;
final int y1 = this.height - 65;
final int y2 = this.height - 65 + sliderHeight + sliderSpacing;
this.sliderPages = sliders.size() / 6;
this.displayedSliderPage = Math.min(this.displayedSliderPage, this.sliderPages);
this.backButton.enabled = (this.displayedSliderPage != 0);
this.fwdButton.enabled = (this.displayedSliderPage != this.sliderPages);
final int start = 6 * this.displayedSliderPage;
final int end = Math.min(6 * (this.displayedSliderPage + 1), sliders.size());
List<SliderClone> s = sliders.subList(start, end);
try {
s.get(0).setCenterPosition(x1, y1);
s.get(1).setCenterPosition(x1, y2);
s.get(2).setCenterPosition(x2, y1);
s.get(3).setCenterPosition(x2, y2);
s.get(4).setCenterPosition(x3, y1);
s.get(5).setCenterPosition(x3, y2);
} catch (IndexOutOfBoundsException ignored) {
// we're on the last page and it has less than 6 sliders to display
}
this.buttonList.addAll(s);
}
private void onClose() {
applyChanges(true);
this.mc.displayGuiScreen(parent);
}
private void applyChanges(boolean finalApply) {
int pos;
pos = selectedPrimaryFontListPos;
if (pos >= 0 && pos < displayedFonts.size()) {
FontConfig.customFontNamePrimary = displayedFonts.get(pos).getFontName();
}
pos = selectedFallbackFontListPos;
if (pos >= 0 && pos < displayedFonts.size()) {
FontConfig.customFontNameFallback = displayedFonts.get(pos).getFontName();
}
FontStrategist.reloadCustomFontProviders();
if (finalApply) {
ConfigurationManager.save(FontConfig.class);
}
}
private void resetValues(IrisButton button) {
try {
ConfigurationManager.getConfigElements(FontConfig.class).forEach(elem -> elem.set(elem.getDefault()));
} catch (ConfigException e) {
throw new RuntimeException(e);
}
currentPrimaryFontName = null;
currentFallbackFontName = null;
selectedPrimaryFontListPos = -1;
selectedFallbackFontListPos = -1;
super.buttonList.clear();
this.initButtons();
}
private void toggleCustomFont(IrisButton button) {
FontConfig.enableCustomFont = !FontConfig.enableCustomFont;
applyChanges(false);
button.displayString = FontConfig.enableCustomFont ?
I18n.format("options.angelica.fontconfig.disable_custom_font") :
I18n.format("options.angelica.fontconfig.enable_custom_font");
}
private void switchPage(int offset) {
this.displayedSliderPage = MathHelper.clamp_int(this.displayedSliderPage + offset, 0, this.sliderPages);
this.buttonList.removeIf(guiButton -> guiButton instanceof SliderClone);
this.initSliders();
}
private int qualityLast = FontConfig.customFontQuality;
private void setFontQuality(float quality) {
FontConfig.customFontQuality = (int) quality;
if (qualityLast != (int) quality) {
applyChanges(false);
}
qualityLast = (int) quality;
}
private String fontAAModeFormat(float AAmode) {
return switch ((int) AAmode) {
case 2 -> I18n.format("options.angelica.fontconfig.aamode.aa_16x");
case 1 -> I18n.format("options.angelica.fontconfig.aamode.aa_4x");
default -> I18n.format("options.angelica.fontconfig.aamode.aa_none");
};
}
private float lastMouseX = 0;
private float lastMouseY = 0;
private long lastStillTime = 0;
@Override
public void drawScreen(int mouseX, int mouseY, float delta) {
drawBackground(0);
this.fontList.drawScreen(mouseX, mouseY, delta);
if (availableFonts.length == 0) {
if (this.noFontsWarning == null) {
this.noFontsWarning = this.fontRendererObj.listFormattedStringToWidth(
I18n.format("options.angelica.fontconfig.no_fonts_warning"), this.width / 2
);
}
int y = 80;
for (String s : this.noFontsWarning) {
drawCenteredString(this.fontRendererObj, s, this.width / 3, y, 0xFFFFFF);
y += this.fontRendererObj.FONT_HEIGHT;
}
}
this.searchBox.drawTextBox();
if (!this.searchBox.isFocused() && this.searchBox.getText().isEmpty()) {
this.drawCenteredString(this.fontRendererObj, this.searchPrompt,
this.searchBox.xPosition + this.fontRendererObj.getStringWidth(this.searchPrompt) / 2 + 4,
this.searchBox.yPosition + this.searchBox.height / 2 - 4, 0xFFFFFF);
}
drawCenteredString(this.fontRendererObj, this.title, (int) (this.width * 0.5), 8, 0xFFFFFF);
drawCenteredString(this.fontRendererObj, I18n.format("options.angelica.fontconfig.currentfonts",
FontConfig.customFontNamePrimary, FontConfig.customFontNameFallback), (int) (this.width * 0.5), this.height - 92, 0xFFFFFF);
if (this.testAreaInfo == null) {
this.testAreaInfo = this.fontRendererObj.listFormattedStringToWidth(
I18n.format("options.angelica.fontconfig.testareainfo"), this.width / 4
);
}
int cumulativeY = 73;
for (String s : this.testAreaInfo) {
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
}
cumulativeY += 4;
this.testArea.yPosition = cumulativeY;
this.testArea.drawTextBox();
if (!this.testArea.isFocused() && this.testArea.getText().isEmpty()) {
this.drawCenteredString(this.fontRendererObj, this.testAreaPrompt,
this.testArea.xPosition + this.fontRendererObj.getStringWidth(this.testAreaPrompt) / 2 + 4,
this.testArea.yPosition + this.testArea.height / 2 - 4, 0xFFFFFF);
}
List<String> testText = this.fontRendererObj.listFormattedStringToWidth(
this.testArea.getText().replace('&', '§'), this.width / 4
);
cumulativeY += this.testArea.height + 4;
for (String s : testText) {
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
}
super.drawScreen(mouseX, mouseY, delta);
for (GuiButton guiButton : buttonList) {
if (!(guiButton instanceof SliderClone slider)) { continue; }
final int top = slider.yPosition;
final int bot = slider.yPosition + slider.height;
final int left = slider.xPosition;
final int right = slider.xPosition + slider.width;
if (mouseY < top || mouseY >= bot || mouseX < left || mouseX >= right) { continue; }
if (mouseX == lastMouseX && mouseY == lastMouseY) {
if (lastStillTime == 0) {
lastStillTime = System.currentTimeMillis();
}
if (lastStillTime + 500L < System.currentTimeMillis()) {
displayTooltip(mouseX, mouseY, slider.tooltipKey);
}
} else {
lastStillTime = 0;
}
break;
}
lastMouseX = mouseX;
lastMouseY = mouseY;
}
private void displayTooltip(int x, int y, String langKey) {
List<String> lines = this.fontRendererObj.listFormattedStringToWidth(I18n.format(langKey), this.width / 2);
this.drawHoveringText(lines, x, y, this.fontRendererObj);
}
@Override
protected void actionPerformed(GuiButton guiButton) {
if (guiButton.enabled) {
if (guiButton instanceof IrisButton irisButton) {
irisButton.onPress();
}
}
}
@Override
protected void keyTyped(char typedChar, int keyCode) {
if (this.searchBox.isFocused()) {
if (keyCode == Keyboard.KEY_ESCAPE) {
this.searchBox.setFocused(false);
return;
}
this.searchBox.textboxKeyTyped(typedChar, keyCode);
this.displayedFonts = filterFonts(this.searchBox.getText().toLowerCase());
}
if (this.testArea.isFocused()) {
if (keyCode == Keyboard.KEY_ESCAPE) {
this.testArea.setFocused(false);
return;
}
this.testArea.textboxKeyTyped(typedChar, keyCode);
}
if (keyCode == Keyboard.KEY_ESCAPE) {
this.onClose();
}
}
@Override
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
super.mouseClicked(mouseX, mouseY, mouseButton);
this.searchBox.mouseClicked(mouseX, mouseY, mouseButton);
this.testArea.mouseClicked(mouseX, mouseY, mouseButton);
this.fontList.mouseClicked(mouseX, mouseY, mouseButton);
}
@Override
public void updateScreen() {
this.searchBox.updateCursorCounter();
this.testArea.updateCursorCounter();
}
// this could be more efficient, but it's good enough as is
private ArrayList<Font> filterFonts(String search) {
ArrayList<Font> results;
if (search == null || search.isEmpty()) {
results = new ArrayList<>(Arrays.asList(availableFonts));
} else {
results = Arrays.stream(availableFonts).filter((font -> font.getFontName().toLowerCase().contains(search))).collect(Collectors.toCollection(ArrayList::new));
}
selectedPrimaryFontListPos = -1;
selectedFallbackFontListPos = -1;
for (int i = 0; i < results.size(); i++) {
if (Objects.equals(currentPrimaryFontName, results.get(i).getFontName())) {
selectedPrimaryFontListPos = i;
}
if (Objects.equals(currentFallbackFontName, results.get(i).getFontName())) {
selectedFallbackFontListPos = i;
}
}
return results;
}
class FontList extends GuiSlot {
public FontList() {
super(FontConfigScreen.this.mc, FontConfigScreen.this.width * 2 / 3, FontConfigScreen.this.height, 52, FontConfigScreen.this.height - 102, 18);
}
private void mouseClicked(int mouseX, int mouseY, int mouseButton) {
if (mouseY < this.top || mouseY > this.bottom) { return; }
int listLeftBound = this.width / 2 - this.getListWidth() / 2;
int listRightBound = this.width / 2 + this.getListWidth() / 2;
if (mouseX < listLeftBound || mouseX > listRightBound) { return; }
int mousePosInList = mouseY - this.top - this.headerPadding + (int)this.amountScrolled - 4;
int slotIndex = mousePosInList / this.slotHeight;
if (slotIndex < 0 || slotIndex >= this.getSize()) { return; }
if (mouseButton == 0) {
onElemClicked(slotIndex, false);
} else if (mouseButton == 1) {
onElemClicked(slotIndex, true);
}
}
protected void onElemClicked(int index, boolean rightClick) {
if (!rightClick) {
selectedPrimaryFontListPos = index;
currentPrimaryFontName = displayedFonts.get(index).getFontName();
} else {
selectedFallbackFontListPos = index;
currentFallbackFontName = displayedFonts.get(index).getFontName();
}
applyChanges(false);
}
protected boolean isSelected(int index) {
return (index == selectedPrimaryFontListPos || index == selectedFallbackFontListPos);
}
protected int getSize() {
return displayedFonts.size();
}
@Override
protected int getContentHeight() {
return this.getSize() * 18;
}
@Override
protected int getScrollBarX() {
return this.width * 15 / 16 - 5;
}
@Override
public int getListWidth() {
return this.width * 2 / 3;
}
// see onElemClicked for the proper version that supports right-clicking
protected void elementClicked(int index, boolean doubleClicked, int mouseX, int mouseY) {}
protected void drawBackground() {
// do nothing, the font list doesn't occupy the entire screen
}
protected void drawSlot(int index, int x, int y, int p_148126_4_, Tessellator tessellator, int p_148126_6_, int p_148126_7_) {
int color = 0xffffff;
if (index == selectedPrimaryFontListPos) {
color &= 0xffff55;
}
if (index == selectedFallbackFontListPos) {
color &= 0x55ffff;
}
drawCenteredString(fontRendererObj, displayedFonts.get(index).getFontName(), this.width / 2, y + 1, color);
}
}
}
@@ -0,0 +1,164 @@
package com.gtnewhorizons.angelica.client.gui;
import lombok.AllArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.resources.I18n;
import org.lwjgl.opengl.GL11;
import java.util.function.Consumer;
import java.util.function.Function;
public class SliderClone extends GuiButton {
private final Function<Float, String> displayStringFormatter;
private final Consumer<Float> valueSetter;
private final Option option;
private float sliderPercentage;
public final String tooltipKey;
public boolean mouseDragging;
@AllArgsConstructor
public static class Option {
float min;
float max;
float step;
float denormalize(float alpha) {
float lerp = min * (1 - alpha) + max * alpha;
return Math.round(lerp / step) * step;
}
float normalize(float value) {
return (value - min) / (max - min);
}
}
public SliderClone(int x, int y, int width, int height, Option option, float initialValue,
Consumer<Float> setter, Function<Float, String> formatter, String tooltipKey) {
super(0, x, y, width, height, "");
this.option = option;
this.sliderPercentage = option.normalize(initialValue);
this.displayString = formatter.apply(initialValue);
this.valueSetter = setter;
this.displayStringFormatter = formatter;
this.tooltipKey = tooltipKey;
}
public static SliderCloneBuilder builder() {
return new SliderCloneBuilder();
}
public void setCenterPosition(int x, int y) {
this.xPosition = x - this.width / 2;
this.yPosition = y - this.height / 2;
}
@Accessors(fluent = true) @Setter
public static class SliderCloneBuilder {
private int x = 0, y = 0; // center position, not a corner
private int width = 144, height = 20;
private Option option;
private float initialValue;
private Consumer<Float> setter;
private String formatString = "%3.2f";
private String langKey;
// overrides: if null, these get set to default values
private String tooltipKey;
private Function<Float, String> formatter;
public SliderClone build() {
if (this.formatter == null) {
this.formatter = value -> I18n.format(this.langKey, String.format(this.formatString, value));
}
if (this.tooltipKey == null) {
this.tooltipKey = this.langKey + ".tooltip";
}
return new SliderClone(
this.x - this.width / 2,
this.y - this.height / 2,
this.width,
this.height,
this.option,
this.initialValue,
this.setter,
this.formatter,
this.tooltipKey
);
}
}
/**
* Returns 0 if the button is disabled, 1 if the mouse is NOT hovering over this button and 2 if it IS hovering over
* this button.
*/
public int getHoverState(boolean mouseOver) {
return 0;
}
/**
* Fired when the mouse button is dragged. Equivalent of MouseListener.mouseDragged(MouseEvent e).
*/
protected void mouseDragged(Minecraft mc, int mouseX, int mouseY) {
if (this.visible) {
if (this.mouseDragging) {
this.sliderPercentage = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8);
if (this.sliderPercentage < 0.0F) {
this.sliderPercentage = 0.0F;
}
if (this.sliderPercentage > 1.0F) {
this.sliderPercentage = 1.0F;
}
float f = this.option.denormalize(this.sliderPercentage);
valueSetter.accept(f);
this.sliderPercentage = this.option.normalize(f);
this.displayString = displayStringFormatter.apply(f);
}
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)), this.yPosition, 0, 66, 4, 20);
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)) + 4, this.yPosition, 196, 66, 4, 20);
}
}
/**
* Returns true if the mouse has been pressed on this control. Equivalent of MouseListener.mousePressed(MouseEvent e).
*/
public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) {
if (super.mousePressed(mc, mouseX, mouseY)) {
this.sliderPercentage = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8);
if (this.sliderPercentage < 0.0F) {
this.sliderPercentage = 0.0F;
}
if (this.sliderPercentage > 1.0F) {
this.sliderPercentage = 1.0F;
}
float f = this.option.denormalize(this.sliderPercentage);
valueSetter.accept(f);
this.sliderPercentage = this.option.normalize(f);
this.displayString = displayStringFormatter.apply(f);
this.mouseDragging = true;
return true;
} else {
return false;
}
}
/**
* Fired when the mouse button is released. Equivalent of MouseListener.mouseReleased(MouseEvent e).
*/
public void mouseReleased(int mouseX, int mouseY) {
this.mouseDragging = false;
}
}
@@ -0,0 +1,94 @@
package com.gtnewhorizons.angelica.commands;
// Debug commands adapted from Beddium by Ven and FalsePattern
import com.gtnewhorizons.angelica.debug.ChunkDebugMinimap;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
import net.minecraft.command.CommandBase;
import net.minecraft.command.ICommand;
import net.minecraft.command.ICommandSender;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class AngelicaCommand extends CommandBase {
private static final List<String> SUBCOMMANDS = Arrays.asList("wireframe", "fog", "minimap", "help");
@Override
public String getCommandName() {
return "angelica";
}
@Override
public String getCommandUsage(ICommandSender sender) {
return "/angelica <wireframe|fog|minimap|help>";
}
@Override
public int getRequiredPermissionLevel() {
return 0; // Available to all players (client-side only)
}
@Override
public List<String> addTabCompletionOptions(ICommandSender sender, String[] args) {
if (args.length == 1) {
return getListOfStringsMatchingLastWord(args, SUBCOMMANDS.toArray(new String[0]));
}
return new ArrayList<>();
}
@Override
public void processCommand(ICommandSender sender, String[] args) {
if (args.length == 0) {
sendHelp(sender);
return;
}
switch (args[0].toLowerCase()) {
case "wireframe" -> handleWireframe(sender);
case "fog" -> handleFog(sender);
case "minimap" -> handleMinimap(sender);
default -> sendHelp(sender);
}
}
private void handleWireframe(ICommandSender sender) {
CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE = !CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE;
final String state = CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE ? "ON" : "OFF";
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Wireframe mode: " + state));
}
private void handleFog(ICommandSender sender) {
CeleritasDebugScreenHandler.showFogDebug = !CeleritasDebugScreenHandler.showFogDebug;
final String f3State = CeleritasDebugScreenHandler.showFogDebug ? "ON" : "OFF";
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Fog debug (F3): " + f3State));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " " + CeleritasDebugScreenHandler.getFogDebugString()));
}
private void handleMinimap(ICommandSender sender) {
ChunkDebugMinimap.toggle();
final String state = ChunkDebugMinimap.isEnabled() ? "ON" : "OFF";
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Chunk debug minimap: " + state));
}
private void sendHelp(ICommandSender sender) {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] Debug Commands:"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica wireframe" + EnumChatFormatting.WHITE + " - Toggle wireframe rendering"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica fog" + EnumChatFormatting.WHITE + " - Toggle fog debug on F3"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica minimap" + EnumChatFormatting.WHITE + " - Toggle chunk debug overlay"));
}
@Override
public int compareTo(Object o) {
if (o instanceof ICommand cmd) {
return this.getCommandName().compareTo(cmd.getCommandName());
}
return 0;
}
}
@@ -26,17 +26,7 @@ public class BlockError extends Block {
public BlockError() {
super(Material.rock);
MinecraftForge.EVENT_BUS.register(this);
}
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void onTextureStitch(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() == 0) {
for (int i = 0; i < icons.length; i++) {
icons[i] = event.map.registerIcon("angelica:error_block_" + i);
}
}
MinecraftForge.EVENT_BUS.register(new EventHandler());
}
@Override
@@ -45,4 +35,15 @@ public class BlockError extends Block {
return meta >= 0 && meta < icons.length ? icons[meta] : null;
}
public static class EventHandler{
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void onTextureStitch(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() == 0) {
for (int i = 0; i < icons.length; i++) {
icons[i] = event.map.registerIcon("angelica:error_block_" + i);
}
}
}
}
}
@@ -1,34 +1,20 @@
package com.gtnewhorizons.angelica.common;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadProvider;
import com.gtnewhorizon.gtnhlib.client.renderer.util.DirectionUtil;
import com.gtnewhorizons.angelica.mixins.interfaces.ModeledBlock;
import com.gtnewhorizons.angelica.utils.AssetLoader;
import lombok.Getter;
import lombok.Setter;
import static com.gtnewhorizon.gtnhlib.client.model.ModelISBRH.JSON_ISBRH_ID;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import org.jetbrains.annotations.NotNull;
import static com.gtnewhorizons.angelica.models.VanillaModels.LECTERN;
public class BlockTest extends Block implements ModeledBlock {
@Getter
@Setter
private QuadProvider model;
/// Unline {@link BlockError}, this *is* registered on both sides. It's intended to test functionality in a SP
/// development environment.
public class BlockTest extends Block {
public BlockTest() {
super(Material.rock);
this.setBlockTextureName("missingno");
this.setModel((world, pos, block, meta, dir, random, color, quadPool) -> {
if (meta < 2 || meta > 5) meta = 2;
return LECTERN.models[meta - 2].getQuads(world, pos, block, meta, dir, random, color, quadPool);
});
super(Material.wood);
setHardness(0.7f);
}
@Override
@@ -36,13 +22,6 @@ public class BlockTest extends Block implements ModeledBlock {
return false;
}
@Override
public void registerBlockIcons(IIconRegister reg) {
for (String s : AssetLoader.testTexs) {
reg.registerIcon(s);
}
}
/**
* Called when a block is placed using its ItemBlock. Args: World, X, Y, Z, side, hitX, hitY, hitZ, block metadata
*/
@@ -50,11 +29,16 @@ public class BlockTest extends Block implements ModeledBlock {
public int onBlockPlaced(@NotNull World worldIn, int x, int y, int z, int side, float subX, float subY, float subZ, int meta) {
// Face NORTH if placed up or down
final ForgeDirection s = DirectionUtil.ALL_DIRECTIONS[side];
final var s = ForgeDirection.getOrientation(side);
if (s == ForgeDirection.UP || s == ForgeDirection.DOWN)
return 2;
// Face the placed side
return side;
}
@Override
public int getRenderType() {
return JSON_ISBRH_ID;
}
}
@@ -1,8 +1,6 @@
package com.gtnewhorizons.angelica.compat;
import com.falsepattern.chunk.api.DataRegistry;
import com.gtnewhorizons.angelica.mixins.interfaces.ExtendedBlockStorageAccessor;
import com.gtnewhorizons.angelica.mixins.interfaces.ExtendedNibbleArray;
import com.gtnewhorizons.neid.mixins.interfaces.IExtendedBlockStorageMixin;
import net.minecraft.world.chunk.Chunk;
@@ -19,7 +17,7 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
}
public ExtendedBlockStorageExt(Chunk chunk, ExtendedBlockStorage storage) {
super(((ExtendedBlockStorageAccessor) storage).getYBase(), storage.getSkylightArray() != null);
super(storage.yBase, storage.getSkylightArray() != null);
if (ModStatus.isChunkAPILoaded) {
if (storage.getSkylightArray() != null) {
@@ -33,7 +31,7 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
System.arraycopy(((IExtendedBlockStorageMixin)(Object)storage).getBlock16BArray(), 0, block16BArray, 0, block16BArray.length);
if(storage.getBlockMSBArray() != null) {
this.setBlockMSBArray(new NibbleArray(block16BArray.length, 4));
copyNibbleArray((ExtendedNibbleArray) storage.getBlockMSBArray(), (ExtendedNibbleArray) this.getBlockMSBArray());
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
}
arrayLen = block16BArray.length;
if (ModStatus.isNEIDMetadataExtended) {
@@ -52,31 +50,31 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
System.arraycopy(storage.getBlockLSBArray(), 0, blockLSBArray, 0, blockLSBArray.length);
if(storage.getBlockMSBArray() != null) {
this.setBlockMSBArray(new NibbleArray(blockLSBArray.length, 4));
copyNibbleArray((ExtendedNibbleArray) storage.getBlockMSBArray(), (ExtendedNibbleArray) this.getBlockMSBArray());
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
}
arrayLen = blockLSBArray.length;
}
if (!ModStatus.isNEIDMetadataExtended) copyNibbleArray((ExtendedNibbleArray) storage.getMetadataArray(), (ExtendedNibbleArray)this.getMetadataArray());
copyNibbleArray((ExtendedNibbleArray) storage.getBlocklightArray(), (ExtendedNibbleArray)this.getBlocklightArray());
if (!ModStatus.isNEIDMetadataExtended) copyNibbleArray(storage.getMetadataArray(), this.getMetadataArray());
copyNibbleArray(storage.getBlocklightArray(), this.getBlocklightArray());
if(storage.getSkylightArray() != null) {
hasSky = true;
if(this.getSkylightArray() == null) {
this.setSkylightArray(new NibbleArray(arrayLen, 4));
}
copyNibbleArray((ExtendedNibbleArray) storage.getSkylightArray(), (ExtendedNibbleArray) this.getSkylightArray());
copyNibbleArray(storage.getSkylightArray(), this.getSkylightArray());
}
}
((ExtendedBlockStorageAccessor) this).setBlockRefCount(((ExtendedBlockStorageAccessor) storage).getBlockRefCount());
this.blockRefCount = storage.blockRefCount;
}
private static void copyNibbleArray(ExtendedNibbleArray srcArray, ExtendedNibbleArray dstArray) {
private static void copyNibbleArray(NibbleArray srcArray, NibbleArray dstArray) {
if (srcArray == null || dstArray == null) {
throw new RuntimeException("NibbleArray is null src: " + (srcArray == null) + " dst: " + (dstArray == null));
}
final byte[] data = srcArray.getData();
System.arraycopy(data, 0, dstArray.getData(), 0, data.length);
final byte[] data = srcArray.data;
System.arraycopy(data, 0, dstArray.data, 0, data.length);
}
}
@@ -1,14 +1,17 @@
package com.gtnewhorizons.angelica.compat;
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.versioning.ArtifactVersion;
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
import mods.battlegear2.Battlegear;
import net.dries007.holoInventory.HoloInventory;
import xonin.backhand.Backhand;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ModStatus {
public static final Logger LOGGER = LogManager.getLogger("ModCompat");
public static BackhandReflectionCompat backhandCompat;
/**
* Mixin Version
*/
@@ -17,7 +20,6 @@ public class ModStatus {
* ASM Version
*/
public static boolean isOldNEIDLoaded;
public static boolean isBetterCrashesLoaded;
public static boolean isNEIDMetadataExtended;
public static boolean isLotrLoaded;
@@ -30,8 +32,14 @@ public class ModStatus {
public static boolean isThaumcraftLoaded;
public static boolean isThaumicHorizonsLoaded;
public static boolean isBaublesLoaded;
public static boolean isFluidLoggedLoaded;
public static void preInit(){
isBackhandLoaded = Loader.isModLoaded("backhand");
if(isBackhandLoaded) {
isBackhandLoaded = BackhandReflectionCompat.isBackhandLoaded();
}
isBetterCrashesLoaded = Loader.isModLoaded("bettercrashes");
isNEIDLoaded = Loader.isModLoaded("neid");
isOldNEIDLoaded = Loader.isModLoaded("notenoughIDs");
@@ -41,17 +49,12 @@ public class ModStatus {
isXaerosMinimapLoaded = Loader.isModLoaded("XaeroMinimap");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
isBattlegearLoaded = Loader.isModLoaded("battlegear2");
isBackhandLoaded = Loader.isModLoaded("backhand");
isThaumcraftLoaded = Loader.isModLoaded("Thaumcraft");
isThaumicHorizonsLoaded = Loader.isModLoaded("ThaumicHorizons");
isBaublesLoaded = Loader.isModLoaded("Baubles");
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
if (isHoloInventoryLoaded){
isHoloInventoryLoaded = new DefaultArtifactVersion("2.4.4-GTNH")
.compareTo(
LoadControllerHelper.getOwningMod(HoloInventory.class).getProcessedVersion()
) <= 0;
}
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
// remove compat with original release of BG2
if (isBattlegearLoaded){
@@ -61,17 +64,9 @@ public class ModStatus {
) <= 0;
}
if (isBackhandLoaded){
isBackhandLoaded = new DefaultArtifactVersion("1.4.1")
.compareTo(
LoadControllerHelper.getOwningMod(Backhand.class).getProcessedVersion()
) <= 0;
}
isNEIDMetadataExtended = false;
if (isNEIDLoaded) {
int majorVersion = Integer.parseInt(Loader.instance().getIndexedModList().get("neid").getVersion().split("\\.")[0]);
final int majorVersion = Integer.parseInt(Loader.instance().getIndexedModList().get("neid").getVersion().split("\\.")[0]);
if (majorVersion >= 2) {
isNEIDMetadataExtended = true;
}
@@ -0,0 +1,80 @@
package com.gtnewhorizons.angelica.compat.backhand;
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
import cpw.mods.fml.relauncher.ReflectionHelper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
public class BackhandReflectionCompat {
private static final String BACKHAND_CLASS = "xonin.backhand.Backhand";
private static final String ITEM_RENDERER_HOOKS_CLASS = "xonin.backhand.client.hooks.ItemRendererHooks";
private static final String BACKHAND_UTILS_CLASS = "xonin.backhand.api.core.BackhandUtils";
private static boolean isLoaded;
private static final MethodHandle renderOffhandReturn;
private static final MethodHandle getOffhandItem;
static {
MethodHandle renderOffhandReturnTemp = null;
MethodHandle getOffhandItemTemp = null;
try {
final Class<?> backhandClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), BACKHAND_CLASS);
final boolean versionCheck = new DefaultArtifactVersion("1.6.9").compareTo(LoadControllerHelper.getOwningMod(backhandClass).getProcessedVersion()) <= 0;
if (versionCheck) {
final MethodHandles.Lookup lookup = MethodHandles.lookup();
final Class<?> itemRendererHooksClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), ITEM_RENDERER_HOOKS_CLASS);
final Class<?> backhandUtilsClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), BACKHAND_UTILS_CLASS);
renderOffhandReturnTemp = lookup.findStatic(itemRendererHooksClass, "renderOffhandReturn", MethodType.methodType(void.class, float.class));
getOffhandItemTemp = lookup.findStatic(backhandUtilsClass, "getOffhandItem", MethodType.methodType(ItemStack.class, EntityPlayer.class));
isLoaded = true;
LOGGER.info("Backhand compat loaded");
}
} catch (Exception e) {
LOGGER.error("Failed to load Backhand compat", e);
isLoaded = false;
}
renderOffhandReturn = renderOffhandReturnTemp;
getOffhandItem = getOffhandItemTemp;
}
public static boolean isBackhandLoaded() {
return isLoaded;
}
public static void renderOffhand(float partialTicks) {
if (isLoaded && renderOffhandReturn != null) {
try {
renderOffhandReturn.invokeExact(partialTicks);
} catch (Error e) {
throw e;
} catch (Throwable t) {
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
isLoaded = false;
}
}
}
public static ItemStack getOffhandItem(EntityPlayer player) {
if (isLoaded && getOffhandItem != null) {
try {
return (ItemStack) getOffhandItem.invokeExact(player);
} catch (Error e) {
throw e;
} catch (Throwable t) {
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
isLoaded = false;
}
}
return null;
}
}
@@ -0,0 +1,15 @@
package com.gtnewhorizons.angelica.compat.battlegear2;
import mods.battlegear2.api.core.IInventoryPlayerBattle;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
/**
* This class exists to isolate classes that exist in Battlegear 2 but not
* in Battlegear 2 for Backhand in order to avoid class loading exceptions.
*/
public class Battlegear2Compat {
public static ItemStack getBattlegear2Offhand(EntityPlayer player) {
return ((IInventoryPlayerBattle) player.inventory).battlegear2$getCurrentOffhandWeapon();
}
}
@@ -27,7 +27,7 @@ public class BetterCrashesCompat implements StateManager.IResettable {
@Override
public void resetState() {
LOGGER.info("Reloading SodiumRenderer");
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).reload();
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).angelica$reload();
LOGGER.info("Resetting GLStateManager");
GLStateManager.reset();
}
@@ -0,0 +1,97 @@
package com.gtnewhorizons.angelica.compat.holoinventory;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
public class HoloInventoryReflectionCompat {
private static final String RENDERER_CLASS = "net.dries007.holoInventory.client.Renderer";
private static boolean isLoaded;
private static final MethodHandle renderEventPostMethod;
private static final MethodHandle renderEventMethod;
private static final MethodHandle angelicaOverrideSetter;
static {
MethodHandle renderEventPostMethodTemp = null;
MethodHandle renderEventMethodTemp = null;
MethodHandle angelicaOverrideSetterTemp = null;
final Object rendererInstance;
final Class<?> rendererClass;
try {
rendererClass = Class.forName(RENDERER_CLASS);
// Get and cache the Renderer.INSTANCE
final Field instanceField = rendererClass.getDeclaredField("INSTANCE");
instanceField.setAccessible(true);
rendererInstance = instanceField.get(null);
final MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
renderEventPostMethodTemp = lookup.findVirtual(rendererClass, "renderEvent", MethodType.methodType(void.class, RenderGameOverlayEvent.Post.class));
} catch (NoSuchMethodException e) {
// Fallback for 2.5.0-GTNH and earlier
renderEventMethodTemp = lookup.findVirtual(rendererClass, "renderEvent", MethodType.methodType(void.class, RenderGameOverlayEvent.class));
}
final Field angelicaOverrideField = rendererClass.getDeclaredField("angelicaOverride");
angelicaOverrideField.setAccessible(true);
angelicaOverrideSetterTemp = lookup.unreflectSetter(angelicaOverrideField);
// Bind the method handles to the instance
if (rendererInstance != null) {
if (renderEventPostMethodTemp != null) {
renderEventPostMethodTemp = renderEventPostMethodTemp.bindTo(rendererInstance);
}
if (renderEventMethodTemp != null) {
renderEventMethodTemp = renderEventMethodTemp.bindTo(rendererInstance);
}
angelicaOverrideSetterTemp = angelicaOverrideSetterTemp.bindTo(rendererInstance);
}
isLoaded = true;
LOGGER.info("Successfully initialized HoloInventory compatibility layer");
} catch (Exception e) {
LOGGER.warn("Failed to initialize HoloInventory compatibility layer", e);
isLoaded = false;
}
renderEventPostMethod = renderEventPostMethodTemp;
renderEventMethod = renderEventMethodTemp;
angelicaOverrideSetter = angelicaOverrideSetterTemp;
}
public static void renderEvent(RenderGameOverlayEvent event) {
if (!isLoaded) return;
try {
if (event instanceof RenderGameOverlayEvent.Post && renderEventPostMethod != null) {
renderEventPostMethod.invokeExact((RenderGameOverlayEvent.Post) event);
} else {
renderEventMethod.invokeExact(event);
}
} catch (Error e) {
throw e;
} catch (Throwable t) {
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
isLoaded = false;
}
}
public static void setAngelicaOverride(boolean value) {
if (!isLoaded) return;
try {
angelicaOverrideSetter.invokeExact(value);
} catch (Error e) {
throw e;
} catch (Throwable t) {
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
isLoaded = false;
}
}
}
@@ -0,0 +1,17 @@
package com.gtnewhorizons.angelica.compat.iris;
/**
* Interface implemented by BiomeGenBase via mixin to cache biome category.
* This avoids recomputing the category every frame for shader uniforms.
*/
public interface BiomeCategoryCache {
/**
* Gets the cached biome category ordinal, or -1 if not yet computed.
*/
int iris$getCachedCategory();
/**
* Sets the cached biome category ordinal.
*/
void iris$setCachedCategory(int category);
}
@@ -0,0 +1,102 @@
package com.gtnewhorizons.angelica.compat.iris;
import net.coderbot.iris.parsing.BiomeCategories;
import net.minecraft.world.biome.BiomeGenBase;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Detects and categorizes biomes from popular modded biome mods using cached class checks.
* Currently supports: BiomesOPlenty (BOP), Realistic World Gen (RWG)
*/
public class ModdedBiomeDetector {
private static boolean initialized = false;
// BiomesOPlenty (BOP) cache - dimension-specific base classes
// BOP 1.7.10 hierarchy: BOPBiome -> BOPEndBiome/BOPNetherBiome/BOPOverworldBiome -> specific biomes
private static Class<?> bopEndBiomeClass = null;
private static Class<?> bopNetherBiomeClass = null;
private static Class<?> bopOceanBiomeClass = null;
// Realistic World Gen (RWG) cache - base biome classes that extend BiomeGenBase
// LinkedHashMap maintains insertion order for deterministic behavior
private static final Map<Class<?>, BiomeCategories> rwgClassMap = new LinkedHashMap<>();
private static synchronized void initialize() {
if (initialized) return;
initialized = true;
initializeBOP();
initializeRWG();
}
private static void initializeBOP() {
try {
bopEndBiomeClass = Class.forName("biomesoplenty.common.biome.BOPEndBiome");
bopNetherBiomeClass = Class.forName("biomesoplenty.common.biome.BOPNetherBiome");
bopOceanBiomeClass = Class.forName("biomesoplenty.common.biome.BOPOceanBiome");
} catch (ClassNotFoundException ignored) {
// BOP not installed
}
}
private static void initializeRWG() {
// RWG adds actual BiomeGenBase subclasses in rwg.biomes.base package
tryAddRWGClass("rwg.biomes.base.BaseBiomeOcean", BiomeCategories.OCEAN);
tryAddRWGClass("rwg.biomes.base.BaseBiomeRiver", BiomeCategories.RIVER);
tryAddRWGClass("rwg.biomes.base.BaseBiomePlains", BiomeCategories.PLAINS);
tryAddRWGClass("rwg.biomes.base.BaseBiomeColdPlains", BiomeCategories.PLAINS);
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotPlains", BiomeCategories.PLAINS);
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotDesert", BiomeCategories.DESERT);
tryAddRWGClass("rwg.biomes.base.BaseBiomeSnowDesert", BiomeCategories.DESERT); // Cold desert, but still desert
tryAddRWGClass("rwg.biomes.base.BaseBiomeJungle", BiomeCategories.JUNGLE);
tryAddRWGClass("rwg.biomes.base.BaseBiomeTropicalIsland", BiomeCategories.JUNGLE); // Tropical islands are jungle-like
tryAddRWGClass("rwg.biomes.base.BaseBiomeRedwood", BiomeCategories.FOREST); // Redwood/Sequoia forests
tryAddRWGClass("rwg.biomes.base.BaseBiomeTemperateForest", BiomeCategories.FOREST);
tryAddRWGClass("rwg.biomes.base.BaseBiomeColdForest", BiomeCategories.TAIGA); // Cold forests are taiga-like
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotForest", BiomeCategories.FOREST);
tryAddRWGClass("rwg.biomes.base.BaseBiomeSnowForest", BiomeCategories.TAIGA); // Snow forests are taiga
}
private static void tryAddRWGClass(String className, BiomeCategories category) {
try {
final Class<?> clazz = Class.forName(className);
rwgClassMap.put(clazz, category);
} catch (ClassNotFoundException ignored) {
// Class not available, RWG not installed or different version
}
}
/**
* Attempts to detect the biome category for modded biomes.
* Returns null if no modded biome detection applies.
*/
public static BiomeCategories detectModdedBiome(BiomeGenBase biome) {
if (!initialized) {
initialize();
}
// BiomesOPlenty detection - dimension-specific classes give us clear categories
if (bopEndBiomeClass != null && bopEndBiomeClass.isInstance(biome)) {
return BiomeCategories.THE_END;
}
if (bopNetherBiomeClass != null && bopNetherBiomeClass.isInstance(biome)) {
return BiomeCategories.NETHER;
}
if (bopOceanBiomeClass != null && bopOceanBiomeClass.isInstance(biome)) {
return BiomeCategories.OCEAN;
}
// For other BOP overworld biomes (e.g., Alps, Arctic, BambooForest),
// fall through to name/property-based detection
// Realistic World Gen detection
for (Map.Entry<Class<?>, BiomeCategories> entry : rwgClassMap.entrySet()) {
if (entry.getKey().isInstance(biome)) {
return entry.getValue();
}
}
return null;
}
}
@@ -1,327 +0,0 @@
package com.gtnewhorizons.angelica.compat.lwjgl;
import static com.gtnewhorizons.angelica.compat.lwjgl.Pointer.BITS64;
import it.unimi.dsi.fastutil.longs.LongPredicate;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
/**
* Backported from LWJGL3 under the BSD 3-Clause "New" or "Revised" License license
*
* <p>This class provides functionality for managing native memory.
*
* <p>All methods in this class will make use of {@link sun.misc.Unsafe} if it's available, for performance. If Unsafe is not available, the fallback
* implementations make use of reflection and, in the worst-case, JNI.</p>
*
* <p>Method names in this class are prefixed with {@code mem} to avoid ambiguities when used with static imports.</p>
*/
public class CompatMemoryUtil {
public static final long NULL = 0;
static final sun.misc.Unsafe UNSAFE;
private static final Class<? extends ByteBuffer> BUFFER_BYTE;
private static final Class<? extends IntBuffer> BUFFER_INT;
private static final Class<? extends FloatBuffer> BUFFER_FLOAT;
private static final long MARK;
private static final long POSITION;
private static final long LIMIT;
private static final long CAPACITY;
private static final long ADDRESS;
static {
ByteBuffer bb = ByteBuffer.allocateDirect(0).order(ByteOrder.nativeOrder());
BUFFER_BYTE = bb.getClass();
BUFFER_INT = bb.asIntBuffer().getClass();
BUFFER_FLOAT = bb.asFloatBuffer().getClass();
UNSAFE = getUnsafeInstance();
MARK = getMarkOffset();
POSITION = getPositionOffset();
LIMIT = getLimitOffset();
CAPACITY = getCapacityOffset();
ADDRESS = getAddressOffset();
}
public static ByteBuffer memReallocDirect(ByteBuffer old, int capacity) {
ByteBuffer newBuf = BufferUtils.createByteBuffer(capacity);
int oldPos = old.position();
old.rewind();
newBuf.put(old);
newBuf.position(Math.min(capacity, oldPos));
return newBuf;
}
public static IntBuffer memReallocDirect(IntBuffer old, int capacity) {
IntBuffer newBuf = BufferUtils.createIntBuffer(capacity);
int oldPos = old.position();
old.rewind();
newBuf.put(old);
newBuf.position(Math.min(capacity, oldPos));
return newBuf;
}
public static FloatBuffer memReallocDirect(FloatBuffer old, int capacity) {
FloatBuffer newBuf = BufferUtils.createFloatBuffer(capacity);
int oldPos = old.position();
old.rewind();
newBuf.put(old);
newBuf.position(Math.min(capacity, oldPos));
return newBuf;
}
/* -------------------------------------
-------------------------------------
UNSAFE MEMORY ACCESS API
-------------------------------------
------------------------------------- */
private static final int FILL_PATTERN_32 = Integer.divideUnsigned(-1, 255);
private static final long FILL_PATTERN_64 = Long.divideUnsigned(-1L, 255L);
/**
* Sets all bytes in a specified block of memory to a fixed value (usually zero).
*
* @param ptr the starting memory address
* @param value the value to set (memSet will convert it to unsigned byte)
* @param bytes the number of bytes to set
*/
public static void memSet(long ptr, int value, long bytes) {
if (/*DEBUG*/ false && (ptr == NULL || bytes < 0)) {
throw new IllegalArgumentException();
}
/*
- Unsafe.setMemory is very slow.
- A custom Java loop is fastest at small sizes, approximately up to 256 bytes.
- The native memset becomes fastest at bigger sizes, when the JNI overhead becomes negligible.
TODO: verify this
*/
//UNSAFE.setMemory(ptr, bytes, (byte)(value & 0xFF));
//if (bytes < 256L) {
int p = (int)ptr;
if (BITS64) {
if ((p & 7) == 0) {
memSet64(ptr, value, (int)bytes & 0xFF);
return;
}
} else {
if ((p & 3) == 0) {
memSet32(p, value, (int)bytes & 0xFF);
return;
}
}
//}
//nmemset(ptr, value, bytes);
}
private static void memSet64(long ptr, int value, int bytes) {
int aligned = bytes & ~7;
// Aligned body
long valuel = (value & 0xFF) * FILL_PATTERN_64;
for (int i = 0; i < aligned; i += 8) {
UNSAFE.putLong(null, ptr + i, valuel);
}
// Unaligned tail
byte valueb = (byte)(value & 0xFF);
for (int i = aligned; i < bytes; i++) {
UNSAFE.putByte(null, ptr + i, valueb);
}
}
private static void memSet32(int ptr, int value, int bytes) {
int aligned = bytes & ~3;
// Aligned body
int vi = (value & 0xFF) * FILL_PATTERN_32;
for (int i = 0; i < aligned; i += 4) {
UNSAFE.putInt(null, (ptr + i) & 0xFFFF_FFFFL, vi);
}
// Unaligned tail
byte vb = (byte)(value & 0xFF);
for (int i = aligned; i < bytes; i++) {
UNSAFE.putByte(null, (ptr + i) & 0xFFFF_FFFFL, vb);
}
}
public static boolean memGetBoolean(long ptr) { return UNSAFE.getByte(null, ptr) != 0; }
public static byte memGetByte(long ptr) { return UNSAFE.getByte(null, ptr); }
public static short memGetShort(long ptr) { return UNSAFE.getShort(null, ptr); }
public static int memGetInt(long ptr) { return UNSAFE.getInt(null, ptr); }
public static long memGetLong(long ptr) { return UNSAFE.getLong(null, ptr); }
public static float memGetFloat(long ptr) { return UNSAFE.getFloat(null, ptr); }
public static double memGetDouble(long ptr) { return UNSAFE.getDouble(null, ptr); }
public static void memPutByte(long ptr, byte value) { UNSAFE.putByte(null, ptr, value); }
public static void memPutShort(long ptr, short value) { UNSAFE.putShort(null, ptr, value); }
public static void memPutInt(long ptr, int value) { UNSAFE.putInt(null, ptr, value); }
public static void memPutLong(long ptr, long value) { UNSAFE.putLong(null, ptr, value); }
public static void memPutFloat(long ptr, float value) { UNSAFE.putFloat(null, ptr, value); }
public static void memPutDouble(long ptr, double value) { UNSAFE.putDouble(null, ptr, value); }
// -------------------------------------------------
// -------------------------------------------------
// -------------------------------------------------
private static sun.misc.Unsafe getUnsafeInstance() {
java.lang.reflect.Field[] fields = sun.misc.Unsafe.class.getDeclaredFields();
/*
Different runtimes use different names for the Unsafe singleton,
so we cannot use .getDeclaredField and we scan instead. For example:
Oracle: theUnsafe
PERC : m_unsafe_instance
Android: THE_ONE
*/
for (java.lang.reflect.Field field : fields) {
if (!field.getType().equals(sun.misc.Unsafe.class)) {
continue;
}
int modifiers = field.getModifiers();
if (!(java.lang.reflect.Modifier.isStatic(modifiers) && java.lang.reflect.Modifier.isFinal(modifiers))) {
continue;
}
try {
field.setAccessible(true);
return (sun.misc.Unsafe)field.get(null);
} catch (Exception ignored) {
}
break;
}
throw new UnsupportedOperationException("LWJGL requires sun.misc.Unsafe to be available.");
}
/**
* Recursively searches an object and its superclasses for a field with the same name and type. If the field isn't
* public, it may not be stable across library/JVM versions, and these calls are likely not portable. You have been
* warned!
*/
private static long getFieldOffset(Class<?> containerType, Class<?> fieldType, String name) {
Class<?> c = containerType;
while (c != Object.class) {
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
if (!field.getType().isAssignableFrom(fieldType) || Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) {
continue;
}
long offset = UNSAFE.objectFieldOffset(field);
if (field.getName().equals(name)) {
return offset;
}
}
c = c.getSuperclass();
}
throw new UnsupportedOperationException("Failed to find field offset in class.");
}
private static long getFieldOffset(Class<?> containerType, Class<?> fieldType, LongPredicate predicate) {
Class<?> c = containerType;
while (c != Object.class) {
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
if (!field.getType().isAssignableFrom(fieldType) || Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) {
continue;
}
long offset = UNSAFE.objectFieldOffset(field);
if (predicate.test(offset)) {
return offset;
}
}
c = c.getSuperclass();
}
throw new UnsupportedOperationException("Failed to find field offset in class.");
}
private static long getFieldOffsetInt(Object container, int value) {
return getFieldOffset(container.getClass(), int.class, offset -> UNSAFE.getInt(container, offset) == value);
}
private static long getAddressOffset() {
final ByteBuffer bb = ByteBuffer.allocateDirect(0);
return getFieldOffset(bb.getClass(), long.class, "address");
// TODO: do this portably
// long MAGIC_ADDRESS = 0xDEADBEEF8BADF00DL & (BITS32 ? 0xFFFF_FFFFL : 0xFFFF_FFFF_FFFF_FFFFL);
// ByteBuffer bb = Objects.requireNonNull(NewDirectByteBuffer(MAGIC_ADDRESS, 0));
//return getFieldOffset(bb.getClass(), long.class, offset -> UNSAFE.getLong(bb, offset) == MAGIC_ADDRESS);
}
private static final int MAGIC_CAPACITY = 0x0D15EA5E;
private static final int MAGIC_POSITION = 0x00FACADE;
private static long getMarkOffset() {
// TODO: is this actually reliable without NewDirectByteBuffer?
ByteBuffer bb = ByteBuffer.allocateDirect(0);
return getFieldOffsetInt(bb, -1);
// ByteBuffer bb = Objects.requireNonNull(NewDirectByteBuffer(1L, 0));
// return getFieldOffsetInt(bb, -1);
}
private static long getPositionOffset() {
ByteBuffer bb = ByteBuffer.allocateDirect(MAGIC_CAPACITY);
bb.position(MAGIC_POSITION);
return getFieldOffsetInt(bb, MAGIC_POSITION);
}
private static long getLimitOffset() {
ByteBuffer bb = ByteBuffer.allocateDirect(MAGIC_CAPACITY);
bb.limit(MAGIC_POSITION);
return getFieldOffsetInt(bb, MAGIC_POSITION);
}
private static long getCapacityOffset() {
ByteBuffer bb = ByteBuffer.allocateDirect(MAGIC_CAPACITY);
bb.limit(0);
return getFieldOffsetInt(bb, MAGIC_CAPACITY);
}
static IntBuffer wrapBufferInt(long address, int capacity) {
IntBuffer buffer;
try {
buffer = (IntBuffer)UNSAFE.allocateInstance(BUFFER_INT);
} catch (InstantiationException e) {
throw new UnsupportedOperationException(e);
}
UNSAFE.putLong(buffer, ADDRESS, address);
UNSAFE.putInt(buffer, MARK, -1);
UNSAFE.putInt(buffer, LIMIT, capacity);
UNSAFE.putInt(buffer, CAPACITY, capacity);
return buffer;
}
static FloatBuffer wrapBufferFloat(long address, int capacity) {
FloatBuffer buffer;
try {
buffer = (FloatBuffer)UNSAFE.allocateInstance(BUFFER_FLOAT);
} catch (InstantiationException e) {
throw new UnsupportedOperationException(e);
}
UNSAFE.putLong(buffer, ADDRESS, address);
UNSAFE.putInt(buffer, MARK, -1);
UNSAFE.putInt(buffer, LIMIT, capacity);
UNSAFE.putInt(buffer, CAPACITY, capacity);
return buffer;
}
}
@@ -1,382 +0,0 @@
/*
* This class is backported from LWJGL3 under the BSD 3-clause "New" or "Revised" License
*/
package com.gtnewhorizons.angelica.compat.lwjgl;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutDouble;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutFloat;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutInt;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutLong;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutShort;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memSet;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.wrapBufferFloat;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.wrapBufferInt;
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.BufferUtils;
import org.lwjgl.MemoryUtil;
/**
* An off-heap memory stack.
*
* <p>This class should be used in a thread-local manner for stack allocations.</p>
*
* @ see Configuration#STACK_SIZE
* @ see Configuration#DEBUG_STACK
*/
@SuppressWarnings("LombokGetterMayBeUsed")
public class MemoryStack extends Pointer.Default implements AutoCloseable {
private static final int DEFAULT_STACK_SIZE = /*Configuration.STACK_SIZE.get(64)*/ 64 * 1024;
private static final int DEFAULT_STACK_FRAMES = 8;
private static final ThreadLocal<MemoryStack> TLS = ThreadLocal.withInitial(MemoryStack::create);
static {
if (DEFAULT_STACK_SIZE < 0) {
throw new IllegalStateException("Invalid stack size.");
}
}
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private final @Nullable ByteBuffer container;
private final int size;
private int pointer;
private int[] frames;
protected int frameIndex;
/**
* Creates a new {@code MemoryStack} backed by the specified memory region.
*
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
*
* @param container the backing memory buffer, may be null
* @param address the backing memory address
* @param size the backing memory size
*/
protected MemoryStack(@Nullable ByteBuffer container, long address, int size) {
super(address);
this.container = container;
this.size = size;
this.pointer = size;
this.frames = new int[DEFAULT_STACK_FRAMES];
}
/**
* Creates a new {@code MemoryStack} with the default size.
*
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
*/
public static MemoryStack create() {
return create(DEFAULT_STACK_SIZE);
}
/**
* Creates a new {@code MemoryStack} with the specified size.
*
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
*
* @param capacity the maximum number of bytes that may be allocated on the stack
*/
public static MemoryStack create(int capacity) {
return create(BufferUtils.createByteBuffer(capacity));
}
/**
* Creates a new {@code MemoryStack} backed by the specified memory buffer.
*
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
*
* @param buffer the backing memory buffer
*/
public static MemoryStack create(ByteBuffer buffer) {
long address = MemoryUtil.getAddress(buffer);
int size = buffer.remaining();
return new MemoryStack(buffer, address, size);
}
/**
* Creates a new {@code MemoryStack} backed by the specified memory region.
*
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
*
* @param address the backing memory address
* @param size the backing memory size
*/
public static MemoryStack ncreate(long address, int size) {
return new MemoryStack(null, address, size);
}
/**
* Stores the current stack pointer and pushes a new frame to the stack.
*
* <p>This method should be called when entering a method, before doing any stack allocations. When exiting a method, call the {@link #pop} method to
* restore the previous stack frame.</p>
*
* <p>Pairs of push/pop calls may be nested. Care must be taken to:</p>
* <ul>
* <li>match every push with a pop</li>
* <li>not call pop before push has been called at least once</li>
* <li>not nest push calls to more than the maximum supported depth</li>
* </ul>
*
* @return this stack
*/
public MemoryStack push() {
if (frameIndex == frames.length) {
frameOverflow();
}
frames[frameIndex++] = pointer;
return this;
}
private void frameOverflow() {
if (/*DEBUG*/ false) {
AngelicaTweaker.LOGGER.warn("[WARNING] Out of frame stack space (" + frames.length + ") in thread: " + Thread.currentThread());
}
frames = Arrays.copyOf(frames, frames.length * 3 / 2);
}
/**
* Pops the current stack frame and moves the stack pointer to the end of the previous stack frame.
*
* @return this stack
*/
public MemoryStack pop() {
pointer = frames[--frameIndex];
return this;
}
/**
* Calls {@link #pop} on this {@code MemoryStack}.
*
* <p>This method should not be used directly. It is called automatically when the {@code MemoryStack} is used as a resource in a try-with-resources
* statement.</p>
*/
@Override
public void close() {
//noinspection resource
pop();
}
/**
* Returns the address of the backing off-heap memory.
*
* <p>The stack grows "downwards", so the bottom of the stack is at {@code address + size}, while the top is at {@code address}.</p>
*/
public long getAddress() {
return address;
}
/**
* Returns the size of the backing off-heap memory.
*
* <p>This is the maximum number of bytes that may be allocated on the stack.</p>
*/
public int getSize() {
return size;
}
/**
* Returns the current frame index.
*
* <p>This is the current number of nested {@link #push} calls.</p>
*/
public int getFrameIndex() {
return frameIndex;
}
/** Returns the memory address at the current stack pointer. */
public long getPointerAddress() {
return address + (pointer & 0xFFFF_FFFFL);
}
/**
* Returns the current stack pointer.
*
* <p>The stack grows "downwards", so when the stack is empty {@code pointer} is equal to {@code size}. On every allocation {@code pointer} is reduced by
* the allocated size (after alignment) and {@code address + pointer} points to the first byte of the last allocation.</p>
*
* <p>Effectively, this methods returns how many more bytes may be allocated on the stack.</p>
*/
public int getPointer() {
return pointer;
}
/**
* Sets the current stack pointer.
*
* <p>This method directly manipulates the stack pointer. Using it irresponsibly may break the internal state of the stack. It should only be used in rare
* cases or in auto-generated code.</p>
*/
public void setPointer(int pointer) {
if (/*CHECKS*/ false) {
checkPointer(pointer);
}
this.pointer = pointer;
}
private void checkPointer(int pointer) {
if (pointer < 0 || size < pointer) {
throw new IndexOutOfBoundsException("Invalid stack pointer");
}
}
private static void checkAlignment(int alignment) {
if (Integer.bitCount(alignment) != 1) {
throw new IllegalArgumentException("Alignment must be a power-of-two value.");
}
}
/**
* Calls {@link #nmalloc(int, int)} with {@code alignment} equal to {@link Pointer#POINTER_SIZE POINTER_SIZE}.
*
* @param size the allocation size
*
* @return the memory address on the stack for the requested allocation
*/
public long nmalloc(int size) {
return nmalloc(POINTER_SIZE, size);
}
/**
* Allocates a block of {@code size} bytes of memory on the stack. The content of the newly allocated block of memory is not initialized, remaining with
* indeterminate values.
*
* @param alignment the required alignment
* @param size the allocation size
*
* @return the memory address on the stack for the requested allocation
*/
public long nmalloc(int alignment, int size) {
// Align address to the specified alignment
long address = (this.address + pointer - size) & ~Integer.toUnsignedLong(alignment - 1);
pointer = (int)(address - this.address);
if (/*CHECKS*/ false && pointer < 0) {
throw new OutOfMemoryError("Out of stack space.");
}
return address;
}
/**
* Allocates a block of memory on the stack for an array of {@code num} elements, each of them {@code size} bytes long, and initializes all its bits to
* zero.
*
* @param alignment the required element alignment
* @param num num the number of elements to allocate
* @param size the size of each element
*
* @return the memory address on the stack for the requested allocation
*/
public long ncalloc(int alignment, int num, int size) {
int bytes = num * size;
long address = nmalloc(alignment, bytes);
memSet(address, 0, bytes);
return address;
}
// -------------------------------------------------
/** Unsafe version of {@link #shorts(short)}. */
public long nshort(short value) {
long a = nmalloc(2, 2);
memPutShort(a, value);
return a;
}
// -------------------------------------------------
/** Int version of {@link #malloc(int)}. */
public IntBuffer mallocInt(int size) { return wrapBufferInt(nmalloc(4, size << 2), size); }
/** Unsafe version of {@link #ints(int)}. */
public long nint(int value) {
long a = nmalloc(4, 4);
memPutInt(a, value);
return a;
}
// -------------------------------------------------
/** Unsafe version of {@link #longs(long)}. */
public long nlong(long value) {
long a = nmalloc(8, 8);
memPutLong(a, value);
return a;
}
// -------------------------------------------------
/** Float version of {@link #malloc(int)}. */
public FloatBuffer mallocFloat(int size) { return wrapBufferFloat(nmalloc(4, size << 2), size); }
/** Unsafe version of {@link #floats(float)}. */
public long nfloat(float value) {
long a = nmalloc(4, 4);
memPutFloat(a, value);
return a;
}
// -------------------------------------------------
/** Unsafe version of {@link #doubles(double)}. */
public long ndouble(double value) {
long a = nmalloc(8, 8);
memPutDouble(a, value);
return a;
}
// -------------------------------------------------
// -------------------------------------------------
// -------------------------------------------------
/** Returns the stack of the current thread. */
public static MemoryStack stackGet() {
return TLS.get();
}
/**
* Calls {@link #push} on the stack of the current thread.
*
* @return the stack of the current thread.
*/
public static MemoryStack stackPush() {
return stackGet().push();
}
/**
* Calls {@link #pop} on the stack of the current thread.
*
* @return the stack of the current thread.
*/
public static MemoryStack stackPop() {
return stackGet().pop();
}
/** Thread-local version of {@link #nmalloc(int)}. */
public static long nstackMalloc(int size) { return stackGet().nmalloc(size); }
/** Thread-local version of {@link #nmalloc(int, int)}. */
public static long nstackMalloc(int alignment, int size) { return stackGet().nmalloc(alignment, size); }
/** Thread-local version of {@link #ncalloc}. */
public static long nstackCalloc(int alignment, int num, int size) { return stackGet().ncalloc(alignment, num, size); }
// -------------------------------------------------
}
@@ -1,93 +0,0 @@
/*
* This class is backported from LWJGL3 under the BSD 3-clause "New" or "Revised" License
*/
package com.gtnewhorizons.angelica.compat.lwjgl;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.NULL;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.UNSAFE;
import static org.lwjgl.LWJGLUtil.CHECKS;
import net.minecraft.util.Util;
import org.jetbrains.annotations.Nullable;
/**
* Pointer interface.
*
* <p>LWJGL can run on both 32bit and 64bit architectures. Since LWJGL applications deal with native memory directly, this interface provides necessary
* information about the underlying architecture of the running JVM process.</p>
*
* <p>When interacting with native functions, pointer values are mapped to Java {@code long}. LWJGL automatically converts long values to the correct pointer
* addresses when used in native code. Native functions sometimes require arrays of pointer values; the {@link PointerBuffer} class may be used for that
* purpose. It has an API similar to a {@link java.nio.LongBuffer} but handles pointer casts automatically.</p>
*/
public interface Pointer {
/** The pointer size in bytes. Will be 4 on a 32bit JVM and 8 on a 64bit one. */
int POINTER_SIZE = UNSAFE.addressSize();
/** The pointer size power-of-two. Will be 2 on a 32bit JVM and 3 on a 64bit one. */
int POINTER_SHIFT = POINTER_SIZE == 8 ? 3 : 2;
/** The value of {@code sizeof(long)} for the current platform. */
int CLONG_SIZE = POINTER_SIZE == 8 && Util.getOSType() == Util.EnumOS.WINDOWS ? 4 : POINTER_SIZE;
/** The value of {@code sizeof(long)} as a power-of-two. */
int CLONG_SHIFT = CLONG_SIZE == 8 ? 3 : 2;
/** Will be true on a 32bit JVM. */
boolean BITS32 = POINTER_SIZE * 8 == 32;
/** Will be true on a 64bit JVM. */
boolean BITS64 = POINTER_SIZE * 8 == 64;
/**
* Returns the raw pointer address as a {@code long} value.
*
* @return the pointer address
*/
long address();
/** Default {@link Pointer} implementation. */
abstract class Default implements Pointer {
// Removed final due to JDK-8139758. TODO: Restore if the fix is backported to JDK 8.
protected long address;
protected Default(long address) {
if (CHECKS && address == NULL) {
throw new NullPointerException();
}
this.address = address;
}
@Override
public long address() {
return address;
}
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Pointer)) {
return false;
}
Pointer that = (Pointer)o;
return address == that.address();
}
public int hashCode() {
return (int)(address ^ (address >>> 32));
}
@Override
public String toString() {
return String.format("%s pointer [0x%X]", getClass().getSimpleName(), address);
}
}
}
@@ -15,6 +15,7 @@ public class ByteBufferBackedInputStream extends InputStream {
this.buf = buf;
}
@Override
public int read() throws IOException {
if (!buf.hasRemaining()) {
return -1;
@@ -9,35 +9,62 @@ import net.minecraft.util.MathHelper;
import org.joml.Matrix4f;
import org.joml.Vector3d;
import org.joml.Vector4f;
import org.joml.Vector3f;
/**
* Singleton camera instance that tracks the current camera position and orientation.
*/
@Getter
public class Camera {
final Vector3d pos = new Vector3d();
final BlockPos blockPos = new BlockPos();
float pitch;
float yaw;
EntityLivingBase entity;
boolean thirdPerson;
final float partialTicks;
public static final Camera INSTANCE = new Camera();
public Camera(EntityLivingBase entity, float partialTicks) {
private final Vector3d pos = new Vector3d();
private final BlockPos blockPos = new BlockPos();
private final Vector3f offset = new Vector3f();
private final Matrix4f inverseModelView = new Matrix4f();
private float pitch;
private float yaw;
private EntityLivingBase entity;
private boolean thirdPerson;
private float partialTicks;
private Camera() {
}
/**
* Updates the camera position and orientation based on the current entity and partial ticks.
*
* @param entity The entity to track (usually the render view entity)
* @param partialTicks The partial tick time for interpolation
*/
public void update(EntityLivingBase entity, float partialTicks) {
this.partialTicks = partialTicks;
final Vector4f offset = new Vector4f(); // third person offset
final Matrix4f inverseModelView = new Matrix4f(RenderingState.INSTANCE.getModelViewMatrix()).invert();
inverseModelView.transform(offset);
final double camX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks + offset.x;
final double camY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks + offset.y;
final double camZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks + offset.z;
this.entity = entity;
// Entity position (interpolated, at feet level)
final double entityX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks;
final double entityY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks;
final double entityZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks;
final double eyeHeight = entity.getEyeHeight();
thirdPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView > 0;
final double camX, camY, camZ;
offset.set(0, 0, 0);
if (thirdPerson) {
// Third person: use inverse modelview to find camera offset
inverseModelView.set(RenderingState.INSTANCE.getModelViewMatrix()).invert();
inverseModelView.transformPosition(offset);
}
camX = entityX + offset.x;
camY = entityY + eyeHeight + offset.y;
camZ = entityZ + offset.z;
pos.set(camX, camY, camZ);
blockPos.set(MathHelper.floor_double(camX), MathHelper.floor_double(camY), MathHelper.floor_double(camZ));
pitch = entity.cameraPitch;
yaw = entity.rotationYaw;
thirdPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView == 1;
}
}
@@ -40,6 +40,7 @@ public class ChunkPos {
return (long)x & 4294967295L | ((long)z & 4294967295L) << 32;
}
@Override
public int hashCode()
{
final int i = 1664525 * this.x + 1013904223;
@@ -47,6 +48,7 @@ public class ChunkPos {
return i ^ j;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
@@ -0,0 +1,17 @@
package com.gtnewhorizons.angelica.compat.mojang;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.PlayerControllerMP;
public final class GameModeUtil {
private GameModeUtil() {
}
public static boolean isSpectator() {
final PlayerControllerMP controller = Minecraft.getMinecraft().playerController;
if (controller == null) {
return false;
}
return controller.currentGameType.getID() == 3;
}
}
@@ -1,10 +1,23 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizons.angelica.compat.ModStatus;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
public enum InteractionHand {
MAIN_HAND,
OFF_HAND;
InteractionHand() {
}
public ItemStack getItemInHand(EntityPlayer player){
if (ModStatus.isBackhandLoaded && this == InteractionHand.OFF_HAND){ // off hand (requires backhand)
return ModStatus.backhandCompat.getOffhandItem(player);
}
else { // main hand
return player.getHeldItem();
}
}
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizons.angelica.compat.lwjgl.MemoryStack;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
@@ -8,6 +7,8 @@ import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import javax.imageio.ImageIO;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import lombok.Getter;
import net.coderbot.iris.Iris;
import org.lwjgl.opengl.GL11;
@@ -58,15 +59,20 @@ public class NativeImage extends BufferedImage {
// final int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_WIDTH);
// final int height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_HEIGHT);
try (final MemoryStack stack = MemoryStack.stackPush()) {
final IntBuffer buffer = stack.mallocInt(size);
final IntBuffer buffer = MemoryUtilities.memAllocInt(size);
try {
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, level, format.glFormat, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
int[] data = new int[size];
buffer.get(data);
setRGB(0, 0, width, height, data, 0, width);
} finally {
MemoryUtilities.memFree(buffer);
}
}
public void writeToFile(File file) throws IOException{
try {
ImageIO.write(this, "png", file);
@@ -104,18 +110,15 @@ public class NativeImage extends BufferedImage {
public enum Format {
RGBA(4, GL11.GL_RGBA, BufferedImage.TYPE_INT_ARGB),
RGB(3, GL11.GL_RGB, BufferedImage.TYPE_INT_RGB);
RGBA(4, GL11.GL_RGBA),
RGB(3, GL11.GL_RGB);
private final int components;
private final int glFormat;
private final int imageType;
Format(int components, int glFormat, int imageType) {
Format(int components, int glFormat) {
this.components = components;
this.glFormat = glFormat;
this.imageType = imageType;
}
}
@@ -1,63 +0,0 @@
package com.gtnewhorizons.angelica.compat.nd;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
public class BufferWriter {
// Adapted from Neodymium
private final ByteBuffer buf;
private final FloatBuffer floatBuffer;
private final ShortBuffer shortBuffer;
private final IntBuffer intBuffer;
public BufferWriter(ByteBuffer buf) {
this.buf = buf;
this.floatBuffer = buf.asFloatBuffer();
this.shortBuffer = buf.asShortBuffer();
this.intBuffer = buf.asIntBuffer();
}
private void incrementPosition(int add) {
buf.position(buf.position() + add);
floatBuffer.position(buf.position() / 4);
shortBuffer.position(buf.position() / 2);
intBuffer.position(buf.position() / 4);
}
public void writeFloat(float x) {
try {
floatBuffer.put(x);
incrementPosition(4);
} catch(Exception e){
e.printStackTrace();
}
}
public void writeInt(int x) {
intBuffer.put(x);
incrementPosition(4);
}
public void writeByte(byte x) {
buf.put(x); // this increments the buffer position by 1
incrementPosition(0);
}
public int position() {
return buf.position();
}
public void writeShort(short s) {
shortBuffer.put(s);
incrementPosition(2);
}
}
@@ -1,47 +0,0 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class ItemVBOQuadWriter implements IWriteQuads {
private boolean direct;
public ItemVBOQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
// Texture
buf.putFloat(quad.getTexU(idx));
buf.putFloat(quad.getTexV(idx));
// Normals + Padding
buf.putInt(quad.getNormal(idx));
}
}
}
@@ -1,47 +0,0 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class PositionColorTextureQuadWriter implements IWriteQuads {
private boolean direct;
public PositionColorTextureQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
// Color
buf.putInt(quad.getColor(idx));
// Texture
buf.putFloat(quad.getTexU(idx));
buf.putFloat(quad.getTexV(idx));
}
}
}
@@ -1,40 +0,0 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class PositionQuadWriter implements IWriteQuads {
private boolean direct;
public PositionQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
}
}
}
@@ -1,49 +0,0 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class PositionTextureColorQuadWriter implements IWriteQuads {
private boolean direct;
public PositionTextureColorQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
// Texture
buf.putFloat(quad.getTexU(idx));
buf.putFloat(quad.getTexV(idx));
// Color
buf.putInt(quad.getColor(idx));
}
}
}
@@ -1,44 +0,0 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class PositionTextureQuadWriter implements IWriteQuads {
private boolean direct;
public PositionTextureQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
// Texture
buf.putFloat(quad.getTexU(idx));
buf.putFloat(quad.getTexV(idx));
}
}
}
@@ -34,10 +34,17 @@ public class MatrixStack {
public void pop() {
this.matrixStack.removeLast();
}
public boolean clear() {
public boolean isEmpty() {
return this.matrixStack.size() == 1;
}
public void reset() {
while (this.matrixStack.size() > 1) {
this.matrixStack.removeLast();
}
loadIdentity();
}
public void translate(double d, double e, double f) {
final Entry lv = (Entry)this.matrixStack.getLast();
lv.model.translate((float)d, (float)e, (float)f);
@@ -72,7 +72,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
private final MultiPhaseParameters phases;
private final int hash;
private final Optional<RenderLayer> affectedOutline;
private final boolean outline;
private MultiPhase(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, boolean hasCrumbling, boolean translucent, MultiPhaseParameters phases) {
super(name, vertexFormat, drawMode, expectedBufferSize, () -> {
@@ -84,7 +83,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
this.affectedOutline = phases.outlineMode == RenderLayer.OutlineMode.AFFECTS_OUTLINE ? phases.texture.getId().map((arg2) -> {
return getOutline(arg2, phases.cull);
}) : Optional.empty();
this.outline = phases.outlineMode == RenderLayer.OutlineMode.IS_OUTLINE;
this.hash = Objects.hash(new Object[]{super.hashCode(), phases});
}
@@ -97,20 +95,23 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
return this.affectedOutline;
}
@Override
public boolean equals(@Nullable Object object) {
return this == object;
}
@Override
public int hashCode() {
return this.hash;
}
@Override
public String toString() {
return "RenderType[" + this.phases + ']';
}
static {
CACHE = new ObjectOpenCustomHashSet(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
CACHE = new ObjectOpenCustomHashSet<>(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
}
static enum HashStrategy implements Hash.Strategy<MultiPhase> {
@@ -168,6 +169,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
this.phases = ImmutableList.of(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.fog, this.layering, this.target, this.texturing, this.writeMaskState);
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
@@ -179,10 +181,12 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
}
}
@Override
public int hashCode() {
return Objects.hash(new Object[]{this.phases, this.outlineMode});
}
@Override
public String toString() {
return "CompositeState[" + this.phases + ", outlineProperty=" + this.outlineMode + ']';
}
@@ -293,6 +297,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
this.name = name;
}
@Override
public String toString() {
return this.name;
}
@@ -11,7 +11,6 @@ import org.lwjgl.opengl.GL11;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalDouble;
public abstract class RenderPhase {
@@ -98,6 +97,7 @@ public abstract class RenderPhase {
this.endAction.run();
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
@@ -109,10 +109,12 @@ public abstract class RenderPhase {
}
}
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public String toString() {
return this.name;
}
@@ -4,10 +4,21 @@ import com.gtnewhorizon.gtnhlib.config.Config;
@Config(modid = "angelica", filename = "angelica-modules")
public class AngelicaConfig {
@Config.Comment("Enable Sodium rendering")
@Config.Comment("Enable Celeritas terrain rendering")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableSodium;
public static boolean enableCeleritas;
@Config.Comment("Enable multi-threaded chunk building for improved performance [Requires Celeritas]")
@Config.DefaultBoolean(true)
@Config.RequiresWorldRestart
public static boolean enableThreadedChunkBuilding;
@Config.Comment("Number of chunk builder threads. 0 = auto-detect, -1 = use single-threaded fallback")
@Config.DefaultInt(0)
@Config.RangeInt(min = -1, max = 16)
@Config.RequiresWorldRestart
public static int chunkBuilderThreadCount;
@Config.Comment("Enable NotFine Options")
@Config.DefaultBoolean(false)
@@ -17,12 +28,7 @@ public class AngelicaConfig {
@Config.DefaultBoolean(true)
public static boolean enableReesesSodiumOptions;
@Config.Comment("Enable Sodium fluid rendering")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean enableSodiumFluidRendering;
@Config.Comment("Inject QuadProvider rendering into some vanilla blocks")
@Config.Comment("Inject BakedModel rendering into some vanilla blocks")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean injectQPRendering;
@@ -32,20 +38,25 @@ public class AngelicaConfig {
@Config.Ignore()
public static boolean enableTestBlocks;
@Config.Comment("Enable Iris Shaders [Requires Sodium]")
@Config.Comment("Enable Iris Shaders [Requires Celeritas]")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableIris;
@Config.Comment("Enable MCPatcherForge features, still in Alpha. Individual features are toggled in mcpatcher.json")
@Config.DefaultBoolean(false)
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableMCPatcherForgeFeatures;
@Config.Comment("Replace some vanilla render paths with more optimized versions. Disable if you encounter mixin conflicts.")
@Config.Comment("Replace cloud renderer with a VBO version.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableVBO;
public static boolean enableVBOClouds;
@Config.Comment("Uses cached attributes for VBO rendering, resulting in less CPU overhead. Disable if you notice any graphical issues.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableVAO;
@Config.Comment("Enable NotFine features")
@Config.DefaultBoolean(true)
@@ -133,6 +144,11 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean enableDebugLogging;
@Config.Comment("Enables PBR atlas dumping")
@Config.DefaultBoolean(false)
@Config.Name("Enable PBR Debug")
public static boolean enablePBRDebug;
@Config.Comment("Enable Zoom")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
@@ -143,9 +159,66 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean optimizeInWorldItemRendering;
@Config.Comment("Max amount of display lists to cache for optimized item rendering. Higher number will use more VRAM")
@Config.DefaultInt(256)
@Config.RangeInt(min = 64, max = 1024)
public static int itemRendererDisplayListCacheSize;
@Config.Comment("Upper limit for the amount of VBO's to cache for optimized item rendering. Higher number can potentially use more VRAM.")
@Config.DefaultInt(512)
@Config.RangeInt(min = 256, max = 1024)
public static int itemRendererCacheSize;
@Config.Comment("Render distance for the spinning mob inside mod spawners")
@Config.DefaultDouble(16D)
@Config.RangeDouble(min = 16D, max = 64D)
public static double mobSpawnerRenderDistance;
@Config.Comment("Switches to an alternate FPS limiter that gives more stable frametimes, in exchange for slightly " +
"more latency. Will never introduce more than one frame of latency, and has a lower impact at higher framerates.")
@Config.DefaultBoolean(false)
public static boolean sleepBeforeSwap;
@Config.Comment("Allows unicode languages to use an odd gui scale")
@Config.DefaultBoolean(true)
public static boolean removeUnicodeEvenScaling;
@Config.Comment({"Block corners and edges between chunks might have \"cracks\" (various lines/dots) in them.",
"While using \"Compact Vertex Format\" makes the situation even worse.",
"This option fixes it.",
"Requires texture reloading (F3 + T) after changing this option to take effect"})
@Config.DefaultBoolean(true)
public static boolean fixBlockCrack;
@Config.Comment({
"The \"epsilon\" value for the fixBlockCrack option. ",
"Set this a bit higher if you can still see lines/dots between solid blocks in dark areas.",
"May cause intense flickering (z-fighting) between blocks if the value is too high"
})
@Config.RangeDouble(min = 0, max = 0.005)
@Config.DefaultDouble(0.001)
public static double blockCrackFixEpsilon;
@Config.Comment("Block classes that have bugs when rendering with the fixBlockCrack can be put here to avoid manipulating them")
@Config.DefaultStringList({
"net.minecraft.block.BlockCauldron",
"net.minecraft.block.BlockStairs"
})
public static String[] blockCrackFixBlacklist;
@Config.Comment({"Block classes that have render pass other than 0 but still need to be manipulated.",
"Add a block class here if you see flickering (z-fighting) with fixBlockCrack enabled"
})
@Config.DefaultStringList({
"gregtech.common.blocks.BlockOres",
"gregtech.common.blocks.GTBlockOre",
"shukaro.artifice.block.world.BlockOre",
"bartworks.system.material.BWMetaGeneratedOres",
"gtPlusPlus.core.block.base.BlockBaseOre",
})
public static String[] blockCrackFixRenderPassWhitelist__;
@Config.Comment("Register HardcodedCustomUniforms in Iris Shaders. May help with compatibility in certain shader packs")
@Config.DefaultBoolean(false)
public static boolean enableHardcodedCustomUniforms;
@Config.Comment("Renders chunks before neighbors are ready. Improves loading at render distance edges, useful for low render distance servers.")
@Config.DefaultBoolean(false)
public static boolean useVanillaChunkTracking;
}
@@ -6,6 +6,13 @@ import net.minecraft.client.gui.GuiScreen;
public class AngelicaGuiConfig extends SimpleGuiConfig {
public AngelicaGuiConfig(GuiScreen parent) throws ConfigException {
super(parent, AngelicaConfig.class, "angelica", "Angelica");
super(parent,
"angelica",
"Angelica",
true,
AngelicaConfig.class,
CompatConfig.class,
FontConfig.class
);
}
}
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.config;
import com.gtnewhorizon.gtnhlib.config.Config;
@Config(modid = "angelica", filename = "angelica-compat")
@Config(modid = "angelica", filename = "angelica-compat", category = "compat")
public class CompatConfig {
@Config.Comment("Apply fixes to the LOTR mod")
@@ -39,4 +39,29 @@ public class CompatConfig {
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean fixImmersiveEngineering;
@Config.Comment("Apply fixes to Security Craft")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean fixSecurityCraft;
@Config.Comment("Apply tweaks to NTM:Space")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean tweakNTMSpace;
@Config.Comment("Apply fixes to NTM:Space")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean fixNTMSpace;
@Config.Comment("Disables rendering of the planet below the player at high altitudes [Requires tweakNTMSpace]")
@Config.DefaultEnum("ShadersOnly")
public static altitudePlanetRendererState NTMSpace_disableAltitudePlanetRenderer;
public enum altitudePlanetRendererState {
Always,
ShadersOnly,
Never
}
}
@@ -0,0 +1,73 @@
package com.gtnewhorizons.angelica.config;
import com.gtnewhorizon.gtnhlib.config.Config;
@Config(modid = "angelica", category = "customfont")
public class FontConfig {
@Config.Comment("Whether or not to use custom fonts when drawing text.")
@Config.DefaultBoolean(false)
public static boolean enableCustomFont;
@Config.Comment("Name of the primary custom font. Best not to set it from here.")
@Config.DefaultString("(none)")
public static String customFontNamePrimary;
@Config.Comment("Name of the fallback custom font. Best not to set it from here.")
@Config.DefaultString("(none)")
public static String customFontNameFallback;
@Config.Comment("The quality at which custom fonts are rendered, making them less pixelated but increasing memory usage.")
@Config.DefaultInt(30)
@Config.RangeInt(min = 6, max = 72)
public static int customFontQuality;
@Config.Comment("Controls the distance at which the font's shadow is drawn.")
@Config.DefaultFloat(1F)
@Config.RangeFloat(min = 0F, max = 2F)
public static float fontShadowOffset;
@Config.Comment("The number of shadows to be drawn behind text at various offsets.")
@Config.DefaultInt(1)
@Config.RangeInt(min = 1, max = 8)
public static int shadowCopies;
@Config.Comment("The number of bold copies to be drawn behind text at various offsets.")
@Config.DefaultInt(2)
@Config.RangeInt(min = 1, max = 8)
public static int boldCopies;
@Config.Comment("Influences the aspect ratio of each glyph.")
@Config.DefaultFloat(0F)
@Config.RangeFloat(min = -1F, max = 1F)
public static float glyphAspect;
@Config.Comment("Scale of each glyph, whitespace excluded.")
@Config.DefaultFloat(1F)
@Config.RangeFloat(min = 0.1F, max = 3F)
public static float glyphScale;
@Config.Comment("Whitespace scale.")
@Config.DefaultFloat(1F)
@Config.RangeFloat(min = 0.1F, max = 3F)
public static float whitespaceScale;
@Config.Comment("Adds extra spacing between glyphs.")
@Config.DefaultFloat(0F)
@Config.RangeFloat(min = -2F, max = 2F)
public static float glyphSpacing;
@Config.Comment("Controls font antialiasing. 0 = none, 1 = 4x MSAA, 2 = 16x MSAA.")
@Config.DefaultInt(2)
@Config.RangeInt(min = 0, max = 2)
public static int fontAAMode;
@Config.Comment("Affects font antialiasing sample spacing. Higher values increase blur.")
@Config.DefaultInt(7)
@Config.RangeInt(min = 1, max = 24)
public static int fontAAStrength;
@Config.Comment("Custom font scale multiplier, for bugfixes.")
@Config.DefaultFloat(1.5F)
@Config.RangeFloat(min = 0.1F, max = 3F)
public static float customFontScale;
}
@@ -0,0 +1,158 @@
/*
* Adapated from: Beddium for usage in Angelica
*
* Copyright (C) 2025 Ven, FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.gtnewhorizons.angelica.debug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaRenderSectionManager;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ChunkProviderClient;
import net.minecraft.world.chunk.EmptyChunk;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import org.lwjgl.opengl.GL11;
/**
* Debug overlay that renders a minimap showing chunk loading and rendering state. Toggle via /angelica minimap
*/
public class ChunkDebugMinimap {
private static final ChunkDebugMinimap INSTANCE = new ChunkDebugMinimap();
@Getter
private static volatile boolean enabled = false;
private static final int RANGE = 32; // Chunks in each direction from player
public static synchronized void toggle() {
if (enabled) disable();
else enable();
}
public static synchronized void enable() {
if (enabled) return;
enabled = true;
MinecraftForge.EVENT_BUS.register(INSTANCE);
}
public static synchronized void disable() {
if (!enabled) return;
enabled = false;
MinecraftForge.EVENT_BUS.unregister(INSTANCE);
}
@SubscribeEvent
public void onRenderOverlay(RenderGameOverlayEvent.Text event) {
final Minecraft mc = Minecraft.getMinecraft();
if (mc.theWorld == null || mc.thePlayer == null) return;
final CeleritasWorldRenderer renderer;
try {
renderer = CeleritasWorldRenderer.getInstance();
} catch (IllegalStateException e) {
return; // Renderer not initialized
}
final AngelicaRenderSectionManager manager = renderer.getRenderSectionManager();
if (manager == null) return;
final float partialTicks = event.partialTicks;
final double pX = mc.thePlayer.prevPosX + (mc.thePlayer.posX - mc.thePlayer.prevPosX) * partialTicks;
final double pZ = mc.thePlayer.prevPosZ + (mc.thePlayer.posZ - mc.thePlayer.prevPosZ) * partialTicks;
final int playerChunkX = ((int) Math.floor(pX)) >> 4;
final int playerChunkZ = ((int) Math.floor(pZ)) >> 4;
// Save GL state
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
GLStateManager.glPushMatrix();
// Setup rendering
GLStateManager.glScaled(2, 2, 1);
GLStateManager.glTranslated(32, 32, 0);
GLStateManager.disableLighting();
GLStateManager.disableTexture();
GLStateManager.disableDepthTest();
GLStateManager.enableBlend();
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLStateManager.glBegin(GL11.GL_QUADS);
final ChunkProviderClient chunkProvider = (ChunkProviderClient) mc.theWorld.getChunkProvider();
for (int x = playerChunkX - RANGE; x < playerChunkX + RANGE; x++) {
for (int z = playerChunkZ - RANGE; z < playerChunkZ + RANGE; z++) {
drawChunk(x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
}
}
GL11.glEnd();
// Restore GL state
GLStateManager.enableLighting();
GLStateManager.enableDepthTest();
GLStateManager.disableBlend();
GLStateManager.glPopMatrix();
GLStateManager.glPopAttrib();
}
private void drawChunk(int chunkX, int chunkZ, int playerChunkX, int playerChunkZ, AngelicaRenderSectionManager manager, ChunkProviderClient chunkProvider) {
boolean initialized = false;
float r = 0, g = 0, b = 0;
// Check all 16 sections in the chunk column
for (int y = 0; y < 16; y++) {
if (manager.isSectionBuilt(chunkX, y, chunkZ) && !manager.isSectionVisuallyEmpty(chunkX, y, chunkZ)) {
initialized = true;
g = 1; // Green = built
if (manager.isSectionVisible(chunkX, y, chunkZ)) {
b = 1; // Cyan = visible
break;
}
}
}
if (!initialized) {
// Check if chunk is loaded
final var chunk = chunkProvider.provideChunk(chunkX, chunkZ);
if (chunk instanceof EmptyChunk) {
r = 1; // Red = unloaded
} else {
b = 1; // Blue = loaded but not built
}
}
GL11.glColor4f(r, g, b, 0.8f);
final int xStart = chunkX - playerChunkX;
final int zStart = chunkZ - playerChunkZ;
final int xEnd = xStart + 1;
final int zEnd = zStart + 1;
GLStateManager.glVertex3d(xStart, zStart, 0);
GLStateManager.glVertex3d(xStart, zEnd, 0);
GLStateManager.glVertex3d(xEnd, zEnd, 0);
GLStateManager.glVertex3d(xEnd, zStart, 0);
}
}
@@ -28,9 +28,10 @@ import static org.lwjgl.opengl.GL20.glUniform1;
import static org.lwjgl.opengl.GL20.glUniform1f;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.*;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
import com.gtnewhorizon.gtnhlib.client.renderer.shader.ShaderProgram;
import com.gtnewhorizons.angelica.compat.lwjgl.MemoryStack;
import java.nio.FloatBuffer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
@@ -131,7 +132,7 @@ public abstract class F3Graph {
// Load vertex buffer
vertBuf = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
try (final MemoryStack stack = MemoryStack.stackPush()) {
try (final MemoryStack stack = stackPush()) {
final FloatBuffer vertices = stack.mallocFloat(VERT_COUNT * VERT_FLOATS);
// Since we use a triangle strip, we only need 4 vertices. The quad extends to the top of the screen so spikes
// don't get truncated. The max height is replaced in the vert shader, no need to be precise.
@@ -6,19 +6,17 @@ import com.gtnewhorizon.gtnhlib.blockpos.IBlockPos;
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
import com.gtnewhorizons.angelica.api.IDynamicLightProducer;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.battlegear2.Battlegear2Compat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
import mods.battlegear2.api.core.IBattlePlayer;
import mods.battlegear2.api.core.IInventoryPlayerBattle;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
@@ -28,7 +26,6 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.MathHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xonin.backhand.api.core.BackhandUtils;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -36,9 +33,12 @@ import java.util.function.Predicate;
public class DynamicLights {
private static DynamicLights instance;
private static IDynamicLightWorldRenderer activeRenderer;
public static DynamicLightsMode Mode = DynamicLightsMode.OFF;
public static boolean ShaderForce = false;
public static final boolean configEnabled = AngelicaConfig.enableDynamicLights;
private static final double MAX_RADIUS = 7.75;
private static final double MAX_RADIUS_SQUARED = MAX_RADIUS * MAX_RADIUS;
private final Set<IDynamicLightSource> dynamicLightSources = new ObjectOpenHashSet<>();
@@ -53,19 +53,27 @@ public class DynamicLights {
}
public static boolean isEnabled() {
return AngelicaConfig.enableDynamicLights &&
return configEnabled &&
Mode.isEnabled() &&
// if shader force is enabled then true
// if not, then true when shaders are not in use
(ShaderForce || !IrisApi.getInstance().isShaderPackInUse());
}
public static IDynamicLightWorldRenderer getActiveRenderer() {
return activeRenderer;
}
public static void setActiveRenderer(IDynamicLightWorldRenderer renderer) {
activeRenderer = renderer;
}
/**
* Updates all light sources.
*
* @param renderer the renderer
*/
public void updateAll(@NotNull SodiumWorldRenderer renderer) {
public void updateAll(@NotNull IDynamicLightWorldRenderer renderer) {
if (!isEnabled())
return;
@@ -107,8 +115,8 @@ public class DynamicLights {
it = dynamicLightSources.next();
if (it.equals(lightSource)) {
dynamicLightSources.remove();
if (SodiumWorldRenderer.getInstance() != null)
lightSource.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
if (activeRenderer != null)
lightSource.angelica$scheduleTrackedChunksRebuild(activeRenderer);
break;
}
}
@@ -130,10 +138,10 @@ public class DynamicLights {
it = dynamicLightSources.next();
if (filter.test(it)) {
dynamicLightSources.remove();
if (SodiumWorldRenderer.getInstance() != null) {
if (activeRenderer != null) {
if (it.angelica$getLuminance() > 0)
it.angelica$resetDynamicLight();
it.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
it.angelica$scheduleTrackedChunksRebuild(activeRenderer);
}
break;
}
@@ -153,10 +161,10 @@ public class DynamicLights {
while (dynamicLightSources.hasNext()) {
it = dynamicLightSources.next();
dynamicLightSources.remove();
if (SodiumWorldRenderer.getInstance() != null) {
if (activeRenderer != null) {
if (it.angelica$getLuminance() > 0)
it.angelica$resetDynamicLight();
it.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
it.angelica$scheduleTrackedChunksRebuild(activeRenderer);
}
}
@@ -203,6 +211,20 @@ public class DynamicLights {
return MathHelper.clamp_double(result, 0, 15);
}
/**
* Gets dynamic light level at an exact world position (for smooth per-vertex lighting).
*/
public double getDynamicLightLevel(double x, double y, double z) {
double result = 0;
this.lightSourcesLock.readLock().lock();
for (var lightSource : this.dynamicLightSources) {
result = maxDynamicLightLevelExact(x, y, z, lightSource, result);
}
this.lightSourcesLock.readLock().unlock();
return MathHelper.clamp_double(result, 0, 15);
}
public double getDynamicLightLevel(@NotNull BlockPos pos) {
return this.getDynamicLightLevel(pos.getX(), pos.getY(), pos.getZ());
}
@@ -229,6 +251,29 @@ public class DynamicLights {
return currentLightLevel;
}
/**
* Calculates dynamic light level at an exact position (no block center offset).
* Used for smooth per-vertex lighting.
*/
public static double maxDynamicLightLevelExact(double x, double y, double z, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
int luminance = lightSource.angelica$getLuminance();
if (luminance > 0) {
double dx = x - lightSource.angelica$getDynamicLightX();
double dy = y - lightSource.angelica$getDynamicLightY();
double dz = z - lightSource.angelica$getDynamicLightZ();
double distanceSquared = dx * dx + dy * dy + dz * dz;
if (distanceSquared <= MAX_RADIUS_SQUARED) {
double multiplier = 1.0 - Math.sqrt(distanceSquared) / MAX_RADIUS;
double lightLevel = multiplier * (double) luminance;
if (lightLevel > currentLightLevel) {
return lightLevel;
}
}
}
return currentLightLevel;
}
/**
* Returns the dynamic light level generated by the light source at the specified position.
*
@@ -275,7 +320,7 @@ public class DynamicLights {
* @param renderer the renderer
* @param chunkPos the chunk position
*/
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, @NotNull IBlockPos chunkPos) {
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, @NotNull IBlockPos chunkPos) {
scheduleChunkRebuild(renderer, chunkPos.getX(), chunkPos.getY(), chunkPos.getZ());
}
@@ -285,11 +330,11 @@ public class DynamicLights {
* @param renderer the renderer
* @param chunkPos the packed chunk position
*/
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, long chunkPos) {
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, long chunkPos) {
scheduleChunkRebuild(renderer, CoordinatePacker.unpackX(chunkPos), CoordinatePacker.unpackY(chunkPos), CoordinatePacker.unpackZ(chunkPos));
}
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, int x, int y, int z) {
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, int x, int y, int z) {
renderer.scheduleRebuildForChunk(x, y, z, false);
}
@@ -378,13 +423,13 @@ public class DynamicLights {
player instanceof IBattlePlayer battlePlayer &&
battlePlayer.battlegear2$isBattlemode()
) {
ItemStack offhand = ((IInventoryPlayerBattle) player.inventory).battlegear2$getCurrentOffhandWeapon();
ItemStack offhand = Battlegear2Compat.getBattlegear2Offhand(player);
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
}
}
else if (ModStatus.isBackhandLoaded && living instanceof EntityPlayer player){
ItemStack offhand = BackhandUtils.getOffhandItem(player);
ItemStack offhand = ModStatus.backhandCompat.getOffhandItem(player);
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.dynamiclights;
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -27,11 +26,13 @@ public interface IDynamicLightSource {
double angelica$getDynamicLightZ();
/**
* Returns the dynamic light source world.
* Returns the dynamic light source world renderer.
*
* @return the world instance
* @return the world renderer instance
*/
SodiumWorldRenderer angelica$getDynamicLightWorld();
default IDynamicLightWorldRenderer angelica$getDynamicLightWorld() {
return DynamicLights.getActiveRenderer();
}
/**
* Returns whether the dynamic light is enabled or not.
@@ -73,7 +74,7 @@ public interface IDynamicLightSource {
*/
void angelica$dynamicLightTick();
boolean angelica$updateDynamicLight(@NotNull SodiumWorldRenderer renderer);
boolean angelica$updateDynamicLight(@NotNull IDynamicLightWorldRenderer renderer);
void angelica$scheduleTrackedChunksRebuild(@NotNull SodiumWorldRenderer renderer);
void angelica$scheduleTrackedChunksRebuild(@NotNull IDynamicLightWorldRenderer renderer);
}
@@ -0,0 +1,17 @@
package com.gtnewhorizons.angelica.dynamiclights;
/**
* Common interface for world renderers that support dynamic lights chunk rebuilds.
* Implemented by CeleritasWorldRenderer.
*/
public interface IDynamicLightWorldRenderer {
/**
* Schedules a chunk rebuild for the render section at the given chunk coordinates.
*/
void scheduleRebuildForChunk(int x, int y, int z, boolean important);
/**
* Returns whether this renderer is currently active (has a world loaded).
*/
boolean isActive();
}
@@ -0,0 +1,52 @@
package com.gtnewhorizons.angelica.glsm;
import org.embeddedt.embeddium.impl.render.chunk.fog.FogService;
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkFogMode;
public class AngelicaFogService implements FogService {
private final float[] fogColorArray = new float[4];
@Override
public float getFogEnd() {
return GLStateManager.getFogState().getEnd();
}
@Override
public float getFogStart() {
return GLStateManager.getFogState().getStart();
}
@Override
public float getFogDensity() {
return GLStateManager.getFogState().getDensity();
}
@Override
public int getFogShapeIndex() {
return 0; // 1.7.10 only has one fog shape
}
@Override
public float getFogCutoff() {
return getFogEnd();
}
@Override
public float[] getFogColor() {
final var fogState = GLStateManager.getFogState();
final var color = fogState.getFogColor();
fogColorArray[0] = (float) color.x;
fogColorArray[1] = (float) color.y;
fogColorArray[2] = (float) color.z;
fogColorArray[3] = fogState.getFogAlpha();
return fogColorArray;
}
@Override
public ChunkFogMode getFogMode() {
if (!GLStateManager.getFogMode().isEnabled()) {
return ChunkFogMode.NONE;
}
return ChunkFogMode.fromGLMode(GLStateManager.getFogState().getFogMode());
}
}
@@ -0,0 +1,369 @@
package com.gtnewhorizons.angelica.glsm;
import com.mitchej123.glsm.GLStateManagerService;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.nio.IntBuffer;
public class AngelicaGLStateManagerService implements GLStateManagerService {
private IntBuffer intArrayBuffer = BufferUtils.createIntBuffer(16);
private IntBuffer getIntArrayBuffer(int size) {
if (intArrayBuffer.capacity() < size) {
intArrayBuffer = BufferUtils.createIntBuffer(size);
}
intArrayBuffer.clear();
return intArrayBuffer;
}
@Override
public int getPriority() {
return 100;
}
// ===================== QUERY OPERATIONS =====================
@Override
public int glGetInteger(int pname) {
return GLStateManager.glGetInteger(pname);
}
@Override
public String glGetString(int pname) {
return GL11.glGetString(pname);
}
// ===================== FRAMEBUFFER OPERATIONS =====================
@Override
public int glGenFramebuffers() {
return GL30.glGenFramebuffers();
}
@Override
public void glDeleteFramebuffers(int framebuffer) {
GL30.glDeleteFramebuffers(framebuffer);
}
@Override
public void glBindFramebuffer(int target, int framebuffer) {
GL30.glBindFramebuffer(target, framebuffer);
}
@Override
public int glCheckFramebufferStatus(int target) {
return GL30.glCheckFramebufferStatus(target);
}
@Override
public void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
GL30.glFramebufferTexture2D(target, attachment, textarget, texture, level);
}
// ===================== SHADER OPERATIONS =====================
@Override
public int glCreateShader(int type) {
return GL20.glCreateShader(type);
}
@Override
public void glCompileShader(int shader) {
GL20.glCompileShader(shader);
}
@Override
public int glGetShaderi(int shader, int pname) {
return GL20.glGetShaderi(shader, pname);
}
@Override
public String glGetShaderInfoLog(int shader, int maxLength) {
return GL20.glGetShaderInfoLog(shader, maxLength);
}
@Override
public void glDeleteShader(int shader) {
GL20.glDeleteShader(shader);
}
@Override
public int glCreateProgram() {
return GL20.glCreateProgram();
}
@Override
public void glAttachShader(int program, int shader) {
GL20.glAttachShader(program, shader);
}
@Override
public void glLinkProgram(int program) {
GL20.glLinkProgram(program);
}
@Override
public int glGetProgrami(int program, int pname) {
return GL20.glGetProgrami(program, pname);
}
@Override
public void glUseProgram(int program) {
GLStateManager.glUseProgram(program);
}
@Override
public void glDeleteProgram(int program) {
GL20.glDeleteProgram(program);
}
// ===================== UNIFORM/ATTRIBUTE OPERATIONS =====================
@Override
public int glGetUniformLocation(int program, CharSequence name) {
return GL20.glGetUniformLocation(program, name);
}
@Override
public void glUniform1i(int location, int value) {
GL20.glUniform1i(location, value);
}
@Override
public int glGetAttribLocation(int program, CharSequence name) {
return GL20.glGetAttribLocation(program, name);
}
@Override
public void glBindAttribLocation(int program, int index, CharSequence name) {
GL20.glBindAttribLocation(program, index, name);
}
// ===================== VAO OPERATIONS =====================
@Override
public int glGenVertexArrays() {
return GL30.glGenVertexArrays();
}
@Override
public void glBindVertexArray(int array) {
GL30.glBindVertexArray(array);
}
@Override
public void glDeleteVertexArrays(int array) {
GL30.glDeleteVertexArrays(array);
}
// ===================== BUFFER OPERATIONS =====================
@Override
public int glGenBuffers() {
return GL15.glGenBuffers();
}
@Override
public void glBindBuffer(int target, int buffer) {
GL15.glBindBuffer(target, buffer);
}
@Override
public void glDeleteBuffers(int buffer) {
GL15.glDeleteBuffers(buffer);
}
// ===================== TEXTURE OPERATIONS =====================
@Override
public int glGenTextures() {
return GL11.glGenTextures();
}
@Override
public void glGenTextures(int[] textures) {
IntBuffer buffer = getIntArrayBuffer(textures.length);
GL11.glGenTextures(buffer);
buffer.get(textures);
}
@Override
public void glDeleteTextures(int texture) {
GLStateManager.glDeleteTextures(texture);
}
@Override
public void glDeleteTextures(int[] textures) {
IntBuffer buffer = getIntArrayBuffer(textures.length);
buffer.put(textures);
buffer.flip();
GLStateManager.glDeleteTextures(buffer);
}
@Override
public void glActiveTexture(int texture) {
GLStateManager.glActiveTexture(texture);
}
@Override
public int glGetTexLevelParameteri(int target, int level, int pname) {
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
}
@Override
public int glGetTexLevelParameter(int target, int level, int pname) {
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
}
@Override
public void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
GL11.glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
}
@Override
public void glPixelStorei(int pname, int param) {
GLStateManager.glPixelStorei(pname, param);
}
// ===================== STATE OPERATIONS =====================
@Override
public void enableCullFace() {
GLStateManager.enableCull();
}
@Override
public void disableCullFace() {
GLStateManager.disableCull();
}
@Override
public void enableBlend() {
GLStateManager.enableBlend();
}
@Override
public void disableBlend() {
GLStateManager.disableBlend();
}
@Override
public void glBlendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
}
@Override
public void enableDepthTest() {
GLStateManager.enableDepthTest();
}
@Override
public void disableDepthTest() {
GLStateManager.disableDepthTest();
}
@Override
public void glDepthFunc(int func) {
GLStateManager.glDepthFunc(func);
}
@Override
public void glDepthMask(boolean flag) {
GLStateManager.glDepthMask(flag);
}
@Override
public void glViewport(int x, int y, int width, int height) {
GLStateManager.glViewport(x, y, width, height);
}
@Override
public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
GLStateManager.glColorMask(red, green, blue, alpha);
}
@Override
public void glClearColor(float red, float green, float blue, float alpha) {
GLStateManager.glClearColor(red, green, blue, alpha);
}
@Override
public void glClear(int mask) {
GLStateManager.glClear(mask);
}
// ===================== MOJANG ADDITIONS =====================
@Override
public void clear(int mask, boolean checkError) {
GLStateManager.glClear(mask);
}
@Override
public void bindTexture(int texture) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
}
@Override
public int getActiveTexture() {
return GLStateManager.getActiveTextureUnit() + GL13.GL_TEXTURE0;
}
@Override
public int getActiveTextureAccessor() {
return GLStateManager.getActiveTextureUnit();
}
@Override
public int getBoundTexture(int internalUnit) {
return GLStateManager.getBoundTextureForServerState(internalUnit);
}
@Override
public int getActiveBoundTexture() {
return GLStateManager.getBoundTextureForServerState();
}
@Override
public int getViewportWidth() {
return GLStateManager.getViewportState().width;
}
@Override
public int getViewportHeight() {
return GLStateManager.getViewportState().height;
}
@Override
public boolean getDepthStateMask() {
return GLStateManager.getDepthState().isEnabled();
}
@Override
public boolean isBlendEnabled() {
return GLStateManager.getBlendMode().isEnabled();
}
@Override
public void setBoundTexture(int unit, int texture) {
int currentUnit = GLStateManager.getActiveTextureUnit();
if (currentUnit != unit) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
}
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
if (currentUnit != unit) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + currentUnit);
}
}
@Override
public int getTextureBinding(int unit) {
return GLStateManager.getBoundTextureForServerState(unit);
}
}
@@ -0,0 +1,244 @@
package com.gtnewhorizons.angelica.glsm;
import com.mitchej123.glsm.RenderSystemService;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
public class AngelicaRenderSystemService implements RenderSystemService {
// Shader state (Mojang-specific, tracked here)
private final int[] shaderTextures = new int[16];
private final float[] shaderColor = {1f, 1f, 1f, 1f};
private final float[] shaderFogColor = {0f, 0f, 0f, 1f};
private float shaderFogStart = 0f;
private float shaderFogEnd = 0f;
private int fogShape = 0;
private float shaderLineWidth = 1f;
private final Matrix4f projectionMatrix = new Matrix4f();
private final FloatBuffer projectionMatrixBuffer = BufferUtils.createFloatBuffer(16);
@Override
public int getPriority() {
return 100;
}
// ===================== TEXTURE OPERATIONS =====================
@Override
public void glActiveTexture(int texture) {
GLStateManager.glActiveTexture(texture);
}
@Override
public void bindTexture(int texture) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
}
// ===================== STATE OPERATIONS =====================
@Override
public void enableCullFace() {
GLStateManager.enableCull();
}
@Override
public void disableCullFace() {
GLStateManager.disableCull();
}
@Override
public void enableBlend() {
GLStateManager.enableBlend();
}
@Override
public void disableBlend() {
GLStateManager.disableBlend();
}
@Override
public void defaultBlendFunc() {
GLStateManager.defaultBlendFunc();
}
@Override
public void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
}
@Override
public void setUnknownBlendState() {
// No-op: Angelica tracks blend state accurately
}
@Override
public void enableDepthTest() {
GLStateManager.enableDepthTest();
}
@Override
public void disableDepthTest() {
GLStateManager.disableDepthTest();
}
@Override
public void depthFunc(int depthFunc) {
GLStateManager.glDepthFunc(depthFunc);
}
@Override
public void depthMask(boolean flag) {
GLStateManager.glDepthMask(flag);
}
@Override
public void glViewport(int x, int y, int width, int height) {
GLStateManager.glViewport(x, y, width, height);
}
@Override
public void glClearColor(float red, float green, float blue, float alpha) {
GLStateManager.glClearColor(red, green, blue, alpha);
}
@Override
public void clear(int mask, boolean checkError) {
GLStateManager.glClear(mask);
}
// ===================== UNIFORM OPERATIONS =====================
@Override
public void glUniform1i(int location, int value) {
GL20.glUniform1i(location, value);
}
@Override
public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) {
GL20.glUniformMatrix3(location, transpose, value);
}
@Override
public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) {
GL20.glUniformMatrix4(location, transpose, value);
}
// ===================== THREAD ASSERTIONS =====================
@Override
public void assertOnRenderThread() {
// No-op: 1.7.10 is single-threaded for rendering
}
@Override
public void assertOnRenderThreadOrInit() {
// No-op: 1.7.10 is single-threaded for rendering
}
// ===================== SHADER STATE =====================
@Override
public void setShaderTexture(int shaderTexture, int textureId) {
if (shaderTexture >= 0 && shaderTexture < 16) {
shaderTextures[shaderTexture] = textureId;
}
}
@Override
public int getShaderTexture(int shaderTexture) {
return (shaderTexture >= 0 && shaderTexture < 16) ? shaderTextures[shaderTexture] : 0;
}
@Override
public void setShaderColor(float red, float green, float blue, float alpha) {
shaderColor[0] = red;
shaderColor[1] = green;
shaderColor[2] = blue;
shaderColor[3] = alpha;
}
@Override
public float[] getShaderColor() {
return shaderColor.clone();
}
@Override
public void setShaderLineWidth(float lineWidth) {
shaderLineWidth = lineWidth;
}
@Override
public float getShaderLineWidth() {
return shaderLineWidth;
}
// ===================== FOG STATE =====================
@Override
public void setShaderFogColor(float red, float green, float blue, float alpha) {
shaderFogColor[0] = red;
shaderFogColor[1] = green;
shaderFogColor[2] = blue;
shaderFogColor[3] = alpha;
}
@Override
public float[] getShaderFogColor() {
return shaderFogColor.clone();
}
@Override
public void setShaderFogStart(float start) {
shaderFogStart = start;
}
@Override
public float getShaderFogStart() {
return shaderFogStart;
}
@Override
public void setShaderFogEnd(float end) {
shaderFogEnd = end;
}
@Override
public float getShaderFogEnd() {
return shaderFogEnd;
}
@Override
public void setFogShape(int shape) {
fogShape = shape;
}
@Override
public int getFogShape() {
return fogShape;
}
// ===================== PROJECTION MATRIX =====================
@Override
public Matrix4f getProjectionMatrix() {
return new Matrix4f(projectionMatrix);
}
@Override
public void setProjectionMatrixOrth(Matrix4f m) {
projectionMatrix.set(m);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
projectionMatrix.get(projectionMatrixBuffer);
projectionMatrixBuffer.rewind();
GLStateManager.glLoadMatrix(projectionMatrixBuffer);
}
@Override
public void setProjectionMatrixOrigin(Matrix4f m) {
setProjectionMatrixOrth(m);
}
}
@@ -0,0 +1,238 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor;
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor.BufferTransformOptimizer;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* Builds final command buffers from raw command buffers and compiled VBO data.
* Transforms are already collapsed to MultMatrix during recording; the optimizer
* tracks transform state to emit deltas when draws need a different transform.
*/
public final class CommandBufferBuilder {
private static final Logger LOGGER = LogManager.getLogger("CommandBufferBuilder");
private CommandBufferBuilder() {}
/** Build with transform delta optimization and merged draw ranges. */
public static void buildOptimizedFromRawBuffer(
CommandBuffer rawBuffer,
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
final CompiledLineBuffer compiledLineBuffer,
final Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers,
VertexBuffer[] ownedVbos,
int glListId,
CommandBuffer finalBuffer) {
// Build VBO index map for efficient lookup (no boxing)
final Object2IntMap<VertexBuffer> vboIndexMap = new Object2IntOpenHashMap<>();
vboIndexMap.defaultReturnValue(-1);
for (int i = 0; i < ownedVbos.length; i++) {
vboIndexMap.put(ownedVbos[i], i);
}
// Collect all merged draw ranges sorted by command index
final List<DrawRangeWithBuffer> allRanges = collectDrawRanges(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers);
// Buffer optimizer state
final BufferTransformOptimizer opt = new BufferTransformOptimizer();
rawBuffer.resetRead();
int cmdIndex = 0;
int rangeIndex = 0;
while (rawBuffer.hasRemaining()) {
// Emit draw ranges at this command position
while (rangeIndex < allRanges.size() && allRanges.get(rangeIndex).range().commandIndex() == cmdIndex) {
emitDrawRangeToBuffer(allRanges.get(rangeIndex++), opt, finalBuffer, vboIndexMap);
}
// Read and process the command
final int opcode = rawBuffer.readInt();
CommandBufferProcessor.processCommandForOptimization(opcode, rawBuffer, opt, finalBuffer);
cmdIndex++;
}
// Emit remaining draw ranges at end of command stream
while (rangeIndex < allRanges.size()) {
emitDrawRangeToBuffer(allRanges.get(rangeIndex++), opt, finalBuffer, vboIndexMap);
}
// Emit residual transform if not identity
if (!opt.isIdentity()) {
opt.emitPendingTransform(finalBuffer);
}
}
/** Debug build: no optimizations, 1:1 draw commands. */
public static void buildUnoptimizedFromRawBuffer(
CommandBuffer rawBuffer,
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
CompiledLineBuffer compiledLineBuffer,
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers,
VertexBuffer[] ownedVbos,
CommandBuffer finalBuffer) {
// Build VBO index map for efficient lookup (no boxing)
final Object2IntMap<VertexBuffer> vboIndexMap = new Object2IntOpenHashMap<>();
vboIndexMap.defaultReturnValue(-1);
for (int i = 0; i < ownedVbos.length; i++) {
vboIndexMap.put(ownedVbos[i], i);
}
// Collect per-draw ranges (no merging) sorted by command index
final List<DrawRangeWithBuffer> allRanges = collectDrawRangesUnoptimized(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers);
rawBuffer.resetRead();
int cmdIndex = 0;
int rangeIndex = 0;
while (rawBuffer.hasRemaining()) {
// Emit draw ranges at this command position
while (rangeIndex < allRanges.size() && allRanges.get(rangeIndex).range().commandIndex() == cmdIndex) {
final DrawRangeWithBuffer drb = allRanges.get(rangeIndex++);
final int vboIdx = vboIndexMap.getInt(drb.vbo());
if (vboIdx >= 0) {
finalBuffer.writeDrawRange(vboIdx, drb.range().startVertex(), drb.range().vertexCount(), drb.hasBrightness());
DisplayListManager.trackDrawRangeSource(drb.source().toString());
}
}
// Copy the command directly
CommandBufferProcessor.copyCommand(rawBuffer, finalBuffer);
cmdIndex++;
}
// Emit remaining draw ranges at end of command stream
while (rangeIndex < allRanges.size()) {
final DrawRangeWithBuffer drb = allRanges.get(rangeIndex++);
final int vboIdx = vboIndexMap.getInt(drb.vbo());
if (vboIdx >= 0) {
finalBuffer.writeDrawRange(vboIdx, drb.range().startVertex(), drb.range().vertexCount(), drb.hasBrightness());
DisplayListManager.trackDrawRangeSource(drb.source().toString());
}
}
}
/** Collect merged draw ranges, sorted by command index. */
static List<DrawRangeWithBuffer> collectDrawRanges(
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
CompiledLineBuffer compiledLineBuffer,
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers) {
return collectDrawRangesInternal(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers, true);
}
/** Collect unmerged draw ranges for debug mode. */
static List<DrawRangeWithBuffer> collectDrawRangesUnoptimized(
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
CompiledLineBuffer compiledLineBuffer,
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers) {
return collectDrawRangesInternal(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers, false);
}
private static List<DrawRangeWithBuffer> collectDrawRangesInternal(
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
CompiledLineBuffer compiledLineBuffer,
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers,
boolean useMerged) {
final List<DrawRangeWithBuffer> allRanges = new ArrayList<>();
// Add quad ranges
final CompiledFormatBuffer[] quadBufferArray = compiledQuadBuffers.values().toArray(new CompiledFormatBuffer[0]);
for (int b = 0; b < quadBufferArray.length; b++) {
final CompiledFormatBuffer compiledBuffer = quadBufferArray[b];
final DrawRange[] ranges = useMerged ? compiledBuffer.mergedRanges() : compiledBuffer.perDrawRanges();
for (int i = 0; i < ranges.length; i++) {
allRanges.add(new DrawRangeWithBuffer(ranges[i], compiledBuffer.vbo(), compiledBuffer.flags(),
DrawRangeWithBuffer.DrawSource.TESSELLATOR_QUADS));
}
}
// Add immediate mode line ranges
// Lines use a minimal flags (color only, no texture/brightness/normals)
if (compiledLineBuffer != null) {
final CapturingTessellator.Flags lineFlags = new CapturingTessellator.Flags(false, false, true, false);
final DrawRange[] ranges = useMerged ? compiledLineBuffer.mergedRanges() : compiledLineBuffer.perDrawRanges();
for (int i = 0; i < ranges.length; i++) {
allRanges.add(new DrawRangeWithBuffer(ranges[i], compiledLineBuffer.vbo(), lineFlags,
DrawRangeWithBuffer.DrawSource.IMMEDIATE_MODE_LINES));
}
}
// Add tessellator primitive ranges (lines and triangles) - grouped by format
if (compiledPrimitiveBuffers != null && !compiledPrimitiveBuffers.isEmpty()) {
final CompiledPrimitiveBuffers[] primBufferArray = compiledPrimitiveBuffers.values().toArray(new CompiledPrimitiveBuffers[0]);
for (int p = 0; p < primBufferArray.length; p++) {
final CompiledPrimitiveBuffers primBuffers = primBufferArray[p];
final CapturingTessellator.Flags primFlags = primBuffers.flags();
// Tessellator lines
if (primBuffers.hasLines()) {
final DrawRange[] ranges = useMerged ? primBuffers.lineMergedRanges() : primBuffers.linePerDrawRanges();
final VertexBuffer vbo = primBuffers.lineVbo();
for (int i = 0; i < ranges.length; i++) {
allRanges.add(new DrawRangeWithBuffer(ranges[i], vbo, primFlags,
DrawRangeWithBuffer.DrawSource.TESSELLATOR_LINES));
}
}
// Tessellator triangles
if (primBuffers.hasTriangles()) {
final DrawRange[] ranges = useMerged ? primBuffers.triangleMergedRanges() : primBuffers.trianglePerDrawRanges();
final VertexBuffer vbo = primBuffers.triangleVbo();
for (int i = 0; i < ranges.length; i++) {
allRanges.add(new DrawRangeWithBuffer(ranges[i], vbo, primFlags,
DrawRangeWithBuffer.DrawSource.TESSELLATOR_TRIANGLES));
}
}
}
}
allRanges.sort(Comparator.comparingInt(r -> r.range().commandIndex()));
return allRanges;
}
/** Emit draw range with delta transform. Uses writeDrawRangeRestore for immediate mode. */
private static void emitDrawRangeToBuffer(
DrawRangeWithBuffer drb,
BufferTransformOptimizer opt,
CommandBuffer out,
Object2IntMap<VertexBuffer> vboIndexMap) {
// Emit transform to reach the draw's expected transform
opt.emitTransformTo(out, drb.range().transform());
// Write the draw range command
final int vboIdx = vboIndexMap.getInt(drb.vbo());
if (vboIdx >= 0) {
final var restoreData = drb.range().restoreData();
if (restoreData != null) {
// Immediate mode draw with attribute restoration
final var flags = drb.flags();
out.writeDrawRangeRestore(
vboIdx, drb.range().startVertex(), drb.range().vertexCount(),
drb.hasBrightness(), flags.hasColor, flags.hasNormals, flags.hasTexture,
restoreData.lastColorR(), restoreData.lastColorG(),
restoreData.lastColorB(), restoreData.lastColorA(),
restoreData.lastNormalX(), restoreData.lastNormalY(), restoreData.lastNormalZ(),
restoreData.lastTexCoordS(), restoreData.lastTexCoordT()
);
} else {
// Regular tessellator draw (no restoration needed)
out.writeDrawRange(vboIdx, drb.range().startVertex(), drb.range().vertexCount(), drb.hasBrightness());
}
DisplayListManager.trackDrawRangeSource(drb.source().toString());
}
}
}
@@ -0,0 +1,23 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
/**
* A compiled VBO with its associated draw ranges.
* All ranges reference vertices within this single VBO.
* <p>
* Contains two range arrays:
* <ul>
* <li><b>mergedRanges</b>: For optimized path - consecutive same-transform draws merged</li>
* <li><b>perDrawRanges</b>: For unoptimized path - 1:1 with original draws</li>
* </ul>
*/
@Desugar
public record CompiledFormatBuffer(
VertexBuffer vbo,
CapturingTessellator.Flags flags,
DrawRange[] mergedRanges,
DrawRange[] perDrawRanges
) {}
@@ -0,0 +1,21 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
/**
* A compiled VBO containing all line geometry with associated draw ranges.
* All ranges reference vertices within this single VBO.
* <p>
* Contains two range arrays:
* <ul>
* <li><b>mergedRanges</b>: For optimized path - consecutive same-transform draws merged</li>
* <li><b>perDrawRanges</b>: For unoptimized path - 1:1 with original draws</li>
* </ul>
*/
@Desugar
public record CompiledLineBuffer(
VertexBuffer vbo,
DrawRange[] mergedRanges,
DrawRange[] perDrawRanges
) {}
@@ -0,0 +1,31 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
/**
* Compiled VBOs containing tessellator primitives (lines and triangles) for a single vertex format.
* Separate VBOs are used for lines (GL_LINES) and triangles (GL_TRIANGLES) since they have different draw modes.
* <p>
* Contains range arrays for both optimized (merged) and unoptimized (per-draw) paths.
* Similar to CompiledFormatBuffer but handles primitives instead of quads.
*/
@Desugar
public record CompiledPrimitiveBuffers(
CapturingTessellator.Flags flags,
VertexBuffer lineVbo,
DrawRange[] lineMergedRanges,
DrawRange[] linePerDrawRanges,
VertexBuffer triangleVbo,
DrawRange[] triangleMergedRanges,
DrawRange[] trianglePerDrawRanges
) {
public boolean hasLines() {
return lineVbo != null;
}
public boolean hasTriangles() {
return triangleVbo != null;
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,25 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import org.joml.Matrix4f;
/**
* Represents a vertex range within a shared VBO, along with its transform.
* Used during format-based batching to track where each draw's vertices are stored.
*
* <p>The restoreData field holds last vertex attributes for GL state restoration
* after VBO rendering (immediate mode draws only). Null for tessellator draws.
*/
@Desugar
public record DrawRange(
int startVertex,
int vertexCount,
Matrix4f transform,
int commandIndex,
AccumulatedDraw.RestoreData restoreData
) {
DrawRange(int startVertex, int vertexCount, Matrix4f transform, int commandIndex) {
this(startVertex, vertexCount, transform, commandIndex, null);
}
}
@@ -0,0 +1,43 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
/**
* Helper record pairing a DrawRange with its VBO and vertex format flags.
* Used for sorting ranges by command index while retaining VBO reference.
* Works for both quad ranges (from CompiledFormatBuffer) and line ranges (from CompiledLineBuffer).
*/
@Desugar
record DrawRangeWithBuffer(
DrawRange range,
VertexBuffer vbo,
CapturingTessellator.Flags flags,
DrawSource source
) {
boolean hasBrightness() {
return flags != null && flags.hasBrightness;
}
/**
* Source of a draw command for debug logging.
*/
enum DrawSource {
TESSELLATOR_QUADS("tessellator quads"),
TESSELLATOR_LINES("tessellator lines"),
TESSELLATOR_TRIANGLES("tessellator triangles"),
IMMEDIATE_MODE_LINES("immediate mode lines");
private final String description;
DrawSource(String description) {
this.description = description;
}
@Override
public String toString() {
return description;
}
}
}
@@ -2,14 +2,17 @@ package com.gtnewhorizons.angelica.glsm;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -19,24 +22,66 @@ public class Feature {
GL13.GL_MULTISAMPLE_BIT, GL11.GL_PIXEL_MODE_BIT, GL11.GL_POINT_BIT, GL11.GL_POLYGON_BIT, GL11.GL_POLYGON_STIPPLE_BIT, GL11.GL_SCISSOR_BIT,
GL11.GL_STENCIL_BUFFER_BIT, GL11.GL_TEXTURE_BIT, GL11.GL_TRANSFORM_BIT, GL11.GL_VIEWPORT_BIT };
static final Int2ObjectMap<Set<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectMap<List<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectMap<IStateStack<?>[]> maskToNonBooleanStacksMap = new Int2ObjectOpenHashMap<>();
static Set<IStateStack<?>> maskToFeatures(int mask) {
static List<IStateStack<?>> maskToFeatures(int mask) {
if(maskToFeaturesMap.containsKey(mask)) {
return maskToFeaturesMap.get(mask);
}
final Set<IStateStack<?>> features = new HashSet<>();
for(int attrib : Feature.supportedAttribs) {
if((mask & attrib) == attrib) {
features.addAll(getFeatures(attrib));
}
}
maskToFeaturesMap.put(mask, features);
return features;
final List<IStateStack<?>> asList = new ArrayList<>(features);
maskToFeaturesMap.put(mask, asList);
return asList;
}
/**
* Returns only non-BooleanStateStack instances for the given mask.
* These use traditional push/pop without global depth tracking.
*/
static IStateStack<?>[] maskToNonBooleanStacks(int mask) {
IStateStack<?>[] cached = maskToNonBooleanStacksMap.get(mask);
if (cached != null) {
return cached;
}
final List<IStateStack<?>> all = maskToFeatures(mask);
final List<IStateStack<?>> nonBooleans = new ArrayList<>();
for (int i = 0; i < all.size(); i++) {
final IStateStack<?> stack = all.get(i);
if (!(stack instanceof BooleanStateStack)) {
nonBooleans.add(stack);
}
}
cached = nonBooleans.toArray(new IStateStack<?>[0]);
maskToNonBooleanStacksMap.put(mask, cached);
return cached;
}
private static final Map<Integer, Set<IStateStack<?>>> attribToFeatures = new HashMap<>();
/**
* Helper method to add all texture gen states (S, T, R, Q) for all texture units to a collection.
* Used by both GL_ENABLE_BIT and GL_TEXTURE_BIT.
*/
private static void addTextureGenStates(Set<IStateStack<?>> collection) {
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
collection.add(GLStateManager.textures.getTexGenSStates(i));
collection.add(GLStateManager.textures.getTexGenTStates(i));
collection.add(GLStateManager.textures.getTexGenRStates(i));
collection.add(GLStateManager.textures.getTexGenQStates(i));
}
}
static {
attribToFeatures.put(GL11.GL_COLOR_BUFFER_BIT, ImmutableSet.of(
GLStateManager.alphaTest // GL_ALPHA_TEST enable bit
@@ -45,10 +90,10 @@ public class Feature {
, GLStateManager.blendState // Blending source and destination functions
// Constant blend color
// Blending equation
// GL_DITHER enable bit
// GL_DRAW_BUFFER setting
// GL_COLOR_LOGIC_OP enable bit
// GL_INDEX_LOGIC_OP enable bit
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP enable bit
, GLStateManager.ditherState // GL_DITHER enable bit
, GLStateManager.drawBuffer // GL_DRAW_BUFFER setting
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP enable bit
// Logic op function
, GLStateManager.colorMask // Color-mode and index-mode writemasks
, GLStateManager.clearColor // Color-mode and index-mode clear values
@@ -74,14 +119,15 @@ public class Feature {
final HashSet<IStateStack<?>> enableBits = new HashSet<>(ImmutableSet.of(
GLStateManager.alphaTest // GL_ALPHA_TEST flag
// GL_AUTO_NORMAL flag
, GLStateManager.autoNormalState // GL_AUTO_NORMAL flag
, GLStateManager.blendMode // GL_BLEND flag
// Enable bits for the user-definable clipping planes
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP flag
, GLStateManager.colorMaterial // GL_COLOR_MATERIAL
, GLStateManager.cullState // GL_CULL_FACE flag
, GLStateManager.depthTest // GL_DEPTH_TEST flag
// GL_DITHER flag
, GLStateManager.ditherState // GL_DITHER flag
, GLStateManager.fogMode // GL_FOG flag
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP flag
, GLStateManager.lightStates[0] // GL_LIGHT0
, GLStateManager.lightStates[1] // GL_LIGHT1
, GLStateManager.lightStates[2] // GL_LIGHT2
@@ -91,36 +137,57 @@ public class Feature {
, GLStateManager.lightStates[6] // GL_LIGHT6
, GLStateManager.lightStates[7] // GL_LIGHT7
, GLStateManager.lightingState // GL_LIGHTING flag
// GL_LINE_SMOOTH flag
// GL_LINE_STIPPLE flag
// GL_INDEX_LOGIC_OP flag
// GL_COLOR_LOGIC_OP flag
// GL_MAP1_x where x is a map type
// GL_MAP2_x where x is a map type
// GL_MULTISAMPLE flag
// GL_NORMALIZE flag
// GL_POINT_SMOOTH flag
// GL_POLYGON_OFFSET_LINE flag
// GL_POLYGON_OFFSET_FILL flag
// GL_POLYGON_OFFSET_POINT flag
// GL_POLYGON_SMOOTH flag
// GL_POLYGON_STIPPLE flag
// GL_SAMPLE_ALPHA_TO_COVERAGE flag
// GL_SAMPLE_ALPHA_TO_ONE flag
// GL_SAMPLE_COVERAGE flag
, GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
, GLStateManager.lineStippleState // GL_LINE_STIPPLE flag
, GLStateManager.map1Color4State // GL_MAP1_COLOR_4
, GLStateManager.map1IndexState // GL_MAP1_INDEX
, GLStateManager.map1NormalState // GL_MAP1_NORMAL
, GLStateManager.map1TextureCoord1State // GL_MAP1_TEXTURE_COORD_1
, GLStateManager.map1TextureCoord2State // GL_MAP1_TEXTURE_COORD_2
, GLStateManager.map1TextureCoord3State // GL_MAP1_TEXTURE_COORD_3
, GLStateManager.map1TextureCoord4State // GL_MAP1_TEXTURE_COORD_4
, GLStateManager.map1Vertex3State // GL_MAP1_VERTEX_3
, GLStateManager.map1Vertex4State // GL_MAP1_VERTEX_4
, GLStateManager.map2Color4State // GL_MAP2_COLOR_4
, GLStateManager.map2IndexState // GL_MAP2_INDEX
, GLStateManager.map2NormalState // GL_MAP2_NORMAL
, GLStateManager.map2TextureCoord1State // GL_MAP2_TEXTURE_COORD_1
, GLStateManager.map2TextureCoord2State // GL_MAP2_TEXTURE_COORD_2
, GLStateManager.map2TextureCoord3State // GL_MAP2_TEXTURE_COORD_3
, GLStateManager.map2TextureCoord4State // GL_MAP2_TEXTURE_COORD_4
, GLStateManager.map2Vertex3State // GL_MAP2_VERTEX_3
, GLStateManager.map2Vertex4State // GL_MAP2_VERTEX_4
, GLStateManager.multisampleState // GL_MULTISAMPLE flag
, GLStateManager.normalizeState // GL_NORMALIZE flag
, GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE flag
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag
, GLStateManager.sampleAlphaToCoverageState // GL_SAMPLE_ALPHA_TO_COVERAGE flag
, GLStateManager.sampleAlphaToOneState // GL_SAMPLE_ALPHA_TO_ONE flag
, GLStateManager.sampleCoverageState // GL_SAMPLE_COVERAGE flag
, GLStateManager.scissorTest // GL_SCISSOR_TEST flag
// GL_STENCIL_TEST flag
// GL_TEXTURE_1D flag
// GL_TEXTURE_2D flag - Below
// GL_TEXTURE_3D flag
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
, GLStateManager.stencilTest // GL_STENCIL_TEST flag
));
// GL_TEXTURE_2D flag
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
enableBits.add(GLStateManager.textures.getTextureUnitStates(i));
// Enable bits for the user-definable clipping planes
for(int i = 0; i < GLStateManager.clipPlaneStates.length; i++) {
enableBits.add(GLStateManager.clipPlaneStates[i]);
}
// GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D flags
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
enableBits.add(GLStateManager.textures.getTexture1DStates(i));
enableBits.add(GLStateManager.textures.getTextureUnitStates(i));
enableBits.add(GLStateManager.textures.getTexture3DStates(i));
}
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
addTextureGenStates(enableBits);
attribToFeatures.put(GL11.GL_ENABLE_BIT, enableBits);
attribToFeatures.put(GL11.GL_EVAL_BIT, ImmutableSet.of(
// GL_MAP1_x enable bits, where x is a map type
@@ -181,10 +248,9 @@ public class Feature {
, GLStateManager.shadeModelState // GL_SHADE_MODEL setting
));
attribToFeatures.put(GL11.GL_LINE_BIT, ImmutableSet.of(
// GL_LINE_SMOOTH flag
// GL_LINE_STIPPLE enable bit
// Line stipple pattern and repeat counter
// Line width
GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
, GLStateManager.lineStippleState // GL_LINE_STIPPLE enable bit
, GLStateManager.lineState // Line stipple pattern, repeat counter, and width
));
attribToFeatures.put(GL11.GL_LIST_BIT, ImmutableSet.of(
// GL_LIST_BASE setting
@@ -209,21 +275,17 @@ public class Feature {
// GL_READ_BUFFER setting
));
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
// GL_POINT_SMOOTH flag
// Point size
GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
, GLStateManager.pointState // Point size
));
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
GLStateManager.cullState // GL_CULL_FACE enable bit
// GL_CULL_FACE_MODE value
// GL_FRONT_FACE indicator
// GL_POLYGON_MODE setting
// GL_POLYGON_SMOOTH flag
// GL_POLYGON_STIPPLE enable bit
// GL_POLYGON_OFFSET_FILL flag
// GL_POLYGON_OFFSET_LINE flag
// GL_POLYGON_OFFSET_POINT flag
// GL_POLYGON_OFFSET_FACTOR
// GL_POLYGON_OFFSET_UNITS
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE enable bit
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
, GLStateManager.polygonState // GL_CULL_FACE_MODE, GL_FRONT_FACE, GL_POLYGON_MODE, GL_POLYGON_OFFSET_FACTOR/UNITS
));
attribToFeatures.put(GL11.GL_POLYGON_STIPPLE_BIT, ImmutableSet.of(
// Polygon stipple pattern
@@ -233,12 +295,8 @@ public class Feature {
// Scissor box
));
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
// GL_STENCIL_TEST enable bit
// Stencil function and reference value
// Stencil value mask
// Stencil fail, pass, and depth buffer pass actions
// Stencil buffer clear value
// Stencil buffer writemask
GLStateManager.stencilTest // GL_STENCIL_TEST enable bit
, GLStateManager.stencilState // Stencil function, ref, mask, ops, writemask, clear value
));
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of(
GLStateManager.activeTextureUnit // Active texture unit
@@ -262,13 +320,16 @@ public class Feature {
// textureAttribs.add(GLStateManager.textures.getInfo(i))
}
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
addTextureGenStates(textureAttribs);
attribToFeatures.put(GL11.GL_TEXTURE_BIT, textureAttribs);
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, ImmutableSet.of(
// Coefficients of the six clipping planes
// Enable bits for the user-definable clipping planes
GLStateManager.matrixMode
// GL_NORMALIZE flag
, GLStateManager.normalizeState // GL_NORMALIZE flag
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag
));
attribToFeatures.put(GL11.GL_VIEWPORT_BIT, ImmutableSet.of(
@@ -0,0 +1,106 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import org.joml.Matrix4f;
import java.util.ArrayList;
import java.util.List;
/**
* Accumulates geometry for a single vertex format during display list compilation.
* Tracks merged ranges (same-transform draws combined) and per-draw ranges (1:1).
*/
class FormatBuffer {
final CapturingTessellator.Flags flags;
final List<ModelQuadViewMutable> allQuads = new ArrayList<>();
// Merged ranges for optimized path (consecutive same-transform draws combined)
final List<DrawRange> mergedRanges = new ArrayList<>();
// Per-draw ranges for unoptimized path (1:1 with input draws)
final List<DrawRange> perDrawRanges = new ArrayList<>();
int currentVertexOffset = 0;
// Track the current range being built for merging consecutive same-transform draws
private int pendingStartVertex = -1;
private int pendingVertexCount = 0;
private Matrix4f pendingTransform = null;
private int pendingCommandIndex = -1;
private int pendingGeneration = -1;
private int pendingStateGen = -1;
// Track last restore data for merged draws (uses the LAST draw's data)
private AccumulatedDraw.RestoreData pendingRestoreData = null;
FormatBuffer(CapturingTessellator.Flags flags) {
this.flags = flags;
}
/** Add a draw, tracking both merged and per-draw ranges. */
void addDraw(AccumulatedDraw draw) {
final int vertexCount = draw.quads.size() * 4; // 4 vertices per quad
// Always track per-draw range for unoptimized path (includes restoreData)
perDrawRanges.add(new DrawRange(
currentVertexOffset, vertexCount, draw.transform, draw.commandIndex, draw.restoreData
));
// Merge logic for optimized path:
// - same matrixGeneration = same matrix transform context
// - same stateGeneration = no state commands (draw barriers) between draws
boolean canMerge = pendingGeneration != -1
&& pendingGeneration == draw.matrixGeneration
&& pendingStateGen == draw.stateGeneration;
if (canMerge) {
// Extend the pending range
pendingVertexCount += vertexCount;
// Update restoreData to the LAST draw's data (used after merged draw completes)
pendingRestoreData = draw.restoreData;
} else {
// Flush pending range if any
flushPendingRange();
// Start new pending range
pendingStartVertex = currentVertexOffset;
pendingVertexCount = vertexCount;
pendingTransform = new Matrix4f(draw.transform);
pendingCommandIndex = draw.commandIndex;
pendingGeneration = draw.matrixGeneration;
pendingStateGen = draw.stateGeneration;
pendingRestoreData = draw.restoreData;
}
allQuads.addAll(draw.quads);
currentVertexOffset += vertexCount;
}
private void flushPendingRange() {
if (pendingGeneration != -1) {
mergedRanges.add(new DrawRange(
pendingStartVertex, pendingVertexCount, pendingTransform, pendingCommandIndex, pendingRestoreData
));
pendingTransform = null;
pendingGeneration = -1;
pendingStateGen = -1;
pendingRestoreData = null;
}
}
/** Compile accumulated quads into a VBO. */
CompiledFormatBuffer finish() {
// Flush any remaining pending range
flushPendingRange();
VertexBuffer vbo = DisplayListManager.compileQuads(allQuads, flags);
return new CompiledFormatBuffer(
vbo,
flags,
mergedRanges.toArray(new DrawRange[0]),
perDrawRanges.toArray(new DrawRange[0])
);
}
}
@@ -9,7 +9,11 @@ import org.lwjgl.opengl.AMDDebugOutput;
import org.lwjgl.opengl.AMDDebugOutputCallback;
import org.lwjgl.opengl.ARBDebugOutput;
import org.lwjgl.opengl.ARBDebugOutputCallback;
import org.lwjgl.opengl.EXTBlendColor;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.KHRDebug;
@@ -99,6 +103,9 @@ public final class GLDebug {
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_NOTIFICATION, null, false);
GL43.glDebugMessageCallback(proc);
// Enable synchronous debug output so errors are reported immediately with accurate stack traces
GL11.glEnable(GL43.GL_DEBUG_OUTPUT_SYNCHRONOUS);
if ((GL11.glGetInteger(GL30.GL_CONTEXT_FLAGS) & GL43.GL_CONTEXT_FLAG_DEBUG_BIT) == 0) {
LOGGER.warn("[GL] Warning: A non-debug context may not produce any debug output.");
GL11.glEnable(GL43.GL_DEBUG_OUTPUT);
@@ -121,6 +128,9 @@ public final class GLDebug {
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_LOW, null, false);
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_NOTIFICATION, null, false);
ARBDebugOutput.glDebugMessageCallbackARB(proc);
// Enable synchronous debug output so errors are reported immediately with accurate stack traces
GL11.glEnable(ARBDebugOutput.GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
return 1;
} else if (GLStateManager.capabilities.GL_AMD_debug_output) {
LOGGER.info("[GL] Using AMD_debug_output for error logging.");
@@ -269,6 +279,304 @@ public final class GLDebug {
};
}
public static String getMatrixModeName(int mode) {
return switch (mode) {
case GL11.GL_MODELVIEW -> "MODELVIEW";
case GL11.GL_PROJECTION -> "PROJECTION";
case GL11.GL_TEXTURE -> "TEXTURE";
case GL11.GL_COLOR -> "COLOR";
default -> String.format("0x%X", mode);
};
}
public static String getCapabilityName(int cap) {
return switch (cap) {
case GL11.GL_ALPHA_TEST -> "ALPHA_TEST";
case GL11.GL_BLEND -> "BLEND";
case GL11.GL_COLOR_MATERIAL -> "COLOR_MATERIAL";
case GL11.GL_CULL_FACE -> "CULL_FACE";
case GL11.GL_DEPTH_TEST -> "DEPTH_TEST";
case GL11.GL_FOG -> "FOG";
case GL11.GL_LIGHTING -> "LIGHTING";
case GL11.GL_LINE_SMOOTH -> "LINE_SMOOTH";
case GL11.GL_NORMALIZE -> "NORMALIZE";
case GL11.GL_POINT_SMOOTH -> "POINT_SMOOTH";
case GL11.GL_POLYGON_OFFSET_FILL -> "POLYGON_OFFSET_FILL";
case GL11.GL_POLYGON_OFFSET_LINE -> "POLYGON_OFFSET_LINE";
case GL11.GL_POLYGON_SMOOTH -> "POLYGON_SMOOTH";
case GL12.GL_RESCALE_NORMAL -> "RESCALE_NORMAL";
case GL11.GL_SCISSOR_TEST -> "SCISSOR_TEST";
case GL11.GL_STENCIL_TEST -> "STENCIL_TEST";
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
case GL11.GL_LIGHT0 -> "LIGHT0";
case GL11.GL_LIGHT1 -> "LIGHT1";
case GL11.GL_LIGHT2 -> "LIGHT2";
case GL11.GL_LIGHT3 -> "LIGHT3";
case GL11.GL_LIGHT4 -> "LIGHT4";
case GL11.GL_LIGHT5 -> "LIGHT5";
case GL11.GL_LIGHT6 -> "LIGHT6";
case GL11.GL_LIGHT7 -> "LIGHT7";
default -> String.format("0x%X", cap);
};
}
public static String getComparisonFuncName(int func) {
return switch (func) {
case GL11.GL_NEVER -> "NEVER";
case GL11.GL_LESS -> "LESS";
case GL11.GL_EQUAL -> "EQUAL";
case GL11.GL_LEQUAL -> "LEQUAL";
case GL11.GL_GREATER -> "GREATER";
case GL11.GL_NOTEQUAL -> "NOTEQUAL";
case GL11.GL_GEQUAL -> "GEQUAL";
case GL11.GL_ALWAYS -> "ALWAYS";
default -> String.format("0x%X", func);
};
}
public static String getBlendFactorName(int factor) {
return switch (factor) {
case GL11.GL_ZERO -> "ZERO";
case GL11.GL_ONE -> "ONE";
case GL11.GL_SRC_COLOR -> "SRC_COLOR";
case GL11.GL_ONE_MINUS_SRC_COLOR -> "ONE_MINUS_SRC_COLOR";
case GL11.GL_DST_COLOR -> "DST_COLOR";
case GL11.GL_ONE_MINUS_DST_COLOR -> "ONE_MINUS_DST_COLOR";
case GL11.GL_SRC_ALPHA -> "SRC_ALPHA";
case GL11.GL_ONE_MINUS_SRC_ALPHA -> "ONE_MINUS_SRC_ALPHA";
case GL11.GL_DST_ALPHA -> "DST_ALPHA";
case GL11.GL_ONE_MINUS_DST_ALPHA -> "ONE_MINUS_DST_ALPHA";
case EXTBlendColor.GL_CONSTANT_COLOR_EXT -> "CONSTANT_COLOR";
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_COLOR_EXT -> "ONE_MINUS_CONSTANT_COLOR";
case EXTBlendColor.GL_CONSTANT_ALPHA_EXT -> "CONSTANT_ALPHA";
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_ALPHA_EXT -> "ONE_MINUS_CONSTANT_ALPHA";
case GL11.GL_SRC_ALPHA_SATURATE -> "SRC_ALPHA_SATURATE";
default -> String.format("0x%X", factor);
};
}
public static String getShadeModelName(int mode) {
return switch (mode) {
case GL11.GL_FLAT -> "FLAT";
case GL11.GL_SMOOTH -> "SMOOTH";
default -> String.format("0x%X", mode);
};
}
public static String getTextureTargetName(int target) {
return switch (target) {
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
case GL12.GL_TEXTURE_3D -> "TEXTURE_3D";
case GL13.GL_TEXTURE_CUBE_MAP -> "TEXTURE_CUBE_MAP";
default -> String.format("0x%X", target);
};
}
public static String getTexturePnameName(int pname) {
return switch (pname) {
case GL11.GL_TEXTURE_MIN_FILTER -> "TEXTURE_MIN_FILTER";
case GL11.GL_TEXTURE_MAG_FILTER -> "TEXTURE_MAG_FILTER";
case GL11.GL_TEXTURE_WRAP_S -> "TEXTURE_WRAP_S";
case GL11.GL_TEXTURE_WRAP_T -> "TEXTURE_WRAP_T";
case GL12.GL_TEXTURE_WRAP_R -> "TEXTURE_WRAP_R";
case GL12.GL_TEXTURE_MIN_LOD -> "TEXTURE_MIN_LOD";
case GL12.GL_TEXTURE_MAX_LOD -> "TEXTURE_MAX_LOD";
case GL12.GL_TEXTURE_BASE_LEVEL -> "TEXTURE_BASE_LEVEL";
case GL12.GL_TEXTURE_MAX_LEVEL -> "TEXTURE_MAX_LEVEL";
case GL11.GL_TEXTURE_BORDER_COLOR -> "TEXTURE_BORDER_COLOR";
case GL11.GL_TEXTURE_PRIORITY -> "TEXTURE_PRIORITY";
case GL14.GL_TEXTURE_LOD_BIAS -> "TEXTURE_LOD_BIAS";
case GL14.GL_GENERATE_MIPMAP -> "GENERATE_MIPMAP";
default -> String.format("0x%X", pname);
};
}
public static String getTextureFormatName(int format) {
return switch (format) {
case GL11.GL_RED -> "RED";
case GL11.GL_GREEN -> "GREEN";
case GL11.GL_BLUE -> "BLUE";
case GL11.GL_ALPHA -> "ALPHA";
case GL11.GL_RGB -> "RGB";
case GL11.GL_RGBA -> "RGBA";
case GL11.GL_LUMINANCE -> "LUMINANCE";
case GL11.GL_LUMINANCE_ALPHA -> "LUMINANCE_ALPHA";
case GL12.GL_BGR -> "BGR";
case GL12.GL_BGRA -> "BGRA";
case GL11.GL_DEPTH_COMPONENT -> "DEPTH_COMPONENT";
case GL11.GL_STENCIL_INDEX -> "STENCIL_INDEX";
case GL30.GL_DEPTH_STENCIL -> "DEPTH_STENCIL";
// Internal formats
case GL11.GL_RGB8 -> "RGB8";
case GL11.GL_RGBA8 -> "RGBA8";
case GL11.GL_ALPHA8 -> "ALPHA8";
case GL11.GL_LUMINANCE8 -> "LUMINANCE8";
case GL11.GL_LUMINANCE8_ALPHA8 -> "LUMINANCE8_ALPHA8";
default -> String.format("0x%X", format);
};
}
public static String getDataTypeName(int type) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> "UNSIGNED_BYTE";
case GL11.GL_BYTE -> "BYTE";
case GL11.GL_UNSIGNED_SHORT -> "UNSIGNED_SHORT";
case GL11.GL_SHORT -> "SHORT";
case GL11.GL_UNSIGNED_INT -> "UNSIGNED_INT";
case GL11.GL_INT -> "INT";
case GL11.GL_FLOAT -> "FLOAT";
case GL11.GL_DOUBLE -> "DOUBLE";
case GL12.GL_UNSIGNED_BYTE_3_3_2 -> "UNSIGNED_BYTE_3_3_2";
case GL12.GL_UNSIGNED_SHORT_4_4_4_4 -> "UNSIGNED_SHORT_4_4_4_4";
case GL12.GL_UNSIGNED_SHORT_5_5_5_1 -> "UNSIGNED_SHORT_5_5_5_1";
case GL12.GL_UNSIGNED_INT_8_8_8_8 -> "UNSIGNED_INT_8_8_8_8";
case GL12.GL_UNSIGNED_INT_10_10_10_2 -> "UNSIGNED_INT_10_10_10_2";
default -> String.format("0x%X", type);
};
}
public static String getLightName(int light) {
return switch (light) {
case GL11.GL_LIGHT0 -> "LIGHT0";
case GL11.GL_LIGHT1 -> "LIGHT1";
case GL11.GL_LIGHT2 -> "LIGHT2";
case GL11.GL_LIGHT3 -> "LIGHT3";
case GL11.GL_LIGHT4 -> "LIGHT4";
case GL11.GL_LIGHT5 -> "LIGHT5";
case GL11.GL_LIGHT6 -> "LIGHT6";
case GL11.GL_LIGHT7 -> "LIGHT7";
default -> String.format("0x%X", light);
};
}
public static String getLightPnameName(int pname) {
return switch (pname) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
case GL11.GL_SPECULAR -> "SPECULAR";
case GL11.GL_POSITION -> "POSITION";
case GL11.GL_SPOT_DIRECTION -> "SPOT_DIRECTION";
case GL11.GL_SPOT_EXPONENT -> "SPOT_EXPONENT";
case GL11.GL_SPOT_CUTOFF -> "SPOT_CUTOFF";
case GL11.GL_CONSTANT_ATTENUATION -> "CONSTANT_ATTENUATION";
case GL11.GL_LINEAR_ATTENUATION -> "LINEAR_ATTENUATION";
case GL11.GL_QUADRATIC_ATTENUATION -> "QUADRATIC_ATTENUATION";
default -> String.format("0x%X", pname);
};
}
public static String getMaterialPnameName(int pname) {
return switch (pname) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
case GL11.GL_SPECULAR -> "SPECULAR";
case GL11.GL_EMISSION -> "EMISSION";
case GL11.GL_SHININESS -> "SHININESS";
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
case GL11.GL_COLOR_INDEXES -> "COLOR_INDEXES";
default -> String.format("0x%X", pname);
};
}
public static String getFaceName(int face) {
return switch (face) {
case GL11.GL_FRONT -> "FRONT";
case GL11.GL_BACK -> "BACK";
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
default -> String.format("0x%X", face);
};
}
public static String getColorMaterialModeName(int mode) {
return switch (mode) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
case GL11.GL_SPECULAR -> "SPECULAR";
case GL11.GL_EMISSION -> "EMISSION";
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
default -> String.format("0x%X", mode);
};
}
public static String getFogPnameName(int pname) {
return switch (pname) {
case GL11.GL_FOG_MODE -> "FOG_MODE";
case GL11.GL_FOG_DENSITY -> "FOG_DENSITY";
case GL11.GL_FOG_START -> "FOG_START";
case GL11.GL_FOG_END -> "FOG_END";
case GL11.GL_FOG_INDEX -> "FOG_INDEX";
case GL11.GL_FOG_COLOR -> "FOG_COLOR";
default -> String.format("0x%X", pname);
};
}
public static String getLightModelPnameName(int pname) {
return switch (pname) {
case GL11.GL_LIGHT_MODEL_AMBIENT -> "LIGHT_MODEL_AMBIENT";
case GL11.GL_LIGHT_MODEL_LOCAL_VIEWER -> "LIGHT_MODEL_LOCAL_VIEWER";
case GL11.GL_LIGHT_MODEL_TWO_SIDE -> "LIGHT_MODEL_TWO_SIDE";
case GL12.GL_LIGHT_MODEL_COLOR_CONTROL -> "LIGHT_MODEL_COLOR_CONTROL";
default -> String.format("0x%X", pname);
};
}
public static String getClearMaskString(int mask) {
StringBuilder sb = new StringBuilder();
boolean first = true;
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
sb.append("COLOR");
first = false;
}
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0) {
if (!first) sb.append("|");
sb.append("DEPTH");
first = false;
}
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
if (!first) sb.append("|");
sb.append("STENCIL");
first = false;
}
if ((mask & GL11.GL_ACCUM_BUFFER_BIT) != 0) {
if (!first) sb.append("|");
sb.append("ACCUM");
}
return sb.toString();
}
public static String getCullFaceName(int mode) {
return switch (mode) {
case GL11.GL_FRONT -> "FRONT";
case GL11.GL_BACK -> "BACK";
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
default -> String.format("0x%X", mode);
};
}
public static String getLogicOpName(int opcode) {
return switch (opcode) {
case GL11.GL_CLEAR -> "CLEAR";
case GL11.GL_AND -> "AND";
case GL11.GL_AND_REVERSE -> "AND_REVERSE";
case GL11.GL_COPY -> "COPY";
case GL11.GL_AND_INVERTED -> "AND_INVERTED";
case GL11.GL_NOOP -> "NOOP";
case GL11.GL_XOR -> "XOR";
case GL11.GL_OR -> "OR";
case GL11.GL_NOR -> "NOR";
case GL11.GL_EQUIV -> "EQUIV";
case GL11.GL_INVERT -> "INVERT";
case GL11.GL_OR_REVERSE -> "OR_REVERSE";
case GL11.GL_COPY_INVERTED -> "COPY_INVERTED";
case GL11.GL_OR_INVERTED -> "OR_INVERTED";
case GL11.GL_NAND -> "NAND";
case GL11.GL_SET -> "SET";
default -> String.format("0x%X", opcode);
};
}
private static DebugState debugState;
private interface DebugState {
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,134 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedLineDraw;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* Accumulates line geometry during display list compilation.
* All line draws are collected here, then compiled into a single VBO.
* <p>
* Lines all use the same format (POSITION_COLOR), so no format-based grouping needed.
* <p>
* Tracks two types of ranges:
* <ul>
* <li><b>mergedRanges</b>: Consecutive same-transform draws merged for optimized path</li>
* <li><b>perDrawRanges</b>: One range per input draw for unoptimized path (1:1 with addDraw calls)</li>
* </ul>
*/
class LineBuffer {
final List<ImmediateModeRecorder.LineVertex> allVertices = new ArrayList<>();
// Merged ranges for optimized path (consecutive same-transform draws combined)
final List<DrawRange> mergedRanges = new ArrayList<>();
// Per-draw ranges for unoptimized path (1:1 with input draws)
final List<DrawRange> perDrawRanges = new ArrayList<>();
int currentVertexOffset = 0;
// Track the current range being built for merging consecutive same-transform draws
private int pendingStartVertex = -1;
private int pendingVertexCount = 0;
private Matrix4f pendingTransform = null;
private int pendingCommandIndex = -1;
private int pendingStateGen = -1;
/**
* Add a line draw to this buffer.
* Tracks both merged (for optimized) and per-draw (for unoptimized) ranges.
*/
void addDraw(AccumulatedLineDraw draw) {
int vertexCount = draw.lines.size(); // Each LineVertex is one vertex, lines come in pairs
// Always track per-draw range for unoptimized path
perDrawRanges.add(new DrawRange(currentVertexOffset, vertexCount, draw.transform, draw.commandIndex));
// Merge logic for optimized path:
// - same transform
// - same stateGeneration = no state commands (draw barriers) between draws
boolean canMerge = pendingTransform != null
&& pendingTransform.equals(draw.transform)
&& pendingStateGen == draw.stateGeneration;
if (canMerge) {
// Extend the pending range
pendingVertexCount += vertexCount;
} else {
// Flush pending range if any
flushPendingRange();
// Start new pending range
pendingStartVertex = currentVertexOffset;
pendingVertexCount = vertexCount;
pendingTransform = new Matrix4f(draw.transform);
pendingCommandIndex = draw.commandIndex;
pendingStateGen = draw.stateGeneration;
}
allVertices.addAll(draw.lines);
currentVertexOffset += vertexCount;
}
private void flushPendingRange() {
if (pendingTransform != null) {
mergedRanges.add(new DrawRange(pendingStartVertex, pendingVertexCount, pendingTransform, pendingCommandIndex));
pendingTransform = null;
pendingStateGen = -1;
}
}
/**
* Compile all accumulated line vertices into a single VBO.
* @return The compiled buffer with VBO and both range types, or null if empty
*/
CompiledLineBuffer finish() {
if (allVertices.isEmpty()) {
return null;
}
// Flush any remaining pending range
flushPendingRange();
VertexBuffer vbo = compileLinesToVBO(allVertices);
return new CompiledLineBuffer(
vbo,
mergedRanges.toArray(new DrawRange[0]),
perDrawRanges.toArray(new DrawRange[0])
);
}
/**
* Compile line vertices into a VertexBuffer (VAO or VBO).
* Lines use POSITION_COLOR format (x, y, z, color).
*/
private static VertexBuffer compileLinesToVBO(List<ImmediateModeRecorder.LineVertex> lines) {
final VertexFormat format = DefaultVertexFormat.POSITION_COLOR;
final VertexBuffer vao = VAOManager.createVAO(format, GL11.GL_LINES);
// POSITION_COLOR format: 3 floats (12 bytes) + 4 bytes color = 16 bytes per vertex
final int vertexSize = format.getVertexSize();
final ByteBuffer buffer = org.lwjgl.BufferUtils.createByteBuffer(vertexSize * lines.size());
for (ImmediateModeRecorder.LineVertex v : lines) {
buffer.putFloat(v.x);
buffer.putFloat(v.y);
buffer.putFloat(v.z);
buffer.putInt(v.color);
}
buffer.flip();
vao.upload(buffer);
return vao;
}
}
@@ -0,0 +1,12 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
/**
* Result of building an optimized display list.
* Contains both the commands and the shared VBOs that need lifecycle management.
*/
@Desugar
record OptimizedListResult(DisplayListCommand[] commands, VertexBuffer[] ownedVbos) {}
@@ -30,7 +30,6 @@ import java.nio.IntBuffer;
public class RenderSystem {
private static final Logger LOGGER = LogManager.getLogger("RenderSystem");
private static DSAAccess dsaState;
private static boolean hasMultibind;
private static boolean supportsCompute;
private RenderSystem() {}
@@ -41,7 +40,6 @@ public class RenderSystem {
dsaState = new DSACore();
LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
}
hasMultibind = GLStateManager.capabilities.OpenGL45;
} catch (NoSuchFieldError ignored) {}
try {
@@ -55,10 +53,6 @@ public class RenderSystem {
LOGGER.info("No DSA support detected, falling back to legacy OpenGL.");
}
try {
hasMultibind |= GLStateManager.capabilities.GL_ARB_multi_bind;
} catch (NoSuchFieldError ignored) {}
supportsCompute = supportsCompute();
}
@@ -80,7 +74,7 @@ public class RenderSystem {
}
public static void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {
GL11.glCopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
GLStateManager.glCopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
}
public static void uniform1f(int location, float v0) {
@@ -103,6 +97,8 @@ public class RenderSystem {
GL20.glUniform3f(location, v0, v1, v2);
}
public static void uniform3i(int location, int v0, int v1, int v2) { GL20.glUniform3i(location, v0, v1, v2); }
public static void uniform4f(int location, float v0, float v1, float v2, float v3) {
GL20.glUniform4f(location, v0, v1, v2, v3);
}
@@ -135,6 +131,22 @@ public class RenderSystem {
dsaState.texParameterf(texture, target, pname, param);
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
}
public static String getProgramInfoLog(int program) {
return GL20.glGetProgramInfoLog(program, GL20.glGetProgrami(program, GL20.GL_INFO_LOG_LENGTH));
}
@@ -183,6 +195,10 @@ public class RenderSystem {
return dsaState.getTexParameteri(texture, target, pname);
}
public static int getTexLevelParameteri(int texture, int level, int pname) {
return dsaState.getTexLevelParameteri(texture, level, pname);
}
public static void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) {
if (GLStateManager.capabilities.OpenGL42) {
GL42.glBindImageTexture(unit, texture, level, layered, layer, access, format);
@@ -241,17 +257,17 @@ public class RenderSystem {
public static final FloatBuffer PROJECTION_MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
public static void setupProjectionMatrix(Matrix4f matrix) {
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glPushMatrix();
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPushMatrix();
matrix.get(0, PROJECTION_MATRIX_BUFFER);
GL11.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
public static void restoreProjectionMatrix() {
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glPopMatrix();
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPopMatrix();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
public static void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2, int bufferChoice, int filter) {
@@ -0,0 +1,246 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.line.ModelLine;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.primitive.ModelPrimitiveView;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.tri.ModelTriangle;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedPrimitiveDraw;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* Accumulates tessellator primitives (lines, triangles) for a single vertex format during display list compilation.
* Similar to FormatBuffer but handles ModelPrimitiveView instances instead of quads.
* <p>
* Unlike LineBuffer which handles ImmediateModeRecorder.LineVertex (fixed 16-byte: pos+color),
* this handles ModelLine/ModelTriangle from the tessellator with format-optimized vertex size.
* <p>
* Primitives are separated by draw mode (GL_LINES, GL_TRIANGLES) into separate VBOs.
*/
class TessellatorPrimitiveBuffer {
final CapturingTessellator.Flags flags;
// Accumulated primitives separated by type
final List<ModelLine> allLines = new ArrayList<>();
final List<ModelTriangle> allTriangles = new ArrayList<>();
// Ranges for lines
final List<DrawRange> lineMergedRanges = new ArrayList<>();
final List<DrawRange> linePerDrawRanges = new ArrayList<>();
int currentLineVertexOffset = 0;
private int pendingLineStartVertex = -1;
private int pendingLineVertexCount = 0;
private Matrix4f pendingLineTransform = null;
private int pendingLineCommandIndex = -1;
private int pendingLineStateGen = -1;
// Ranges for triangles
final List<DrawRange> triangleMergedRanges = new ArrayList<>();
final List<DrawRange> trianglePerDrawRanges = new ArrayList<>();
int currentTriangleVertexOffset = 0;
private int pendingTriangleStartVertex = -1;
private int pendingTriangleVertexCount = 0;
private Matrix4f pendingTriangleTransform = null;
private int pendingTriangleCommandIndex = -1;
private int pendingTriangleStateGen = -1;
TessellatorPrimitiveBuffer(CapturingTessellator.Flags flags) {
this.flags = flags;
}
/**
* Add a primitive draw to this buffer.
* Separates lines and triangles, tracking ranges for both merged and per-draw paths.
*/
void addDraw(AccumulatedPrimitiveDraw draw) {
final List<ModelPrimitiveView> primitives = draw.primitives;
final int size = primitives.size();
// Single pass: count and collect primitives by type
int lineCount = 0;
int triangleCount = 0;
for (int i = 0; i < size; i++) {
final ModelPrimitiveView prim = primitives.get(i);
if (prim instanceof ModelLine ml) {
allLines.add(ml);
lineCount++;
} else if (prim instanceof ModelTriangle mt) {
allTriangles.add(mt);
triangleCount++;
}
}
// Process lines
if (lineCount > 0) {
final int lineVertexCount = lineCount * 2; // 2 vertices per line
linePerDrawRanges.add(new DrawRange(currentLineVertexOffset, lineVertexCount, draw.transform, draw.commandIndex));
// Merge logic for optimized path:
// - same transform
// - same stateGeneration = no state commands (draw barriers) between draws
boolean canMerge = pendingLineTransform != null
&& pendingLineTransform.equals(draw.transform)
&& pendingLineStateGen == draw.stateGeneration;
if (canMerge) {
pendingLineVertexCount += lineVertexCount;
} else {
flushPendingLineRange();
pendingLineStartVertex = currentLineVertexOffset;
pendingLineVertexCount = lineVertexCount;
pendingLineTransform = new Matrix4f(draw.transform);
pendingLineCommandIndex = draw.commandIndex;
pendingLineStateGen = draw.stateGeneration;
}
currentLineVertexOffset += lineVertexCount;
}
// Process triangles
if (triangleCount > 0) {
final int triangleVertexCount = triangleCount * 3; // 3 vertices per triangle
trianglePerDrawRanges.add(new DrawRange(currentTriangleVertexOffset, triangleVertexCount, draw.transform, draw.commandIndex));
// Merge logic for optimized path:
// - same transform
// - same stateGeneration = no state commands (draw barriers) between draws
boolean canMerge = pendingTriangleTransform != null
&& pendingTriangleTransform.equals(draw.transform)
&& pendingTriangleStateGen == draw.stateGeneration;
if (canMerge) {
pendingTriangleVertexCount += triangleVertexCount;
} else {
flushPendingTriangleRange();
pendingTriangleStartVertex = currentTriangleVertexOffset;
pendingTriangleVertexCount = triangleVertexCount;
pendingTriangleTransform = new Matrix4f(draw.transform);
pendingTriangleCommandIndex = draw.commandIndex;
pendingTriangleStateGen = draw.stateGeneration;
}
currentTriangleVertexOffset += triangleVertexCount;
}
}
private void flushPendingLineRange() {
if (pendingLineTransform != null) {
lineMergedRanges.add(new DrawRange(pendingLineStartVertex, pendingLineVertexCount, pendingLineTransform, pendingLineCommandIndex));
pendingLineTransform = null;
pendingLineStateGen = -1;
}
}
private void flushPendingTriangleRange() {
if (pendingTriangleTransform != null) {
triangleMergedRanges.add(new DrawRange(pendingTriangleStartVertex, pendingTriangleVertexCount, pendingTriangleTransform, pendingTriangleCommandIndex));
pendingTriangleTransform = null;
pendingTriangleStateGen = -1;
}
}
/**
* Compile all accumulated primitives into VBOs using optimal vertex format.
* @return The compiled buffers with separate VBOs for lines and triangles, or null if empty
*/
CompiledPrimitiveBuffers finish() {
flushPendingLineRange();
flushPendingTriangleRange();
if (allLines.isEmpty() && allTriangles.isEmpty()) {
return null;
}
// Use optimal format based on flags (same logic as quads)
final VertexFormat format = DisplayListManager.selectOptimalFormat(flags);
final VertexBuffer lineVbo = allLines.isEmpty() ? null : compileLinesToVBO(format);
final VertexBuffer triangleVbo = allTriangles.isEmpty() ? null : compileTrianglesToVBO(format);
return new CompiledPrimitiveBuffers(
flags,
lineVbo,
lineMergedRanges.toArray(new DrawRange[0]),
linePerDrawRanges.toArray(new DrawRange[0]),
triangleVbo,
triangleMergedRanges.toArray(new DrawRange[0]),
trianglePerDrawRanges.toArray(new DrawRange[0])
);
}
private VertexBuffer compileLinesToVBO(VertexFormat format) {
final int vertexSize = format.getVertexSize();
final int size = allLines.size();
final ByteBuffer buffer = BufferUtils.createByteBuffer(vertexSize * size * 2);
for (int i = 0; i < size; i++) {
writePrimitiveToBuffer(allLines.get(i), buffer);
}
buffer.flip();
final VertexBuffer vbo = VAOManager.createVAO(format, GL11.GL_LINES);
vbo.upload(buffer);
return vbo;
}
private VertexBuffer compileTrianglesToVBO(VertexFormat format) {
final int vertexSize = format.getVertexSize();
final int size = allTriangles.size();
final ByteBuffer buffer = BufferUtils.createByteBuffer(vertexSize * size * 3);
for (int i = 0; i < size; i++) {
writePrimitiveToBuffer(allTriangles.get(i), buffer);
}
buffer.flip();
final VertexBuffer vbo = VAOManager.createVAO(format, GL11.GL_TRIANGLES);
vbo.upload(buffer);
return vbo;
}
/**
* Write a primitive's vertices to the buffer using the format specified by flags.
* Only writes attributes that are present in the format.
*/
private void writePrimitiveToBuffer(ModelPrimitiveView prim, ByteBuffer buffer) {
final int vertexCount = prim.getVertexCount();
final boolean hasTexture = flags.hasTexture;
final boolean hasBrightness = flags.hasBrightness;
final boolean hasColor = flags.hasColor;
final boolean hasNormals = flags.hasNormals;
for (int i = 0; i < vertexCount; i++) {
// Position (always present) - 12 bytes
buffer.putFloat(prim.getX(i));
buffer.putFloat(prim.getY(i));
buffer.putFloat(prim.getZ(i));
// Color (4 bytes, ABGR) - if present
if (hasColor) {
buffer.putInt(prim.getColor(i));
}
// Texture (2 floats = 8 bytes) - if present
if (hasTexture) {
buffer.putFloat(prim.getTexU(i));
buffer.putFloat(prim.getTexV(i));
}
// Light/Brightness (4 bytes, packed) - if present
if (hasBrightness) {
buffer.putInt(prim.getLight(i));
}
// Normal (4 bytes, packed) - if present
if (hasNormals) {
buffer.putInt(prim.getForgeNormal(i));
}
}
}
}
@@ -0,0 +1,136 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.MultMatrixCmd;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
/**
* Helper class for tracking and collapsing matrix transforms during optimization.
* Implements the transform accumulation and emission strategy:
* <ul>
* <li>accumulated: total relative transform (what GL matrix should be)</li>
* <li>lastEmitted: what we've emitted (what GL matrix is)</li>
* <li>stack: saved accumulated values at Push points</li>
* </ul>
*/
class TransformOptimizer {
private static final Logger LOGGER = LogManager.getLogger("TransformOptimizer");
private final Matrix4f accumulated = new Matrix4f(); // Current accumulated transform
private final Matrix4f lastEmitted = new Matrix4f(); // Last emitted transform
private final Deque<Matrix4f> stack = new ArrayDeque<>(); // For Push/Pop
private final int listId; // For logging
private boolean absoluteMatrixFlag = false; // True after LoadMatrix, cleared by LoadIdentity emit
TransformOptimizer(int listId) {
this.listId = listId;
accumulated.identity();
lastEmitted.identity();
}
Matrix4f getAccumulated() {
return accumulated;
}
void loadIdentity() {
accumulated.identity();
}
void pushTransform() {
stack.push(new Matrix4f(accumulated));
}
void popTransform() {
if (!stack.isEmpty()) {
final Matrix4f popped = stack.pop();
accumulated.set(popped);
// After Pop, GL state is restored to the pushed value so lastEmitted should also reflect this
lastEmitted.set(accumulated);
} else {
LOGGER.warn("[TransformOptimizer] list={} Pop with empty stack - resetting to identity", listId);
accumulated.identity();
lastEmitted.identity();
}
}
boolean isIdentity() {
return DisplayListManager.isIdentity(accumulated);
}
/**
* Check if there's a pending transform that hasn't been emitted yet.
*/
boolean hasPendingTransform() {
return !accumulated.equals(lastEmitted);
}
/**
* Emit a MultMatrix command if accumulated differs from lastEmitted.
* Updates lastEmitted to match accumulated after emission.
*/
void emitPendingTransform(List<DisplayListCommand> output) {
if (!accumulated.equals(lastEmitted)) {
emitTransformTo(output, accumulated);
}
}
/**
* Emit a MultMatrix command to make GL state match a target transform.
* Used when a draw expects a specific transform that may differ from accumulated.
* Updates lastEmitted to match target after emission.
*
* @param output The command list to emit to
* @param target The target transform to reach
*/
void emitTransformTo(List<DisplayListCommand> output, Matrix4f target) {
if (target.equals(lastEmitted)) {
// Already at target - nothing to emit
return;
}
// Calculate delta: what we need to multiply to go from lastEmitted to target
// delta = inverse(lastEmitted) * target
// But for simplicity, if lastEmitted is identity, just emit target
if (DisplayListManager.isIdentity(lastEmitted)) {
// Simple case: just emit target
output.add(MultMatrixCmd.create(target));
} else {
// Need to emit delta: inv(lastEmitted) * target
final Matrix4f delta = new Matrix4f(lastEmitted).invert().mul(target);
output.add(MultMatrixCmd.create(delta));
}
lastEmitted.set(target);
}
/**
* Check if a draw's expected transform differs from what we've emitted.
*/
boolean needsTransformForDraw(Matrix4f drawTransform) {
return !drawTransform.equals(lastEmitted);
}
/**
* Mark that the GL matrix is at an absolute value (after LoadMatrix).
* Subsequent LoadIdentity commands must be emitted to actually reset GL to identity.
*/
void markAbsoluteMatrix() {
absoluteMatrixFlag = true;
}
/**
* Check if the GL matrix is at an absolute value and clear the flag.
* Used to determine if LoadIdentity needs to emit.
* @return true if LoadMatrix was called and not yet reset by LoadIdentity
*/
boolean checkAndClearAbsoluteMatrix() {
boolean was = absoluteMatrixFlag;
absoluteMatrixFlag = false;
return was;
}
}
@@ -0,0 +1,27 @@
package com.gtnewhorizons.angelica.glsm;
/// Matches
public enum Vendor {
AMD("amd"),
INTEL("intel"),
MESA("mesa"),
NVIDIA("nvidia"),
UNKNOWN("");
final String[] names;
Vendor(String... names) {
this.names = names;
}
public static Vendor getVendor(String vendorString) {
for (var v : values()) {
for (var name : v.names) {
if (vendorString.contains(name)) return v;
}
}
// Shouldn't be needed as "" matches everything, but sanity checks good.
return UNKNOWN;
}
}
@@ -0,0 +1,11 @@
package com.gtnewhorizons.angelica.glsm.compat;
/**
* Simple holder for fog color values captured from vanilla rendering.
* Used by terrain renderers to access fog state.
*/
public class FogHelper {
public static float red;
public static float green;
public static float blue;
}
@@ -1,21 +1,33 @@
package com.gtnewhorizons.angelica.glsm.debug;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
// Adapted from https://github.com/makamys/CoreTweaks/blob/master/src/main/java/makamys/coretweaks/util/OpenGLDebugging.java;
// originally from https://github.com/TheGreyGhost/MinecraftByExample/blob/1-8final/src/main/java/minecraftbyexample/usefultools/OpenGLdebugging.java
@@ -25,12 +37,13 @@ public class OpenGLDebugging {
public static final Logger LOGGER = LogManager.getLogger("gldumper");
private static final Consumer<String> LINE_LOGGER = LOGGER::debug;
private static final Set<String> seenErrors = new HashSet<>();
public static final String GL_IS_ENABLED = "glIsEnabled()";
public static final String GL_GET_BOOLEANV = "glGetBooleanv()";
public static final String GL_GET_INTEGERV = "glGetIntegerv()";
public static final String GL_GET_FLOATV = "glGetFloatv()";
public class GLproperty {
public static class GLproperty {
public GLproperty(int glConstant, String name, String description, String category, String fetchCommand) {
this.gLConstant = glConstant;
@@ -445,30 +458,250 @@ public class OpenGLDebugging {
}
}
public static void checkGLSM() {
public static String getGLSMErrorLog() {
return getGLSMErrorLog(null, false, false);
}
// Re-entrancy guard - getGLSMErrorLog calls glActiveTexture which could trigger checkGLSMOnce
private static boolean inCheck = false;
/**
* Enhanced GLSM error logging with context, stack trace, and first-occurrence filtering.
*
* @param context Optional context string to include in error messages (e.g., "During rendering")
* @param logStacktrace If true, includes the current stack trace in the error output
* @param onlyFirstOccurrence If true, only logs each unique error once
* @return Error log string if mismatches found, null otherwise
*/
public static String getGLSMErrorLog(String context, boolean logStacktrace, boolean onlyFirstOccurrence) {
if (inCheck) return null;
if (!GLStateManager.isCachingEnabled() || GLStateManager.isRecordingDisplayList()) {
return null;
}
inCheck = true;
try {
String group = "checkGLSM" + (context == null ? "" : " " + context);
GLDebug.pushGroup(group);
final StringBuilder error = new StringBuilder();
boolean mismatch = false;
if (context != null && !context.isEmpty()) {
error.append("[").append(context).append("] ");
}
for (int i = 0; i < instance.propertyList.length; ++i) {
final GLproperty gLProperty = instance.propertyList[i];
if(gLProperty.fetchCommand.equals(GL_GET_FLOATV) && gLProperty.name.endsWith("_MATRIX")) {
if (gLProperty.fetchCommand.equals(GL_GET_FLOATV) && gLProperty.name.endsWith("_MATRIX")) {
final Matrix4f cached = gLProperty.getAsMatrix4f(true);
final Matrix4f uncached = gLProperty.getAsMatrix4f(false);
// Some precision issues due to RAD vs degrees, and translated (vs f)
if (!cached.equals(uncached, 0.001f)) {
LOGGER.error("GLSM mismatch: " + gLProperty.name + "\ncached:\n" + cached + "uncached:\n" + uncached);
mismatch = true;
// Skip comparison if either matrix is degenerate (contains NaN/Infinity) these represent invalid GL state from bad parameters, not cache bugs
if (isMatrixDegenerate(cached) || isMatrixDegenerate(uncached)) continue;
if (!matricesEquivalent(cached, uncached, 0.001f)) {
final String errorKey = "MATRIX:" + gLProperty.name;
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
final String errorMsg = "GLSM mismatch: " + gLProperty.name + "\ncached:\n" + cached + "uncached:\n" + uncached;
LOGGER.error(errorMsg);
error.append(errorMsg).append('\n');
mismatch = true;
}
}
} else {
final String cached = gLProperty.getAsString(true);
final String uncached = gLProperty.getAsString(false);
if (!cached.equals(uncached)) {
LOGGER.error("GLSM mismatch: " + gLProperty.name + " cached: " + cached + " uncached: " + uncached);
final String errorKey = "PROPERTY:" + gLProperty.name + ":" + cached + ":" + uncached;
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
error.append("GLSM mismatch: ").append(gLProperty.name).append(" cached: ").append(cached).append(" uncached: ").append(uncached);
error.append('\n');
mismatch = true;
}
}
}
}
final TextureUnitArray textureUnits = GLStateManager.getTextures();
final int currentActiveTexture = GLStateManager.getActiveTextureUnit();
// Check GL_ACTIVE_TEXTURE
final int glActiveUnit = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE) - GL13.GL_TEXTURE0;
if (glActiveUnit != currentActiveTexture) {
final String errorKey = "ACTIVE_TEXTURE:" + currentActiveTexture + ":" + glActiveUnit;
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
error.append("GLSM mismatch: GL_ACTIVE_TEXTURE cached: ").append(currentActiveTexture)
.append(" uncached: ").append(glActiveUnit).append('\n');
mismatch = true;
}
}
// Check per-unit texture state
for (int i = 0; i < textureUnits.textureMatricies.length; i++) {
GL13.glActiveTexture(GL13.GL_TEXTURE0 + i);
// Check GL_TEXTURE_2D enabled
final boolean cachedEnabled = textureUnits.getTextureUnitStates(i).isEnabled();
final boolean uncachedEnabled = GL11.glIsEnabled(GL11.GL_TEXTURE_2D);
if (cachedEnabled != uncachedEnabled) {
final String errorKey = "TEXTURE_2D:" + i + ":" + cachedEnabled + ":" + uncachedEnabled;
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
error.append("GLSM mismatch: GL_TEXTURE_2D cached: ").append(cachedEnabled)
.append(" uncached: ").append(uncachedEnabled)
.append(", texture unit: GL_TEXTURE").append(i).append('\n');
mismatch = true;
}
}
// Check GL_TEXTURE_BINDING_2D
final int cachedBinding = textureUnits.getTextureUnitBindings(i).getBinding();
final int uncachedBinding = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
if (cachedBinding != uncachedBinding) {
final String errorKey = "TEXTURE_BINDING_2D:" + i + ":" + cachedBinding + ":" + uncachedBinding;
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
error.append("GLSM mismatch: GL_TEXTURE_BINDING_2D cached: ").append(cachedBinding)
.append(" uncached: ").append(uncachedBinding)
.append(", texture unit: GL_TEXTURE").append(i).append('\n');
mismatch = true;
}
}
}
if (!mismatch) {
LOGGER.info("GLSM state matches!");
GL13.glActiveTexture(GL13.GL_TEXTURE0 + currentActiveTexture);
// Add stack trace if requested
if (mismatch && logStacktrace) {
error.append("\nStack trace:\n");
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (int i = 2; i < stackTrace.length; i++) {
error.append(" at ").append(stackTrace[i].toString()).append('\n');
}
}
GLDebug.popGroup();
return mismatch ? error.toString() : null;
} finally {
inCheck = false;
}
}
public static boolean checkGLSMOnce(String context) {
final String output = getGLSMErrorLog(context, true, true);
if (output != null) {
// Only log on actual mismatch
LOGGER.error("[{}] MISMATCH thread={}\n{}", context, Thread.currentThread().getName(), output);
return false;
}
return true;
}
/**
* Check if a matrix contains any non-finite values (NaN or Infinity).
* Such matrices represent invalid/degenerate GL state (e.g., zero near/far in projection).
*/
private static boolean isMatrixDegenerate(Matrix4f matrix) {
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 4; row++) {
if (!Float.isFinite(matrix.get(col, row))) {
return true;
}
}
}
return false;
}
/**
* Compare two matrices with tolerance for NaN/Infinity.
* Treats NaN == NaN and Infinity == Infinity as equal (both are degenerate states).
* For finite values, uses epsilon comparison.
*/
private static boolean matricesEquivalent(Matrix4f a, Matrix4f b, float epsilon) {
// Compare all 16 elements
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 4; row++) {
final float aVal = a.get(col, row);
final float bVal = b.get(col, row);
// If both are non-finite (NaN or Infinity), consider them equal
if (!Float.isFinite(aVal) && !Float.isFinite(bVal)) {
// Both non-finite: check if both NaN or both same Infinity
if (Float.isNaN(aVal) && Float.isNaN(bVal)) continue;
if (Float.isInfinite(aVal) && Float.isInfinite(bVal)) {
// Check same sign of infinity
if ((aVal > 0) == (bVal > 0)) continue;
}
return false; // Different non-finite values
}
// If one is finite and other is not, they differ
if (Float.isFinite(aVal) != Float.isFinite(bVal)) {
return false;
}
// Both finite: use epsilon comparison
if (Math.abs(aVal - bVal) > epsilon) {
return false;
}
}
}
return true;
}
public static void checkGLSM() {
final String output = getGLSMErrorLog();
if (output == null) {
LOGGER.info("GLSM state matches!");
} else {
LOGGER.error(output);
}
}
public static void copyTexture(ResourceLocation resource, File output) {
final Object object = Minecraft.getMinecraft().renderEngine.mapTextureObjects.get(resource);
if (object != null) {
copyTexture(((ITextureObject) object).getGlTextureId(), output);
}
}
public static void copyTexture(int textureID, File output) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, textureID);
int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH);
int height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT);
BufferedImage bufferedimage = new BufferedImage(width, height, 1);
final int[] pixelValues = ((DataBufferInt) bufferedimage.getRaster().getDataBuffer()).getData();
IntBuffer pixelBuffer = BufferUtils.createIntBuffer(pixelValues.length);
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer);
pixelBuffer.get(pixelValues);
// Flip texture
final int halfHeight = height / 2;
final int[] rowBuffer = new int[width];
for (int y = 0; y < halfHeight; y++) {
final int top = y * width;
final int bottom = (height - 1 - y) * width;
System.arraycopy(pixelValues, top, rowBuffer, 0, width);
System.arraycopy(pixelValues, bottom, pixelValues, top, width);
System.arraycopy(rowBuffer, 0, pixelValues, bottom, width);
}
File dir = output.getParentFile();
dir.mkdirs();
copyBufferedImageToFile(bufferedimage, output);
bufferedimage.flush();
}
private static void copyBufferedImageToFile(BufferedImage image, File file) {
try {
ImageIO.write(image, "png", file);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.ARBDirectStateAccess;
import org.lwjgl.opengl.GL45;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
@@ -14,6 +15,29 @@ public class DSAARB extends DSAUnsupported {
ARBDirectStateAccess.glGenerateTextureMipmap(texture);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
// Note: DSA glTextureImage2D doesn't exist in ARB_direct_state_access
// We need to use glTextureStorage2D + glTextureSubImage2D, or fall back to bind-based approach
// For simplicity, use the fallback which binds, uploads, and restores
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
}
@Override
public void texParameteri(int texture, int target, int pname, int param) {
if(!GLStateManager.updateTexParameteriCache(target, texture, pname, param)) return;
@@ -48,6 +72,11 @@ public class DSAARB extends DSAUnsupported {
return GLStateManager.getTexParameterOrDefault(texture, pname, () -> ARBDirectStateAccess.glGetTextureParameteri(texture, pname));
}
@Override
public int getTexLevelParameteri(int texture, int level, int pname) {
return ARBDirectStateAccess.glGetTextureLevelParameteri(texture, level, pname);
}
@Override
public void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
ARBDirectStateAccess.glCopyTextureSubImage2D(destTexture, i, i1, i2, i3, i4, width, height);
@@ -55,7 +84,7 @@ public class DSAARB extends DSAUnsupported {
@Override
public void bindTextureToUnit(int unit, int texture) {
if(GLStateManager.getBoundTexture(unit) == texture) return;
if(GLStateManager.getBoundTextureForServerState(unit) == texture) return;
if (texture == 0) {
super.bindTextureToUnit(unit, texture);
@@ -1,11 +1,18 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
public interface DSAAccess {
void generateMipmaps(int texture, int target);
// Texture image upload - DSA allows specifying texture directly without binding
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels);
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels);
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
void texParameteri(int texture, int target, int pname, int param);
void texParameterf(int texture, int target, int pname, float param);
void texParameteriv(int texture, int target, int pname, IntBuffer params);
@@ -16,6 +23,8 @@ public interface DSAAccess {
int getTexParameteri(int texture, int target, int pname);
int getTexLevelParameteri(int texture, int level, int pname);
void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height);
void bindTextureToUnit(int unit, int texture);
@@ -9,6 +9,7 @@ import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
@@ -20,6 +21,39 @@ public class DSAUnsupported implements DSAAccess {
GL30.glGenerateMipmap(target);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
// Get what cache thinks is bound - we'll restore to this to keep cache/GL in sync
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
GL11.glBindTexture(target, texture);
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
GL11.glBindTexture(target, cachedBinding); // Restore to cache value
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
GL11.glBindTexture(target, texture);
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
GL11.glBindTexture(target, cachedBinding);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
GL11.glBindTexture(target, texture);
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
GL11.glBindTexture(target, cachedBinding);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
GL11.glBindTexture(target, texture);
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
GL11.glBindTexture(target, cachedBinding);
}
@Override
public void texParameteri(int texture, int target, int pname, int param) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
@@ -56,9 +90,18 @@ public class DSAUnsupported implements DSAAccess {
return GLStateManager.glGetTexParameteri(target, pname);
}
@Override
public int getTexLevelParameteri(int texture, int level, int pname) {
final int previous = GLStateManager.getBoundTextureForServerState();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
final int result = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, pname);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previous);
return result;
}
@Override
public void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
final int previous = GLStateManager.getBoundTexture();
final int previous = GLStateManager.getBoundTextureForServerState();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, destTexture);
GL11.glCopyTexSubImage2D(target, i, i1, i2, i3, i4, width, height);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, previous);
@@ -0,0 +1,66 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil;
import org.joml.Matrix4f;
import java.util.List;
/**
* Represents an accumulated draw call during display list compilation.
* Matrix-as-Data Architecture: Stores untransformed vertices + captured matrix.
* The matrix will be applied at runtime, not baked into vertices.
*/
public class AccumulatedDraw {
public final List<ModelQuadViewMutable> quads;
public final Matrix4f transform;
public final CapturingTessellator.Flags flags;
public final int commandIndex; // Position in command list for state tracking
public final int matrixGeneration; // For batching: same generation = same transform context
public final int stateGeneration; // Same value = no state commands between draws, can merge
/**
* Last vertex restoration data for immediate mode draws.
* Null for tessellator draws (no restoration needed).
*/
public final RestoreData restoreData;
/**
* Holds last vertex attribute values for GL state restoration after VBO draw.
* Used to sync GLSM cache with actual GL state after VBO rendering.
*/
@Desugar
public record RestoreData(
float lastColorR, float lastColorG, float lastColorB, float lastColorA,
float lastNormalX, float lastNormalY, float lastNormalZ,
float lastTexCoordS, float lastTexCoordT) {}
public AccumulatedDraw(List<ModelQuadViewMutable> quads, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration) {
this(quads, transform, flags, commandIndex, matrixGeneration, 0, null);
}
public AccumulatedDraw(List<ModelQuadViewMutable> quads, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration, int stateGeneration) {
this(quads, transform, flags, commandIndex, matrixGeneration, stateGeneration, null);
}
public AccumulatedDraw(List<ModelQuadViewMutable> quads, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration, int stateGeneration, RestoreData restoreData) {
// Deep copy quads - pooled quads are reused after callback
this.quads = ModelQuadUtil.deepCopyQuads(quads);
this.transform = new Matrix4f(transform); // Snapshot for runtime application
this.flags = flags;
this.commandIndex = commandIndex;
this.matrixGeneration = matrixGeneration;
this.stateGeneration = stateGeneration;
this.restoreData = restoreData;
}
@Override
public String toString() {
return String.format("AccumulatedDraw[quads=%d, cmdIdx=%d, matrixGen=%d, stateGen=%d, flags=C%dT%dL%dN%d, restore=%s]",
quads.size(), commandIndex, matrixGeneration, stateGeneration, flags.hasColor ? 1 : 0, flags.hasTexture ? 1 : 0, flags.hasBrightness ? 1 : 0, flags.hasNormals ? 1 : 0,
restoreData != null ? "yes" : "no");
}
}
@@ -0,0 +1,39 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import org.joml.Matrix4f;
import java.util.ArrayList;
import java.util.List;
/**
* Represents an accumulated line draw call during display list compilation.
* Similar to AccumulatedDraw but for line primitives.
* Stores untransformed line vertices + captured matrix.
*/
public class AccumulatedLineDraw {
public final List<ImmediateModeRecorder.LineVertex> lines;
public final Matrix4f transform;
public final CapturingTessellator.Flags flags;
public final int commandIndex; // Position in command list for state tracking
public final int stateGeneration; // Same value = no state commands between draws, can merge
public AccumulatedLineDraw(List<ImmediateModeRecorder.LineVertex> lines, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex) {
this(lines, transform, flags, commandIndex, 0);
}
public AccumulatedLineDraw(List<ImmediateModeRecorder.LineVertex> lines, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int stateGeneration) {
// Copy lines - the source list may be cleared
this.lines = new ArrayList<>(lines);
this.transform = new Matrix4f(transform); // Snapshot for runtime application
this.flags = flags;
this.commandIndex = commandIndex;
this.stateGeneration = stateGeneration;
}
@Override
public String toString() {
return String.format("AccumulatedLineDraw[lines=%d, cmdIdx=%d, flags=C%dT%dL%dN%d]",
lines.size() / 2, commandIndex, flags.hasColor ? 1 : 0, flags.hasTexture ? 1 : 0, flags.hasBrightness ? 1 : 0, flags.hasNormals ? 1 : 0);
}
}
@@ -0,0 +1,66 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.line.ModelLine;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.primitive.ModelPrimitiveView;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.tri.ModelTriangle;
import org.joml.Matrix4f;
import java.util.ArrayList;
import java.util.List;
/**
* Represents an accumulated primitive draw call during display list compilation.
* Stores primitives (lines, triangles) from tessellator callback with their transform.
* <p>
* Unlike AccumulatedLineDraw which stores ImmediateModeRecorder.LineVertex (16-byte: pos+color),
* this stores ModelPrimitiveView (32-byte: pos+color+tex+light+normal) from the tessellator.
*/
public class AccumulatedPrimitiveDraw {
public final List<ModelPrimitiveView> primitives;
public final Matrix4f transform;
public final CapturingTessellator.Flags flags;
public final int commandIndex;
public final int matrixGeneration; // For batching: same generation = same transform context
public final int stateGeneration; // Same value = no state commands between draws, can merge
public AccumulatedPrimitiveDraw(List<ModelPrimitiveView> primitives, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration) {
this(primitives, transform, flags, commandIndex, matrixGeneration, 0);
}
public AccumulatedPrimitiveDraw(List<ModelPrimitiveView> primitives, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration, int stateGeneration) {
// Deep copy primitives - pooled primitives are reused after callback
this.primitives = deepCopyPrimitives(primitives);
this.transform = new Matrix4f(transform);
this.flags = flags;
this.commandIndex = commandIndex;
this.matrixGeneration = matrixGeneration;
this.stateGeneration = stateGeneration;
}
/**
* Creates deep copies of all primitives in the list.
* Required because CapturingTessellator pools and reuses primitive objects.
*/
private static List<ModelPrimitiveView> deepCopyPrimitives(List<ModelPrimitiveView> source) {
final int size = source.size();
final List<ModelPrimitiveView> copies = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
final ModelPrimitiveView prim = source.get(i);
if (prim instanceof ModelLine ml) {
copies.add(new ModelLine().copyFrom(ml));
} else if (prim instanceof ModelTriangle mt) {
copies.add(new ModelTriangle().copyFrom(mt));
}
}
return copies;
}
@Override
public String toString() {
return String.format("AccumulatedPrimitiveDraw[primitives=%d, cmdIdx=%d, matrixGen=%d, stateGen=%d, mode=%d, flags=C%dT%dL%dN%d]",
primitives.size(), commandIndex, matrixGeneration, stateGeneration, flags.drawMode,
flags.hasColor ? 1 : 0, flags.hasTexture ? 1 : 0,
flags.hasBrightness ? 1 : 0, flags.hasNormals ? 1 : 0);
}
}
@@ -0,0 +1,764 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.joml.Matrix4f;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
public final class CommandBuffer {
private static final int DEFAULT_CAPACITY = 4096; // Guess at typical display list size
// Flag bits for DRAW_RANGE_RESTORE command
public static final int FLAG_HAS_BRIGHTNESS = 1 << 0;
public static final int FLAG_HAS_COLOR = 1 << 1;
public static final int FLAG_HAS_NORMALS = 1 << 2;
public static final int FLAG_HAS_TEXTURE = 1 << 3;
private ByteBuffer buffer;
private long basePointer;
private long writePointer;
private final List<Object> complexObjects = new ArrayList<>();
public CommandBuffer() {
this(DEFAULT_CAPACITY);
}
public CommandBuffer(int initialCapacity) {
this.buffer = memAlloc(initialCapacity);
this.basePointer = memAddress(buffer);
this.writePointer = basePointer;
}
// === Low-level write operations ===
private void writeInt(int value) {
memPutInt(writePointer, value);
writePointer += 4;
}
private void writeFloat(float value) {
memPutFloat(writePointer, value);
writePointer += 4;
}
private void writeDouble(double value) {
memPutDouble(writePointer, value);
writePointer += 8;
}
private void ensureCapacity(int needed) {
final int size = size();
final int remaining = buffer.capacity() - size;
if (remaining < needed) {
final int newCapacity = Math.max(buffer.capacity() * 2, buffer.capacity() + needed);
buffer = memRealloc(buffer, newCapacity);
basePointer = memAddress(buffer);
writePointer = basePointer + size;
}
}
// === Single int commands ===
public void writeEnable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.ENABLE);
writeInt(cap);
}
public void writeDisable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.DISABLE);
writeInt(cap);
}
public void writeClear(int mask) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR);
writeInt(mask);
}
public void writeClearStencil(int s) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR_STENCIL);
writeInt(s);
}
public void writeCullFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.CULL_FACE);
writeInt(mode);
}
public void writeDepthFunc(int func) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_FUNC);
writeInt(func);
}
public void writeShadeModel(int mode) {
ensureCapacity(8);
writeInt(GLCommand.SHADE_MODEL);
writeInt(mode);
}
public void writeLogicOp(int opcode) {
ensureCapacity(8);
writeInt(GLCommand.LOGIC_OP);
writeInt(opcode);
}
public void writeMatrixMode(int mode) {
ensureCapacity(8);
writeInt(GLCommand.MATRIX_MODE);
writeInt(mode);
}
public void writeActiveTexture(int texture) {
ensureCapacity(8);
writeInt(GLCommand.ACTIVE_TEXTURE);
writeInt(texture);
}
public void writeUseProgram(int program) {
ensureCapacity(8);
writeInt(GLCommand.USE_PROGRAM);
writeInt(program);
}
public void writePushAttrib(int mask) {
ensureCapacity(8);
writeInt(GLCommand.PUSH_ATTRIB);
writeInt(mask);
}
public void writePopAttrib() {
ensureCapacity(8);
writeInt(GLCommand.POP_ATTRIB);
writeInt(0); // unused padding
}
public void writeLoadIdentity() {
ensureCapacity(4);
writeInt(GLCommand.LOAD_IDENTITY);
}
public void writePushMatrix() {
ensureCapacity(4);
writeInt(GLCommand.PUSH_MATRIX);
}
public void writePopMatrix() {
ensureCapacity(4);
writeInt(GLCommand.POP_MATRIX);
}
public void writeStencilMask(int mask) {
ensureCapacity(8);
writeInt(GLCommand.STENCIL_MASK);
writeInt(mask);
}
public void writeDepthMask(boolean flag) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_MASK);
writeInt(flag ? 1 : 0);
}
public void writeFrontFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.FRONT_FACE);
writeInt(mode);
}
// === Two int commands ===
public void writeBindTexture(int target, int texture) {
ensureCapacity(12);
writeInt(GLCommand.BIND_TEXTURE);
writeInt(target);
writeInt(texture);
}
public void writePolygonMode(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_MODE);
writeInt(face);
writeInt(mode);
}
public void writeColorMaterial(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.COLOR_MATERIAL);
writeInt(face);
writeInt(mode);
}
public void writeLineStipple(int factor, int pattern) {
ensureCapacity(12);
writeInt(GLCommand.LINE_STIPPLE);
writeInt(factor);
writeInt(pattern);
}
public void writeStencilMaskSeparate(int face, int mask) {
ensureCapacity(12);
writeInt(GLCommand.STENCIL_MASK_SEPARATE);
writeInt(face);
writeInt(mask);
}
public void writeFogi(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.FOGI);
writeInt(pname);
writeInt(param);
}
public void writeHint(int target, int mode) {
ensureCapacity(12);
writeInt(GLCommand.HINT);
writeInt(target);
writeInt(mode);
}
// === Three int commands ===
public void writeStencilFunc(int func, int ref, int mask) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_FUNC);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void writeStencilOp(int fail, int zfail, int zpass) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_OP);
writeInt(fail);
writeInt(zfail);
writeInt(zpass);
}
public void writeTexParameteri(int target, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERI);
writeInt(target);
writeInt(pname);
writeInt(param);
}
// === Four int commands ===
public void writeViewport(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.VIEWPORT);
writeInt(x);
writeInt(y);
writeInt(width);
writeInt(height);
}
public void writeBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_FUNC);
writeInt(srcRgb);
writeInt(dstRgb);
writeInt(srcAlpha);
writeInt(dstAlpha);
}
public void writeColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
ensureCapacity(20);
writeInt(GLCommand.COLOR_MASK);
writeInt(red ? 1 : 0);
writeInt(green ? 1 : 0);
writeInt(blue ? 1 : 0);
writeInt(alpha ? 1 : 0);
}
public void writeStencilFuncSeparate(int face, int func, int ref, int mask) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_FUNC_SEPARATE);
writeInt(face);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void writeStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_OP_SEPARATE);
writeInt(face);
writeInt(sfail);
writeInt(dpfail);
writeInt(dppass);
}
// === Float commands ===
public void writePointSize(float size) {
ensureCapacity(8);
writeInt(GLCommand.POINT_SIZE);
writeFloat(size);
}
public void writeLineWidth(float width) {
ensureCapacity(8);
writeInt(GLCommand.LINE_WIDTH);
writeFloat(width);
}
public void writePolygonOffset(float factor, float units) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_OFFSET);
writeFloat(factor);
writeFloat(units);
}
public void writeColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void writeClearDepth(double depth) {
ensureCapacity(12);
writeInt(GLCommand.CLEAR_DEPTH);
writeDouble(depth);
}
public void writeBlendColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
// === Mixed int+float commands ===
public void writeAlphaFunc(int func, float ref) {
ensureCapacity(12);
writeInt(GLCommand.ALPHA_FUNC);
writeInt(func);
writeFloat(ref);
}
public void writeFogf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.FOGF);
writeInt(pname);
writeFloat(param);
}
public void writeLightf(int light, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTF);
writeInt(light);
writeInt(pname);
writeFloat(param);
}
public void writeLightModelf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELF);
writeInt(pname);
writeFloat(param);
}
public void writeLighti(int light, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTI);
writeInt(light);
writeInt(pname);
writeInt(param);
}
public void writeLightModeli(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELI);
writeInt(pname);
writeInt(param);
}
public void writeMaterialf(int face, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.MATERIALF);
writeInt(face);
writeInt(pname);
writeFloat(param);
}
public void writeTexParameterf(int target, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERF);
writeInt(target);
writeInt(pname);
writeFloat(param);
}
// === Double commands ===
public void writeTranslate(double x, double y, double z) {
ensureCapacity(28);
writeInt(GLCommand.TRANSLATE);
writeDouble(x);
writeDouble(y);
writeDouble(z);
}
public void writeRotate(double angle, double x, double y, double z) {
ensureCapacity(36);
writeInt(GLCommand.ROTATE);
writeDouble(angle);
writeDouble(x);
writeDouble(y);
writeDouble(z);
}
public void writeScale(double x, double y, double z) {
ensureCapacity(28);
writeInt(GLCommand.SCALE);
writeDouble(x);
writeDouble(y);
writeDouble(z);
}
public void writeOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
ensureCapacity(52);
writeInt(GLCommand.ORTHO);
writeDouble(left);
writeDouble(right);
writeDouble(bottom);
writeDouble(top);
writeDouble(zNear);
writeDouble(zFar);
}
public void writeFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
ensureCapacity(52);
writeInt(GLCommand.FRUSTUM);
writeDouble(left);
writeDouble(right);
writeDouble(bottom);
writeDouble(top);
writeDouble(zNear);
writeDouble(zFar);
}
// === Matrix commands ===
public void writeMultMatrix(Matrix4f matrix) {
ensureCapacity(68);
writeInt(GLCommand.MULT_MATRIX);
writeMatrix4f(matrix);
}
public void writeLoadMatrix(Matrix4f matrix) {
ensureCapacity(68);
writeInt(GLCommand.LOAD_MATRIX);
writeMatrix4f(matrix);
}
/**
* Write a Matrix4f to the buffer in column-major order (OpenGL convention).
* Uses accessor methods instead of getToAddress() to avoid JOML MemUtil issues.
*/
private void writeMatrix4f(Matrix4f m) {
// Column 0
writeFloat(m.m00()); writeFloat(m.m01()); writeFloat(m.m02()); writeFloat(m.m03());
// Column 1
writeFloat(m.m10()); writeFloat(m.m11()); writeFloat(m.m12()); writeFloat(m.m13());
// Column 2
writeFloat(m.m20()); writeFloat(m.m21()); writeFloat(m.m22()); writeFloat(m.m23());
// Column 3
writeFloat(m.m30()); writeFloat(m.m31()); writeFloat(m.m32()); writeFloat(m.m33());
}
// === Buffer commands (inline 4 floats) ===
public void writeFog(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.FOG);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLight(int light, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.LIGHT);
writeInt(light);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLightModel(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.LIGHT_MODEL);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeMaterial(int face, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.MATERIAL);
writeInt(face);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
// === Draw commands ===
public void writeDrawRange(int vboIndex, int startVertex, int vertexCount, boolean hasBrightness) {
ensureCapacity(20);
writeInt(GLCommand.DRAW_RANGE);
writeInt(vboIndex);
writeInt(startVertex);
writeInt(vertexCount);
writeInt(hasBrightness ? 1 : 0);
}
/**
* Write a DRAW_RANGE_RESTORE command that draws a VBO range and restores GL current state after.
* Used for immediate mode VBOs to restore GL_CURRENT_COLOR, GL_CURRENT_NORMAL, GL_CURRENT_TEXTURE_COORDS.
*
* @param vboIndex Index into ownedVbos array
* @param startVertex First vertex to draw
* @param vertexCount Number of vertices to draw
* @param hasBrightness True if VBO has brightness/lightmap data
* @param hasColor True if color should be restored after draw
* @param hasNormals True if normal should be restored after draw
* @param hasTexture True if texcoord should be restored after draw
* @param lastColorR Last vertex's red color component
* @param lastColorG Last vertex's green color component
* @param lastColorB Last vertex's blue color component
* @param lastColorA Last vertex's alpha color component
* @param lastNormalX Last vertex's normal X
* @param lastNormalY Last vertex's normal Y
* @param lastNormalZ Last vertex's normal Z
* @param lastTexS Last vertex's texture S coordinate
* @param lastTexT Last vertex's texture T coordinate
*/
public void writeDrawRangeRestore(
int vboIndex, int startVertex, int vertexCount,
boolean hasBrightness, boolean hasColor, boolean hasNormals, boolean hasTexture,
float lastColorR, float lastColorG, float lastColorB, float lastColorA,
float lastNormalX, float lastNormalY, float lastNormalZ,
float lastTexS, float lastTexT
) {
ensureCapacity(56);
writeInt(GLCommand.DRAW_RANGE_RESTORE);
writeInt(vboIndex);
writeInt(startVertex);
writeInt(vertexCount);
int flags = (hasBrightness ? FLAG_HAS_BRIGHTNESS : 0)
| (hasColor ? FLAG_HAS_COLOR : 0)
| (hasNormals ? FLAG_HAS_NORMALS : 0)
| (hasTexture ? FLAG_HAS_TEXTURE : 0);
writeInt(flags);
// Last color (4 floats = 16 bytes)
writeFloat(lastColorR);
writeFloat(lastColorG);
writeFloat(lastColorB);
writeFloat(lastColorA);
// Last normal (3 floats = 12 bytes)
writeFloat(lastNormalX);
writeFloat(lastNormalY);
writeFloat(lastNormalZ);
// Last texcoord (2 floats = 8 bytes)
writeFloat(lastTexS);
writeFloat(lastTexT);
}
public void writeCallList(int listId) {
ensureCapacity(8);
writeInt(GLCommand.CALL_LIST);
writeInt(listId);
}
public void writeDrawBuffer(int mode) {
ensureCapacity(8);
writeInt(GLCommand.DRAW_BUFFER);
writeInt(mode);
}
private static final int MAX_DRAW_BUFFERS = 8;
// cmd (4) + count (4) + buffers (4 * MAX_DRAW_BUFFERS)
private static final int DRAW_BUFFERS_SIZE = 4 + 4 + 4 * MAX_DRAW_BUFFERS;
public void writeDrawBuffers(int count, int buffer) {
ensureCapacity(DRAW_BUFFERS_SIZE);
writeInt(GLCommand.DRAW_BUFFERS);
writeInt(count);
writeInt(buffer);
for (int i = 1; i < MAX_DRAW_BUFFERS; i++) {
writeInt(0);
}
}
public void writeDrawBuffers(int count, IntBuffer bufs) {
ensureCapacity(DRAW_BUFFERS_SIZE);
writeInt(GLCommand.DRAW_BUFFERS);
writeInt(count);
final int pos = bufs.position();
for (int i = 0; i < count && i < MAX_DRAW_BUFFERS; i++) {
writeInt(bufs.get(pos + i));
}
for (int i = count; i < MAX_DRAW_BUFFERS; i++) {
writeInt(0);
}
}
// === Complex object reference ===
public void writeComplexRef(DisplayListCommand cmd) {
ensureCapacity(8);
final int index = complexObjects.size();
complexObjects.add(cmd);
writeInt(GLCommand.COMPLEX_REF);
writeInt(index);
}
// === Reading methods for optimization pass ===
private long readPointer = 0;
/**
* Reset read position to the beginning of the buffer.
*/
public void resetRead() {
readPointer = basePointer;
}
/**
* Check if there's more data to read.
*/
public boolean hasRemaining() {
return readPointer < writePointer;
}
/**
* Read an int from the current position.
*/
public int readInt() {
final int value = memGetInt(readPointer);
readPointer += 4;
return value;
}
/**
* Read a float from the current position.
*/
public float readFloat() {
final float value = memGetFloat(readPointer);
readPointer += 4;
return value;
}
/**
* Read a double from the current position.
*/
public double readDouble() {
final double value = memGetDouble(readPointer);
readPointer += 8;
return value;
}
/**
* Read a Matrix4f from the current position (16 floats in column-major order).
*/
public Matrix4f readMatrix4f(Matrix4f dest) {
dest.m00(readFloat()); dest.m01(readFloat()); dest.m02(readFloat()); dest.m03(readFloat());
dest.m10(readFloat()); dest.m11(readFloat()); dest.m12(readFloat()); dest.m13(readFloat());
dest.m20(readFloat()); dest.m21(readFloat()); dest.m22(readFloat()); dest.m23(readFloat());
dest.m30(readFloat()); dest.m31(readFloat()); dest.m32(readFloat()); dest.m33(readFloat());
return dest;
}
// === Lifecycle ===
public int size() {
return (int) (writePointer - basePointer);
}
public long address() {
return basePointer;
}
public ByteBuffer toBuffer() {
buffer.limit(size());
buffer.position(0);
return buffer;
}
public Object[] getComplexObjects() {
return complexObjects.toArray();
}
public Object getComplexObject(int index) {
return complexObjects.get(index);
}
public boolean isEmpty() {
return size() == 0 && complexObjects.isEmpty();
}
/**
* Clear the buffer for reuse. Does NOT free memory.
*/
public void clear() {
writePointer = basePointer;
complexObjects.clear();
}
/**
* Free the underlying buffer. Must be called when done.
*/
public void free() {
if (buffer != null) {
memFree(buffer);
buffer = null;
basePointer = 0;
writePointer = 0;
}
}
}
@@ -0,0 +1,528 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.lwjgl.BufferUtils;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
import static com.gtnewhorizons.angelica.glsm.recording.CommandBuffer.*;
/**
* Executes commands from a CommandBuffer
*/
public final class CommandBufferExecutor {
private CommandBufferExecutor() {}
// Static reusable buffer for matrix ops (render thread only, LWJGL2/3 compatible)
private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
// Static reusable buffer for fog/light/material params
private static final FloatBuffer PARAMS_BUFFER = BufferUtils.createFloatBuffer(4);
// Static reusable buffer for draw buffers
private static final int MAX_DRAW_BUFFERS = 8;
private static final IntBuffer DRAW_BUFFERS_BUFFER = BufferUtils.createIntBuffer(MAX_DRAW_BUFFERS);
/**
* Execute all commands in the buffer.
*
* @param buffer The command buffer (position should be at 0, limit at size)
* @param complexObjects Array of complex objects (TexImage2DCmd, etc.)
* @param ownedVbos Array of VBOs owned by the display list (indexed by DrawRange commands)
*/
public static void execute(ByteBuffer buffer, Object[] complexObjects, VertexBuffer[] ownedVbos) {
long ptr = memAddress(buffer);
final long end = ptr + buffer.limit();
while (ptr < end) {
final int cmd = memGetInt(ptr);
ptr += 4;
switch (cmd) {
// === Single int commands ===
case GLCommand.ENABLE -> {
GLStateManager.glEnable(memGetInt(ptr));
ptr += 4;
}
case GLCommand.DISABLE -> {
GLStateManager.glDisable(memGetInt(ptr));
ptr += 4;
}
case GLCommand.CLEAR -> {
GLStateManager.glClear(memGetInt(ptr));
ptr += 4;
}
case GLCommand.CLEAR_STENCIL -> {
GLStateManager.glClearStencil(memGetInt(ptr));
ptr += 4;
}
case GLCommand.CULL_FACE -> {
GLStateManager.glCullFace(memGetInt(ptr));
ptr += 4;
}
case GLCommand.DEPTH_FUNC -> {
GLStateManager.glDepthFunc(memGetInt(ptr));
ptr += 4;
}
case GLCommand.SHADE_MODEL -> {
GLStateManager.glShadeModel(memGetInt(ptr));
ptr += 4;
}
case GLCommand.LOGIC_OP -> {
GLStateManager.glLogicOp(memGetInt(ptr));
ptr += 4;
}
case GLCommand.MATRIX_MODE -> {
GLStateManager.glMatrixMode(memGetInt(ptr));
ptr += 4;
}
case GLCommand.ACTIVE_TEXTURE -> {
GLStateManager.glActiveTexture(memGetInt(ptr));
ptr += 4;
}
case GLCommand.USE_PROGRAM -> {
GLStateManager.glUseProgram(memGetInt(ptr));
ptr += 4;
}
case GLCommand.PUSH_ATTRIB -> {
GLStateManager.glPushAttrib(memGetInt(ptr));
ptr += 4;
}
case GLCommand.POP_ATTRIB -> {
GLStateManager.glPopAttrib();
ptr += 4; // Skip unused padding
}
case GLCommand.LOAD_IDENTITY -> {
GLStateManager.glLoadIdentity();
}
case GLCommand.PUSH_MATRIX -> {
GLStateManager.glPushMatrix();
}
case GLCommand.POP_MATRIX -> {
GLStateManager.glPopMatrix();
}
case GLCommand.STENCIL_MASK -> {
GLStateManager.glStencilMask(memGetInt(ptr));
ptr += 4;
}
case GLCommand.DEPTH_MASK -> {
GLStateManager.glDepthMask(memGetInt(ptr) != 0);
ptr += 4;
}
case GLCommand.FRONT_FACE -> {
GLStateManager.glFrontFace(memGetInt(ptr));
ptr += 4;
}
// === Two int commands ===
case GLCommand.BIND_TEXTURE -> {
final int target = memGetInt(ptr);
final int texture = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glBindTexture(target, texture);
}
case GLCommand.POLYGON_MODE -> {
final int face = memGetInt(ptr);
final int mode = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glPolygonMode(face, mode);
}
case GLCommand.COLOR_MATERIAL -> {
final int face = memGetInt(ptr);
final int mode = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glColorMaterial(face, mode);
}
case GLCommand.LINE_STIPPLE -> {
final int factor = memGetInt(ptr);
final int pattern = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glLineStipple(factor, (short) pattern);
}
case GLCommand.STENCIL_MASK_SEPARATE -> {
final int face = memGetInt(ptr);
final int mask = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glStencilMaskSeparate(face, mask);
}
case GLCommand.FOGI -> {
final int pname = memGetInt(ptr);
final int param = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glFogi(pname, param);
}
case GLCommand.HINT -> {
final int target = memGetInt(ptr);
final int mode = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glHint(target, mode);
}
// === Three int commands ===
case GLCommand.STENCIL_FUNC -> {
final int func = memGetInt(ptr);
final int ref = memGetInt(ptr + 4);
final int mask = memGetInt(ptr + 8);
ptr += 12;
GLStateManager.glStencilFunc(func, ref, mask);
}
case GLCommand.STENCIL_OP -> {
final int fail = memGetInt(ptr);
final int zfail = memGetInt(ptr + 4);
final int zpass = memGetInt(ptr + 8);
ptr += 12;
GLStateManager.glStencilOp(fail, zfail, zpass);
}
case GLCommand.TEX_PARAMETERI -> {
final int target = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final int param = memGetInt(ptr + 8);
ptr += 12;
GLStateManager.glTexParameteri(target, pname, param);
}
// === Four int commands ===
case GLCommand.VIEWPORT -> {
final int x = memGetInt(ptr);
final int y = memGetInt(ptr + 4);
final int width = memGetInt(ptr + 8);
final int height = memGetInt(ptr + 12);
ptr += 16;
GLStateManager.glViewport(x, y, width, height);
}
case GLCommand.BLEND_FUNC -> {
final int srcRgb = memGetInt(ptr);
final int dstRgb = memGetInt(ptr + 4);
final int srcAlpha = memGetInt(ptr + 8);
final int dstAlpha = memGetInt(ptr + 12);
ptr += 16;
if (srcRgb == srcAlpha && dstRgb == dstAlpha) {
GLStateManager.glBlendFunc(srcRgb, dstRgb);
} else {
GLStateManager.tryBlendFuncSeparate(srcRgb, dstRgb, srcAlpha, dstAlpha);
}
}
case GLCommand.COLOR_MASK -> {
final boolean r = memGetInt(ptr) != 0;
final boolean g = memGetInt(ptr + 4) != 0;
final boolean b = memGetInt(ptr + 8) != 0;
final boolean a = memGetInt(ptr + 12) != 0;
ptr += 16;
GLStateManager.glColorMask(r, g, b, a);
}
case GLCommand.STENCIL_FUNC_SEPARATE -> {
final int face = memGetInt(ptr);
final int func = memGetInt(ptr + 4);
final int ref = memGetInt(ptr + 8);
final int mask = memGetInt(ptr + 12);
ptr += 16;
GLStateManager.glStencilFuncSeparate(face, func, ref, mask);
}
case GLCommand.STENCIL_OP_SEPARATE -> {
final int face = memGetInt(ptr);
final int sfail = memGetInt(ptr + 4);
final int dpfail = memGetInt(ptr + 8);
final int dppass = memGetInt(ptr + 12);
ptr += 16;
GLStateManager.glStencilOpSeparate(face, sfail, dpfail, dppass);
}
// === Float commands ===
case GLCommand.POINT_SIZE -> {
GLStateManager.glPointSize(memGetFloat(ptr));
ptr += 4;
}
case GLCommand.LINE_WIDTH -> {
GLStateManager.glLineWidth(memGetFloat(ptr));
ptr += 4;
}
case GLCommand.POLYGON_OFFSET -> {
final float factor = memGetFloat(ptr);
final float units = memGetFloat(ptr + 4);
ptr += 8;
GLStateManager.glPolygonOffset(factor, units);
}
case GLCommand.COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
final float b = memGetFloat(ptr + 8);
final float a = memGetFloat(ptr + 12);
ptr += 16;
GLStateManager.glColor4f(r, g, b, a);
}
case GLCommand.CLEAR_COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
final float b = memGetFloat(ptr + 8);
final float a = memGetFloat(ptr + 12);
ptr += 16;
GLStateManager.glClearColor(r, g, b, a);
}
case GLCommand.CLEAR_DEPTH -> {
final double depth = memGetDouble(ptr);
ptr += 8;
GLStateManager.glClearDepth(depth);
}
case GLCommand.BLEND_COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
final float b = memGetFloat(ptr + 8);
final float a = memGetFloat(ptr + 12);
ptr += 16;
GLStateManager.glBlendColor(r, g, b, a);
}
// === Mixed int+float commands ===
case GLCommand.ALPHA_FUNC -> {
final int func = memGetInt(ptr);
final float ref = memGetFloat(ptr + 4);
ptr += 8;
GLStateManager.glAlphaFunc(func, ref);
}
case GLCommand.FOGF -> {
final int pname = memGetInt(ptr);
final float param = memGetFloat(ptr + 4);
ptr += 8;
GLStateManager.glFogf(pname, param);
}
case GLCommand.LIGHTF -> {
final int light = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final float param = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glLightf(light, pname, param);
}
case GLCommand.LIGHT_MODELF -> {
final int pname = memGetInt(ptr);
final float param = memGetFloat(ptr + 4);
ptr += 8;
GLStateManager.glLightModelf(pname, param);
}
case GLCommand.LIGHTI -> {
final int light = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final int param = memGetInt(ptr + 8);
ptr += 12;
GLStateManager.glLighti(light, pname, param);
}
case GLCommand.LIGHT_MODELI -> {
final int pname = memGetInt(ptr);
final int param = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glLightModeli(pname, param);
}
case GLCommand.MATERIALF -> {
final int face = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final float param = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glMaterialf(face, pname, param);
}
case GLCommand.TEX_PARAMETERF -> {
final int target = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final float param = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glTexParameterf(target, pname, param);
}
// === Double commands ===
case GLCommand.TRANSLATE -> {
final double x = memGetDouble(ptr);
final double y = memGetDouble(ptr + 8);
final double z = memGetDouble(ptr + 16);
ptr += 24;
GLStateManager.glTranslated(x, y, z);
}
case GLCommand.ROTATE -> {
final double angle = memGetDouble(ptr);
final double x = memGetDouble(ptr + 8);
final double y = memGetDouble(ptr + 16);
final double z = memGetDouble(ptr + 24);
ptr += 32;
GLStateManager.glRotated(angle, x, y, z);
}
case GLCommand.SCALE -> {
final double x = memGetDouble(ptr);
final double y = memGetDouble(ptr + 8);
final double z = memGetDouble(ptr + 16);
ptr += 24;
GLStateManager.glScaled(x, y, z);
}
case GLCommand.ORTHO -> {
final double left = memGetDouble(ptr);
final double right = memGetDouble(ptr + 8);
final double bottom = memGetDouble(ptr + 16);
final double top = memGetDouble(ptr + 24);
final double zNear = memGetDouble(ptr + 32);
final double zFar = memGetDouble(ptr + 40);
ptr += 48;
GLStateManager.glOrtho(left, right, bottom, top, zNear, zFar);
}
case GLCommand.FRUSTUM -> {
final double left = memGetDouble(ptr);
final double right = memGetDouble(ptr + 8);
final double bottom = memGetDouble(ptr + 16);
final double top = memGetDouble(ptr + 24);
final double zNear = memGetDouble(ptr + 32);
final double zFar = memGetDouble(ptr + 40);
ptr += 48;
GLStateManager.glFrustum(left, right, bottom, top, zNear, zFar);
}
// === Matrix commands ===
case GLCommand.MULT_MATRIX -> {
// Mode-agnostic: just multiply current matrix
MATRIX_BUFFER.clear();
for (int i = 0; i < 16; i++) {
MATRIX_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
MATRIX_BUFFER.flip();
GLStateManager.glMultMatrix(MATRIX_BUFFER);
}
case GLCommand.LOAD_MATRIX -> {
MATRIX_BUFFER.clear();
for (int i = 0; i < 16; i++) {
MATRIX_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
MATRIX_BUFFER.flip();
GLStateManager.glLoadMatrix(MATRIX_BUFFER);
}
// === Buffer commands ===
case GLCommand.FOG -> {
final int pname = memGetInt(ptr);
final int count = memGetInt(ptr + 4);
ptr += 8;
PARAMS_BUFFER.clear();
for (int i = 0; i < count; i++) {
PARAMS_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
PARAMS_BUFFER.flip();
GLStateManager.glFog(pname, PARAMS_BUFFER);
}
case GLCommand.LIGHT -> {
final int light = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final int count = memGetInt(ptr + 8);
ptr += 12;
PARAMS_BUFFER.clear();
for (int i = 0; i < count; i++) {
PARAMS_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
PARAMS_BUFFER.flip();
GLStateManager.glLight(light, pname, PARAMS_BUFFER);
}
case GLCommand.LIGHT_MODEL -> {
final int pname = memGetInt(ptr);
final int count = memGetInt(ptr + 4);
ptr += 8;
PARAMS_BUFFER.clear();
for (int i = 0; i < count; i++) {
PARAMS_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
PARAMS_BUFFER.flip();
GLStateManager.glLightModel(pname, PARAMS_BUFFER);
}
case GLCommand.MATERIAL -> {
final int face = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final int count = memGetInt(ptr + 8);
ptr += 12;
PARAMS_BUFFER.clear();
for (int i = 0; i < count; i++) {
PARAMS_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
PARAMS_BUFFER.flip();
GLStateManager.glMaterial(face, pname, PARAMS_BUFFER);
}
// === Draw commands ===
case GLCommand.DRAW_RANGE -> {
final int vboIndex = memGetInt(ptr);
final int start = memGetInt(ptr + 4);
final int count = memGetInt(ptr + 8);
final boolean hasBrightness = memGetInt(ptr + 12) != 0;
ptr += 16;
final VertexBuffer vbo = ownedVbos[vboIndex];
vbo.setupState();
vbo.draw(start, count);
vbo.cleanupState();
}
case GLCommand.DRAW_RANGE_RESTORE -> {
// Draw VBO range then restore GL current state from last vertex attributes
final int vboIndex = memGetInt(ptr);
final int start = memGetInt(ptr + 4);
final int count = memGetInt(ptr + 8);
final int flags = memGetInt(ptr + 12);
// Draw the VBO
final VertexBuffer vbo = ownedVbos[vboIndex];
vbo.setupState();
vbo.draw(start, count);
vbo.cleanupState();
// Restore attributes based on flags
if ((flags & FLAG_HAS_COLOR) != 0) {
final float r = memGetFloat(ptr + 16);
final float g = memGetFloat(ptr + 20);
final float b = memGetFloat(ptr + 24);
final float a = memGetFloat(ptr + 28);
GLStateManager.glColor4f(r, g, b, a);
}
if ((flags & FLAG_HAS_NORMALS) != 0) {
final float nx = memGetFloat(ptr + 32);
final float ny = memGetFloat(ptr + 36);
final float nz = memGetFloat(ptr + 40);
GLStateManager.glNormal3f(nx, ny, nz);
}
if ((flags & FLAG_HAS_TEXTURE) != 0) {
final float s = memGetFloat(ptr + 44);
final float t = memGetFloat(ptr + 48);
GLStateManager.glTexCoord2f(s, t);
}
ptr += 52; // Skip full command size (56 - 4 for cmd already read)
}
case GLCommand.CALL_LIST -> {
final int listId = memGetInt(ptr);
ptr += 4;
DisplayListManager.glCallList(listId);
}
case GLCommand.DRAW_BUFFER -> {
GLStateManager.glDrawBuffer(memGetInt(ptr));
ptr += 4;
}
case GLCommand.DRAW_BUFFERS -> {
final int count = memGetInt(ptr);
ptr += 4;
DRAW_BUFFERS_BUFFER.clear();
for (int i = 0; i < count; i++) {
DRAW_BUFFERS_BUFFER.put(memGetInt(ptr + i * 4));
}
DRAW_BUFFERS_BUFFER.flip();
ptr += 4 * MAX_DRAW_BUFFERS;
GLStateManager.glDrawBuffers(DRAW_BUFFERS_BUFFER);
}
// === Complex object reference ===
case GLCommand.COMPLEX_REF -> {
final int index = memGetInt(ptr);
ptr += 4;
((DisplayListCommand) complexObjects[index]).execute();
}
default -> throw new IllegalStateException("Unknown command opcode: " + cmd);
}
}
}
}
@@ -0,0 +1,316 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import java.util.ArrayDeque;
import java.util.Deque;
public final class CommandBufferProcessor {
private static final Logger LOGGER = LogManager.getLogger("CommandBufferProcessor");
private static final Matrix4f tempMatrix = new Matrix4f();
private CommandBufferProcessor() {}
public static void processCommandForOptimization(int opcode, CommandBuffer raw, BufferTransformOptimizer opt, CommandBuffer out) {
switch (opcode) {
// === MultMatrix - already collapsed transforms from flushMatrix() ===
case GLCommand.MULT_MATRIX -> {
raw.readMatrix4f(tempMatrix);
opt.multMatrix(tempMatrix); // accumulated = accumulated * tempMatrix
out.writeMultMatrix(tempMatrix); // Copy to output
opt.markLastEmittedAsAccumulated(); // lastEmitted = accumulated (what GL now has)
}
// === Load commands - flush pending, emit, reset accumulator ===
case GLCommand.LOAD_IDENTITY -> {
opt.emitPendingTransform(out); // Emit any pending transforms first
out.writeLoadIdentity(); // Always emit LoadIdentity
opt.loadIdentity(); // Reset accumulated to identity
opt.resetLastEmitted(); // Reset lastEmitted to identity
opt.checkAndClearAbsoluteMatrix(); // Clear the flag if set
}
case GLCommand.LOAD_MATRIX -> {
raw.readMatrix4f(tempMatrix);
opt.emitPendingTransform(out);
out.writeLoadMatrix(tempMatrix);
opt.loadIdentity();
opt.resetLastEmitted(); // Reset lastEmitted to identity
opt.markAbsoluteMatrix();
}
// === Stack operations - flush/update optimizer state and copy ===
case GLCommand.PUSH_MATRIX -> {
opt.emitPendingTransform(out);
opt.push();
// Reset accumulated/lastEmitted to identity after push.
opt.loadIdentity();
opt.resetLastEmitted();
out.writePushMatrix();
}
case GLCommand.POP_MATRIX -> {
opt.pop();
out.writePopMatrix();
}
// === Barriers - emit pending transform before copying ===
case GLCommand.MATRIX_MODE -> {
final int mode = raw.readInt();
opt.emitPendingTransform(out); // Pending transforms apply to OLD mode
out.writeMatrixMode(mode);
opt.loadIdentity(); // Reset accumulated for new mode
opt.resetLastEmitted(); // Reset lastEmitted for new mode
}
case GLCommand.CALL_LIST -> {
final int listId = raw.readInt();
opt.emitPendingTransform(out);
out.writeCallList(listId);
}
case GLCommand.PUSH_ATTRIB -> {
final int mask = raw.readInt();
if ((mask & GL11.GL_TRANSFORM_BIT) != 0) {
opt.emitPendingTransform(out);
}
out.writePushAttrib(mask);
}
// === All other commands - just copy ===
default -> copyCommandData(opcode, raw, out);
}
}
/**
* Copy a complete command (opcode already read) from raw to final buffer.
*/
public static void copyCommand(CommandBuffer raw, CommandBuffer out) {
final int opcode = raw.readInt();
copyCommandData(opcode, raw, out);
}
/**
* Copy command data based on opcode (opcode already consumed from raw).
*/
public static void copyCommandData(int opcode, CommandBuffer raw, CommandBuffer out) {
switch (opcode) {
// Single int commands
case GLCommand.ENABLE -> out.writeEnable(raw.readInt());
case GLCommand.DISABLE -> out.writeDisable(raw.readInt());
case GLCommand.CLEAR -> out.writeClear(raw.readInt());
case GLCommand.CLEAR_STENCIL -> out.writeClearStencil(raw.readInt());
case GLCommand.CULL_FACE -> out.writeCullFace(raw.readInt());
case GLCommand.DEPTH_FUNC -> out.writeDepthFunc(raw.readInt());
case GLCommand.SHADE_MODEL -> out.writeShadeModel(raw.readInt());
case GLCommand.LOGIC_OP -> out.writeLogicOp(raw.readInt());
case GLCommand.MATRIX_MODE -> out.writeMatrixMode(raw.readInt());
case GLCommand.ACTIVE_TEXTURE -> out.writeActiveTexture(raw.readInt());
case GLCommand.USE_PROGRAM -> out.writeUseProgram(raw.readInt());
case GLCommand.PUSH_ATTRIB -> out.writePushAttrib(raw.readInt());
case GLCommand.POP_ATTRIB -> { raw.readInt(); out.writePopAttrib(); }
case GLCommand.LOAD_IDENTITY -> out.writeLoadIdentity();
case GLCommand.PUSH_MATRIX -> out.writePushMatrix();
case GLCommand.POP_MATRIX -> out.writePopMatrix();
case GLCommand.STENCIL_MASK -> out.writeStencilMask(raw.readInt());
case GLCommand.DEPTH_MASK -> out.writeDepthMask(raw.readInt() != 0);
case GLCommand.FRONT_FACE -> out.writeFrontFace(raw.readInt());
// Two int commands
case GLCommand.BIND_TEXTURE -> out.writeBindTexture(raw.readInt(), raw.readInt());
case GLCommand.POLYGON_MODE -> out.writePolygonMode(raw.readInt(), raw.readInt());
case GLCommand.COLOR_MATERIAL -> out.writeColorMaterial(raw.readInt(), raw.readInt());
case GLCommand.LINE_STIPPLE -> out.writeLineStipple(raw.readInt(), raw.readInt());
case GLCommand.STENCIL_MASK_SEPARATE -> out.writeStencilMaskSeparate(raw.readInt(), raw.readInt());
case GLCommand.FOGI -> out.writeFogi(raw.readInt(), raw.readInt());
case GLCommand.HINT -> out.writeHint(raw.readInt(), raw.readInt());
// Three int commands
case GLCommand.STENCIL_FUNC -> out.writeStencilFunc(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.STENCIL_OP -> out.writeStencilOp(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.TEX_PARAMETERI -> out.writeTexParameteri(raw.readInt(), raw.readInt(), raw.readInt());
// Four int commands
case GLCommand.VIEWPORT -> out.writeViewport(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.BLEND_FUNC -> out.writeBlendFunc(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.COLOR_MASK -> out.writeColorMask(raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0);
case GLCommand.STENCIL_FUNC_SEPARATE -> out.writeStencilFuncSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.STENCIL_OP_SEPARATE -> out.writeStencilOpSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
// Float commands
case GLCommand.POINT_SIZE -> out.writePointSize(raw.readFloat());
case GLCommand.LINE_WIDTH -> out.writeLineWidth(raw.readFloat());
case GLCommand.POLYGON_OFFSET -> out.writePolygonOffset(raw.readFloat(), raw.readFloat());
case GLCommand.COLOR -> out.writeColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
case GLCommand.CLEAR_COLOR -> out.writeClearColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
// Single double commands
case GLCommand.CLEAR_DEPTH -> out.writeClearDepth(raw.readDouble());
// Four float commands
case GLCommand.BLEND_COLOR -> out.writeBlendColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
// Mixed int+float commands
case GLCommand.ALPHA_FUNC -> out.writeAlphaFunc(raw.readInt(), raw.readFloat());
case GLCommand.FOGF -> out.writeFogf(raw.readInt(), raw.readFloat());
case GLCommand.LIGHTF -> out.writeLightf(raw.readInt(), raw.readInt(), raw.readFloat());
case GLCommand.LIGHT_MODELF -> out.writeLightModelf(raw.readInt(), raw.readFloat());
case GLCommand.LIGHTI -> out.writeLighti(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.LIGHT_MODELI -> out.writeLightModeli(raw.readInt(), raw.readInt());
case GLCommand.MATERIALF -> out.writeMaterialf(raw.readInt(), raw.readInt(), raw.readFloat());
case GLCommand.TEX_PARAMETERF -> out.writeTexParameterf(raw.readInt(), raw.readInt(), raw.readFloat());
// Double commands
case GLCommand.TRANSLATE -> out.writeTranslate(raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.ROTATE -> out.writeRotate(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.SCALE -> out.writeScale(raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.ORTHO -> out.writeOrtho(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.FRUSTUM -> out.writeFrustum(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
// Matrix commands
case GLCommand.MULT_MATRIX -> { out.writeMultMatrix(raw.readMatrix4f(tempMatrix)); }
case GLCommand.LOAD_MATRIX -> { out.writeLoadMatrix(raw.readMatrix4f(tempMatrix)); }
// FloatBuffer commands (inline 4 floats max)
case GLCommand.FOG -> {
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeFog(pname, fb);
}
case GLCommand.LIGHT -> {
final int light = raw.readInt();
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeLight(light, pname, fb);
}
case GLCommand.LIGHT_MODEL -> {
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeLightModel(pname, fb);
}
case GLCommand.MATERIAL -> {
final int face = raw.readInt();
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeMaterial(face, pname, fb);
}
// Draw/call commands
case GLCommand.DRAW_RANGE -> out.writeDrawRange(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt() != 0);
case GLCommand.CALL_LIST -> out.writeCallList(raw.readInt());
// Complex object reference - transfer the object to output buffer
case GLCommand.COMPLEX_REF -> {
final int index = raw.readInt();
final Object obj = raw.getComplexObject(index);
out.writeComplexRef((DisplayListCommand) obj);
}
default -> LOGGER.warn("[CommandBufferProcessor] Unknown opcode in buffer copy: {}", opcode);
}
}
/**
* Lightweight transform optimizer for buffer-to-buffer processing.
* Tracks accumulated transform (mode-agnostic) and emits MultMatrix commands when needed.
*
* <p>This optimizer processes raw transform commands (TRANSLATE, ROTATE, SCALE) and
* collapses them into single MultMatrix commands at barriers. It must stay synchronized
* with the relativeTransform tracking in DisplayListManager - both systems must reset
* at the same barriers (MatrixMode, LoadIdentity, LoadMatrix, Push/Pop).</p>
*/
public static class BufferTransformOptimizer {
private final Matrix4f accumulated = new Matrix4f();
private final Matrix4f lastEmitted = new Matrix4f();
private final Matrix4f delta = new Matrix4f(); // Reused for delta computation
private final Deque<Matrix4f> stack = new ArrayDeque<>();
private boolean absoluteMatrixFlag = false;
public BufferTransformOptimizer() {
accumulated.identity();
lastEmitted.identity();
}
public void multMatrix(Matrix4f m) {
accumulated.mul(m);
}
public void loadIdentity() {
accumulated.identity();
}
public void push() {
stack.push(new Matrix4f(accumulated));
}
public void pop() {
if (!stack.isEmpty()) {
accumulated.set(stack.pop());
lastEmitted.set(accumulated);
} else {
accumulated.identity();
lastEmitted.identity();
}
}
public boolean isIdentity() {
return DisplayListManager.isIdentity(accumulated);
}
public void markAbsoluteMatrix() {
absoluteMatrixFlag = true;
}
public boolean checkAndClearAbsoluteMatrix() {
final boolean was = absoluteMatrixFlag;
absoluteMatrixFlag = false;
return was;
}
public void resetLastEmitted() {
lastEmitted.identity();
}
public void setLastEmitted(Matrix4f m) {
lastEmitted.set(m);
}
public void markLastEmittedAsAccumulated() {
lastEmitted.set(accumulated);
}
public void emitPendingTransform(CommandBuffer out) {
if (!accumulated.equals(lastEmitted)) {
emitTransformTo(out, accumulated);
}
}
public void emitTransformTo(CommandBuffer out, Matrix4f target) {
if (target.equals(lastEmitted)) {
return;
}
if (DisplayListManager.isIdentity(lastEmitted)) {
out.writeMultMatrix(target);
} else {
delta.set(lastEmitted).invert().mul(target);
out.writeMultMatrix(delta);
}
lastEmitted.set(target);
}
}
}
@@ -0,0 +1,344 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.joml.Matrix4f;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
/**
* Records GL commands to a CommandBuffer during display list compilation.
*/
public class CommandRecorder {
private final CommandBuffer buffer;
private int commandCount;
public CommandRecorder() {
this.buffer = new CommandBuffer();
this.commandCount = 0;
}
public CommandBuffer getBuffer() {
return buffer;
}
public int getCommandCount() {
return commandCount;
}
public void free() {
buffer.free();
}
// ==================== Recording Methods ====================
public void recordEnable(int cap) {
buffer.writeEnable(cap);
commandCount++;
}
public void recordDisable(int cap) {
buffer.writeDisable(cap);
commandCount++;
}
public void recordClear(int mask) {
buffer.writeClear(mask);
commandCount++;
}
public void recordClearColor(float r, float g, float b, float a) {
buffer.writeClearColor(r, g, b, a);
commandCount++;
}
public void recordClearDepth(double depth) {
buffer.writeClearDepth(depth);
commandCount++;
}
public void recordBlendColor(float r, float g, float b, float a) {
buffer.writeBlendColor(r, g, b, a);
commandCount++;
}
public void recordClearStencil(int s) {
buffer.writeClearStencil(s);
commandCount++;
}
public void recordColor(float r, float g, float b, float a) {
buffer.writeColor(r, g, b, a);
commandCount++;
}
public void recordColorMask(boolean r, boolean g, boolean b, boolean a) {
buffer.writeColorMask(r, g, b, a);
commandCount++;
}
public void recordDepthMask(boolean flag) {
buffer.writeDepthMask(flag);
commandCount++;
}
public void recordFrontFace(int mode) {
buffer.writeFrontFace(mode);
commandCount++;
}
public void recordDepthFunc(int func) {
buffer.writeDepthFunc(func);
commandCount++;
}
public void recordBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
buffer.writeBlendFunc(srcRgb, dstRgb, srcAlpha, dstAlpha);
commandCount++;
}
public void recordAlphaFunc(int func, float ref) {
buffer.writeAlphaFunc(func, ref);
commandCount++;
}
public void recordCullFace(int mode) {
buffer.writeCullFace(mode);
commandCount++;
}
public void recordShadeModel(int mode) {
buffer.writeShadeModel(mode);
commandCount++;
}
public void recordBindTexture(int target, int texture) {
buffer.writeBindTexture(target, texture);
commandCount++;
}
public void recordTexParameteri(int target, int pname, int param) {
buffer.writeTexParameteri(target, pname, param);
commandCount++;
}
public void recordTexParameterf(int target, int pname, float param) {
buffer.writeTexParameterf(target, pname, param);
commandCount++;
}
public void recordMatrixMode(int mode) {
buffer.writeMatrixMode(mode);
commandCount++;
}
public void recordLoadIdentity() {
buffer.writeLoadIdentity();
commandCount++;
}
public void recordPushMatrix() {
buffer.writePushMatrix();
commandCount++;
}
public void recordPopMatrix() {
buffer.writePopMatrix();
commandCount++;
}
public void recordMultMatrix(Matrix4f matrix) {
buffer.writeMultMatrix(matrix);
commandCount++;
}
public void recordLoadMatrix(Matrix4f matrix) {
buffer.writeLoadMatrix(matrix);
commandCount++;
}
public void recordOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
buffer.writeOrtho(left, right, bottom, top, zNear, zFar);
commandCount++;
}
public void recordFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
buffer.writeFrustum(left, right, bottom, top, zNear, zFar);
commandCount++;
}
public void recordViewport(int x, int y, int width, int height) {
buffer.writeViewport(x, y, width, height);
commandCount++;
}
public void recordPointSize(float size) {
buffer.writePointSize(size);
commandCount++;
}
public void recordLineWidth(float width) {
buffer.writeLineWidth(width);
commandCount++;
}
public void recordLineStipple(int factor, int pattern) {
buffer.writeLineStipple(factor, pattern);
commandCount++;
}
public void recordPolygonOffset(float factor, float units) {
buffer.writePolygonOffset(factor, units);
commandCount++;
}
public void recordPolygonMode(int face, int mode) {
buffer.writePolygonMode(face, mode);
commandCount++;
}
public void recordColorMaterial(int face, int mode) {
buffer.writeColorMaterial(face, mode);
commandCount++;
}
public void recordLogicOp(int opcode) {
buffer.writeLogicOp(opcode);
commandCount++;
}
public void recordActiveTexture(int texture) {
buffer.writeActiveTexture(texture);
commandCount++;
}
public void recordUseProgram(int program) {
buffer.writeUseProgram(program);
commandCount++;
}
public void recordPushAttrib(int mask) {
buffer.writePushAttrib(mask);
commandCount++;
}
public void recordPopAttrib() {
buffer.writePopAttrib();
commandCount++;
}
public void recordFogf(int pname, float param) {
buffer.writeFogf(pname, param);
commandCount++;
}
public void recordFogi(int pname, int param) {
buffer.writeFogi(pname, param);
commandCount++;
}
public void recordHint(int target, int mode) {
buffer.writeHint(target, mode);
commandCount++;
}
public void recordFog(int pname, FloatBuffer params) {
buffer.writeFog(pname, params);
commandCount++;
}
public void recordLightf(int light, int pname, float param) {
buffer.writeLightf(light, pname, param);
commandCount++;
}
public void recordLighti(int light, int pname, int param) {
buffer.writeLighti(light, pname, param);
commandCount++;
}
public void recordLight(int light, int pname, FloatBuffer params) {
buffer.writeLight(light, pname, params);
commandCount++;
}
public void recordLightModelf(int pname, float param) {
buffer.writeLightModelf(pname, param);
commandCount++;
}
public void recordLightModeli(int pname, int param) {
buffer.writeLightModeli(pname, param);
commandCount++;
}
public void recordLightModel(int pname, FloatBuffer params) {
buffer.writeLightModel(pname, params);
commandCount++;
}
public void recordMaterialf(int face, int pname, float val) {
buffer.writeMaterialf(face, pname, val);
commandCount++;
}
public void recordMaterial(int face, int pname, FloatBuffer params) {
buffer.writeMaterial(face, pname, params);
commandCount++;
}
public void recordStencilFunc(int func, int ref, int mask) {
buffer.writeStencilFunc(func, ref, mask);
commandCount++;
}
public void recordStencilFuncSeparate(int face, int func, int ref, int mask) {
buffer.writeStencilFuncSeparate(face, func, ref, mask);
commandCount++;
}
public void recordStencilOp(int fail, int zfail, int zpass) {
buffer.writeStencilOp(fail, zfail, zpass);
commandCount++;
}
public void recordStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
buffer.writeStencilOpSeparate(face, sfail, dpfail, dppass);
commandCount++;
}
public void recordStencilMask(int mask) {
buffer.writeStencilMask(mask);
commandCount++;
}
public void recordStencilMaskSeparate(int face, int mask) {
buffer.writeStencilMaskSeparate(face, mask);
commandCount++;
}
public void recordCallList(int listId) {
buffer.writeCallList(listId);
commandCount++;
}
public void recordDrawBuffer(int mode) {
buffer.writeDrawBuffer(mode);
commandCount++;
}
public void recordDrawBuffers(int count, int buf) {
buffer.writeDrawBuffers(count, buf);
commandCount++;
}
public void recordDrawBuffers(int count, IntBuffer bufs) {
buffer.writeDrawBuffers(count, bufs);
commandCount++;
}
public void recordComplexCommand(DisplayListCommand cmd) {
buffer.writeComplexRef(cmd);
commandCount++;
}
}
@@ -0,0 +1,204 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
/**
* Represents a compiled display list.
*
* <p>Contains commands serialized to a direct ByteBuffer for off-heap storage,
* with complex objects (TexImage2DCmd, TexSubImage2DCmd) stored separately.
*
* <p>The ownedVbos array contains VBOs that are referenced by DrawRange commands.
*/
public final class CompiledDisplayList {
/**
* Empty display list singleton. Per OpenGL spec, an empty list is still valid.
* This instance does nothing when rendered and is never deleted (shared singleton).
*/
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null);
private final ByteBuffer commandBuffer; // Off-heap command storage, must be freed
private final Object[] complexObjects; // Complex commands (TexImage2D, etc.)
private final VertexBuffer[] ownedVbos; // GPU resources referenced by index
public CompiledDisplayList(ByteBuffer commandBuffer, Object[] complexObjects, VertexBuffer[] ownedVbos) {
this.commandBuffer = commandBuffer;
this.complexObjects = complexObjects;
this.ownedVbos = ownedVbos;
}
/**
* Render this display list by executing all commands.
*/
public void render() {
if (commandBuffer != null && commandBuffer.limit() > 0) {
CommandBufferExecutor.execute(commandBuffer, complexObjects, ownedVbos);
}
}
/**
* Delete all resources held by this display list.
*/
public void delete() {
// Delete complex objects that hold resources
if (complexObjects != null) {
for (Object obj : complexObjects) {
if (obj instanceof DisplayListCommand cmd) {
cmd.delete();
}
}
}
// Close VBOs
if (ownedVbos != null) {
for (VertexBuffer vbo : ownedVbos) {
if (vbo != null) {
vbo.close();
}
}
}
// Free the command buffer (off-heap memory)
if (commandBuffer != null) {
memFree(commandBuffer);
}
}
/**
* Get the command buffer for debugging/inspection.
*/
public ByteBuffer getCommandBuffer() {
return commandBuffer;
}
/**
* Get complex objects for inlining into another display list (nested lists).
*/
public Object[] getComplexObjects() {
return complexObjects;
}
/**
* Get owned VBOs for inlining into another display list.
*/
public VertexBuffer[] getOwnedVbos() {
return ownedVbos;
}
// === Test inspection methods ===
/**
* Get counts of each command type in the buffer.
* Used for testing to verify optimization results.
*/
public Int2IntMap getCommandCounts() {
final Int2IntMap counts = new Int2IntOpenHashMap();
if (commandBuffer == null || commandBuffer.limit() == 0) {
return counts;
}
long ptr = memAddress(commandBuffer);
final long end = ptr + commandBuffer.limit();
while (ptr < end) {
final int cmd = memGetInt(ptr);
counts.mergeInt(cmd, 1, Integer::sum);
ptr += getCommandSize(cmd, ptr);
}
return counts;
}
/**
* Get the sequence of command opcodes in order.
* Used for testing to verify command ordering.
*/
public IntList getCommandOpcodes() {
final IntList opcodes = new IntArrayList();
if (commandBuffer == null || commandBuffer.limit() == 0) {
return opcodes;
}
long ptr = memAddress(commandBuffer);
final long end = ptr + commandBuffer.limit();
while (ptr < end) {
final int cmd = memGetInt(ptr);
opcodes.add(cmd);
ptr += getCommandSize(cmd, ptr);
}
return opcodes;
}
/**
* Get size of a command in bytes (including the opcode).
*/
private static int getCommandSize(int cmd, long ptr) {
return switch (cmd) {
// Opcode-only commands (4 bytes)
case GLCommand.LOAD_IDENTITY, GLCommand.PUSH_MATRIX, GLCommand.POP_MATRIX -> 4;
// Single int commands (8 bytes)
case GLCommand.ENABLE, GLCommand.DISABLE, GLCommand.CLEAR, GLCommand.CLEAR_STENCIL,
GLCommand.CULL_FACE, GLCommand.DEPTH_FUNC, GLCommand.SHADE_MODEL, GLCommand.LOGIC_OP,
GLCommand.MATRIX_MODE, GLCommand.ACTIVE_TEXTURE, GLCommand.USE_PROGRAM,
GLCommand.PUSH_ATTRIB, GLCommand.POP_ATTRIB, GLCommand.STENCIL_MASK,
GLCommand.DEPTH_MASK, GLCommand.FRONT_FACE, GLCommand.POINT_SIZE, GLCommand.LINE_WIDTH,
GLCommand.CALL_LIST, GLCommand.COMPLEX_REF -> 8;
// Two int commands (12 bytes)
case GLCommand.BIND_TEXTURE, GLCommand.POLYGON_MODE, GLCommand.COLOR_MATERIAL,
GLCommand.LINE_STIPPLE, GLCommand.STENCIL_MASK_SEPARATE, GLCommand.FOGI,
GLCommand.HINT, GLCommand.POLYGON_OFFSET, GLCommand.ALPHA_FUNC, GLCommand.FOGF,
GLCommand.LIGHT_MODELF, GLCommand.LIGHT_MODELI -> 12;
// Three int commands (16 bytes)
case GLCommand.STENCIL_FUNC, GLCommand.STENCIL_OP, GLCommand.TEX_PARAMETERI,
GLCommand.LIGHTF, GLCommand.LIGHTI,
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF -> 16;
// Four int commands (20 bytes)
case GLCommand.VIEWPORT, GLCommand.BLEND_FUNC, GLCommand.COLOR_MASK,
GLCommand.STENCIL_FUNC_SEPARATE, GLCommand.STENCIL_OP_SEPARATE,
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR,
GLCommand.DRAW_RANGE -> 20;
// DRAW_RANGE_RESTORE: 56 bytes
// [cmd:4][vboIndex:4][start:4][count:4][flags:4][color:16f][normal:12f][texcoord:8f]
case GLCommand.DRAW_RANGE_RESTORE -> 56;
// DRAW_BUFFER: 8 bytes [cmd:4][mode:4]
case GLCommand.DRAW_BUFFER -> 8;
// DRAW_BUFFERS: 40 bytes [cmd:4][count:4][bufs:4*8]
case GLCommand.DRAW_BUFFERS -> 40;
// Double commands
case GLCommand.TRANSLATE, GLCommand.SCALE -> 28; // cmd + 3 doubles
case GLCommand.CLEAR_DEPTH -> 12; // cmd + 1 double
case GLCommand.ROTATE -> 36; // cmd + 4 doubles
case GLCommand.ORTHO, GLCommand.FRUSTUM -> 52; // cmd + 6 doubles
// Matrix commands
case GLCommand.MULT_MATRIX, GLCommand.LOAD_MATRIX -> 68; // cmd + 16 floats
// Buffer commands (variable, read count)
case GLCommand.FOG, GLCommand.LIGHT_MODEL -> {
final int count = memGetInt(ptr + 8); // pname at +4, count at +8
yield 12 + count * 4;
}
case GLCommand.LIGHT, GLCommand.MATERIAL -> {
final int count = memGetInt(ptr + 12); // light/face at +4, pname at +8, count at +12
yield 16 + count * 4;
}
default -> throw new IllegalStateException("Unknown command: " + cmd);
};
}
}
@@ -0,0 +1,179 @@
package com.gtnewhorizons.angelica.glsm.recording;
/**
* Command opcodes for ByteBuffer-based display list serialization.
* Each command has a fixed layout described in comments.
* All values are 4-byte aligned.
*/
public final class GLCommand {
private GLCommand() {}
// === Single int commands (8 bytes: opcode + param) ===
public static final int ENABLE = 1; // [cmd:4][cap:4]
public static final int DISABLE = 2; // [cmd:4][cap:4]
public static final int CLEAR = 3; // [cmd:4][mask:4]
public static final int CLEAR_STENCIL = 4; // [cmd:4][s:4]
public static final int CULL_FACE = 5; // [cmd:4][mode:4]
public static final int DEPTH_FUNC = 6; // [cmd:4][func:4]
public static final int SHADE_MODEL = 7; // [cmd:4][mode:4]
public static final int LOGIC_OP = 8; // [cmd:4][opcode:4]
public static final int MATRIX_MODE = 9; // [cmd:4][mode:4]
public static final int ACTIVE_TEXTURE = 10; // [cmd:4][texture:4]
public static final int USE_PROGRAM = 11; // [cmd:4][program:4]
public static final int PUSH_ATTRIB = 12; // [cmd:4][mask:4]
public static final int POP_ATTRIB = 13; // [cmd:4][unused:4]
public static final int LOAD_IDENTITY = 14; // [cmd:4][matrixMode:4]
public static final int PUSH_MATRIX = 15; // [cmd:4][matrixMode:4]
public static final int POP_MATRIX = 16; // [cmd:4][matrixMode:4]
public static final int STENCIL_MASK = 17; // [cmd:4][mask:4]
public static final int DEPTH_MASK = 18; // [cmd:4][flag:4] (0 or 1)
public static final int FRONT_FACE = 19; // [cmd:4][mode:4]
// === Two int commands (12 bytes) ===
public static final int BIND_TEXTURE = 20; // [cmd:4][target:4][texture:4]
public static final int POLYGON_MODE = 21; // [cmd:4][face:4][mode:4]
public static final int COLOR_MATERIAL = 22; // [cmd:4][face:4][mode:4]
public static final int LINE_STIPPLE = 23; // [cmd:4][factor:4][pattern:4]
public static final int STENCIL_MASK_SEPARATE = 24; // [cmd:4][face:4][mask:4]
public static final int FOGI = 25; // [cmd:4][pname:4][param:4]
public static final int HINT = 26; // [cmd:4][target:4][mode:4]
// === Three int commands (16 bytes) ===
public static final int STENCIL_FUNC = 30; // [cmd:4][func:4][ref:4][mask:4]
public static final int STENCIL_OP = 31; // [cmd:4][fail:4][zfail:4][zpass:4]
public static final int TEX_PARAMETERI = 32; // [cmd:4][target:4][pname:4][param:4]
// === Four int commands (20 bytes) ===
public static final int VIEWPORT = 40; // [cmd:4][x:4][y:4][width:4][height:4]
public static final int BLEND_FUNC = 41; // [cmd:4][srcRgb:4][dstRgb:4][srcAlpha:4][dstAlpha:4]
public static final int COLOR_MASK = 42; // [cmd:4][r:4][g:4][b:4][a:4] (0 or 1 each)
public static final int STENCIL_FUNC_SEPARATE = 43; // [cmd:4][face:4][func:4][ref:4][mask:4]
public static final int STENCIL_OP_SEPARATE = 44; // [cmd:4][face:4][sfail:4][dpfail:4][dppass:4]
// === Float commands ===
public static final int POINT_SIZE = 50; // [cmd:4][size:4f]
public static final int LINE_WIDTH = 51; // [cmd:4][width:4f]
public static final int POLYGON_OFFSET = 52; // [cmd:4][factor:4f][units:4f]
public static final int NORMAL = 53; // [cmd:4][x:4f][y:4f][z:4f]
public static final int COLOR = 54; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int CLEAR_COLOR = 55; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int BLEND_COLOR = 56; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
// === Mixed int+float commands ===
public static final int ALPHA_FUNC = 60; // [cmd:4][func:4][ref:4f]
public static final int FOGF = 61; // [cmd:4][pname:4][param:4f]
public static final int LIGHTF = 62; // [cmd:4][light:4][pname:4][param:4f]
public static final int LIGHT_MODELF = 63; // [cmd:4][pname:4][param:4f]
public static final int LIGHTI = 64; // [cmd:4][light:4][pname:4][param:4]
public static final int LIGHT_MODELI = 65; // [cmd:4][pname:4][param:4]
public static final int MATERIALF = 66; // [cmd:4][face:4][pname:4][param:4f]
public static final int TEX_PARAMETERF = 67; // [cmd:4][target:4][pname:4][param:4f]
// === Double commands ===
public static final int TRANSLATE = 70; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int ROTATE = 71; // [cmd:4][mode:4][angle:8d][x:8d][y:8d][z:8d] = 40 bytes
public static final int SCALE = 72; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int ORTHO = 73; // [cmd:4][left:8d][right:8d][bottom:8d][top:8d][zNear:8d][zFar:8d] = 52 bytes
public static final int FRUSTUM = 74; // [cmd:4][left:8d][right:8d][bottom:8d][top:8d][zNear:8d][zFar:8d] = 52 bytes
public static final int CLEAR_DEPTH = 75; // [cmd:4][depth:8d] = 12 bytes
// === Matrix commands (72 bytes) ===
public static final int MULT_MATRIX = 80; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
public static final int LOAD_MATRIX = 81; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
// === Buffer commands (inline 4 floats max) ===
public static final int FOG = 90; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
public static final int LIGHT = 91; // [cmd:4][light:4][pname:4][count:4][params:16f] = 32 bytes
public static final int LIGHT_MODEL = 92; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
public static final int MATERIAL = 93; // [cmd:4][face:4][pname:4][count:4][params:16f] = 32 bytes
// === Draw commands ===
public static final int DRAW_RANGE = 100; // [cmd:4][vboIndex:4][start:4][count:4][hasBrightness:4] = 20 bytes
public static final int CALL_LIST = 101; // [cmd:4][listId:4] = 8 bytes
public static final int DRAW_BUFFER = 102; // [cmd:4][mode:4] = 8 bytes
public static final int DRAW_BUFFERS = 103; // [cmd:4][count:4][bufs:4*8] = 40 bytes (up to 8 buffers)
/**
* Draw VBO range with attribute restoration after draw.
* Used for immediate mode VBOs to restore GL current state (color, normal, texcoord).
*/
public static final int DRAW_RANGE_RESTORE = 104; // [cmd:4][vboIndex:4][start:4][count:4][flags:4]
// [lastColor:16f][lastNormal:12f][lastTexCoord:8f] = 56 bytes
// === Complex object reference ===
public static final int COMPLEX_REF = 255; // [cmd:4][index:4] = 8 bytes
/**
* Get a human-readable name for an opcode (for debugging).
*/
public static String getName(int opcode) {
return switch (opcode) {
case ENABLE -> "ENABLE";
case DISABLE -> "DISABLE";
case CLEAR -> "CLEAR";
case CLEAR_STENCIL -> "CLEAR_STENCIL";
case CULL_FACE -> "CULL_FACE";
case DEPTH_FUNC -> "DEPTH_FUNC";
case SHADE_MODEL -> "SHADE_MODEL";
case LOGIC_OP -> "LOGIC_OP";
case MATRIX_MODE -> "MATRIX_MODE";
case ACTIVE_TEXTURE -> "ACTIVE_TEXTURE";
case USE_PROGRAM -> "USE_PROGRAM";
case PUSH_ATTRIB -> "PUSH_ATTRIB";
case POP_ATTRIB -> "POP_ATTRIB";
case LOAD_IDENTITY -> "LOAD_IDENTITY";
case PUSH_MATRIX -> "PUSH_MATRIX";
case POP_MATRIX -> "POP_MATRIX";
case STENCIL_MASK -> "STENCIL_MASK";
case DEPTH_MASK -> "DEPTH_MASK";
case FRONT_FACE -> "FRONT_FACE";
case BIND_TEXTURE -> "BIND_TEXTURE";
case POLYGON_MODE -> "POLYGON_MODE";
case COLOR_MATERIAL -> "COLOR_MATERIAL";
case LINE_STIPPLE -> "LINE_STIPPLE";
case STENCIL_MASK_SEPARATE -> "STENCIL_MASK_SEPARATE";
case FOGI -> "FOGI";
case HINT -> "HINT";
case STENCIL_FUNC -> "STENCIL_FUNC";
case STENCIL_OP -> "STENCIL_OP";
case TEX_PARAMETERI -> "TEX_PARAMETERI";
case VIEWPORT -> "VIEWPORT";
case BLEND_FUNC -> "BLEND_FUNC";
case COLOR_MASK -> "COLOR_MASK";
case STENCIL_FUNC_SEPARATE -> "STENCIL_FUNC_SEPARATE";
case STENCIL_OP_SEPARATE -> "STENCIL_OP_SEPARATE";
case POINT_SIZE -> "POINT_SIZE";
case LINE_WIDTH -> "LINE_WIDTH";
case POLYGON_OFFSET -> "POLYGON_OFFSET";
case COLOR -> "COLOR";
case CLEAR_COLOR -> "CLEAR_COLOR";
case BLEND_COLOR -> "BLEND_COLOR";
case ALPHA_FUNC -> "ALPHA_FUNC";
case FOGF -> "FOGF";
case LIGHTF -> "LIGHTF";
case LIGHT_MODELF -> "LIGHT_MODELF";
case LIGHTI -> "LIGHTI";
case LIGHT_MODELI -> "LIGHT_MODELI";
case MATERIALF -> "MATERIALF";
case TEX_PARAMETERF -> "TEX_PARAMETERF";
case TRANSLATE -> "TRANSLATE";
case ROTATE -> "ROTATE";
case SCALE -> "SCALE";
case ORTHO -> "ORTHO";
case FRUSTUM -> "FRUSTUM";
case CLEAR_DEPTH -> "CLEAR_DEPTH";
case MULT_MATRIX -> "MULT_MATRIX";
case LOAD_MATRIX -> "LOAD_MATRIX";
case FOG -> "FOG";
case LIGHT -> "LIGHT";
case LIGHT_MODEL -> "LIGHT_MODEL";
case MATERIAL -> "MATERIAL";
case DRAW_RANGE -> "DRAW_RANGE";
case CALL_LIST -> "CALL_LIST";
case DRAW_BUFFER -> "DRAW_BUFFER";
case DRAW_BUFFERS -> "DRAW_BUFFERS";
case DRAW_RANGE_RESTORE -> "DRAW_RANGE_RESTORE";
case COMPLEX_REF -> "COMPLEX_REF";
default -> "UNKNOWN(" + opcode + ")";
};
}
}
@@ -0,0 +1,750 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuad;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* Records immediate mode GL calls (glBegin/glEnd/glVertex) during display list compilation.
* Converts immediate mode geometry to ModelQuad format for VBO compilation.
*
* <p>Tracks current immediate mode state:
* <ul>
* <li>TexCoord: Set by glTexCoord* calls</li>
* <li>Normal: Set by glNormal* calls</li>
* <li>Color: Read from GLStateManager.getColor() at vertex emission time</li>
* </ul>
*
* <p>Supported primitive types:
* <ul>
* <li>GL_QUADS: 4 vertices per quad (native support)</li>
* <li>GL_TRIANGLES: 3 vertices per triangle (converted to degenerate quad)</li>
* <li>GL_TRIANGLE_FAN: Fan of triangles (converted to individual quads)</li>
* <li>GL_TRIANGLE_STRIP: Strip of triangles (converted to individual quads)</li>
* <li>GL_QUAD_STRIP: Strip of quads (converted to individual quads)</li>
* <li>GL_POLYGON: Polygon (treated as triangle fan)</li>
* </ul>
*
* <p>Unsupported primitive types (logged warning, geometry discarded):
* <ul>
* <li>GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP: Line primitives</li>
* <li>GL_POINTS: Point primitives</li>
* </ul>
*/
public class ImmediateModeRecorder {
private static final Logger LOGGER = LogManager.getLogger("ImmediateModeRecorder");
// Accumulated quads from completed primitives
private final List<ModelQuadViewMutable> quads = new ArrayList<>();
// Accumulated line vertices (pairs of vertices for GL_LINES)
private final List<LineVertex> lineVertices = new ArrayList<>();
// Current primitive state
private int currentPrimitiveType = -1;
private boolean inPrimitive = false;
// Vertices for current primitive (cleared on glEnd)
private final List<ImmediateVertex> currentVertices = new ArrayList<>();
// Current immediate mode state (set by glTexCoord*, glNormal*)
private float texCoordS = 0.0f;
private float texCoordT = 0.0f;
private float normalX = 0.0f;
private float normalY = 0.0f;
private float normalZ = 1.0f; // Default normal points +Z
// Track which attributes have been set (for flags)
private boolean hasTexCoord = false;
private boolean hasNormal = false;
private boolean hasColor = false;
/**
* Represents a single vertex captured during immediate mode.
*/
private static class ImmediateVertex {
float x, y, z; // Position
int color; // Packed ABGR color
float texU, texV; // Texture coordinates
int normal; // Packed normal
ImmediateVertex(float x, float y, float z, int color, float texU, float texV, int normal) {
this.x = x;
this.y = y;
this.z = z;
this.color = color;
this.texU = texU;
this.texV = texV;
this.normal = normal;
}
}
/**
* Represents a vertex for line primitives.
* Simpler than ImmediateVertex - just position and color.
*/
public static class LineVertex {
public final float x, y, z;
public final int color; // Packed ABGR color
public LineVertex(float x, float y, float z, int color) {
this.x = x;
this.y = y;
this.z = z;
this.color = color;
}
}
public ImmediateModeRecorder() {
// Recorder is ready to capture geometry
}
/**
* Set the current texture coordinate.
* Called by GLStateManager.glTexCoord*.
*/
public void setTexCoord(float s, float t) {
this.texCoordS = s;
this.texCoordT = t;
this.hasTexCoord = true;
}
/**
* Set the current normal vector.
* Called by GLStateManager.glNormal*.
*/
public void setNormal(float x, float y, float z) {
this.normalX = x;
this.normalY = y;
this.normalZ = z;
this.hasNormal = true;
}
/**
* Check if currently in a primitive (between glBegin and glEnd).
*/
public boolean isInPrimitive() {
return inPrimitive;
}
/**
* Start recording a new primitive.
*/
public void begin(int primitiveType) {
if (inPrimitive) {
throw new IllegalStateException("glBegin called while already in primitive");
}
this.currentPrimitiveType = primitiveType;
this.currentVertices.clear();
this.inPrimitive = true;
}
/**
* End the current primitive, convert to quads/lines, and return them immediately.
* This allows the caller to create AccumulatedDraw at the correct command position.
*
* @return Result containing quads, lines, and flags, or null if no geometry was produced
*/
public Result end() {
if (!inPrimitive) {
throw new IllegalStateException("glEnd called without glBegin");
}
// Capture last vertex attributes BEFORE clearing currentVertices
// These will be used to restore GL current state after VBO playback
float lastColorR = 1.0f, lastColorG = 1.0f, lastColorB = 1.0f, lastColorA = 1.0f;
float lastNormalX = 0.0f, lastNormalY = 0.0f, lastNormalZ = 1.0f;
float lastTexS = 0.0f, lastTexT = 0.0f;
if (!currentVertices.isEmpty()) {
ImmediateVertex last = currentVertices.get(currentVertices.size() - 1);
// Unpack color from ABGR format
lastColorR = (last.color & 0xFF) / 255.0f;
lastColorG = ((last.color >> 8) & 0xFF) / 255.0f;
lastColorB = ((last.color >> 16) & 0xFF) / 255.0f;
lastColorA = ((last.color >> 24) & 0xFF) / 255.0f;
// Unpack normal from NormI8 format (signed bytes packed into int)
lastNormalX = NormI8.unpackX(last.normal);
lastNormalY = NormI8.unpackY(last.normal);
lastNormalZ = NormI8.unpackZ(last.normal);
// Texture coords are stored directly as floats
lastTexS = last.texU;
lastTexT = last.texV;
}
// Convert this primitive's vertices to quads or lines
convertPrimitiveToQuads();
this.currentVertices.clear();
this.inPrimitive = false;
// Return geometry from this primitive immediately (don't accumulate)
if (quads.isEmpty() && lineVertices.isEmpty()) {
return null;
}
// Copy geometry and create flags
List<ModelQuadViewMutable> resultQuads = new ArrayList<>(quads);
List<LineVertex> resultLines = new ArrayList<>(lineVertices);
CapturingTessellator.Flags flags = new CapturingTessellator.Flags(
hasTexCoord, // hasTexture
false, // hasBrightness - immediate mode doesn't typically use lightmap
hasColor, // hasColor
hasNormal // hasNormals
);
// Clear for next primitive (but keep attribute tracking state)
quads.clear();
lineVertices.clear();
return new Result(resultQuads, resultLines, flags,
lastColorR, lastColorG, lastColorB, lastColorA,
lastNormalX, lastNormalY, lastNormalZ,
lastTexS, lastTexT);
}
/**
* Record a vertex with current attributes.
* Color is read from GLStateManager at call time.
*/
public void vertex(float x, float y, float z) {
if (!inPrimitive) {
throw new IllegalStateException("glVertex called outside glBegin/glEnd");
}
// Get current color from GLStateManager
int packedColor = packColor(
GLStateManager.getColor().getRed(),
GLStateManager.getColor().getGreen(),
GLStateManager.getColor().getBlue(),
GLStateManager.getColor().getAlpha()
);
if (packedColor != 0xFFFFFFFF) {
hasColor = true;
}
// Pack normal
int packedNormal = NormI8.pack(normalX, normalY, normalZ);
currentVertices.add(new ImmediateVertex(
x, y, z,
packedColor,
texCoordS, texCoordT,
packedNormal
));
}
/**
* Pack RGBA floats (0-1) into ABGR int format used by ModelQuad.
*/
private static int packColor(float r, float g, float b, float a) {
int ri = (int) (r * 255.0f) & 0xFF;
int gi = (int) (g * 255.0f) & 0xFF;
int bi = (int) (b * 255.0f) & 0xFF;
int ai = (int) (a * 255.0f) & 0xFF;
// ABGR format (little-endian: stored as RGBA bytes)
return (ai << 24) | (bi << 16) | (gi << 8) | ri;
}
/**
* Convert the current primitive vertices to quads.
* Handles different primitive types.
*/
private void convertPrimitiveToQuads() {
int vertexCount = currentVertices.size();
if (vertexCount == 0) {
return;
}
switch (currentPrimitiveType) {
case GL11.GL_QUADS -> convertQuads();
case GL11.GL_TRIANGLES -> convertTriangles();
case GL11.GL_TRIANGLE_FAN, GL11.GL_POLYGON -> convertTriangleFan();
case GL11.GL_TRIANGLE_STRIP -> convertTriangleStrip();
case GL11.GL_QUAD_STRIP -> convertQuadStrip();
case GL11.GL_LINES -> convertLines();
case GL11.GL_LINE_STRIP -> convertLineStripToLines();
case GL11.GL_LINE_LOOP -> convertLineLoopToLines();
case GL11.GL_POINTS -> {
LOGGER.warn("[ImmediateModeRecorder] Point primitives not supported, discarding {} vertices", vertexCount);
}
default -> {
LOGGER.warn("[ImmediateModeRecorder] Unknown primitive type {}, discarding {} vertices",
currentPrimitiveType, vertexCount);
}
}
}
/**
* Convert GL_QUADS vertices to ModelQuads (4 vertices per quad).
*/
private void convertQuads() {
int vertexCount = currentVertices.size();
int fullQuads = vertexCount / 4;
for (int i = 0; i < fullQuads; i++) {
int base = i * 4;
addQuad(
currentVertices.get(base),
currentVertices.get(base + 1),
currentVertices.get(base + 2),
currentVertices.get(base + 3)
);
}
int remaining = vertexCount % 4;
if (remaining > 0) {
LOGGER.warn("[ImmediateModeRecorder] GL_QUADS: Incomplete quad, discarding {} vertices", remaining);
}
}
/**
* Convert GL_TRIANGLES vertices to degenerate quads (3 vertices per triangle, duplicate last).
*/
private void convertTriangles() {
int vertexCount = currentVertices.size();
int fullTriangles = vertexCount / 3;
for (int i = 0; i < fullTriangles; i++) {
int base = i * 3;
ImmediateVertex v0 = currentVertices.get(base);
ImmediateVertex v1 = currentVertices.get(base + 1);
ImmediateVertex v2 = currentVertices.get(base + 2);
// Degenerate quad: duplicate third vertex
addQuad(v0, v1, v2, v2);
}
int remaining = vertexCount % 3;
if (remaining > 0) {
LOGGER.warn("[ImmediateModeRecorder] GL_TRIANGLES: Incomplete triangle, discarding {} vertices", remaining);
}
}
/**
* Convert GL_TRIANGLE_FAN vertices to triangles then to degenerate quads.
* First vertex is center, subsequent vertices form fan.
*/
private void convertTriangleFan() {
int vertexCount = currentVertices.size();
if (vertexCount < 3) {
LOGGER.warn("[ImmediateModeRecorder] GL_TRIANGLE_FAN: Need at least 3 vertices, got {}", vertexCount);
return;
}
ImmediateVertex center = currentVertices.get(0);
for (int i = 1; i < vertexCount - 1; i++) {
ImmediateVertex v1 = currentVertices.get(i);
ImmediateVertex v2 = currentVertices.get(i + 1);
// Triangle: center, v1, v2 -> degenerate quad
addQuad(center, v1, v2, v2);
}
}
/**
* Convert GL_TRIANGLE_STRIP vertices to triangles then to degenerate quads.
*/
private void convertTriangleStrip() {
int vertexCount = currentVertices.size();
if (vertexCount < 3) {
LOGGER.warn("[ImmediateModeRecorder] GL_TRIANGLE_STRIP: Need at least 3 vertices, got {}", vertexCount);
return;
}
for (int i = 0; i < vertexCount - 2; i++) {
ImmediateVertex v0, v1, v2;
if ((i & 1) == 0) {
// Even triangle: v[i], v[i+1], v[i+2]
v0 = currentVertices.get(i);
v1 = currentVertices.get(i + 1);
v2 = currentVertices.get(i + 2);
} else {
// Odd triangle: v[i+1], v[i], v[i+2] (reverse winding)
v0 = currentVertices.get(i + 1);
v1 = currentVertices.get(i);
v2 = currentVertices.get(i + 2);
}
addQuad(v0, v1, v2, v2);
}
}
/**
* Convert GL_QUAD_STRIP vertices to quads.
* Vertices come in pairs: (v0,v1), (v2,v3), (v4,v5)...
* Each quad uses 4 consecutive vertices in pattern: v[2i], v[2i+1], v[2i+3], v[2i+2]
*/
private void convertQuadStrip() {
int vertexCount = currentVertices.size();
if (vertexCount < 4) {
LOGGER.warn("[ImmediateModeRecorder] GL_QUAD_STRIP: Need at least 4 vertices, got {}", vertexCount);
return;
}
int numQuads = (vertexCount - 2) / 2;
for (int i = 0; i < numQuads; i++) {
int base = i * 2;
// Quad strip winding: v[0], v[1], v[3], v[2]
addQuad(
currentVertices.get(base),
currentVertices.get(base + 1),
currentVertices.get(base + 3),
currentVertices.get(base + 2)
);
}
}
/**
* Convert GL_LINES vertices to line segments (pairs of vertices).
*/
private void convertLines() {
int vertexCount = currentVertices.size();
int fullLines = vertexCount / 2;
for (int i = 0; i < fullLines; i++) {
int base = i * 2;
ImmediateVertex v0 = currentVertices.get(base);
ImmediateVertex v1 = currentVertices.get(base + 1);
lineVertices.add(new LineVertex(v0.x, v0.y, v0.z, v0.color));
lineVertices.add(new LineVertex(v1.x, v1.y, v1.z, v1.color));
}
int remaining = vertexCount % 2;
if (remaining > 0) {
LOGGER.warn("[ImmediateModeRecorder] GL_LINES: Incomplete line, discarding {} vertices", remaining);
}
}
/**
* Convert GL_LINE_STRIP to individual line segments.
* Vertices 0-1-2-3 become lines: 0-1, 1-2, 2-3
*/
private void convertLineStripToLines() {
int vertexCount = currentVertices.size();
if (vertexCount < 2) {
LOGGER.warn("[ImmediateModeRecorder] GL_LINE_STRIP: Need at least 2 vertices, got {}", vertexCount);
return;
}
for (int i = 0; i < vertexCount - 1; i++) {
ImmediateVertex v0 = currentVertices.get(i);
ImmediateVertex v1 = currentVertices.get(i + 1);
lineVertices.add(new LineVertex(v0.x, v0.y, v0.z, v0.color));
lineVertices.add(new LineVertex(v1.x, v1.y, v1.z, v1.color));
}
}
/**
* Convert GL_LINE_LOOP to individual line segments.
* Like LINE_STRIP but also closes the loop from last to first vertex.
*/
private void convertLineLoopToLines() {
int vertexCount = currentVertices.size();
if (vertexCount < 2) {
LOGGER.warn("[ImmediateModeRecorder] GL_LINE_LOOP: Need at least 2 vertices, got {}", vertexCount);
return;
}
// Convert as strip first
convertLineStripToLines();
// Close the loop: add last-to-first segment
ImmediateVertex vLast = currentVertices.get(vertexCount - 1);
ImmediateVertex vFirst = currentVertices.get(0);
lineVertices.add(new LineVertex(vLast.x, vLast.y, vLast.z, vLast.color));
lineVertices.add(new LineVertex(vFirst.x, vFirst.y, vFirst.z, vFirst.color));
}
/**
* Create a ModelQuad from 4 ImmediateVertex objects.
*/
private void addQuad(ImmediateVertex v0, ImmediateVertex v1, ImmediateVertex v2, ImmediateVertex v3) {
ModelQuad quad = new ModelQuad();
setVertex(quad, 0, v0);
setVertex(quad, 1, v1);
setVertex(quad, 2, v2);
setVertex(quad, 3, v3);
// Set light face based on computed normal
quad.setLightFace(ModelQuadUtil.findLightFace(quad.getComputedFaceNormal()));
quads.add(quad);
}
/**
* Set a single vertex in a ModelQuad.
*/
private void setVertex(ModelQuad quad, int idx, ImmediateVertex v) {
quad.setX(idx, v.x);
quad.setY(idx, v.y);
quad.setZ(idx, v.z);
quad.setColor(idx, v.color);
quad.setTexU(idx, v.texU);
quad.setTexV(idx, v.texV);
quad.setForgeNormal(idx, v.normal);
// Light defaults to full brightness (no lightmap for immediate mode typically)
quad.setLight(idx, ModelQuadUtil.DEFAULT_LIGHTMAP);
}
/**
* Check if any immediate mode geometry has been captured.
*
* <p>Note: With immediate flush mode, this will typically return false as geometry
* is returned from end() immediately. This method is kept for edge cases
* where primitives might fail to convert (e.g., incomplete quads).
*/
public boolean hasGeometry() {
return !quads.isEmpty() || !lineVertices.isEmpty();
}
/**
* Get any remaining accumulated geometry and clear the recorder.
*
* <p>Note: With immediate flush mode, this will typically return null as geometry
* is returned from end() immediately. This method is kept for cleanup and
* edge cases where geometry might remain (e.g., if end() was never called).
*
* @return Result containing remaining quads, lines, and flags, or null if no geometry
*/
public Result getQuadsAndClear() {
if (inPrimitive) {
throw new IllegalStateException("Cannot get geometry while in primitive (missing glEnd)");
}
if (quads.isEmpty() && lineVertices.isEmpty()) {
return null;
}
// Copy geometry and create flags
List<ModelQuadViewMutable> resultQuads = new ArrayList<>(quads);
List<LineVertex> resultLines = new ArrayList<>(lineVertices);
CapturingTessellator.Flags flags = new CapturingTessellator.Flags(
hasTexCoord, // hasTexture
false, // hasBrightness - immediate mode doesn't typically use lightmap
hasColor, // hasColor
hasNormal // hasNormals
);
// Clear state for reuse
quads.clear();
lineVertices.clear();
hasColor = false;
hasTexCoord = false;
hasNormal = false;
// Note: current texcoord/normal values are NOT reset - they persist per OpenGL spec
// For getQuadsAndClear, we don't have last vertex data (it was cleared in end())
// Use default values - this method is for edge cases/cleanup anyway
return new Result(resultQuads, resultLines, flags,
1.0f, 1.0f, 1.0f, 1.0f, // Default white color
0.0f, 0.0f, 1.0f, // Default +Z normal
0.0f, 0.0f); // Default (0,0) texcoord
}
/**
* Result of immediate mode recording: quads, lines, attribute flags, and last vertex state.
* The last vertex state is used to restore GL_CURRENT_COLOR etc. after VBO playback.
*/
@com.github.bsideup.jabel.Desugar
public record Result(
List<ModelQuadViewMutable> quads,
List<LineVertex> lines,
CapturingTessellator.Flags flags,
// Last vertex attributes for restoration after VBO draw
float lastColorR, float lastColorG, float lastColorB, float lastColorA,
float lastNormalX, float lastNormalY, float lastNormalZ,
float lastTexCoordS, float lastTexCoordT
) {}
/**
* Reset all state including current texcoord/normal.
* Called when starting a new display list.
*/
public void reset() {
quads.clear();
lineVertices.clear();
currentVertices.clear();
inPrimitive = false;
currentPrimitiveType = -1;
texCoordS = 0.0f;
texCoordT = 0.0f;
normalX = 0.0f;
normalY = 0.0f;
normalZ = 1.0f;
hasColor = false;
hasTexCoord = false;
hasNormal = false;
}
/**
* Process a glDrawArrays call during display list recording.
* Reads vertex data from client arrays (tracked globally in GLStateManager) and converts to immediate mode geometry.
*
* @param mode Primitive type (GL_QUADS, GL_LINES, etc.)
* @param first Starting index in arrays
* @param count Number of vertices
* @return Result containing converted geometry, or null if no geometry produced
*/
public Result processDrawArrays(int mode, int first, int count) {
final var cas = GLStateManager.getClientArrayState();
final var vertexPointer = cas.isVertexArrayEnabled() ? cas.getVertexPointer() : null;
final var colorPointer = cas.isColorArrayEnabled() ? cas.getColorPointer() : null;
return convertClientArrays(
mode, first, count,
vertexPointer,
cas.getVertexPointerType(), cas.getVertexPointerSize(), cas.getVertexPointerStride(),
colorPointer,
cas.getColorPointerType(), cas.getColorPointerSize(), cas.getColorPointerStride()
);
}
/**
* Convert client array data to immediate mode.
* Called by glDrawArrays during display list recording.
* Like Mesa's save_DrawArrays, reads vertex data from arrays and emits as immediate mode.
*
* @param mode Primitive type (GL_QUADS, GL_LINES, etc.)
* @param first Starting index in arrays
* @param count Number of vertices
* @param vertexPointer Vertex position buffer (or null if not enabled)
* @param vertexType GL type (GL_FLOAT, GL_DOUBLE, etc.)
* @param vertexSize Size of vertex (2, 3, or 4)
* @param vertexStride Stride in bytes (0 for tightly packed)
* @param colorPointer Color buffer (or null if not enabled)
* @param colorType GL type (GL_FLOAT, GL_UNSIGNED_BYTE, etc.)
* @param colorSize Size of color (3 or 4)
* @param colorStride Stride in bytes (0 for tightly packed)
* @return Result containing converted geometry, or null if no geometry produced
*/
public Result convertClientArrays(
int mode, int first, int count,
java.nio.Buffer vertexPointer, int vertexType, int vertexSize, int vertexStride,
java.nio.Buffer colorPointer, int colorType, int colorSize, int colorStride
) {
begin(mode);
// Hoist type checks and stride calculations outside the loop for performance.
// Mesa's algorithm: offset_bytes = index * effective_stride
// effective_stride = (stride == 0) ? (numComponents * sizeof(element)) : stride
// Determine color reader (hoisted out of loop)
final ColorReader colorReader;
final int colorEffectiveStride;
if (colorPointer == null) {
colorReader = null;
colorEffectiveStride = 0;
} else if (colorType == GL11.GL_FLOAT && colorPointer instanceof FloatBuffer fb) {
colorEffectiveStride = (colorStride == 0) ? colorSize * 4 : colorStride;
final int strideInFloats = colorEffectiveStride / 4;
colorReader = (idx) -> {
final int offset = idx * strideInFloats;
final float r = fb.get(offset);
final float g = fb.get(offset + 1);
final float b = fb.get(offset + 2);
final float a = (colorSize == 4) ? fb.get(offset + 3) : 1.0f;
GLStateManager.glColor4f(r, g, b, a);
};
} else if ((colorType == GL11.GL_UNSIGNED_BYTE || colorType == GL11.GL_BYTE) && colorPointer instanceof ByteBuffer bb) {
colorEffectiveStride = (colorStride == 0) ? colorSize : colorStride;
colorReader = (idx) -> {
final int offset = idx * colorEffectiveStride;
final float r = (bb.get(offset) & 0xFF) / 255.0f;
final float g = (bb.get(offset + 1) & 0xFF) / 255.0f;
final float b = (bb.get(offset + 2) & 0xFF) / 255.0f;
final float a = (colorSize == 4) ? (bb.get(offset + 3) & 0xFF) / 255.0f : 1.0f;
GLStateManager.glColor4f(r, g, b, a);
};
} else {
// Unsupported color type - use white
colorEffectiveStride = 0;
colorReader = (idx) -> GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
// Determine vertex reader (hoisted out of loop)
final VertexReader vertexReader;
if (vertexPointer == null) {
vertexReader = null;
} else if (vertexType == GL11.GL_FLOAT && vertexPointer instanceof FloatBuffer fb) {
final int effectiveStride = (vertexStride == 0) ? vertexSize * 4 : vertexStride;
final int strideInFloats = effectiveStride / 4;
vertexReader = (idx) -> {
final int offset = idx * strideInFloats;
final float x = fb.get(offset);
final float y = fb.get(offset + 1);
final float z = (vertexSize >= 3) ? fb.get(offset + 2) : 0.0f;
vertex(x, y, z);
};
} else if (vertexType == GL11.GL_DOUBLE && vertexPointer instanceof DoubleBuffer db) {
final int effectiveStride = (vertexStride == 0) ? vertexSize * 8 : vertexStride;
final int strideInDoubles = effectiveStride / 8;
vertexReader = (idx) -> {
final int offset = idx * strideInDoubles;
final float x = (float) db.get(offset);
final float y = (float) db.get(offset + 1);
final float z = (vertexSize >= 3) ? (float) db.get(offset + 2) : 0.0f;
vertex(x, y, z);
};
} else if (vertexType == GL11.GL_INT && vertexPointer instanceof IntBuffer ib) {
final int effectiveStride = (vertexStride == 0) ? vertexSize * 4 : vertexStride;
final int strideInInts = effectiveStride / 4;
vertexReader = (idx) -> {
final int offset = idx * strideInInts;
final float x = ib.get(offset);
final float y = ib.get(offset + 1);
final float z = (vertexSize >= 3) ? ib.get(offset + 2) : 0.0f;
vertex(x, y, z);
};
} else if (vertexType == GL11.GL_SHORT && vertexPointer instanceof ShortBuffer sb) {
final int effectiveStride = (vertexStride == 0) ? vertexSize * 2 : vertexStride;
final int strideInShorts = effectiveStride / 2;
vertexReader = (idx) -> {
final int offset = idx * strideInShorts;
final float x = sb.get(offset);
final float y = sb.get(offset + 1);
final float z = (vertexSize >= 3) ? sb.get(offset + 2) : 0.0f;
vertex(x, y, z);
};
} else {
// Unsupported vertex type
vertexReader = null;
}
// Main loop - no type checks, just function calls
for (int i = first; i < first + count; i++) {
if (colorReader != null) {
colorReader.read(i);
}
if (vertexReader != null) {
vertexReader.read(i);
}
}
return end();
}
@FunctionalInterface
private interface ColorReader {
void read(int index);
}
@FunctionalInterface
private interface VertexReader {
void read(int index);
}
}
@@ -0,0 +1,38 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
/**
* A single command recorded during display list compilation.
* Commands are executed in sequence during glCallList() to replay the display list.
*/
public interface DisplayListCommand {
/**
* Execute this command by calling the appropriate GL function.
*/
void execute();
/**
* Release any resources held by this command (e.g., VBOs).
* Called when the display list is deleted.
*/
default void delete() {
// Most commands don't hold resources
}
/**
* Handle this command during display list optimization.
*
* <p>Commands override this to implement their optimization behavior:
* <ul>
* <li>Matrix transforms: accumulate into the optimizer, return false</li>
* <li>Push/Pop: update stack, return true to emit</li>
* <li>Barriers: emit pending transforms, return true to emit</li>
* <li>State changes: return true to emit</li>
* </ul>
*
* @param ctx The optimization context providing access to transform state
* @return true if this command should be added to output, false if handled internally
*/
default boolean handleOptimization(OptimizationContext ctx) {
return true;
}
}
@@ -0,0 +1,14 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
/**
* Represents a single draw call within a compiled display list.
* Each DrawCommand corresponds to one glBegin/glEnd block.
*/
@Desugar
public record DrawCommand(
int firstVertex,
int vertexCount,
int primitiveType
) {}
@@ -0,0 +1,42 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import org.jetbrains.annotations.NotNull;
/**
* Command: Draw a range of vertices from a shared VBO.
* <p>
* DrawRangeCmd references a shared VBO owned by CompiledDisplayList.ownedVbos.
* Multiple DrawRangeCmds can reference different ranges within the same VBO,
* enabling format-based batching where all draws with the same vertex format
* share a single VBO.
* <p>
* Transform is NOT stored here - handled by TransformOptimizer emitting
* MultMatrixCmd before this command during optimization.
*/
@Desugar
public record DrawRangeCmd(
VertexBuffer sharedVbo,
int startVertex,
int vertexCount,
boolean hasBrightness
) implements DisplayListCommand {
@Override
public void execute() {
sharedVbo.setupState();
sharedVbo.draw(startVertex, vertexCount);
sharedVbo.cleanupState();
}
@Override
public void delete() {
// No-op: VBO lifetime is managed by CompiledDisplayList.ownedVbos
}
@Override
public @NotNull String toString() {
return "DrawRange(start=" + startVertex + ", count=" + vertexCount + ", hasBrightness=" + hasBrightness + ")";
}
}
Loaded 100 of 829 files, more files were not shown because too many files have changed in this diff. Show more