Compare commits

..
340 Commits
Author SHA1 Message Date
Jason Mitchell 0ad69f3433 FINE, we'll work with java8/forge. 2026-03-28 20:09:42 -07:00
Fabian Maurer 0dabecc784 Handle GL_POLYGON in legacy conversion 2026-03-28 14:41:23 -07:00
Jason Mitchell 9b3fad4c6d Ensure sufficient coverage + optimize the check 2026-03-28 13:16:54 -07:00
Kaiz e1e8256861 fix: crash when client-side vertex attrib pointers are used in core profile 2026-03-28 13:16:54 -07:00
Jason Mitchell e65c01c2a3 Handle method handles in invokedynamic 2026-03-28 12:46:19 -07:00
Jason Mitchell b9de4df18e Update alpha test to hopefully work with RTM and AE2 (#1597) 2026-03-28 09:40:10 -07:00
Kotmatross a5d31cd1b6 Return phaseChangeNotifier (#1596) 2026-03-28 09:15:46 -07:00
DarkShadow44 29f8d718cc Pass texturemap to composite shader and honor texture replacements (#1589) 2026-03-27 19:07:45 -07:00
12411234 52b0db2566 Fix missing glGetTexImage method crash when using XaerosWorldMap (#1592) 2026-03-27 19:02:51 -07:00
Eclipse-Sol e9eb45f4f2 Centers the Wither Armor (#1590) 2026-03-27 18:37:17 -07:00
Jason Mitchell 92e28a4cd5 Blur Compat (#1593) 2026-03-27 18:32:24 -07:00
Jason Mitchell f46a86889c Fix crash with fancy block particles. (#1591) 2026-03-27 17:52:14 -07:00
Jason MitchellandAlexdoru c827d446d5 External Redirector API (#1580)
Co-authored-by: Alexdoru <[email protected]>
2026-03-27 11:20:29 -07:00
Eclipse-Sol 00ef355ce8 Fix sky lighting on drawCrossSquare rendered blocks. (#1576) 2026-03-25 18:58:40 -07:00
Kotmatross 1282056fff Fix blend leak in BatchingFontRenderer (#1575) 2026-03-25 15:34:43 -07:00
Alexdoru 6c422d47a2 Make the mod no-op on server (#1574) 2026-03-25 12:59:23 -07:00
DarkShadow44 057ea8035c Distant Horizons shader support (#1553) 2026-03-25 10:48:14 -07:00
Jason Mitchell 3ea47c8385 Various fixes (#1571) 2026-03-24 22:17:40 -07:00
Eclipse-Sol 306d732d81 Fixes 3rd person enchant glints z-fighting with the item model (#1569) 2026-03-24 17:24:23 -07:00
Jason Mitchell a483669579 Maybe fix botania mana pool rendering 2026-03-23 21:09:33 -07:00
Eclipse-SolandJason Mitchell 53996500d7 Fix modded enchant glints not being animated (#1566)
Co-authored-by: Jason Mitchell <[email protected]>
2026-03-23 22:41:29 -04:00
Jason Mitchell 04dd8ce6c7 narrow scope of mixin per Eclipse 2026-03-23 19:24:16 -07:00
Jason Mitchell 8c9edfa4c3 Fix alpha test 2026-03-23 19:24:16 -07:00
Jason Mitchell 3f44195756 Change compat config 2026-03-23 09:27:56 -07:00
Jason Mitchell c98f056d74 ASM Hook Compat
* Adds a config to allow disabling
* Narrows Advanced LightSabers
* Narrows Alfheim (unreleased versions have compat, might make this version restricted)
2026-03-23 09:00:24 -07:00
Jason Mitchell 7e25f5d03e tweak wording 2026-03-22 18:41:58 -07:00
Jason Mitchell b5553654d0 More pass throughs 2026-03-22 18:41:58 -07:00
Jason Mitchell a3fffb47be Ban GL usage.
* Everything must go through GLSM/Render Backend
2026-03-22 11:56:08 -07:00
Jason Mitchell 09cc557be0 Clear cache on reload shaders 2026-03-22 11:07:37 -07:00
SpacEagle17 aff5ff7e7b Add a second profiles button to Angelica 2026-03-22 10:09:23 -07:00
Jason Mitchell e7d08a53b6 Add debug context 2026-03-22 10:08:49 -07:00
Jason Mitchell 3b2a0af5c4 Add missing overrides 2026-03-22 10:08:49 -07:00
Jason Mitchell 8c44e79990 Add always translucent config for brandyn 2026-03-22 10:08:49 -07:00
Jason Mitchell d2e491ed47 Misc fixes
* Add missing glMapBuffer overload
* Keep track of bound pixel buffer and suspend when needed
2026-03-21 22:07:15 -07:00
Jason Mitchell bccc5b00ca Supernova Support 2026-03-21 22:06:38 -07:00
Jason Mitchellandbrandyn 5ed26f3af7 Fix font alpha test transparency and BlockGrass subclass rendering with Celeritas AO
Co-Authored-By: brandyn <[email protected]>
2026-03-21 11:21:39 -07:00
DarkShadow44 9e7d19b883 Add missing DH lwjgl3 functions (#1546) 2026-03-21 10:56:19 -07:00
Jason Mitchell 5f61e387fb Use GLSM instead of relying on the redirector 2026-03-21 10:56:19 -07:00
Jason Mitchell 70cfa2ae90 Swappable RenderBackend
* Adds Lwjgl2GLRenderBackend & Lwjgl3GLRenderBackend
* Lwjgl3GLRenderBackend uses lwjgl3 natively w/o lwjgl3ify
* GLSMRedirector now redirects LWJGL3 methods as well for mods with a hard dep on lwjgl3 (distant horizons standalone)
* GLSM uses RENDER_BACKEND instead of GL directly
2026-03-21 10:56:19 -07:00
Jason Mitchell 37717bd4db GLSM Extraction (#1527) 2026-03-19 22:53:47 -07:00
Eclipse-Sol b179340a0f Fix mob's eyes from z-fighting (#1519) 2026-03-19 09:33:52 -07:00
Alexdoru 465e36148c use mixin to change the f3 text background instead of canceling the event (#1537) 2026-03-19 09:32:56 -07:00
Caedis a3f0efd629 Make fog controls work with BoP fog (#1539) 2026-03-19 09:24:40 -07:00
Death Fuel 2386d630c5 Update FragmentShaderGenerator.java (#1526) 2026-03-16 17:35:01 -07:00
Alexdoru 6a784d8d3b use classnode name in CeleritasBlockTransform (#1521) 2026-03-15 15:49:41 +01:00
Jason Mitchell fe9028d300 Log once instead of every time. (#1520) 2026-03-14 19:15:06 -07:00
Death Fuel 4c5e5df290 Replace compatibility mode texture formats (#1518) 2026-03-14 18:42:26 -07:00
Jason Mitchell 39ee364418 Improvements (#1516) 2026-03-14 15:06:30 -07:00
sisyphussy 4c9cae5e0b Optimize Streaming (#1515) 2026-03-14 13:13:40 -07:00
DarkShadow44 11b53c9f8f Add glTexImage3D with IntBuffer (#1506) 2026-03-11 11:55:47 -07:00
Jason Mitchell c769a08a4e Fix matching across newlines (#1505) 2026-03-11 09:52:21 -07:00
Jason Mitchell 90dac9f39d Add some missing glGetTexEnv* (#1503) 2026-03-10 19:18:58 -07:00
Jason Mitchell c5c4b290b1 Add missing overload (#1495) 2026-03-10 11:50:58 -07:00
Jason Mitchell 4b46de4566 Botania fix take two (#1501) 2026-03-10 10:22:06 -07:00
Jason Mitchell 9f2a71be4e Add missing overloads (#1500) 2026-03-10 10:06:12 -07:00
Jason Mitchell 79fe08417c Fix overwrite visibility crash (#1499) 2026-03-10 09:45:50 -07:00
Jason Mitchell 5de64abdef Maybe make Xaero work on j8 (#1494) 2026-03-09 19:47:41 -07:00
Death Fuel 2e07465166 UI Fix (#1492) 2026-03-09 16:22:44 -07:00
Death Fuel 2b610b9aea Better font rendering MC parity + book mode (#1489) 2026-03-09 11:49:33 -07:00
MalTeeez ab192ddd2a Split MixinRenderManager into two for DAPI compat (#1480) 2026-03-08 06:07:01 +01:00
Jason Mitchell cb5e2ed7d4 Lazy init entity lighting maps (#1476) 2026-03-07 14:01:55 -08:00
ah-OOG-ah e914ef7563 Actually sleep in FPS limiter (#1471) 2026-03-07 12:20:28 -08:00
Jason Mitchell 748dbd55a1 So secret you can't see them. (#1475) 2026-03-07 11:48:24 -08:00
Jason Mitchell 140c0c2285 Snapshot VAO/VBO data during display list compilation, like mesa does. (#1473) 2026-03-07 11:17:08 -08:00
sisyphussy af26442b98 Fix intel tests (#1442) 2026-03-07 10:12:56 -08:00
danyadevandAlexdoru ef07ee9f77 Refactor CeleritasBlockTransform (#1455)
Co-authored-by: Alexdoru <[email protected]>
2026-03-07 17:26:39 +01:00
Jason Mitchell 1b8087e8a3 Emulate glLineWidth on core using geom shaders (#1468) 2026-03-06 19:05:06 -08:00
Jason Mitchell 9107ea9ae5 Fix load -> enter -> leave: panorama rendering, cleanup state & fix test (#1467) 2026-03-06 19:01:43 -08:00
Jason Mitchell 938a6bda90 Replace panorama with modern opengl equivalent (#1466) 2026-03-06 16:01:27 -08:00
Jason Mitchell e9f6f72741 More core profile followons (#1464) 2026-03-06 15:50:30 -08:00
Jason Mitchell 379330f997 Core followups (#1445) 2026-03-05 11:35:05 -08:00
sisyphussy 2fecb3a05b Add MixinWavefrontObject back (#1452) 2026-03-05 11:07:45 -08:00
Raven Szewczyk c085af35bd Update buildscript with latest lwjgl3ify fixes (#1456) 2026-03-05 11:00:53 -08:00
Caedis a36aec36a2 Invert Horizon and Terrain Fog toggles (#1454) 2026-03-05 17:36:56 +01:00
sisyphussy bdd6259c3f Optimize fontrenderer (#1447) 2026-03-04 18:37:50 -08:00
Jason Mitchell d7d2228a6c Target OpenGL 3.3 core profile (#1412) 2026-03-02 22:18:11 -08:00
Alexdoru cf9993fce4 change rfb exclusions (#1440) 2026-03-02 15:11:34 -08:00
Alexdoru 3ae57ca594 Fixes for backhand compat (#1437) 2026-03-01 13:04:11 -08:00
sisyphussyandMustafarYasmin 9816cb2148 Replace framebufferRender with a VBO (#1432)
Co-authored-by: MustafarYasmin <[email protected]>
2026-03-01 13:03:27 -08:00
Alexdoru b1e496f56d remove boxing unboxing in RenderPassHelper 2026-03-01 13:02:04 -08:00
danyadevandAlexdoru c8e50227fb Speedup asm redirector transformers and fix missing classes in AngelicaRedirector (#1433)
* add shouldTransform & make it significantly faster

* do not optimize non-FRB CeleritasBlockTransformer

* move closer to usage

---------

Co-authored-by: Alexdoru <[email protected]>
2026-02-28 20:12:29 +01:00
Jason Mitchell 066381a7c0 Stop demoting, per face, once we see a non demoted material 2026-02-27 21:29:20 -08:00
Jason Mitchell 9a0ea1ad65 Workaround apparent JVM 25 bug... 🤣 2026-02-27 21:29:20 -08:00
Alexdoru 8e26a0179b Speedup block transformer by transforming every class directly (#1420)
* speedup block transformer by transforming every class directly

* stop using the cstpoolparser.add method
2026-02-24 15:45:35 +01:00
Alexdoru 1d96af8e3a re-enable Block Transformer when not running with rfb (#1418) 2026-02-23 14:58:40 -08:00
Yan Huang f7eb86cdb6 Parallelizing CTMUtils (#1393) 2026-02-23 20:21:35 +00:00
Alexdoru b38fafa831 ASM redirectors micro optimizations (#1416)
* redirector transformer micro optimizations

* use map for block fields redirect
2026-02-23 20:20:38 +01:00
Jason Mitchell 7dc2625f8c Maybe less leaky (#1414) 2026-02-23 08:57:21 -08:00
Jason Mitchell 3d97977050 Maybe fix holo inventory again - v2 - final.xlsx (#1413) 2026-02-22 20:37:08 -08:00
Eclipse-Sol 5f41c60bc2 Remove force blend as commit 0325a96b fixed this properly. (#1409) 2026-02-22 11:35:35 -08:00
Jason Mitchell 0325a96bc2 Add unknown blend state and BufferBlendOverride handling from modern Iris (#1408) 2026-02-21 15:27:22 -08:00
Caedis ad123d0bd5 Now with 200% more screen shake (#1402) 2026-02-18 09:20:47 -05:00
Caedis 4b89997985 Add slider for hurt screen shake intensity (#1401) 2026-02-18 04:23:57 +01:00
Death Fuel f5c9fa3301 slightly less stupid glyph size loading (#1390) 2026-02-15 18:49:44 -08:00
Death Fuel d6a6d3b525 Less logging + small refactor (#1379)
* less logging

* refactor

* missed a spot

* consistency
2026-02-15 18:48:51 -08:00
Ruling-0 f079bed977 TESRs Translucency Sorting (#1384)
* Iterate blocks in region towards the camera

* Sort translucent TESRs when translucency sorting is enabled
2026-02-15 18:48:05 -08:00
Caedis c9ec40e94b use mc.renderViewEntity instead of mc.thePlayer in shader shadow rendering (#1392) 2026-02-15 18:47:19 -08:00
embeddedt 8380f3853d Detect quads rendered by vanilla AO pipeline and only recalculate lighting for those (#1391)
* Detect quads rendered by vanilla AO pipeline and only recalculate lighting for those

* Handle oldLighting correctly
2026-02-15 18:46:58 -08:00
Yan Huang 534ae6b05c Remove support for NotEnoughIds-Legacy and update BetterCrashes dependency version, as the old files have been removed from the GTNH source. (#1388) 2026-02-15 09:35:09 -08:00
Jason Mitchell 68d523d3df Sign Culling (#1386) 2026-02-14 04:09:00 -08:00
Jason Mitchell 2f22a7b3ad Swap vanilla clipping helper with joml (#1385) 2026-02-13 14:29:31 -08:00
Jason Mitchell 7b79b90dd6 Fix crash without speedupAnimations enabled (#1377) 2026-02-09 12:00:36 -08:00
Jason Mitchell 417f23f826 Add runtime toggle for HUD Caching, and disable by default (for now at least) (#1374) 2026-02-07 14:44:49 -08:00
TeterosandTeteros ca3c46c873 Add PFAA Geologica cobble/bricks to the BlockCrackFix Whitelist (#1370)
Co-authored-by: Teteros <[email protected]>
2026-02-06 11:36:41 -08:00
Jason Mitchell 82fa9ade2b Add new emitter modes (#1368)
* Indirect
* Individual
2026-02-05 20:58:10 -08:00
Jason Mitchell aaab8fd0bc Thread safe biome cache & VAO Redirection (#1367)
* Thread safe biome cache

* VAO Redirection
* Delegate GLSM VAO calls to UniversalVAO
* Redirect usages of UniversalVAO & LWJGLService VAO functions to GLSM
* Avoid infinite recursion
2026-02-05 13:56:44 -08:00
Jason Mitchell bd56ccc4b8 Totally not experimental 2026-02-04 22:42:03 -08:00
sisyphussy e17f60308d Display List Refactor (#1360) 2026-02-04 10:39:20 -08:00
Kotmatross 1dd98978ad [Hotfix] Fix NTM:Space mixin targets for X5593_H261 (#1356) 2026-02-02 10:02:36 -08:00
Jason Mitchell dae986eb0b Fix crash with clearBufferSubData (#1355) 2026-02-01 12:41:53 -08:00
Jason Mitchell 5fdf84edd0 Camera and Menu Tweaks (#1354) 2026-02-01 12:14:32 -08:00
Jason Mitchell b19d023c11 Bump gtnhgradle 2026-02-01 09:26:19 -08:00
Jason Mitchell 41eafbd151 GLSL Downgrading (#1348) 2026-01-31 13:23:02 -08:00
sisyphussy dd970b4c6a Fix nested display lists rendering twice (#1349)
* Fix nested display lists rendering twice

* cleanup
2026-01-31 05:45:48 -08:00
Eclipse-Sol f79ad7f22a Fix arrows inheriting item material IDs (#1347) 2026-01-30 18:35:04 -08:00
Jason Mitchell 6947ffa3e2 Add IS_IRIS define as an option. 2026-01-30 18:10:27 -08:00
Jason Mitchell 969d2c3710 Backports tessellation shader support, safe zone shadow culling, and shader pack rendering directives. 2026-01-30 18:10:27 -08:00
Jason Mitchell c5988d29c3 Fix camera position and nether portal 3rd party rendering 2026-01-30 18:10:27 -08:00
Jason Mitchell dc68c2c265 Fix end portal w/ Shaders 2026-01-30 18:10:27 -08:00
Jason Mitchell 51533a81ec Misc Shaderpack loading fixes (#1343) 2026-01-30 14:53:41 -08:00
Eclipse-Sol 90eabb5d40 Add hardcoded entities and player_cape (#1327) 2026-01-30 14:53:26 -08:00
sisyphussyandJason Mitchell 74843cb1f9 Glc redirects (#1340)
Co-authored-by: Jason Mitchell <[email protected]>
2026-01-30 14:50:54 -08:00
Death Fuel 5e0344a529 remove unnecessary resource refresh (#1342) 2026-01-30 14:40:51 -08:00
Jason Mitchell 3cdd934987 First pass at setup/begin pass support (#1335) 2026-01-29 07:53:04 -08:00
Léa Gris 9d90ff137c fix(render): adapt hodgepodge fixBottomFaceUV (#1334) 2026-01-28 10:55:53 -08:00
Jason Mitchell bee9541797 Shadow Compute Shaders (#1333) 2026-01-28 00:03:04 -08:00
Jason Mitchell b75a5d8f7d Ya that won't work (#1332) 2026-01-27 15:32:42 -08:00
Jason Mitchell 8d3ab41c53 Use entity position instead of camera pos (#1330) 2026-01-26 22:19:54 -08:00
Jason Mitchell 48d032e654 Tweaks (#1329) 2026-01-26 21:08:09 -08:00
sisyphussy 1968914582 Fix hud flickering (#1325) 2026-01-26 10:19:49 -08:00
Guus Waals a70bd76ff3 Fix deadlock in CTMUtils (#1189) 2026-01-26 10:08:32 -08:00
mist475 db919b5f37 add biomeDictionary and lotr compat to BiomeUniforms (#1324) 2026-01-26 09:58:08 -08:00
MalTeeez cde47ea423 Add option to disable f3 additions (#1308) 2026-01-26 09:56:11 -08:00
Jason Mitchell 5ef6cb11c0 Don't double check infinite bounds (#1321) 2026-01-25 21:13:14 -08:00
Eclipse-Sol b7015bf10e Adds "name_tag" feature (#1318) 2026-01-25 20:00:49 -08:00
Jason Mitchell 46602ab921 Chunk Thread Saftey (#1320) 2026-01-25 19:58:33 -08:00
Jason Mitchell 63e9fa0df1 Dynamic Lights Improvements (#1319) 2026-01-25 19:07:11 -08:00
Eclipse-Sol dcea970a77 Various Shader Fixes (#1315) 2026-01-25 16:56:54 -08:00
Jason Mitchell b29240d6da Use the proper blockAccess on the main thread (#1317) 2026-01-25 16:55:45 -08:00
Jason Mitchell 9d5a02ed21 Optimize TileEntities (#1314) 2026-01-25 10:54:45 -08:00
Jason Mitchell b454483aae Add a nvidia workarounds from celeritas (#1286) 2026-01-24 18:52:52 -08:00
Jason Mitchell 72b13543c6 AABB Caching (#1309) 2026-01-24 18:52:22 -08:00
Jason Mitchell 36d98fcb22 Fix currentRenderedItemId for ItemBlocks (#1307) 2026-01-24 12:43:03 -08:00
Jason Mitchell 44c5b9617a Lighting fixes (#1306) 2026-01-24 12:23:59 -08:00
Kotmatross f3f1677db8 [Shaders] Fix NTM:Space sunset not rendering {2.0 regression} (#1275) 2026-01-24 19:47:30 +00:00
Jason Mitchell d396b611ad Buffer fixes (#1304) 2026-01-24 11:30:05 -08:00
Jason Mitchell 08f4c0f9db Make feature flag defines available in shader.properties preprocessing (#1303) 2026-01-24 10:10:13 -08:00
Eclipse-Sol 50b0d22dc2 Fix entity material handling. (#1298) 2026-01-23 20:24:27 -08:00
Jason Mitchell 7fed54264e No more OpenGLHelper (#1297) 2026-01-23 19:48:52 -08:00
embeddedt be87401be6 Improve behavior of Celeritas-based lighting pipeline 2026-01-23 19:37:57 -08:00
Jason Mitchell 94254006df Properly cleanup (#1294) 2026-01-23 18:19:46 -08:00
Jason Mitchell f79774df65 Respect shader render layer overrides (#1293) 2026-01-23 16:43:01 -08:00
Eclipse-Sol 2f2fc22df3 Add support for dimension.properties (#1283) 2026-01-22 19:17:16 -08:00
Jason Mitchell 26300bbb1b Celeritas Smooth Lighting is now default and working. UseSeparateAO, Emissives, and oldLighting=false now work. 2026-01-22 16:32:15 -08:00
Jason Mitchell 88e4d2fd6c Use Separate AO & SmoothLightPipeline 2026-01-22 16:32:15 -08:00
Jason Mitchell bb52476d27 Custom images, textures, SSBO, and ShaderPack/ShaderProperties backports 2026-01-22 16:32:15 -08:00
Jason Mitchell 6d60477b93 Actually allocate the (minimal) display lists so we stop clobbering the hand renderer on shader reload (#1278) 2026-01-19 13:05:27 -08:00
Jason MitchellandEclipse-Sol 362724610c Add Various Features (#1209) (#1277)
Co-authored-by: Eclipse-Sol <[email protected]>
2026-01-19 13:03:56 -08:00
Jason Mitchell ea4a3cdf0b Fix a race condition (#1273) 2026-01-16 16:46:36 -08:00
Jason Mitchell 65959268d0 Support glCallLists (#1265) 2026-01-13 10:41:09 -08:00
Jason Mitchell 58d820cd7e Fix OSX F3 crash
Fixes #1256
2026-01-13 09:37:28 -08:00
Death Fuel 685d68bd9c Improve glyph replacement logic (#1247) 2026-01-12 15:17:20 -08:00
Jason MitchellandCaedis ad63d2d62c Dynamic light improvements (#1255)
Co-authored-by: Caedis <[email protected]>
2026-01-10 23:50:32 -08:00
Jason Mitchell d2730ccca8 Better video settings search (#1254) 2026-01-10 21:25:03 -08:00
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
1172 changed files with 67506 additions and 30677 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
+2 -3
View File
@@ -4,7 +4,7 @@
/.vs/
/.vscode/
/run/
/build/
build/
/eclipse/
.classpath
.project
@@ -36,5 +36,4 @@ addon.local.gradle
addon.local.gradle.kts
addon.late.local.gradle
addon.late.local.gradle.kts
/*.csv
!gl_functions.csv
local/
+29 -13
View File
@@ -7,40 +7,49 @@
* 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
## Core Profile
Targets GL 3.3+ core profile
* FFP emulation via generated GLSL shaders
* Display list emulation
* Software matrix stacks and state management through GLSM
* GL_QUADS → GL_TRIANGLES conversion
* Automatic transformation of mod shaders that use compat-only GLSL builtins (`gl_ModelViewMatrix`, `texture2D`, etc.)
# 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
@@ -55,16 +64,16 @@
* The [Iris](https://github.com/IrisShaders/Iris) Team: coderbot, IMS212, Justsnoopy30, FoundationGames
* Asek3 for the Iris Forge port [Oculus](https://github.com/Asek3/Oculus/tree/839ce8eca8cf0c4f6b7a1322b906e5c99125ca69)
* Backported code under the LGPL
* Sodium
* Sodium / Celeritas
* JellySquid & the CaffeineMC team, for making Sodium in the first place
* Embeddedt for [Embeddium](https://github.com/embeddedt/embeddium/tree/dc59ca357c25beefd6288f0d1d40b4cd8e670ab8) under the [LGPL 3.0](https://github.com/embeddedt/embeddium/blob/16.x/forge/LICENSE)
* Embeddedt for Celeritas used under the LGPL 3.0
* Reese's Sodium Options
* FlashyReese for [Reese's Sodium Options](https://github.com/FlashyReese/reeses-sodium-options/tree/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b) under the [MIT license](https://github.com/FlashyReese/reeses-sodium-options/blob/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b/LICENSE.txt)
* Neodymium
* 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 +91,10 @@
* 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
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Code ported and/or inspired from Mesa - under the MIT licensed
* Display list ID allocator ported from [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
+65 -2
View File
@@ -1,9 +1,18 @@
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
plugins {
id("com.gtnewhorizons.gtnhconvention")
}
minecraft {
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
// extraRunJvmArguments.add("-Dangelica.useVulkan=true")
// extraRunJvmArguments.add("-Dangelica.vulkanDebug=true")
// extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=true")
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
}
tasks.test {
@@ -11,17 +20,23 @@ 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 +46,51 @@ tasks.register<Copy>("copyDependencies") {
from(configurations.default)
into("testDependencies")
}
val embedOnly: Configuration by configurations
val shadowImplementation: Configuration by configurations
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = embedOnly
}
val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
dependsOn(shadowImplementation)
from(shadowImplementation.map(::zipTree))
exclude("module-info.class")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveClassifier = "shadow-stripped"
}
val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = files(stripModuleInfoFromShadow.map { it.archiveFile })
outputs.dir(temporaryDir)
}
tasks.test {
dependsOn(downgradeEmbedOnlyForTest, downgradeShadowImplForTest)
classpath = classpath
.minus(embedOnly)
.plus(files(downgradeEmbedOnlyForTest.map { it.outputCollection }))
.minus(shadowImplementation)
.plus(files({ fileTree(downgradeShadowImplForTest.get().temporaryDir) }))
}
tasks.shadowJar {
dependsOn(embedOnly)
from(embedOnly.map(::zipTree))
duplicatesStrategy = DuplicatesStrategy.INCLUDE
mergeServiceFiles()
minimize {
exclude(project(":glsm"))
exclude(project(":lwjgl3-backend"))
}
relocate("com.mitchej123", "com.mitchej123")
relocate("org.embeddedt", "org.embeddedt")
relocate("com.gtnewhorizons.angelica.glsm", "com.gtnewhorizons.angelica.glsm")
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3")
}
+68 -32
View File
@@ -46,45 +46,68 @@ configurations {
// Can be enabled for testing if desired
// runtimeClasspath.extendsFrom(transformedMod)
// testRuntimeClasspath.extendsFrom(transformedMod)
testRuntimeClasspath.exclude group: "com.github.GTNewHorizons", module: "lwjgl3ify"
// Embed in JAR without relocation (compile only, shadowed into JAR)
embedOnly
embedOnly.canBeConsumed = false
embedOnly.canBeResolved = true
compileClasspath.extendsFrom(embedOnly)
testCompileClasspath.extendsFrom(embedOnly)
testRuntimeClasspath.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 }
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:lwjgl3ify:3.0.99")
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.36:dev")
shadowImplementation(project(":glsm")) { transitive = false }
shadowImplementation(project(":lwjgl3-backend")) { transitive = false }
shadowImplementation("net.minecraftforge:eventbus:7.0-beta.15")
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-26.g5ee7d96-GTNH") {
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.4-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-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,29 +117,42 @@ dependencies {
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.6:dev") // Mixin Version
compileOnly("com.github.GTNewHorizons:NotEnoughIds-Legacy:1.4.7:dev") // ASM Version
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev")
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5634_H261"))
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(rfg.deobf("curse.maven:et-futurum-requiem-441392:5873837"))
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
compileOnly("com.github.GTNewHorizons:HoloInventory:2.4.13-GTNH:dev")
runtimeOnlyNonPublishable(rfg.deobf("CoreTweaks:CoreTweaks:0.3.3.2"))
// For testing alternative splash screens — https://github.com/MalTeeez/ModernSplash/releases
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
// Hodgepodge
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
// Better Crashes
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.0-GTNH:dev")
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.1-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")
// Distant Horizons
compileOnly("maven.modrinth:DistantHorizonsApi:5.1.0")
devOnlyNonPublishable("com.github.javaparser:javaparser-symbol-solver-core:3.26.2") {
exclude module: "guava"
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')
testImplementation('com.tngtech.archunit:archunit-junit5:1.4.1')
}
}
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
}
-2084
View File
File diff suppressed because it is too large. Load diff
+193
View File
@@ -0,0 +1,193 @@
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
plugins {
`java-library`
`maven-publish`
}
apply(plugin = "xyz.wagyourtail.jvmdowngrader")
version = rootProject.version
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
withSourcesJar()
}
// RFG variant attributes: the root Angelica project uses RetroFuturaGradle which expects
// these attributes on dependencies. Without them, Gradle can't resolve the subproject variant.
val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation", String::class.java)
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
configurations {
listOf("apiElements", "runtimeElements").forEach { name ->
named(name) {
attributes {
attribute(rfgObfAttr, "mcp")
attribute(rfgTransformedAttr, true)
}
}
}
}
// Compile stubs: MC class signatures needed to resolve GTNHLib's type hierarchy.
// Not included in the published jar — real MC classes are on the classpath at runtime.
sourceSets {
create("stubs") {
java.srcDir("src/stubs/java")
}
}
// Exclude stubs from all output jars
tasks.withType<Jar>().configureEach {
exclude("net/minecraft/**")
}
repositories {
maven {
name = "Mojang"
url = uri("https://libraries.minecraft.net")
}
maven {
name = "GTNH Maven"
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
}
maven {
name = "Forge"
url = uri("https://maven.minecraftforge.net/")
}
maven {
name = "taumc"
url = uri("https://maven.taumc.org/releases")
}
maven {
name = "WagYourTail"
url = uri("https://maven.wagyourtail.xyz/releases")
content { includeGroup("xyz.wagyourtail.jvmdowngrader") }
}
mavenCentral()
}
dependencies {
// MC compile stubs
compileOnly(sourceSets["stubs"].output)
// LWJGL
compileOnly("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
compileOnly("org.lwjgl.lwjgl:lwjgl_util:${property("lwjglVersion")}")
compileOnly("org.ow2.asm:asm-tree:5.0.3")
// Our Deps
api("com.github.GTNewHorizons:GTNHLib:${property("gtnhlibVersion")}:dev")
api("net.minecraftforge:eventbus:${property("eventbusVersion")}")
compileOnly("org.embeddedt.celeritas:celeritas-common:${property("celeritasVersion")}") { isTransitive = false }
implementation("org.taumc:glsl-transformation-lib:${property("glslTransformLibVersion")}") { exclude(module = "antlr4") }
implementation("org.antlr:antlr4-runtime:${property("antlr4RuntimeVersion")}")
implementation("org.anarres:jcpp:${property("jcppVersion")}")
compileOnly("org.projectlombok:lombok:${property("lombokVersion")}") { isTransitive = false }
annotationProcessor("org.projectlombok:lombok:${property("lombokVersion")}")
compileOnly("org.jetbrains:annotations:${property("jetbrainsAnnotationsVersion")}")
compileOnly("org.apache.logging.log4j:log4j-api:${property("log4jVersion")}")
// Deps (normally from MC) - compile only, but needed at test runtime
compileOnly("com.google.guava:guava:${property("guavaVersion")}")
testRuntimeOnly("com.google.guava:guava:${property("guavaVersion")}")
// Test
testImplementation(sourceSets["stubs"].output)
testImplementation(platform("org.junit:junit-bom:${property("junitBomVersion")}"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.ow2.asm:asm-tree:9.7")
testImplementation("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.apache.logging.log4j:log4j-core:${property("log4jVersion")}")
testRuntimeOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:${property("celeritasVersion")}") { isTransitive = false }
testRuntimeOnly("xyz.wagyourtail.jvmdowngrader:jvmdowngrader-java-api:${property("jvmDowngraderVersion")}:downgraded-8")
testRuntimeOnly("org.apache.commons:commons-lang3:${property("commonsLang3Version")}")
}
val depsToDowngrade by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
depsToDowngrade("net.minecraftforge:eventbus:${property("eventbusVersion")}")
depsToDowngrade("org.embeddedt.celeritas:celeritas-common:${property("celeritasVersion")}") { isTransitive = false }
}
tasks.withType<DowngradeFiles>().configureEach {
downgradeTo.set(JavaVersion.VERSION_1_8)
multiReleaseOriginal.set(false)
multiReleaseVersions.set(emptySet())
}
val downgradeDepsForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = depsToDowngrade
}
val downgradeMainClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["main"].output.classesDirs.plus(sourceSets["stubs"].output.classesDirs)
classpath = sourceSets["main"].compileClasspath
dependsOn(tasks.named("classes"), tasks.named("stubsClasses"))
}
val downgradeTestClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["test"].output.classesDirs
classpath = sourceSets["test"].compileClasspath
dependsOn(tasks.named("testClasses"))
}
tasks.test {
useJUnitPlatform()
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
}
dependsOn(downgradeMainClasses, downgradeTestClasses, downgradeDepsForTest)
val mainClassesDirs = sourceSets["main"].output.classesDirs
.plus(sourceSets["stubs"].output.classesDirs)
val testClassesDirs = sourceSets["test"].output.classesDirs
val downgradedTest = files(downgradeTestClasses.map { it.outputCollection })
val downgradedMain = files(downgradeMainClasses.map { it.outputCollection })
val downgradedDeps = files(downgradeDepsForTest.map { it.outputCollection })
setTestClassesDirs(downgradedTest)
classpath = downgradedTest
.plus(downgradedMain)
.plus(downgradedDeps)
.plus(classpath.minus(mainClassesDirs).minus(testClassesDirs).minus(depsToDowngrade))
val extractNatives = rootProject.tasks.named("extractNatives2")
dependsOn(extractNatives)
jvmArgs("-Djava.library.path=${extractNatives.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}")
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
groupId = "com.gtnewhorizons.angelica"
artifactId = "glsm"
version = rootProject.version.toString()
from(components["java"])
}
}
repositories {
if (System.getenv("MAVEN_USER") != null) {
maven {
name = "GTNHMaven"
url = uri(rootProject.findProperty("mavenPublishUrl")?.toString() ?: "https://nexus.gtnewhorizons.com/repository/releases/")
credentials {
username = System.getenv("MAVEN_USER")
password = System.getenv("MAVEN_PASSWORD")
}
}
}
}
}
+14
View File
@@ -0,0 +1,14 @@
lwjglVersion = 2.9.4-nightly-20150209
eventbusVersion = 7.0-beta.15
celeritasVersion = 2.5.3-GTNH
lombokVersion = 1.18.42
log4jVersion = 2.0-beta9
guavaVersion = 18.0
gtnhlibVersion = 0.9.36
glslTransformLibVersion = 0.2.0-26.g5ee7d96-GTNH
antlr4RuntimeVersion = 4.13.2
jcppVersion = 1.4.14
jetbrainsAnnotationsVersion = 26.0.2
junitBomVersion = 5.14.1
jvmDowngraderVersion = 1.3.5
commonsLang3Version = 3.17.0
@@ -0,0 +1,67 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor;
import java.util.List;
/**
* Builds final command buffers from raw command buffers and compiled VBO data.
* Bridges the gap between VBO compilation (glsm types) and buffer processing (recording types).
*/
public final class CommandBufferBuilder {
private CommandBufferBuilder() {}
/**
* Build commands with transform collapsing optimization.
* Accumulates MODELVIEW transforms and emits MultMatrix at barriers (draws, CallList).
* Uses mergedRanges where consecutive same-transform draws are combined.
*/
public static void buildFromRawBuffer(
CommandBuffer rawBuffer,
List<AccumulatedDraw> accumulatedDraws,
CommandBuffer finalBuffer) {
rawBuffer.resetRead();
int cmdIndex = 0;
int rangeIndex = 0;
AccumulatedDraw draw;
while (rawBuffer.hasRemaining()) {
// Read and process the command
CommandBufferProcessor.copyCommand(rawBuffer, finalBuffer);
// Emit draw ranges at this command position
while (rangeIndex < accumulatedDraws.size() && (draw = accumulatedDraws.get(rangeIndex)).commandIndex == cmdIndex) {
emitDrawRangeToBuffer(draw, finalBuffer, rangeIndex);
rangeIndex++;
}
cmdIndex++;
}
while (rangeIndex < accumulatedDraws.size()) {
emitDrawRangeToBuffer(accumulatedDraws.get(rangeIndex), finalBuffer, rangeIndex);
rangeIndex++;
}
}
/**
* Emit a draw range to the output buffer, with transform if needed.
*/
private static void emitDrawRangeToBuffer(
AccumulatedDraw draw,
CommandBuffer out,
int vboIdx) {
if (draw.restoreData != null) {
out.writeDrawRangeRestore(vboIdx, draw.restoreData);
return;
}
// Write the draw range command
out.writeDrawRange(vboIdx);
}
}
@@ -0,0 +1,452 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import org.taumc.glsl.ShaderParser;
import org.taumc.glsl.Transformer;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Compat transformation for mod shaders running under core profile.
*
* <p>Handles:
* <ul>
* <li>Matrix builtin replacement (gl_ModelViewMatrix, gl_ProjectionMatrix, etc.)</li>
* <li>Vertex attribute replacement (gl_Vertex, gl_Color, gl_MultiTexCoord0/1, gl_Normal)</li>
* <li>gl_TexCoord[N] varying array → per-index in/out declarations</li>
* <li>Texture function renames (texture2D → texture, etc.)</li>
* <li>Fragment output handling (gl_FragColor → layout-qualified out declarations)</li>
* <li>Fog builtins (gl_Fog, gl_FogFragCoord)</li>
* <li>gl_FrontColor → local variable in vertex shaders</li>
* <li>shadow2D/shadow2DLod → texture/textureLod with vec4 wrapping</li>
* <li>Version upgrade to 330 core minimum</li>
* <li>Reserved word pre-parse renaming (texture-as-variable, sample, etc.)</li>
* </ul>
*/
public class CompatShaderTransformer {
private static final Pattern VERSION_PATTERN = Pattern.compile("#version[ \\t]+(\\d+)(?:[ \\t]+(\\w+))?");
private static final Pattern DEFINE_PATTERN = Pattern.compile("^\\s*#\\s*define\\s+.+$", Pattern.MULTILINE);
/** Compat builtins that trigger AST transformation. */
private static final Set<String> COMPAT_BUILTINS = Set.of(
"gl_ModelView", "gl_Projection", "gl_NormalMatrix", "gl_TextureMatrix",
"gl_FragColor", "gl_Fog", "gl_FrontColor", "gl_Color",
"gl_Vertex", "gl_MultiTexCoord", "gl_TexCoord", "gl_Normal", "ftransform",
"texture2D", "texture3D", "texelFetch2D", "texelFetch3D", "textureSize2D",
"shadow2D", "gl_FrontLightModelProduct",
"gl_LightSource", "gl_FrontMaterial"
);
private static final Pattern NEEDS_TRANSFORM_PATTERN = Pattern.compile(
String.join("|", COMPAT_BUILTINS) + "|\\b(?:attribute|varying)\\b"
);
private static final Map<String, String> MATRIX_RENAMES = Map.of(
"gl_ModelViewMatrix", "angelica_ModelViewMatrix",
"gl_ModelViewMatrixInverse", "angelica_ModelViewMatrixInverse",
"gl_ProjectionMatrix", "angelica_ProjectionMatrix",
"gl_ProjectionMatrixInverse", "angelica_ProjectionMatrixInverse",
"gl_NormalMatrix", "angelica_NormalMatrix"
);
private static final Path DUMP_DIR;
private static final AtomicInteger dumpCounter = new AtomicInteger(0);
static {
DUMP_DIR = Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false")) ? Paths.get("compat_shaders") : null;
}
private static final int CACHE_SIZE = 32;
private record CacheKey(String source, boolean isFragment) {}
private static final Map<CacheKey, String> cache = Collections.synchronizedMap(
new LinkedHashMap<>(32, 0.75f, true) {
@Override protected boolean removeEldestEntry(Map.Entry<CacheKey, String> eldest) {
return size() > CACHE_SIZE;
}
});
public static void clearCache() {
cache.clear();
}
/**
* Transform a mod shader source for core profile compatibility.
*/
public static String transform(String source, boolean isFragment) {
final boolean needsTransform = needsTransformation(source);
String result;
if (!needsTransform) {
result = fixupVersion(source);
} else {
final CacheKey key = new CacheKey(source, isFragment);
final String cached = cache.get(key);
if (cached != null) {
dumpShader(source, cached, isFragment, needsTransform);
return cached;
}
try {
result = transformInternal(source, isFragment);
cache.put(key, result);
} catch (Exception e) {
GLStateManager.LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
result = fixupVersion(source);
}
}
dumpShader(source, result, isFragment, needsTransform);
return result;
}
private static boolean needsTransformation(String source) {
// Shaders already at 330+ core don't need compat transformation
final Matcher vm = VERSION_PATTERN.matcher(source);
if (vm.find()) {
final int version = Integer.parseInt(vm.group(1));
if (version >= 330 && "core".equals(vm.group(2))) return false;
}
return NEEDS_TRANSFORM_PATTERN.matcher(source).find();
}
private static String transformInternal(String source, boolean isFragment) {
final Matcher versionMatcher = VERSION_PATTERN.matcher(source);
int declaredVersion = 110;
if (versionMatcher.find()) {
declaredVersion = Integer.parseInt(versionMatcher.group(1));
}
final int targetVersion = Math.max(declaredVersion, RENDER_BACKEND != null ? RENDER_BACKEND.getMinGLSLVersion() : 330);
// Pre-parse reserved word renaming — prevents ANTLR parse failures
source = GlslTransformUtils.replaceTexture(source);
source = GlslTransformUtils.renameReservedWords(source, targetVersion);
final ShaderParser.ParsedShader parsedShader = ShaderParser.parseShader(source);
final Transformer transformer = new Transformer(parsedShader.full());
injectMatrixUniforms(transformer);
transformFog(transformer, isFragment, source);
// gl_FrontLightModelProduct.sceneColor → angelica_SceneColor uniform
if (source.contains("gl_FrontLightModelProduct")) {
transformer.injectVariable("uniform vec4 angelica_SceneColor;");
transformer.replaceExpression("gl_FrontLightModelProduct.sceneColor", "angelica_SceneColor");
}
// gl_LightSource[i] → struct + uniform array (both via injectFunction to keep struct before uniform)
if (source.contains("gl_LightSource")) {
transformer.injectFunction("struct angelica_LightSourceParameters {"
+ "vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;"
+ "vec3 spotDirection;float spotExponent;float spotCutoff;float spotCosCutoff;"
+ "float constantAttenuation;float linearAttenuation;float quadraticAttenuation;"
+ "};");
transformer.injectFunction("uniform angelica_LightSourceParameters angelica_LightSource[2];");
transformer.rename("gl_LightSource", "angelica_LightSource");
}
// gl_FrontMaterial → struct + uniform (both via injectFunction to keep struct before uniform)
if (source.contains("gl_FrontMaterial")) {
transformer.injectFunction("struct angelica_MaterialParameters {"
+ "vec4 emission;vec4 ambient;vec4 diffuse;vec4 specular;float shininess;"
+ "};");
transformer.injectFunction("uniform angelica_MaterialParameters angelica_FrontMaterial;");
transformer.rename("gl_FrontMaterial", "angelica_FrontMaterial");
}
// gl_FrontColor (vertex) → gl_Color (fragment) varying chain
// Vertex side is unconditional: fragment may read gl_Color without vertex writing gl_FrontColor.
// Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
if (!isFragment) {
transformer.injectVariable("out vec4 angelica_FrontColor;");
transformer.rename("gl_FrontColor", "angelica_FrontColor");
transformer.prependMain("angelica_FrontColor = vec4(1.0);");
// Vertex attributes — replaces removed FFP vertex inputs with explicit in declarations
transformVertexAttributes(transformer, source);
} else {
if (source.contains("gl_Color")) {
transformer.injectVariable("in vec4 angelica_FrontColor;");
transformer.rename("gl_Color", "angelica_FrontColor");
}
}
// gl_TexCoord[N] varying array → per-index in/out declarations
final Set<Integer> texCoordIndices = new HashSet<>();
transformer.renameArray("gl_TexCoord", "angelica_TexCoord", texCoordIndices);
for (Integer i : texCoordIndices) {
final String qualifier = isFragment ? "in" : "out";
transformer.injectVariable(qualifier + " vec4 angelica_TexCoord" + i + ";");
}
// Fragment output handling + alpha test discard
if (isFragment) {
transformFragmentOutputs(transformer);
}
// texture-as-variable collision handling
if (transformer.containsCall("texture") && transformer.hasVariable("texture")) {
transformer.rename("texture", "gtexture");
}
if (transformer.hasVariable("angelica_renamed_texture")) {
transformer.rename("angelica_renamed_texture", "gtexture");
}
transformer.renameFunctionCall(GlslTransformUtils.TEXTURE_RENAMES);
transformer.renameAndWrapShadow("shadow2D", "texture");
transformer.renameAndWrapShadow("shadow2DLod", "textureLod");
final String versionDirective = "#version " + targetVersion + " core\n";
final String extensions = VERSION_PATTERN.matcher(GlslTransformUtils.getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
// Preserve #define directives
final StringBuilder defines = new StringBuilder();
final Matcher defineMatcher = DEFINE_PATTERN.matcher(source);
while (defineMatcher.find()) {
defines.append(defineMatcher.group().trim()).append('\n');
}
final String header = versionDirective + (extensions.isEmpty() ? "" : "\n" + extensions) + (defines.isEmpty() ? "" : "\n" + defines);
final StringBuilder result = new StringBuilder();
transformer.mutateTree(tree -> result.append(GlslTransformUtils.getFormattedShader(tree, header)));
// Restore pre-parse renames
String output = GlslTransformUtils.restoreReservedWords(result.toString());
// Core profile: attribute → in, varying → out (vertex) / in (fragment)
output = fixupQualifiers(output, isFragment);
return output;
}
/**
* Inject matrix uniforms and rename compat builtins.
*/
private static void injectMatrixUniforms(Transformer transformer) {
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrix;");
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrixInverse;");
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrix;");
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrixInverse;");
transformer.injectVariable("uniform mat3 angelica_NormalMatrix;");
transformer.injectVariable("uniform mat4 angelica_LightmapTextureMatrix;");
transformer.rename(MATRIX_RENAMES);
// Expression replacements
transformer.replaceExpression("gl_ModelViewProjectionMatrix", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix)");
transformer.replaceExpression("gl_TextureMatrix[0]", "mat4(1.0)");
transformer.replaceExpression("gl_TextureMatrix[1]", "angelica_LightmapTextureMatrix");
transformer.replaceExpression(
"gl_TextureMatrix",
"mat4[8](mat4(1.0), angelica_LightmapTextureMatrix, mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0))");
}
/**
* Transform fragment outputs for core profile.
*/
private static void transformFragmentOutputs(Transformer transformer) {
if (transformer.containsCall("gl_FragColor")) {
transformer.replaceExpression("gl_FragColor", "gl_FragData[0]");
}
final Set<Integer> found = new HashSet<>();
transformer.renameArray("gl_FragData", "angelica_FragData", found);
for (Integer i : found) {
transformer.injectVariable("layout (location = " + i + ") out vec4 angelica_FragData" + i + ";");
}
// Core profile: GL_ALPHA_TEST is removed - inject runtime discard using GLSM-tracked alpha reference (uploaded by CompatUniformManager).
if (found.contains(0)) {
transformer.injectVariable("uniform float angelica_currentAlphaTest;");
transformer.appendMain("if (angelica_FragData0.a <= angelica_currentAlphaTest) discard;");
}
}
private static void transformFog(Transformer transformer, boolean isFragment, String source) {
// Vertex side is unconditional: fragment may read gl_FogFragCoord without vertex writing it
transformer.rename("gl_FogFragCoord", "angelica_FogFragCoord");
if (!isFragment) {
transformer.injectVariable("out float angelica_FogFragCoord;");
transformer.prependMain("angelica_FogFragCoord = 0.0;");
} else {
if (source.contains("gl_FogFragCoord")) {
transformer.injectVariable("in float angelica_FogFragCoord;");
}
}
transformer.rename("gl_Fog", "angelica_Fog");
transformer.injectVariable("uniform float angelica_FogDensity;");
transformer.injectVariable("uniform float angelica_FogStart;");
transformer.injectVariable("uniform float angelica_FogEnd;");
transformer.injectVariable("uniform vec4 angelica_FogColor;");
transformer.injectFunction("struct angelica_FogParameters {vec4 color;float density;float start;float end;float scale;};");
transformer.injectFunction("angelica_FogParameters angelica_Fog = angelica_FogParameters("
+ "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
+ "1.0 / (angelica_FogEnd - angelica_FogStart));");
}
/**
* Replace removed FFP vertex attributes with explicit {@code in} declarations at core profile attribute locations.
*/
private static void transformVertexAttributes(Transformer transformer, String source) {
if (source.contains("gl_Vertex") || source.contains("ftransform")) {
transformer.injectVariable("layout(location = 0) in vec4 angelica_Vertex;");
transformer.rename("gl_Vertex", "angelica_Vertex");
}
// gl_Color in vertex shaders is the per-vertex color attribute, distinct from the fragment gl_Color (interpolated gl_FrontColor) handled above
if (source.contains("gl_Color")) {
transformer.injectVariable("layout(location = 1) in vec4 angelica_Color;");
transformer.rename("gl_Color", "angelica_Color");
}
if (source.contains("gl_MultiTexCoord0")) {
transformer.injectVariable("layout(location = 2) in vec4 angelica_MultiTexCoord0;");
transformer.rename("gl_MultiTexCoord0", "angelica_MultiTexCoord0");
}
if (source.contains("gl_MultiTexCoord1")) {
transformer.injectVariable("layout(location = 3) in vec4 angelica_MultiTexCoord1;");
transformer.rename("gl_MultiTexCoord1", "angelica_MultiTexCoord1");
}
if (source.contains("gl_Normal")) {
transformer.injectVariable("layout(location = 4) in vec3 angelica_Normal;");
transformer.rename("gl_Normal", "angelica_Normal");
}
if (source.contains("ftransform")) {
transformer.replaceExpression("ftransform()", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix * angelica_Vertex)");
}
}
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
if (DUMP_DIR == null) return;
final int id = dumpCounter.getAndIncrement();
final String suffix = isFragment ? ".frag.glsl" : ".vert.glsl";
try {
Files.createDirectories(DUMP_DIR);
// Capture caller info for identification
final String caller = identifyCaller();
final String header = "// Compat shader dump #" + id + " (" + (isFragment ? "fragment" : "vertex") + ")"
+ "\n// Transformed: " + wasTransformed
+ "\n// Caller: " + caller + "\n\n";
Files.writeString(DUMP_DIR.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8);
Files.writeString(DUMP_DIR.resolve(id + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
}
}
private static String identifyCaller() {
for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
final String cls = frame.getClassName();
if (!cls.startsWith("com.gtnewhorizons.angelica.glsm.")
&& !cls.startsWith("java.")
&& !cls.equals("org.lwjgl.opengl.GL20")) {
return cls + "." + frame.getMethodName() + ":" + frame.getLineNumber();
}
}
return "unknown";
}
private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\battribute\\b");
private static final Pattern VARYING_PATTERN = Pattern.compile("\\bvarying\\b");
/** Replace legacy storage qualifiers removed in core profile. Safe on AST-serialized output (no comments). */
public static String fixupQualifiers(String source, boolean isFragment) {
source = ATTRIBUTE_PATTERN.matcher(source).replaceAll("in");
source = VARYING_PATTERN.matcher(source).replaceAll(isFragment ? "in" : "out");
return source;
}
// Patterns for parsing fragment shader in declarations to generate passthrough vertex shaders
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+vec4\\s+angelica_FrontColor\\b");
private static final Pattern FRAG_IN_TEXCOORD = Pattern.compile("\\bin\\s+vec4\\s+angelica_TexCoord(\\d+)\\b");
private static final Pattern FRAG_IN_FOGCOORD = Pattern.compile("\\bin\\s+float\\s+angelica_FogFragCoord\\b");
/**
* Generate a passthrough vertex shader for a fragment-only program.
*
* @param fragmentSource the transformed fragment shader source
* @return a complete vertex shader source string
*/
public static String generatePassthroughVertexShader(String fragmentSource) {
final StringBuilder sb = new StringBuilder(512);
sb.append("#version 330 core\n\n");
// Always have position
sb.append("layout(location = 0) in vec4 a_Position;\n");
// Matrix uniforms for position transform
sb.append("uniform mat4 angelica_ModelViewMatrix;\n");
sb.append("uniform mat4 angelica_ProjectionMatrix;\n");
final StringBuilder varyings = new StringBuilder();
final StringBuilder assignments = new StringBuilder();
if (FRAG_IN_COLOR.matcher(fragmentSource).find()) {
sb.append("layout(location = 1) in vec4 a_Color;\n");
varyings.append("out vec4 angelica_FrontColor;\n");
assignments.append(" angelica_FrontColor = a_Color;\n");
}
final Matcher texCoordMatcher = FRAG_IN_TEXCOORD.matcher(fragmentSource);
final Set<Integer> texCoordIndices = new HashSet<>();
while (texCoordMatcher.find()) {
texCoordIndices.add(Integer.parseInt(texCoordMatcher.group(1)));
}
for (int i : texCoordIndices) {
// PRIMARY_UV=2, SECONDARY_UV=3. Only indices 0 and 1 are supported; higher indices collide at location 3.
final int location = i == 0 ? 2 : 3;
sb.append("layout(location = ").append(location).append(") in vec4 a_TexCoord").append(i).append(";\n");
varyings.append("out vec4 angelica_TexCoord").append(i).append(";\n");
assignments.append(" angelica_TexCoord").append(i).append(" = a_TexCoord").append(i).append(";\n");
}
if (FRAG_IN_FOGCOORD.matcher(fragmentSource).find()) {
varyings.append("out float angelica_FogFragCoord;\n");
assignments.append(" angelica_FogFragCoord = abs((angelica_ModelViewMatrix * a_Position).z);\n");
}
sb.append(varyings);
sb.append("\nvoid main() {\n");
sb.append(" gl_Position = angelica_ProjectionMatrix * angelica_ModelViewMatrix * a_Position;\n");
sb.append(assignments);
sb.append("}\n");
return sb.toString();
}
/** Ensure #version is at least 330 core, strip 'compatibility' profile. */
private static String fixupVersion(String source) {
final Matcher m = VERSION_PATTERN.matcher(source);
if (!m.find()) {
return "#version 330 core\n" + source;
}
final int version = Integer.parseInt(m.group(1));
final String profile = m.group(2);
if (version >= 330 && !"compatibility".equals(profile)) {
if ("core".equals(profile)) return source;
return m.replaceFirst("#version " + version + " core");
}
final int targetVersion = Math.max(version, 330);
return m.replaceFirst("#version " + targetVersion + " core");
}
}
@@ -0,0 +1,414 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
/**
* Per-program compat uniform location cache and upload manager.
*
* <p>Manages {@code angelica_*} uniforms injected by {@link CompatShaderTransformer} and {@code iris_*} matrix uniforms used by Iris/celeritas shader
* programs.
*/
public class CompatUniformManager {
// Uniform location indices within the cached array (package-private for tests)
static final int LOC_MODELVIEW = 0;
static final int LOC_MODELVIEW_INVERSE = 1;
static final int LOC_PROJECTION = 2;
static final int LOC_PROJECTION_INVERSE = 3;
static final int LOC_NORMAL = 4;
static final int LOC_LIGHTMAP_TEXTURE_MATRIX = 5;
static final int LOC_FOG_DENSITY = 6;
static final int LOC_FOG_START = 7;
static final int LOC_FOG_END = 8;
static final int LOC_FOG_COLOR = 9;
static final int LOC_IRIS_MODELVIEW = 10;
static final int LOC_IRIS_PROJECTION = 11;
static final int LOC_IRIS_NORMAL = 12;
static final int LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX = 13;
static final int LOC_IRIS_TEXTURE_MATRIX = 14;
static final int LOC_ALPHA_TEST_REF = 15;
static final int LOC_SCENE_COLOR = 16;
static final int LOC_CLIP_PLANES = 17;
static final int LOC_CLIP_PLANES_ENABLED = 18;
// Light source fields: 12 per light × 2 lights = 24 locations
static final int LIGHT_FIELDS = 12;
static final int LF_AMBIENT = 0, LF_DIFFUSE = 1, LF_SPECULAR = 2, LF_POSITION = 3;
static final int LF_HALF_VECTOR = 4, LF_SPOT_DIRECTION = 5, LF_SPOT_EXPONENT = 6;
static final int LF_SPOT_CUTOFF = 7, LF_SPOT_COS_CUTOFF = 8;
static final int LF_CONSTANT_ATTEN = 9, LF_LINEAR_ATTEN = 10, LF_QUADRATIC_ATTEN = 11;
static final int LOC_LIGHT_BASE = 19;
// Material fields: 5 locations
static final int MF_EMISSION = 0, MF_AMBIENT = 1, MF_DIFFUSE = 2, MF_SPECULAR = 3, MF_SHININESS = 4;
static final int MAT_FIELDS = 5;
static final int LOC_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 43
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 48
private static final String[] LIGHT_FIELD_NAMES = {
"ambient", "diffuse", "specular", "position", "halfVector",
"spotDirection", "spotExponent", "spotCutoff", "spotCosCutoff",
"constantAttenuation", "linearAttenuation", "quadraticAttenuation",
};
private static final String[] MAT_FIELD_NAMES = {
"emission", "ambient", "diffuse", "specular", "shininess",
};
private static final String[] UNIFORM_NAMES;
static {
UNIFORM_NAMES = new String[LOC_COUNT];
UNIFORM_NAMES[LOC_MODELVIEW] = "angelica_ModelViewMatrix";
UNIFORM_NAMES[LOC_MODELVIEW_INVERSE] = "angelica_ModelViewMatrixInverse";
UNIFORM_NAMES[LOC_PROJECTION] = "angelica_ProjectionMatrix";
UNIFORM_NAMES[LOC_PROJECTION_INVERSE] = "angelica_ProjectionMatrixInverse";
UNIFORM_NAMES[LOC_NORMAL] = "angelica_NormalMatrix";
UNIFORM_NAMES[LOC_LIGHTMAP_TEXTURE_MATRIX] = "angelica_LightmapTextureMatrix";
UNIFORM_NAMES[LOC_FOG_DENSITY] = "angelica_FogDensity";
UNIFORM_NAMES[LOC_FOG_START] = "angelica_FogStart";
UNIFORM_NAMES[LOC_FOG_END] = "angelica_FogEnd";
UNIFORM_NAMES[LOC_FOG_COLOR] = "angelica_FogColor";
UNIFORM_NAMES[LOC_IRIS_MODELVIEW] = "iris_ModelViewMatrix";
UNIFORM_NAMES[LOC_IRIS_PROJECTION] = "iris_ProjectionMatrix";
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
UNIFORM_NAMES[LOC_IRIS_TEXTURE_MATRIX] = "iris_TextureMatrix";
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
UNIFORM_NAMES[LOC_CLIP_PLANES_ENABLED] = "angelica_ClipPlanesEnabled";
for (int li = 0; li < 2; li++) {
for (int fi = 0; fi < LIGHT_FIELDS; fi++) {
UNIFORM_NAMES[LOC_LIGHT_BASE + li * LIGHT_FIELDS + fi] = "angelica_LightSource[" + li + "]." + LIGHT_FIELD_NAMES[fi];
}
}
for (int fi = 0; fi < MAT_FIELDS; fi++) {
UNIFORM_NAMES[LOC_MAT_BASE + fi] = "angelica_FrontMaterial." + MAT_FIELD_NAMES[fi];
}
}
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
// Reusable NIO buffers for upload
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
private static final FloatBuffer mat3Buf = BufferUtils.createFloatBuffer(9);
private static final FloatBuffer vec4Buf = BufferUtils.createFloatBuffer(4);
private static final FloatBuffer clipPlaneBuf = BufferUtils.createFloatBuffer(32); // 8 planes * vec4
private static final Matrix3f normalMatrix = new Matrix3f();
private static final Matrix4f scratchMatrix = new Matrix4f();
private static final float LIGHTMAP_SCALE = 1.0f / 256.0f;
private static final FloatBuffer lightmapMatrixBuf;
static {
lightmapMatrixBuf = BufferUtils.createFloatBuffer(16);
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
}
// Dirty tracking: skip uploads when state hasn't changed and program is the same
private static int lastProgram = -1;
private static int lastMvGen = -1;
private static int lastProjGen = -1;
private static int lastTexMatGen = -1;
private static int lastFragmentGen = -1;
private static int lastLightingGen = -1;
private static int lastClipPlaneGen = -1;
private CompatUniformManager() {}
public static void onLinkProgram(int program) {
int[] locs = new int[LOC_COUNT];
boolean hasAny = false;
for (int i = 0; i < LOC_COUNT; i++) {
locs[i] = RENDER_BACKEND.getUniformLocation(program, UNIFORM_NAMES[i]);
if (locs[i] != -1) hasAny = true;
}
if (hasAny) {
programLocations.put(program, locs);
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
}
}
public static void onUseProgram(int program) {
if (program == 0) return;
int[] locs = programLocations.get(program);
if (locs == null) return;
final boolean programChanged = program != lastProgram;
lastProgram = program;
// Matrix uniforms — skip if generation unchanged and same program
final int mvGen = GLStateManager.mvGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
final boolean mvChanged = programChanged || mvGen != lastMvGen;
final boolean projChanged = programChanged || projGen != lastProjGen;
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(locs, mvChanged, projChanged, texMatChanged);
lastMvGen = mvGen;
lastProjGen = projGen;
lastTexMatGen = texMatGen;
}
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
lastFragmentGen = fragGen;
uploadFragmentUniforms(locs);
}
// Lighting-derived uniforms (scene color, light sources, material)
final int litGen = GLStateManager.lightingGeneration;
if (programChanged || litGen != lastLightingGen) {
lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
uploadLightSources(locs);
uploadMaterial(locs);
}
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
}
}
}
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
if (mvChanged) {
// ModelView
if (locs[LOC_MODELVIEW] != -1 || locs[LOC_IRIS_MODELVIEW] != -1) {
mv.get(mat4Buf);
if (locs[LOC_MODELVIEW] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW], false, mat4Buf);
}
if (locs[LOC_IRIS_MODELVIEW] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_MODELVIEW], false, mat4Buf);
}
}
// ModelView Inverse
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
mv.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
}
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
mv.normal(normalMatrix);
normalMatrix.get(mat3Buf);
if (locs[LOC_NORMAL] != -1) {
RENDER_BACKEND.uniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
}
if (locs[LOC_IRIS_NORMAL] != -1) {
RENDER_BACKEND.uniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
}
}
}
if (projChanged) {
// Projection
if (locs[LOC_PROJECTION] != -1 || locs[LOC_IRIS_PROJECTION] != -1) {
proj.get(mat4Buf);
if (locs[LOC_PROJECTION] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION], false, mat4Buf);
}
if (locs[LOC_IRIS_PROJECTION] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_PROJECTION], false, mat4Buf);
}
}
// Projection Inverse
if (locs[LOC_PROJECTION_INVERSE] != -1) {
proj.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
}
}
if (texMatChanged) {
// Iris lightmap texture matrix (constant)
if (locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX], false, lightmapMatrixBuf);
}
// angelica_LightmapTextureMatrix — actual GLSM texture unit 1 matrix (set by enableLightmap())
if (locs[LOC_LIGHTMAP_TEXTURE_MATRIX] != -1) {
GLStateManager.getTextures().getTextureUnitMatrix(1).get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_LIGHTMAP_TEXTURE_MATRIX], false, mat4Buf);
}
// iris_TextureMatrix — texture unit 0 matrix (animated by glints, etc.)
if (locs[LOC_IRIS_TEXTURE_MATRIX] != -1) {
GLStateManager.getTextures().getTextureUnitMatrix(0).get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_TEXTURE_MATRIX], false, mat4Buf);
}
}
}
private static void uploadFragmentUniforms(int[] locs) {
// Fog uniforms
FogStateStack fog = GLStateManager.getFogState();
if (locs[LOC_FOG_DENSITY] != -1) {
RENDER_BACKEND.uniform1f(locs[LOC_FOG_DENSITY], Math.max(0.0f, fog.getDensity()));
}
if (locs[LOC_FOG_START] != -1) {
RENDER_BACKEND.uniform1f(locs[LOC_FOG_START], fog.getStart());
}
if (locs[LOC_FOG_END] != -1) {
RENDER_BACKEND.uniform1f(locs[LOC_FOG_END], fog.getEnd());
}
if (locs[LOC_FOG_COLOR] != -1) {
vec4Buf.clear();
vec4Buf.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y).put((float) fog.getFogColor().z).put(fog.getFogAlpha());
vec4Buf.flip();
RENDER_BACKEND.uniform4(locs[LOC_FOG_COLOR], vec4Buf);
}
// Alpha test reference — core profile replacement for GL_ALPHA_TEST
// Return -1.0f for ALWAYS or disabled so `alpha <= ref` never discards.
if (locs[LOC_ALPHA_TEST_REF] != -1) {
final float ref;
if (!GLStateManager.getAlphaTest().isEnabled() || GLStateManager.getAlphaState().getFunction() == GL11.GL_ALWAYS) {
ref = -1.0f;
} else {
ref = GLStateManager.getAlphaState().getReference();
}
RENDER_BACKEND.uniform1f(locs[LOC_ALPHA_TEST_REF], ref);
}
}
/**
* Scene color = emission + ambient * lightModel.ambient, alpha = diffuse.a Same computation as {@code Uniforms.uploadLightingPreComputed()}.
*/
private static void uploadSceneColor(int[] locs) {
final MaterialState mat = GLStateManager.getFrontMaterial();
final LightModelState lm = GLStateManager.getLightModel();
vec4Buf.clear();
vec4Buf.put(mat.emission.x + mat.ambient.x * lm.ambient.x)
.put(mat.emission.y + mat.ambient.y * lm.ambient.y)
.put(mat.emission.z + mat.ambient.z * lm.ambient.z)
.put(mat.diffuse.w);
vec4Buf.flip();
RENDER_BACKEND.uniform4(locs[LOC_SCENE_COLOR], vec4Buf);
}
private static void uploadLightSources(int[] locs) {
// Early exit: if the first light field isn't present, no lighting uniforms exist
if (locs[LOC_LIGHT_BASE] == -1) return;
final LightStateStack[] lights = GLStateManager.getLightDataStates();
for (int li = 0; li < 2; li++) {
final int base = LOC_LIGHT_BASE + li * LIGHT_FIELDS;
final LightState light = lights[li];
if (locs[base + LF_AMBIENT] != -1)
RENDER_BACKEND.uniform4f(locs[base + LF_AMBIENT], light.ambient.x, light.ambient.y, light.ambient.z, light.ambient.w);
if (locs[base + LF_DIFFUSE] != -1)
RENDER_BACKEND.uniform4f(locs[base + LF_DIFFUSE], light.diffuse.x, light.diffuse.y, light.diffuse.z, light.diffuse.w);
if (locs[base + LF_SPECULAR] != -1)
RENDER_BACKEND.uniform4f(locs[base + LF_SPECULAR], light.specular.x, light.specular.y, light.specular.z, light.specular.w);
if (locs[base + LF_POSITION] != -1)
RENDER_BACKEND.uniform4f(locs[base + LF_POSITION], light.position.x, light.position.y, light.position.z, light.position.w);
if (locs[base + LF_HALF_VECTOR] != -1) {
// halfVector = normalize(normalize(position.xyz) + eye), eye=(0,0,1) for infinite viewer
float hx, hy, hz;
if (light.position.w == 0.0f) {
final float len = (float) Math.sqrt(light.position.x * light.position.x
+ light.position.y * light.position.y
+ light.position.z * light.position.z);
if (len > 0) {
hx = light.position.x / len;
hy = light.position.y / len;
hz = light.position.z / len + 1.0f;
} else {
hx = 0; hy = 0; hz = 1;
}
} else {
hx = 0; hy = 0; hz = 1;
}
final float hlen = (float) Math.sqrt(hx * hx + hy * hy + hz * hz);
if (hlen > 0) { hx /= hlen; hy /= hlen; hz /= hlen; }
RENDER_BACKEND.uniform4f(locs[base + LF_HALF_VECTOR], hx, hy, hz, 0.0f);
}
if (locs[base + LF_SPOT_DIRECTION] != -1) RENDER_BACKEND.uniform3f(locs[base + LF_SPOT_DIRECTION], light.spotDirection.x, light.spotDirection.y, light.spotDirection.z);
if (locs[base + LF_SPOT_EXPONENT] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_EXPONENT], light.spotExponent);
if (locs[base + LF_SPOT_CUTOFF] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_CUTOFF], light.spotCutoff);
if (locs[base + LF_SPOT_COS_CUTOFF] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_SPOT_COS_CUTOFF], light.spotCosCutoff);
if (locs[base + LF_CONSTANT_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_CONSTANT_ATTEN], light.constantAttenuation);
if (locs[base + LF_LINEAR_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_LINEAR_ATTEN], light.linearAttenuation);
if (locs[base + LF_QUADRATIC_ATTEN] != -1) RENDER_BACKEND.uniform1f(locs[base + LF_QUADRATIC_ATTEN], light.quadraticAttenuation);
}
}
private static void uploadMaterial(int[] locs) {
// Early exit: if the first material field isn't present, no material uniforms exist
if (locs[LOC_MAT_BASE] == -1) return;
final MaterialState mat = GLStateManager.getFrontMaterial();
if (locs[LOC_MAT_BASE + MF_EMISSION] != -1)
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_EMISSION], mat.emission.x, mat.emission.y, mat.emission.z, mat.emission.w);
if (locs[LOC_MAT_BASE + MF_AMBIENT] != -1)
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_AMBIENT], mat.ambient.x, mat.ambient.y, mat.ambient.z, mat.ambient.w);
if (locs[LOC_MAT_BASE + MF_DIFFUSE] != -1)
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_DIFFUSE], mat.diffuse.x, mat.diffuse.y, mat.diffuse.z, mat.diffuse.w);
if (locs[LOC_MAT_BASE + MF_SPECULAR] != -1)
RENDER_BACKEND.uniform4f(locs[LOC_MAT_BASE + MF_SPECULAR], mat.specular.x, mat.specular.y, mat.specular.z, mat.specular.w);
if (locs[LOC_MAT_BASE + MF_SHININESS] != -1)
RENDER_BACKEND.uniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
}
private static void uploadClipPlanes(int[] locs) {
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
}
if (locs[LOC_CLIP_PLANES] != -1) {
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
clipPlaneBuf.flip();
RENDER_BACKEND.uniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
}
}
public static void onDeleteProgram(int program) {
programLocations.remove(program);
}
public static boolean hasProgram(int program) {
return programLocations.containsKey(program);
}
public static int[] getLocations(int program) {
return programLocations.get(program);
}
}
@@ -0,0 +1,187 @@
package com.gtnewhorizons.angelica.glsm;
/*
* Ported from Mesa's util_idalloc (src/util/u_idalloc.c).
*
* Original copyright and license:
*
* Copyright 2017 Valve Corporation
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sub license, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Original author: Samuel Pitoiset <[email protected]>
*/
/**
* Bitmap ID allocator for display list IDs.
*
* <p>Bitmap-based: each bit in the {@code data} array represents one ID.
* Bit set = allocated, bit clear = free. ID 0 is reserved (never returned).
*/
public class DisplayListIDAllocator {
private int[] data;
private int numElements; // number of uint32 words allocated
private int numSetElements; // highest word index with any bit set + 1
private int lowestFreeIdx; // optimization: skip fully-allocated words
public DisplayListIDAllocator() {
this(32); // 1024 IDs initially
}
public DisplayListIDAllocator(int initialWords) {
if (initialWords < 1) initialWords = 1;
this.data = new int[initialWords];
this.numElements = initialWords;
this.numSetElements = 0;
this.lowestFreeIdx = 0;
// Reserve ID 0 (never returned), matching Mesa's skip_zero pattern
data[0] = 1;
numSetElements = 1;
}
public int allocRange(int count) {
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
if (count == 1) return allocSingle();
return allocMultiple(count);
}
private int allocSingle() {
for (int i = lowestFreeIdx; i < numElements; i++) {
if (data[i] == 0xFFFFFFFF) continue;
int bit = Integer.numberOfTrailingZeros(~data[i]);
data[i] |= 1 << bit;
lowestFreeIdx = i;
numSetElements = Math.max(numSetElements, i + 1);
return i * 32 + bit;
}
// No slots available, grow. Record old end, then mark first bit there.
int oldNumElements = numElements;
resize(Math.max(numElements, 1) * 2);
lowestFreeIdx = oldNumElements;
data[oldNumElements] |= 1;
numSetElements = Math.max(numSetElements, oldNumElements + 1);
return oldNumElements * 32;
}
private int allocMultiple(int num) {
int numWords = divRoundUp(num, 32);
int base = findFreeBlock(lowestFreeIdx);
boolean found = false;
while (!found) {
int i;
for (i = base; i < numElements && i - base < numWords && data[i] == 0; i++)
;
if (i - base == numWords) {
found = true;
} else if (i == numElements) {
// Hit end — base is valid: data[base..end] are zero, resize extends the run into newly zeroed words.
resize(numElements * 2 + numWords);
found = true;
} else {
// Non-zero word at i, skip past it
base = i + 1;
}
}
// Mark bits as used
int fullWords = num / 32;
int remainder = num % 32;
for (int i = base; i < base + fullWords; i++) {
data[i] = 0xFFFFFFFF;
}
if (remainder != 0) {
data[base + fullWords] |= (1 << remainder) - 1;
}
if (lowestFreeIdx == base) {
lowestFreeIdx = base + num / 32;
}
numSetElements = Math.max(numSetElements, base + numWords);
return base * 32;
}
private int findFreeBlock(int start) {
for (int i = start; i < numElements; i++) {
if (data[i] == 0) return i;
}
return numElements;
}
public void free(int id) {
if (id <= 0) return; // ID 0 is reserved
int idx = id / 32;
if (idx >= numElements) return;
lowestFreeIdx = Math.min(idx, lowestFreeIdx);
data[idx] &= ~(1 << (id % 32));
if (numSetElements == idx + 1) {
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
numSetElements--;
}
}
}
public void freeRange(int base, int count) {
if (count <= 0 || base <= 0) return;
int startIdx = base / 32;
if (startIdx >= numElements) return;
int end = base + count;
for (int id = base; id < end; id++) {
int idx = id / 32;
if (idx >= numElements) break;
data[idx] &= ~(1 << (id % 32));
}
lowestFreeIdx = Math.min(startIdx, lowestFreeIdx);
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
numSetElements--;
}
}
public boolean isAllocated(int id) {
if (id <= 0) return false;
int idx = id / 32;
if (idx >= numSetElements) return false;
return (data[idx] & (1 << (id % 32))) != 0;
}
private void resize(int newNumElements) {
if (newNumElements <= numElements) return;
int[] newData = new int[newNumElements];
System.arraycopy(data, 0, newData, 0, numElements);
data = newData;
numElements = newNumElements;
}
private static int divRoundUp(int a, int b) {
return (a + b - 1) / b;
}
}
File diff suppressed because it is too large. Load diff
@@ -2,14 +2,18 @@ package com.gtnewhorizons.angelica.glsm;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
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,45 +23,85 @@ 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
, GLStateManager.alphaState // Alpha test function and reference value
, GLStateManager.blendMode // GL_BLEND enable bit
, 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
// Logic op function
, GLStateManager.blendState // Blending source/destination functions, equation, blend color
, 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
, GLStateManager.logicOpMode // Logic op function
, GLStateManager.colorMask // Color-mode and index-mode writemasks
, GLStateManager.clearColor // Color-mode and index-mode clear values
));
attribToFeatures.put(GL11.GL_CURRENT_BIT, ImmutableSet.of(
GLStateManager.color // Current RGBA color
, ShaderManager.getNormalStack() // Current normal vector
, ShaderManager.getTexCoordStack() // Current texture coordinates
// Current color index
// Current normal vector
// Current texture coordinates
// Current raster position
// GL_CURRENT_RASTER_POSITION_VALID flag
// RGBA color associated with current raster position
@@ -74,14 +118,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 +136,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 +247,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 +274,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,15 +294,12 @@ 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
// GL_TEXTURE_ENV_MODE + GL_TEXTURE_ENV_COLOR — now per-unit in TexEnvState (added below)
// Enable bits for the four texture coordinates
// Border color for each texture image
@@ -249,26 +307,28 @@ public class Feature {
// Magnification function for each texture image
// Texture coordinates and wrap mode for each texture image
// Color and mode for each texture environment
// Enable bits GL_TEXTURE_GEN_x, x is S, T, R, and Q
// GL_TEXTURE_GEN_MODE setting for S, T, R, and Q
// glTexGen plane equations for S, T, R, and Q
// Current texture bindings (for example, GL_TEXTURE_BINDING_2D) - Below
));
// Current Texture Bindings - GL_TEXTURE_BINDING_2D
// Current Texture Bindings - GL_TEXTURE_BINDING_2D + per-unit TexEnvState
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
textureAttribs.add(GLStateManager.textures.getTextureUnitBindings(i));
// textureAttribs.add(GLStateManager.textures.getInfo(i))
textureAttribs.add(GLStateManager.textures.getTexEnvState(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,370 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
/**
* Software emulation of GL feedback mode
*/
public class FeedbackManager {
private static final int FB_3D = 0x01;
private static final int FB_4D = 0x02;
private static final int FB_COLOR = 0x04;
private static final int FB_TEXTURE = 0x08;
private static int renderMode = GL11.GL_RENDER;
private static FloatBuffer feedbackBuffer;
private static int feedbackBufferSize;
private static int feedbackMask;
private static int feedbackCount;
private static final Matrix4f mvpMatrix = new Matrix4f();
private static final Vector4f clipVec = new Vector4f();
private static ByteBuffer readbackBuf = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
private static ByteBuffer indexReadbackBuf = ByteBuffer.allocateDirect(4 * 1024).order(ByteOrder.nativeOrder());
@FunctionalInterface
private interface IndexReader {
int read(int i);
}
public static boolean isFeedbackMode() {
return renderMode == GL11.GL_FEEDBACK;
}
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
if (renderMode == GL11.GL_FEEDBACK) {
GLStateManager.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
return;
}
feedbackBuffer = buffer;
feedbackBufferSize = buffer.remaining();
feedbackMask = computeMask(type);
feedbackCount = 0;
}
public static int glRenderMode(int mode) {
int result = switch (renderMode) {
case GL11.GL_FEEDBACK -> (feedbackCount > feedbackBufferSize) ? -1 : feedbackCount;
case GL11.GL_RENDER, GL11.GL_SELECT -> 0;
default -> 0;
};
if (mode == GL11.GL_SELECT) {
GLStateManager.LOGGER.warn("glRenderMode(GL_SELECT): selection mode not emulated");
}
feedbackCount = 0;
renderMode = mode;
return result;
}
public static void glPassThrough(float token) {
if (renderMode == GL11.GL_FEEDBACK) {
feedbackToken(GL11.GL_PASS_THROUGH_TOKEN);
feedbackToken(token);
}
}
private static void feedbackToken(float token) {
if (feedbackCount < feedbackBufferSize) {
feedbackBuffer.put(feedbackBuffer.position() + feedbackCount, token);
}
feedbackCount++;
}
private static void feedbackVertex(float winX, float winY, float winZ, float winW) {
feedbackToken(winX);
feedbackToken(winY);
if ((feedbackMask & FB_3D) != 0) feedbackToken(winZ);
if ((feedbackMask & FB_4D) != 0) feedbackToken(winW);
if ((feedbackMask & FB_COLOR) != 0) {
feedbackToken(1f);
feedbackToken(1f);
feedbackToken(1f);
feedbackToken(1f);
}
if ((feedbackMask & FB_TEXTURE) != 0) {
feedbackToken(0f);
feedbackToken(0f);
feedbackToken(0f);
feedbackToken(1f);
}
}
private static void transformAndEmitVertex(float x, float y, float z) {
clipVec.set(x, y, z, 1.0f);
mvpMatrix.transform(clipVec);
final float w = clipVec.w;
final float ndcX = clipVec.x / w;
final float ndcY = clipVec.y / w;
final float ndcZ = clipVec.z / w;
final ViewportState vp = GLStateManager.getViewportState();
final float winX = vp.x + vp.width * (ndcX + 1.0f) * 0.5f;
final float winY = vp.y + vp.height * (ndcY + 1.0f) * 0.5f;
final float winZ = (float) ((vp.depthRangeFar - vp.depthRangeNear) * ndcZ + (vp.depthRangeFar + vp.depthRangeNear)) * 0.5f;
final float winW = 1.0f / w;
feedbackVertex(winX, winY, winZ, winW);
}
private static int positionStride() {
final int s = VertexAttribState.get(0).effectiveStride();
return (s != 0) ? s : 12;
}
private static long positionOffset() {
return VertexAttribState.get(0).offset;
}
public static void processDrawArrays(int mode, int first, int count) {
computeMVP();
final int stride = positionStride();
final long posOffset = positionOffset();
final int byteCount = count * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) first * stride + posOffset, readbackBuf);
emitAllPrimitives(mode, count, i -> i, stride);
}
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
computeMVP();
final int stride = positionStride();
final int elementSize = VertexAttribState.Attrib.glTypeSizeBytes(type);
final int indexByteCount = indexCount * elementSize;
ensureIndexReadbackCapacity(indexByteCount);
indexReadbackBuf.clear().limit(indexByteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboOffset, indexReadbackBuf);
final ByteBuffer indexBuf = indexReadbackBuf;
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < indexCount; i++) {
final int idx = ImmediateModeRecorder.readIndex(indexBuf, type, 0, i);
if (idx < minIdx) minIdx = idx;
if (idx > maxIdx) maxIdx = idx;
}
final long posOffset = positionOffset();
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
final int base = minIdx;
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
}
public static void processDrawElements(int mode, ByteBuffer indices) {
final int basePos = indices.position();
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFF);
}
public static void processDrawElements(int mode, ShortBuffer indices) {
final int basePos = indices.position();
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFFFF);
}
public static void processDrawElements(int mode, IntBuffer indices) {
final int basePos = indices.position();
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i));
}
public static void processDrawElements(int mode, int count, int type, ByteBuffer indices) {
final int pos = indices.position();
final IndexReader reader = switch (type) {
case GL11.GL_UNSIGNED_BYTE -> i -> indices.get(pos + i) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> i -> indices.getShort(pos + i * 2) & 0xFFFF;
case GL11.GL_UNSIGNED_INT -> i -> indices.getInt(pos + i * 4);
default -> i -> 0;
};
processDrawElementsImpl(mode, count, reader);
}
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
computeMVP();
final int stride = positionStride();
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < count; i++) {
final int idx = reader.read(i);
if (idx < minIdx) minIdx = idx;
if (idx > maxIdx) maxIdx = idx;
}
readVerticesFromVBO(minIdx, maxIdx, stride);
final int base = minIdx;
emitAllPrimitives(mode, count, i -> reader.read(i) - base, stride);
}
private static void computeMVP() {
mvpMatrix.set(GLStateManager.getProjectionMatrix()).mul(GLStateManager.getModelViewMatrix());
}
private static int computeMask(int type) {
return switch (type) {
case GL11.GL_2D -> 0;
case GL11.GL_3D -> FB_3D;
case GL11.GL_3D_COLOR -> FB_3D | FB_COLOR;
case GL11.GL_3D_COLOR_TEXTURE -> FB_3D | FB_COLOR | FB_TEXTURE;
case GL11.GL_4D_COLOR_TEXTURE -> FB_3D | FB_4D | FB_COLOR | FB_TEXTURE;
default -> 0;
};
}
private static void ensureReadbackCapacity(int bytes) {
if (readbackBuf.capacity() < bytes) {
readbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
}
}
private static void ensureIndexReadbackCapacity(int bytes) {
if (indexReadbackBuf.capacity() < bytes) {
indexReadbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
}
}
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
final long posOffset = positionOffset();
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
}
private static void emitVertex(int vertexIndex, int stride) {
final float x = readbackBuf.getFloat(vertexIndex * stride);
final float y = readbackBuf.getFloat(vertexIndex * stride + 4);
final float z = readbackBuf.getFloat(vertexIndex * stride + 8);
transformAndEmitVertex(x, y, z);
}
private static void emitAllPrimitives(int mode, int count, IndexReader reader, int stride) {
switch (mode) {
case GL11.GL_POINTS:
for (int i = 0; i < count; i++) {
feedbackToken(GL11.GL_POINT_TOKEN);
emitVertex(reader.read(i), stride);
}
break;
case GL11.GL_LINES:
for (int i = 0; i + 1 < count; i += 2) {
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
break;
case GL11.GL_LINE_STRIP:
for (int i = 0; i + 1 < count; i++) {
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
break;
case GL11.GL_LINE_LOOP:
for (int i = 0; i + 1 < count; i++) {
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
if (count > 2) {
feedbackToken(GL11.GL_LINE_TOKEN);
emitVertex(reader.read(count - 1), stride);
emitVertex(reader.read(0), stride);
}
break;
case GL11.GL_TRIANGLES:
for (int i = 0; i + 2 < count; i += 3) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(3);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i + 2), stride);
}
break;
case GL11.GL_TRIANGLE_STRIP:
for (int i = 0; i + 2 < count; i++) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(3);
if ((i & 1) == 0) {
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
} else {
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i), stride);
}
emitVertex(reader.read(i + 2), stride);
}
break;
case GL11.GL_TRIANGLE_FAN:
for (int i = 1; i + 1 < count; i++) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(3);
emitVertex(reader.read(0), stride);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
break;
case GL11.GL_QUADS:
for (int i = 0; i + 3 < count; i += 4) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(4);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i + 2), stride);
emitVertex(reader.read(i + 3), stride);
}
break;
case GL11.GL_POLYGON:
if (count >= 3) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(count);
for (int i = 0; i < count; i++) {
emitVertex(reader.read(i), stride);
}
}
break;
default:
throw new UnsupportedOperationException("Feedback mode: unsupported primitive mode 0x" + Integer.toHexString(mode));
}
}
static void reset() {
renderMode = GL11.GL_RENDER;
feedbackBuffer = null;
feedbackBufferSize = 0;
feedbackMask = 0;
feedbackCount = 0;
}
}
@@ -0,0 +1,600 @@
/*
* Copyright LWJGL. All rights reserved. Modified by IMS for use in Iris (net.coderbot.iris.gl).
* License terms: https://www.lwjgl.org/license
*/
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
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;
public final class GLDebug {
/**
* Sets up debug callbacks
*
* @return 0 for failure, 1 for success, 2 for restart required.
*/
public static int setupDebugMessageCallback() {
if (Thread.currentThread() != GLStateManager.getMainThread()) {
GLStateManager.LOGGER.warn("setupDebugMessageCallback called from non-main thread!");
return 0;
}
return setupDebugMessageCallbackImpl();
}
public static Throwable filterStackTrace(Throwable throwable, int offset) {
final StackTraceElement[] elems = throwable.getStackTrace();
final StackTraceElement[] filtered = new StackTraceElement[elems.length];
int j = 0;
for (int i = offset; i < elems.length; i++) {
String className = elems[i].getClassName();
if (className == null) {
className = "";
}
filtered[j++] = elems[i];
}
final StackTraceElement[] newElems = new StackTraceElement[j];
System.arraycopy(filtered, 0, newElems, 0, j);
throwable.setStackTrace(newElems);
return throwable;
}
private static String buildStackTrace() {
final StackTraceElement[] elems = filterStackTrace(new Throwable(), 4).getStackTrace();
final StringBuilder sb = new StringBuilder();
for (StackTraceElement elem : elems) {
sb.append("\n\t").append(elem.toString());
}
return sb.toString();
}
private static void logDebugMessage(int id, String source, String type, String severity, String message) {
final String fullMessage = String.format("[GL] %s %s (0x%X) from %s: %s%s", severity, type, id, source, message, buildStackTrace());
if ("HIGH".equals(severity)) {
GLStateManager.LOGGER.error(fullMessage);
} else if ("MEDIUM".equals(severity)) {
GLStateManager.LOGGER.warn(fullMessage);
} else {
GLStateManager.LOGGER.info(fullMessage);
}
}
private static int setupDebugMessageCallbackImpl() {
return BackendManager.RENDER_BACKEND.setupDebugOutput(
(source, type, id, severity, message) ->
logDebugMessage(id, getDebugSource(source), getDebugType(type), getDebugSeverity(severity), message)
);
}
public static int disableDebugMessages() {
return BackendManager.RENDER_BACKEND.disableDebugOutput();
}
private static String getDebugSource(int source) {
return switch (source) {
case GL43.GL_DEBUG_SOURCE_API -> "API";
case GL43.GL_DEBUG_SOURCE_WINDOW_SYSTEM -> "WINDOW SYSTEM";
case GL43.GL_DEBUG_SOURCE_SHADER_COMPILER -> "SHADER COMPILER";
case GL43.GL_DEBUG_SOURCE_THIRD_PARTY -> "THIRD PARTY";
case GL43.GL_DEBUG_SOURCE_APPLICATION -> "APPLICATION";
case GL43.GL_DEBUG_SOURCE_OTHER -> "OTHER";
default -> String.format("Unknown [0x%X]", source);
};
}
private static String getDebugType(int type) {
return switch (type) {
case GL43.GL_DEBUG_TYPE_ERROR -> "ERROR";
case GL43.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR -> "DEPRECATED BEHAVIOR";
case GL43.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR -> "UNDEFINED BEHAVIOR";
case GL43.GL_DEBUG_TYPE_PORTABILITY -> "PORTABILITY";
case GL43.GL_DEBUG_TYPE_PERFORMANCE -> "PERFORMANCE";
case GL43.GL_DEBUG_TYPE_OTHER -> "OTHER";
case GL43.GL_DEBUG_TYPE_MARKER -> "MARKER";
default -> String.format("Unknown [0x%X]", type);
};
}
private static String getDebugSeverity(int severity) {
return switch (severity) {
case GL43.GL_DEBUG_SEVERITY_NOTIFICATION -> "NOTIFICATION";
case GL43.GL_DEBUG_SEVERITY_HIGH -> "HIGH";
case GL43.GL_DEBUG_SEVERITY_MEDIUM -> "MEDIUM";
case GL43.GL_DEBUG_SEVERITY_LOW -> "LOW";
default -> String.format("Unknown [0x%X]", severity);
};
}
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);
};
}
public static String getFramebufferStatusName(int status) {
return switch (status) {
case GL30.GL_FRAMEBUFFER_COMPLETE -> "COMPLETE";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT -> "INCOMPLETE_ATTACHMENT";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT -> "INCOMPLETE_MISSING_ATTACHMENT";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER -> "INCOMPLETE_DRAW_BUFFER";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER -> "INCOMPLETE_READ_BUFFER";
case GL30.GL_FRAMEBUFFER_UNSUPPORTED -> "UNSUPPORTED";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE -> "INCOMPLETE_MULTISAMPLE";
case 0 -> "NO_FRAMEBUFFER_BOUND";
default -> String.format("UNKNOWN(0x%X)", status);
};
}
public static String getInternalFormatName(int format) {
return switch (format) {
// Basic
case GL11.GL_RGBA -> "RGBA";
case GL11.GL_RGB -> "RGB";
// 8-bit normalized
case GL30.GL_R8 -> "R8";
case GL30.GL_RG8 -> "RG8";
case GL11.GL_RGB8 -> "RGB8";
case GL11.GL_RGBA8 -> "RGBA8";
// 8-bit signed normalized
case 0x8F94 -> "R8_SNORM";
case 0x8F95 -> "RG8_SNORM";
case 0x8F96 -> "RGB8_SNORM";
case 0x8F97 -> "RGBA8_SNORM";
// 16-bit normalized
case GL30.GL_R16 -> "R16";
case GL30.GL_RG16 -> "RG16";
case GL11.GL_RGB16 -> "RGB16";
case GL11.GL_RGBA16 -> "RGBA16";
// 16-bit float
case GL30.GL_R16F -> "R16F";
case GL30.GL_RG16F -> "RG16F";
case GL30.GL_RGB16F -> "RGB16F";
case GL30.GL_RGBA16F -> "RGBA16F";
// 32-bit float
case GL30.GL_R32F -> "R32F";
case GL30.GL_RG32F -> "RG32F";
case GL30.GL_RGB32F -> "RGB32F";
case GL30.GL_RGBA32F -> "RGBA32F";
// Packed
case GL30.GL_R11F_G11F_B10F -> "R11F_G11F_B10F";
case GL11.GL_RGB10_A2 -> "RGB10_A2";
default -> String.format("0x%X", format);
};
}
private static DebugState debugState;
private interface DebugState {
void nameObject(int id, int object, String name);
void pushGroup(String name);
void popGroup();
void debugMessage(String name);
String getObjectLabel(int glProgram, int program);
}
private static class KHRDebugState implements DebugState {
private static final int ID = 0;
private int depth = 0;
private static final int maxDepth = BackendManager.RENDER_BACKEND.getInteger(GL43.GL_MAX_DEBUG_GROUP_STACK_DEPTH);
private final String[] groupStack = new String[maxDepth + 1];
@Override
public void nameObject(int id, int object, String name) {
BackendManager.RENDER_BACKEND.objectLabel(id, object, name);
}
@Override
public void pushGroup(String name) {
if (depth + 1 > maxDepth) {
final StringBuilder sb = new StringBuilder();
sb.append("Debug group stack overflow (maxDepth=").append(maxDepth).append("), current stack:\n");
for (int i = 0; i < depth; i++) {
sb.append(" [").append(i).append("] ").append(groupStack[i]).append('\n');
}
sb.append(" -> ").append(name).append(" (rejected)");
throw new RuntimeException(sb.toString());
}
groupStack[depth] = name;
BackendManager.RENDER_BACKEND.pushDebugGroup(GL43.GL_DEBUG_SOURCE_APPLICATION, ID, name);
depth++;
}
@Override
public void popGroup() {
if (depth - 1 < 0) {
throw new RuntimeException("Stack underflow");
}
BackendManager.RENDER_BACKEND.popDebugGroup();
depth--;
groupStack[depth] = null;
}
@Override
public void debugMessage(String message) {
BackendManager.RENDER_BACKEND.debugMessageInsert(
GL43.GL_DEBUG_SOURCE_APPLICATION,
GL43.GL_DEBUG_TYPE_MARKER,
ID,
GL43.GL_DEBUG_SEVERITY_NOTIFICATION,
message);
}
@Override
public String getObjectLabel(int glProgram, int program) {
if (program == 0) return "";
return BackendManager.RENDER_BACKEND.getObjectLabel(glProgram, program);
}
}
private static class UnsupportedDebugState implements DebugState {
@Override
public void nameObject(int id, int object, String name) {
}
@Override
public void pushGroup(String name) {
}
@Override
public void popGroup() {
}
@Override
public void debugMessage(String name) {
}
@Override
public String getObjectLabel(int glProgram, int program) {
return "";
}
}
public static void initDebugState() {
if (BackendManager.RENDER_BACKEND.supportsDebugOutput()) {
debugState = new KHRDebugState();
} else {
debugState = new UnsupportedDebugState();
}
}
public static void nameObject(int id, int object, String name) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.nameObject(id, object, name);
}
}
public static void pushGroup(String group) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.pushGroup(group);
}
}
public static void popGroup() {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.popGroup();
}
}
public static void debugMessage(String message) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.debugMessage(message);
}
}
public static String getObjectLabel(int glProgram, int program) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
return debugState.getObjectLabel(glProgram, program);
}
return "";
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,119 @@
package com.gtnewhorizons.angelica.glsm;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Shared GLSL shader transformation utilities.
*/
public class GlslTransformUtils {
private static final String RENAMED_PREFIX = "angelica_renamed_";
/** Texture function renames */
public static final Map<String, String> TEXTURE_RENAMES = Map.ofEntries(
Map.entry("texture2D", "texture"),
Map.entry("texture3D", "texture"),
Map.entry("texture2DLod", "textureLod"),
Map.entry("texture3DLod", "textureLod"),
Map.entry("texture2DProj", "textureProj"),
Map.entry("texture3DProj", "textureProj"),
Map.entry("texture2DGrad", "textureGrad"),
Map.entry("texture2DGradARB", "textureGrad"),
Map.entry("texture3DGrad", "textureGrad"),
Map.entry("texelFetch2D", "texelFetch"),
Map.entry("texelFetch3D", "texelFetch"),
Map.entry("textureSize2D", "textureSize")
);
private static final Pattern TEXTURE_PATTERN = Pattern.compile("\\btexture\\s*\\(|(\\btexture\\b)");
/** Reserved words added in later GLSL versions that may appear as identifiers in older shaders. */
private record ReservedWordRename(Pattern pattern, String replacement) {}
private static final Map<Integer, List<ReservedWordRename>> VERSIONED_RESERVED_WORDS = Map.of(
// Always rename sample, glsl-transformation-lib doesn't like it
0, List.of(
new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample")
),
400, List.of(
new ReservedWordRename(Pattern.compile("\\bsampler\\b(?!\\d)"), RENAMED_PREFIX + "sampler")
)
);
public static String replaceTexture(String input) {
final Matcher matcher = TEXTURE_PATTERN.matcher(input);
final StringBuilder builder = new StringBuilder();
while (matcher.find()) {
if (matcher.group(1) != null) {
matcher.appendReplacement(builder, RENAMED_PREFIX + "texture");
} else {
matcher.appendReplacement(builder, Matcher.quoteReplacement(matcher.group(0)));
}
}
matcher.appendTail(builder);
return builder.toString();
}
public static String renameReservedWords(String source, int targetVersion) {
for (var entry : VERSIONED_RESERVED_WORDS.entrySet()) {
// Rename identifiers that become reserved words at or above this GLSL version
if (targetVersion >= entry.getKey()) {
for (var rename : entry.getValue()) {
source = rename.pattern().matcher(source).replaceAll(rename.replacement());
}
}
}
return source;
}
public static String restoreReservedWords(String source) {
source = source.replace(RENAMED_PREFIX + "texture", "texture");
source = source.replace(RENAMED_PREFIX + "sampler", "sampler");
source = source.replace(RENAMED_PREFIX + "sample", "sample");
return source;
}
public static String getFormattedShader(ParseTree tree, String header) {
final StringBuilder sb = new StringBuilder(header + "\n");
final String[] tabHolder = {""};
getFormattedShader(tree, sb, tabHolder);
return sb.toString();
}
private static void getFormattedShader(ParseTree tree, StringBuilder stringBuilder, String[] tabHolder) {
if (tree instanceof TerminalNode) {
final String text = tree.getText();
if (text.equals("<EOF>")) {
return;
}
if (text.equals("#")) {
stringBuilder.append("\n#");
return;
}
stringBuilder.append(text);
if (text.equals("{")) {
stringBuilder.append(" \n\t");
tabHolder[0] = "\t";
}
if (text.equals("}")) {
if (stringBuilder.length() >= 2) {
stringBuilder.deleteCharAt(stringBuilder.length() - 2);
}
tabHolder[0] = "";
stringBuilder.append(" \n");
} else {
stringBuilder.append(text.equals(";") ? " \n" + tabHolder[0] : " ");
}
} else {
for (int i = 0; i < tree.getChildCount(); ++i) {
getFormattedShader(tree.getChild(i), stringBuilder, tabHolder);
}
}
}
}
@@ -0,0 +1,295 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutShort;
/**
* Shared quad-to-triangle EBO for core profile GL_QUADS emulation.
*
* <p>Maintains a single growable element buffer with pre-computed indices mapping
* GL_QUADS vertex order to GL_TRIANGLES: {@code (0,1,3, 1,2,3)} per quad.
* Uses {@code GL_LAST_VERTEX_CONVENTION} winding (Mesa {@code do_quad} LAST path)
* so both emitted triangles share the quad's provoking vertex (v3).
*/
public final class QuadConverter {
public static final int INDEX_TYPE = GL11.GL_UNSIGNED_INT;
private static int eboId;
private static int maxQuads;
/** Scratch EBO for converted quad-element draws (drawQuadElementsAsTriangles). */
private static int scratchEboId;
private static int scratchEboCapacity; // in bytes
private QuadConverter() {}
private static void ensureCapacity(int quadCount) {
if (quadCount <= maxQuads) return;
// Round up to power of 2 for amortized growth
int newMaxQuads = Math.max(256, maxQuads);
while (newMaxQuads < quadCount) {
newMaxQuads *= 2;
}
if (eboId == 0) {
eboId = RENDER_BACKEND.genBuffers();
}
// Generate index data: 6 ints per quad
final ByteBuffer indexData = memAlloc(newMaxQuads * 6 * 4);
long ptr = memAddress0(indexData);
for (int i = 0; i < newMaxQuads; i++) {
final int base = i * 4;
// GL_LAST_VERTEX_CONVENTION: both triangles share v3 as provoking vertex
// Triangle 1: 0, 1, 3
memPutInt(ptr, base);
memPutInt(ptr + 4, base + 1);
memPutInt(ptr + 8, base + 3);
// Triangle 2: 1, 2, 3
memPutInt(ptr + 12, base + 1);
memPutInt(ptr + 16, base + 2);
memPutInt(ptr + 20, base + 3);
ptr += 24;
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);
memFree(indexData);
maxQuads = newMaxQuads;
}
/**
* Convert a GL_QUADS glDrawArrays call to indexed GL_TRIANGLES.
* Binds the shared EBO to the current VAO and issues glDrawElements.
*
* @param first first vertex index (must be aligned to quad boundary, i.e. multiple of 4)
* @param vertexCount number of vertices (must be multiple of 4)
*/
public static void drawQuadsAsTriangles(int first, int vertexCount) {
assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4";
assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4";
final int quadCount = vertexCount / 4;
final int prevEbo = GLStateManager.getBoundEBO();
ensureCapacity(first / 4 + quadCount);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
final long indexOffset = (long) (first / 4) * 6 * 4;
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
}
/**
* Upload converted triangle indices to the scratch EBO and draw.
* Saves/restores the caller's EBO binding so subsequent indexed draws
* (or glGetBufferSubData reads) still see the original EBO.
*
* @param dst off-heap buffer with triangle index data (freed by this method)
* @param triIndexCount number of triangle indices
* @param indexType GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
* @param bytesPerIndex bytes per index element
*/
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = GLStateManager.getBoundEBO();
if (scratchEboId == 0) {
scratchEboId = RENDER_BACKEND.genBuffers();
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
if (needed > scratchEboCapacity) {
// Power-of-2 growth — allocate full capacity, upload actual data
int newCap = Math.max(4096, scratchEboCapacity);
while (newCap < needed) newCap *= 2;
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
scratchEboCapacity = newCap;
}
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
memFree(dst);
}
/**
* Convert an indexed GL_QUADS draw to GL_TRIANGLES by reading the source EBO,
* building a triangle index buffer, and issuing the converted draw.
*
* @param indexCount number of quad indices (must be multiple of 4)
* @param type GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, or GL_UNSIGNED_BYTE
* @param offset byte offset into the currently bound EBO
*/
public static void drawQuadElementsAsTriangles(int indexCount, int type, long offset) {
if (indexCount == 0) return;
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final int bytesPerIndex = VertexAttribState.Attrib.glTypeSizeBytes(type);
// Read source indices from caller's EBO
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
final long srcAddr = memAddress0(src);
// Allocate output as GL_UNSIGNED_INT
final ByteBuffer dst = memAlloc(triIndexCount * 4);
final long dstAddr = memAddress0(dst);
for (int i = 0; i < quadCount; i++) {
final int a, b, c, d;
switch (type) {
case GL11.GL_UNSIGNED_INT -> {
final long p = srcAddr + (long) i * 16;
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
}
case GL11.GL_UNSIGNED_SHORT -> {
final long p = srcAddr + (long) i * 8;
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
}
default /* GL_UNSIGNED_BYTE */ -> {
final long p = srcAddr + (long) i * 4;
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
}
}
// GL_LAST_VERTEX_CONVENTION: (a,b,d), (b,c,d)
final long o = (long) i * 24;
memPutInt(dstAddr + o, a);
memPutInt(dstAddr + o + 4, b);
memPutInt(dstAddr + o + 8, d);
memPutInt(dstAddr + o + 12, b);
memPutInt(dstAddr + o + 16, c);
memPutInt(dstAddr + o + 20, d);
}
memFree(src);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Convert a client-side IntBuffer of quad indices to triangles.
*/
public static void drawQuadElementsAsTriangles(IntBuffer indices) {
final int indexCount = indices.remaining();
if (indexCount == 0) return;
assert indexCount % 4 == 0;
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final ByteBuffer dst = memAlloc(triIndexCount * 4);
final long dstAddr = memAddress0(dst);
final int basePos = indices.position();
for (int i = 0; i < quadCount; i++) {
final int base = basePos + i * 4;
final int a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
final long o = (long) i * 24;
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
}
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Convert a client-side ShortBuffer of quad indices to triangles.
*/
public static void drawQuadElementsAsTriangles(ShortBuffer indices) {
final int indexCount = indices.remaining();
if (indexCount == 0) return;
assert indexCount % 4 == 0;
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final ByteBuffer dst = memAlloc(triIndexCount * 2);
final long dstAddr = memAddress0(dst);
final int basePos = indices.position();
for (int i = 0; i < quadCount; i++) {
final int base = basePos + i * 4;
final short a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
final long o = (long) i * 12;
memPutShort(dstAddr + o, a); memPutShort(dstAddr + o + 2, b); memPutShort(dstAddr + o + 4, d);
memPutShort(dstAddr + o + 6, b); memPutShort(dstAddr + o + 8, c); memPutShort(dstAddr + o + 10, d);
}
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
}
/**
* Convert a client-side ByteBuffer of quad indices (type-agnostic) to triangles.
*/
public static void drawQuadElementsAsTriangles(int count, int type, ByteBuffer indices) {
if (count == 0) return;
assert count % 4 == 0;
final int quadCount = count / 4;
final int triIndexCount = quadCount * 6;
final long srcAddr = memAddress0(indices) + indices.position();
final ByteBuffer dst = memAlloc(triIndexCount * 4);
final long dstAddr = memAddress0(dst);
for (int i = 0; i < quadCount; i++) {
final int a, b, c, d;
switch (type) {
case GL11.GL_UNSIGNED_INT -> {
final long p = srcAddr + (long) i * 16;
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
}
case GL11.GL_UNSIGNED_SHORT -> {
final long p = srcAddr + (long) i * 8;
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
}
case GL11.GL_UNSIGNED_BYTE -> {
final long p = srcAddr + (long) i * 4;
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
}
default -> { memFree(dst); return; }
}
final long o = (long) i * 24;
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
}
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Clean up the shared EBO.
*/
public static void destroy() {
if (eboId != 0) {
RENDER_BACKEND.deleteBuffers(eboId);
eboId = 0;
maxQuads = 0;
}
if (scratchEboId != 0) {
RENDER_BACKEND.deleteBuffers(scratchEboId);
scratchEboId = 0;
scratchEboCapacity = 0;
}
}
}
@@ -0,0 +1,531 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
import com.gtnewhorizons.angelica.glsm.dsa.DSACore;
import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Vector3i;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBShaderImageLoadStore;
import org.lwjgl.opengl.ARBShaderStorageBufferObject;
import org.lwjgl.opengl.EXTShaderImageLoadStore;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.NVXGpuMemoryInfo;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* This class is responsible for abstracting calls to OpenGL and asserting that calls are run on the render thread.
*/
public class RenderSystem {
private static DSAAccess dsaState;
private static boolean supportsCompute;
private static boolean supportsImageLoadStore;
private static boolean supportsSSBO;
private static boolean supportsBufferStorage;
private static boolean supportsClearTexture;
private static boolean supportsTesselation;
private static boolean supportsSamplerObjects;
private static int maxImageUnits;
private static int maxSSBOBindings;
private static int maxGlslVersion;
private static boolean supportsGpuShader4;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
private static final Pattern SEMVER_PATTERN = Pattern.compile("(?<major>\\d+)\\.(?<minor>\\d+)\\.*(?<bugfix>\\d*)(.*)");
private RenderSystem() {}
private static boolean rendererInitialized;
public static void initRenderer() {
if (rendererInitialized) return;
rendererInitialized = true;
try {
if (GLStateManager.capabilities.OpenGL45) {
dsaState = (Runtime.version().feature() > 8 && GLStateManager.capabilities.GL_EXT_direct_state_access) ? new DSAEXT() : new DSACore();
GLStateManager.LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
}
} catch (NoSuchFieldError ignored) {
}
try {
if (dsaState == null && GLStateManager.capabilities.GL_ARB_direct_state_access) {
dsaState = new DSAARB();
GLStateManager.LOGGER.info("ARB_direct_state_access detected, enabling DSA.");
}
} catch (NoSuchFieldError ignored) {
}
if (dsaState == null) {
dsaState = new DSAUnsupported();
GLStateManager.LOGGER.info("No DSA support detected, falling back to legacy OpenGL.");
}
BackendManager.init();
supportsCompute = supportsCompute();
supportsTesselation = GLStateManager.capabilities.GL_ARB_tessellation_shader || GLStateManager.capabilities.OpenGL40;
supportsImageLoadStore = GLStateManager.capabilities.OpenGL42
|| GLStateManager.capabilities.GL_ARB_shader_image_load_store
|| GLStateManager.capabilities.GL_EXT_shader_image_load_store;
supportsSSBO = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_shader_storage_buffer_object;
supportsBufferStorage = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage;
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
// Cache maximum image units
if (supportsImageLoadStore) {
if (GLStateManager.capabilities.OpenGL42) {
maxImageUnits = RENDER_BACKEND.getInteger(GL42.GL_MAX_IMAGE_UNITS);
} else if (GLStateManager.capabilities.GL_ARB_shader_image_load_store) {
maxImageUnits = RENDER_BACKEND.getInteger(ARBShaderImageLoadStore.GL_MAX_IMAGE_UNITS);
} else {
maxImageUnits = RENDER_BACKEND.getInteger(EXTShaderImageLoadStore.GL_MAX_IMAGE_UNITS_EXT);
}
} else {
maxImageUnits = 0;
}
// Cache maximum SSBO bindings
if (supportsSSBO) {
if (GLStateManager.capabilities.OpenGL43) {
maxSSBOBindings = RENDER_BACKEND.getInteger(GL43.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
} else {
maxSSBOBindings = RENDER_BACKEND.getInteger(ARBShaderStorageBufferObject.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
}
} else {
maxSSBOBindings = 0;
}
// Check for sampler objects support (GL 3.3+ or ARB extension)
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
if (supportsSamplerObjects) {
samplers = new int[RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)];
}
maxGlslVersion = Integer.parseInt(parseGlVersionString(RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION)));
supportsGpuShader4 = GLStateManager.capabilities.GL_EXT_gpu_shader4;
GLStateManager.LOGGER.info("Max GLSL version: {}, GPU Shader4: {}", maxGlslVersion, supportsGpuShader4);
GLStateManager.LOGGER.info("Image Load/Store: {}, Max Image Units: {}", supportsImageLoadStore, maxImageUnits);
GLStateManager.LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings);
GLStateManager.LOGGER.info("Buffer Storage: {}, Clear Texture: {}, Sampler Objects: {}", supportsBufferStorage, supportsClearTexture, supportsSamplerObjects);
if (GLStateManager.capabilities.OpenGL32) {
final int profileMask = RENDER_BACKEND.getInteger(GL32.GL_CONTEXT_PROFILE_MASK);
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
GLStateManager.LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
ShaderManager.getInstance().enable();
}
}
}
public static void generateMipmaps(int texture, int mipmapTarget) {
dsaState.generateMipmaps(texture, mipmapTarget);
}
public static void bindAttributeLocation(int program, int index, CharSequence name) {
RENDER_BACKEND.bindAttribLocation(program, index, name);
}
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
GLStateManager.glBindTexture(target, texture);
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
if (target == GL11.GL_TEXTURE_2D && level == 0) {
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
}
}
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
RENDER_BACKEND.uniformMatrix4(location, transpose, matrix);
}
public static void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {
GLStateManager.glCopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
}
public static void uniform1f(int location, float v0) {
RENDER_BACKEND.uniform1f(location, v0);
}
public static void uniform1i(int location, int v0) {
RENDER_BACKEND.uniform1i(location, v0);
}
public static void uniform2f(int location, float v0, float v1) {
RENDER_BACKEND.uniform2f(location, v0, v1);
}
public static void uniform2i(int location, int v0, int v1) {
RENDER_BACKEND.uniform2i(location, v0, v1);
}
public static void uniform3f(int location, float v0, float v1, float v2) {
RENDER_BACKEND.uniform3f(location, v0, v1, v2);
}
public static void uniform3i(int location, int v0, int v1, int v2) {RENDER_BACKEND.uniform3i(location, v0, v1, v2);}
public static void uniform4f(int location, float v0, float v1, float v2, float v3) {
RENDER_BACKEND.uniform4f(location, v0, v1, v2, v3);
}
public static void uniform4i(int location, int v0, int v1, int v2, int v3) {
RENDER_BACKEND.uniform4i(location, v0, v1, v2, v3);
}
public static int getAttribLocation(int programId, String name) {
return RENDER_BACKEND.getAttribLocation(programId, name);
}
public static int getUniformLocation(int programId, String name) {
return RENDER_BACKEND.getUniformLocation(programId, name);
}
public static void texParameteriv(int texture, int target, int pname, IntBuffer params) {
dsaState.texParameteriv(texture, target, pname, params);
}
public static void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
dsaState.copyTexSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
}
public static void texParameteri(int texture, int target, int pname, int param) {
dsaState.texParameteri(texture, target, pname, param);
}
public static void texParameterf(int texture, int target, int pname, float param) {
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 void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
}
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
}
public static String getProgramInfoLog(int program) {
return RENDER_BACKEND.getProgramInfoLog(program, RENDER_BACKEND.getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
}
public static String getShaderInfoLog(int shader) {
return RENDER_BACKEND.getShaderInfoLog(shader, RENDER_BACKEND.getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
}
public static void drawBuffers(int framebuffer, IntBuffer buffers) {
dsaState.drawBuffers(framebuffer, buffers);
}
public static void readBuffer(int framebuffer, int buffer) {
dsaState.readBuffer(framebuffer, buffer);
}
public static String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType) {
return RENDER_BACKEND.getActiveUniform(program, index, maxLength, sizeType);
}
public static void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
RENDER_BACKEND.readPixels(x, y, width, height, format, type, pixels);
}
public static void bufferData(int target, FloatBuffer data, int usage) {
RENDER_BACKEND.bufferData(target, data, usage);
}
public static int bufferStorage(int target, FloatBuffer data, int usage) {
return dsaState.bufferStorage(target, data, usage);
}
public static void vertexAttrib4f(int index, float v0, float v1, float v2, float v3) {
RENDER_BACKEND.vertexAttrib4f(index, v0, v1, v2, v3);
}
public static void detachShader(int program, int shader) {
RENDER_BACKEND.detachShader(program, shader);
}
public static void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
dsaState.framebufferTexture2D(fb, fbtarget, attachment, target, texture, levels);
}
public static int getTexParameteri(int texture, int target, int pname) {
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) {
RENDER_BACKEND.bindImageTexture(unit, texture, level, layered, layer, access, format);
}
public static int getMaxImageUnits() {
return maxImageUnits;
}
public static boolean supportsImageLoadStore() {
return supportsImageLoadStore;
}
public static boolean supportsSSBO() {
return supportsSSBO;
}
public static boolean supportsBufferStorage() {
return supportsBufferStorage;
}
public static boolean supportsClearTexture() {
return supportsClearTexture;
}
public static int getMaxSSBOBindings() {
return maxSSBOBindings;
}
public static void getProgramiv(int program, int value, IntBuffer storage) {
RENDER_BACKEND.getProgramiv(program, value, storage);
}
public static void dispatchCompute(int workX, int workY, int workZ) {
RENDER_BACKEND.dispatchCompute(workX, workY, workZ);
}
public static void dispatchCompute(Vector3i workGroups) {
RENDER_BACKEND.dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
}
public static void dispatchComputeIndirect(long offset) {
RENDER_BACKEND.dispatchComputeIndirect(offset);
}
public static void bindBuffer(int target, int buffer) {
GLStateManager.glBindBuffer(target, buffer);
}
public static void memoryBarrier(int barriers) {
if (supportsCompute) {
RENDER_BACKEND.memoryBarrier(barriers);
}
}
public static boolean supportsBufferBlending() {
return GLStateManager.capabilities.GL_ARB_draw_buffers_blend || GLStateManager.capabilities.OpenGL40;
}
public static void disableBufferBlend(int buffer) {
RENDER_BACKEND.disablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
}
public static void enableBufferBlend(int buffer) {
RENDER_BACKEND.enablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
}
public static void blendFuncSeparatei(int buffer, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
RENDER_BACKEND.blendFuncSeparatei(buffer, srcRGB, dstRGB, srcAlpha, dstAlpha);
}
public static void bindTextureToUnit(int unit, int texture) {
dsaState.bindTextureToUnit(unit, texture);
}
public static void bindTextureToUnit(int target, int unit, int texture) {
dsaState.bindTextureToUnit(target, unit, texture);
}
public static final FloatBuffer PROJECTION_MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
public static void setupProjectionMatrix(Matrix4f matrix) {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPushMatrix();
matrix.get(0, PROJECTION_MATRIX_BUFFER);
GLStateManager.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
public static void restoreProjectionMatrix() {
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) {
dsaState.blitFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
}
public static int createFramebuffer() {
return dsaState.createFramebuffer();
}
public static int createTexture(int target) {
return dsaState.createTexture(target);
}
// TODO: Proper notification of compute support
public static boolean supportsCompute() {
try {
return GLStateManager.capabilities.GL_ARB_compute_shader;
} catch (Exception ignored) {
return false;
}
}
public static int createBuffers() {
return RENDER_BACKEND.genBuffers();
}
public static void bindBufferBase(int target, int index, int buffer) {
RENDER_BACKEND.bindBufferBase(target, index, buffer);
}
public static void bufferStorage(int target, long size, int flags) {
RENDER_BACKEND.bufferStorage(target, size, flags);
}
public static void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, int[] data) {
final ByteBuffer buf = BufferUtils.createByteBuffer(data.length * 4);
buf.asIntBuffer().put(data);
RENDER_BACKEND.clearBufferSubData(target, internalFormat, offset, size, format, type, buf);
}
public static void deleteBuffers(int buffer) {
RENDER_BACKEND.deleteBuffers(buffer);
}
public static long getVRAM() {
if (GLStateManager.capabilities.GL_NVX_gpu_memory_info) {
return RENDER_BACKEND.getInteger(NVXGpuMemoryInfo.GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX) * 1024L;
} else {
// Default to 4GB if we can't query VRAM
return 4294967296L;
}
}
public static void clearTexImage(int texture, int target, int level, int format, int type) {
RENDER_BACKEND.clearTexImage(texture, level, format, type);
}
public static void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
dsaState.textureStorage1D(texture, target, levels, internalFormat, width);
}
public static void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
dsaState.textureStorage2D(texture, target, levels, internalFormat, width, height);
}
public static void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
dsaState.textureStorage3D(texture, target, levels, internalFormat, width, height, depth);
}
public static int getMaxGlslVersion() {
return maxGlslVersion;
}
public static boolean supportsGpuShader4() {
return supportsGpuShader4;
}
public static boolean supportsSnormFormats() {
return GLStateManager.capabilities.OpenGL31;
}
public static boolean supportsPackedFloatRenderable() {
return GLStateManager.capabilities.OpenGL30 && maxGlslVersion >= 130;
}
public static boolean supportsTesselation() {
return supportsTesselation;
}
public static boolean supportsSamplerObjects() {
return supportsSamplerObjects;
}
public static int genSampler() {
if (!supportsSamplerObjects) return 0;
return RENDER_BACKEND.genSamplers();
}
public static void destroySampler(int sampler) {
if (!supportsSamplerObjects || sampler == 0) return;
RENDER_BACKEND.deleteSamplers(sampler);
}
public static void samplerParameteri(int sampler, int pname, int param) {
if (!supportsSamplerObjects || sampler == 0) return;
RENDER_BACKEND.samplerParameteri(sampler, pname, param);
}
public static void bindSamplerToUnit(int unit, int sampler) {
if (!supportsSamplerObjects) return;
if (samplers[unit] == sampler) return;
RENDER_BACKEND.bindSampler(unit, sampler);
samplers[unit] = sampler;
}
public static void unbindAllSamplers() {
if (!supportsSamplerObjects) return;
for (int i = 0; i < samplers.length; i++) {
if (samplers[i] != 0) {
RENDER_BACKEND.bindSampler(i, 0);
samplers[i] = 0;
}
}
}
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Inlined from StandardMacros.getGlVersion(). */
static String parseGlVersionString(String info) {
final Matcher matcher = SEMVER_PATTERN.matcher(Objects.requireNonNull(info));
if (!matcher.matches()) {
throw new IllegalStateException("Could not parse GL version from \"" + info + "\"");
}
final String major = matcher.group("major");
final String minor = matcher.group("minor");
String bugfix = matcher.group("bugfix");
if (bugfix == null || bugfix.isEmpty()) bugfix = "0";
return major + minor + bugfix;
}
}
@@ -0,0 +1,132 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.MultMatrixCmd;
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 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 {
GLStateManager.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,54 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
public final class BackendManager {
private static final Logger LOGGER = LogManager.getLogger("BackendManager");
public static final RenderBackend RENDER_BACKEND = loadService();
private BackendManager() {}
public static void init() {
RENDER_BACKEND.init();
LOGGER.info("Initialized backend: {} (priority {})", RENDER_BACKEND.getName(), RENDER_BACKEND.getPriority());
}
public static void shutdown() {
RENDER_BACKEND.shutdown();
}
private static RenderBackend loadService() {
final ServiceLoader<RenderBackend> loader = ServiceLoader.load(RenderBackend.class, RenderBackend.class.getClassLoader());
RenderBackend best = null;
final Iterator<RenderBackend> iterator = loader.iterator();
int attempts = 0;
while (attempts < 16) {
try {
attempts++;
if (!iterator.hasNext()) break;
final RenderBackend backend = iterator.next();
LOGGER.info("Found backend: {} (priority {})", backend.getName(), backend.getPriority());
if (backend.isAvailable() && (best == null || backend.getPriority() > best.getPriority())) {
best = backend;
}
} catch (ServiceConfigurationError | LinkageError e) {
LOGGER.debug("Skipping unavailable backend: {}", e.getMessage());
}
}
if (best == null) {
throw new IllegalStateException("No available render backend!");
}
LOGGER.info("Selected backend: {} (priority {})", best.getName(), best.getPriority());
return best;
}
}
@@ -0,0 +1,6 @@
package com.gtnewhorizons.angelica.glsm.backend;
@FunctionalInterface
public interface DebugMessageHandler {
void handleMessage(int source, int type, int id, int severity, String message);
}
@@ -0,0 +1,322 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
/**
* Abstract rendering backend.
*/
public abstract class RenderBackend {
public abstract void init();
public abstract void shutdown();
public abstract boolean isAvailable();
public abstract String getName();
/** Higher priority backends are preferred. */
public int getPriority() { return 0; }
public abstract int getMinGLSLVersion();
public abstract void flush();
public abstract void finish();
public void onFrameBegin() {}
public void onFrameEnd() {}
public abstract void enable(int cap);
public abstract void enablei(int cap, int index);
public abstract void disable(int cap);
public abstract void disablei(int cap, int index);
public abstract void blendFuncSeparatei(int buf, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha);
public abstract void blendFunc(int sfactor, int dfactor);
public abstract void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha);
public abstract void blendEquation(int mode);
public abstract void blendEquationSeparate(int modeRGB, int modeAlpha);
public abstract void blendColor(float red, float green, float blue, float alpha);
public abstract void depthFunc(int func);
public abstract void depthMask(boolean flag);
public abstract void colorMask(boolean red, boolean green, boolean blue, boolean alpha);
public abstract void cullFace(int mode);
public abstract void frontFace(int mode);
public abstract void polygonMode(int face, int mode);
public abstract void polygonOffset(float factor, float units);
public abstract void stencilFunc(int func, int ref, int mask);
public abstract void stencilOp(int sfail, int dpfail, int dppass);
public abstract void stencilMask(int mask);
public abstract void stencilFuncSeparate(int face, int func, int ref, int mask);
public abstract void stencilOpSeparate(int face, int sfail, int dpfail, int dppass);
public abstract void stencilMaskSeparate(int face, int mask);
public abstract void viewport(int x, int y, int width, int height);
public abstract void depthRange(double nearVal, double farVal);
public abstract void scissor(int x, int y, int width, int height);
public abstract void clearColor(float red, float green, float blue, float alpha);
public abstract void clearDepth(double depth);
public abstract void clearStencil(int s);
public abstract void clear(int mask);
public abstract void lineWidth(float width);
public abstract void pointSize(float size);
public abstract void logicOp(int opcode);
public abstract void hint(int target, int mode);
public abstract void drawArrays(int mode, int first, int count);
public abstract void drawElements(int mode, int count, int type, long indices);
public void drawElements(int mode, ByteBuffer indices) {}
public void drawElements(int mode, IntBuffer indices) {}
public void drawElements(int mode, ShortBuffer indices) {}
public void drawElements(int mode, int count, int type, ByteBuffer indices) {}
public abstract void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride);
public abstract void copyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size);
public abstract void drawElementsInstanced(int mode, int count, int type, long indices, int primcount);
public abstract void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex);
public abstract void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex);
public abstract void drawBuffer(int mode);
public abstract void dispatchCompute(int numGroupsX, int numGroupsY, int numGroupsZ);
public abstract void dispatchComputeIndirect(long offset);
public abstract int genTextures();
public abstract void genTextures(IntBuffer textures);
public abstract void deleteTextures(int texture);
public abstract void bindTexture(int target, int texture);
public abstract void activeTexture(int texture);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, DoubleBuffer pixels);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, FloatBuffer pixels);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, IntBuffer pixels);
public abstract void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, long pixelBufferOffset);
public abstract void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
public abstract void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
public abstract void copyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height);
public void texImage1D(int target, int level, int internalFormat, int width, int border, int format, int type, ByteBuffer pixels) {}
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {}
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {}
public void texSubImage1D(int target, int level, int xoffset, int width, int format, int type, ByteBuffer pixels) {}
public void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pboOffset) {}
public void texSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer pixels) {}
public void copyTexImage1D(int target, int level, int internalFormat, int x, int y, int width, int border) {}
public void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {}
public void copyTexSubImage1D(int target, int level, int xoffset, int x, int y, int width) {}
public void copyTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int x, int y, int width, int height) {}
public abstract void texParameteri(int target, int pname, int param);
public abstract void texParameterf(int target, int pname, float param);
public abstract void texParameteriv(int target, int pname, IntBuffer params);
public abstract void texParameterfv(int target, int pname, FloatBuffer params);
public abstract int getTexParameteri(int target, int pname);
public abstract float getTexParameterf(int target, int pname);
public abstract int getTexLevelParameteri(int target, int level, int pname);
public abstract void generateMipmap(int target);
public abstract void pixelStorei(int pname, int param);
public void pixelStoref(int pname, float param) {}
public void sampleCoverage(float value, boolean invert) {}
public abstract int genSamplers();
public abstract void deleteSamplers(int sampler);
public abstract void bindSampler(int unit, int sampler);
public abstract void samplerParameteri(int sampler, int pname, int param);
public abstract void samplerParameterf(int sampler, int pname, float param);
public abstract int genFramebuffers();
public abstract void deleteFramebuffers(int framebuffer);
public abstract void bindFramebuffer(int target, int framebuffer);
public abstract void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level);
public abstract void framebufferTexture(int target, int attachment, int texture, int level);
public abstract int checkFramebufferStatus(int target);
public abstract void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public abstract void drawBuffers(int buffer);
public abstract void drawBuffers(IntBuffer bufs);
public abstract void readBuffer(int mode);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, ByteBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, IntBuffer pixels);
public abstract int getFramebufferAttachmentParameteri(int target, int attachment, int pname);
public abstract int createShader(int type);
public abstract void deleteShader(int shader);
public abstract void shaderSource(int shader, CharSequence source);
public abstract void compileShader(int shader);
public abstract int createProgram();
public abstract void deleteProgram(int program);
public abstract void attachShader(int program, int shader);
public abstract void detachShader(int program, int shader);
public abstract void linkProgram(int program);
public abstract void useProgram(int program);
public abstract String getShaderInfoLog(int shader, int maxLength);
public String getShaderInfoLog(int shader) {
return getShaderInfoLog(shader, getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
}
public abstract void getShaderInfoLog(int shader, IntBuffer length, ByteBuffer infoLog);
public abstract String getProgramInfoLog(int program, int maxLength);
public String getProgramInfoLog(int program) {
return getProgramInfoLog(program, getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
}
public abstract void getProgramInfoLog(int program, IntBuffer length, ByteBuffer infoLog);
public abstract int getShaderi(int shader, int pname);
public abstract int getProgrami(int program, int pname);
public abstract void getProgramiv(int program, int pname, IntBuffer params);
public abstract String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType);
public abstract void getActiveUniform(int program, int index, IntBuffer length, IntBuffer size, IntBuffer type, ByteBuffer name);
public abstract void bindAttribLocation(int program, int index, CharSequence name);
public abstract int getAttribLocation(int program, CharSequence name);
public abstract int getAttribLocation(int program, ByteBuffer name);
public abstract int getUniformLocation(int program, CharSequence name);
public abstract int getUniformLocation(int program, ByteBuffer name);
public boolean isShader(int obj) { return false; }
public boolean isProgram(int obj) { return false; }
public void validateProgram(int program) {}
public void getAttachedShaders(int program, IntBuffer count, IntBuffer shaders) {
if (count != null) count.put(0, 0);
}
public String getShaderSource(int shader, int maxLength) { return ""; }
public abstract void getShaderSource(int shader, IntBuffer length, ByteBuffer source);
public abstract void uniform1i(int location, int v0);
public abstract void uniform1f(int location, float v0);
public void uniform1fv(int location, FloatBuffer values) {}
public abstract void uniform2f(int location, float v0, float v1);
public abstract void uniform2i(int location, int v0, int v1);
public abstract void uniform3f(int location, float v0, float v1, float v2);
public abstract void uniform3i(int location, int v0, int v1, int v2);
public abstract void uniform4f(int location, float v0, float v1, float v2, float v3);
public abstract void uniform4i(int location, int v0, int v1, int v2, int v3);
public abstract void uniform3(int location, FloatBuffer value);
public abstract void uniform4(int location, FloatBuffer value);
public abstract void uniformMatrix3(int location, boolean transpose, FloatBuffer value);
public abstract void uniformMatrix4(int location, boolean transpose, FloatBuffer value);
public void uniformMatrix2(int location, boolean transpose, FloatBuffer matrices) {}
public void getUniformfv(int program, int location, FloatBuffer params) {}
public void getUniformiv(int program, int location, IntBuffer params) {}
public abstract void vertexAttrib2f(int index, float v0, float v1);
public abstract void vertexAttrib3f(int index, float v0, float v1, float v2);
public abstract void vertexAttrib4f(int index, float v0, float v1, float v2, float v3);
public abstract int genBuffers();
public abstract void deleteBuffers(int buffer);
public abstract void deleteBuffers(IntBuffer buffers);
public abstract void bindBuffer(int target, int buffer);
public abstract void bindBufferBase(int target, int index, int buffer);
public abstract void bufferData(int target, long size, int usage);
public abstract void bufferData(int target, ByteBuffer data, int usage);
public abstract void bufferData(int target, FloatBuffer data, int usage);
public abstract void bufferData(int target, ShortBuffer data, int usage);
public abstract void bufferData(int target, IntBuffer data, int usage);
public abstract void bufferData(int target, DoubleBuffer data, int usage);
public abstract void bufferData(int target, int[] data, int usage);
public abstract void bufferData(int target, float[] data, int usage);
public abstract void bufferSubData(int target, long offset, ByteBuffer data);
public abstract void bufferSubData(int target, long offset, ShortBuffer data);
public abstract void bufferSubData(int target, long offset, IntBuffer data);
public abstract void bufferSubData(int target, long offset, FloatBuffer data);
public abstract void bufferSubData(int target, long offset, DoubleBuffer data);
public abstract ByteBuffer mapBuffer(int target, int access);
public abstract ByteBuffer mapBuffer(int target, int access, long length, ByteBuffer old_buffer);
public abstract boolean unmapBuffer(int target);
public abstract void bufferStorage(int target, ByteBuffer data, int flags);
public abstract void bufferStorage(int target, long size, int flags);
public abstract void getBufferSubData(int target, long offset, ByteBuffer data);
public abstract void getBufferSubData(int target, long offset, ShortBuffer data);
public abstract void getBufferSubData(int target, long offset, IntBuffer data);
public abstract void getBufferSubData(int target, long offset, FloatBuffer data);
public abstract void getBufferSubData(int target, long offset, DoubleBuffer data);
public abstract int getBufferParameteri(int target, int pname);
public abstract boolean isBuffer(int buffer);
public abstract ByteBuffer mapBufferRange(int target, long offset, long length, int access);
public abstract int genVertexArrays();
public abstract void deleteVertexArrays(int array);
public abstract void bindVertexArray(int array);
public abstract void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer);
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
vertexAttribPointer(index, size, type, normalized, stride, MemoryUtilities.memAddress0(pointer));
}
public void vertexAttribIPointer(int index, int size, int type, int stride, ByteBuffer pointer) {
vertexAttribIPointer(index, size, type, stride, MemoryUtilities.memAddress0(pointer));
}
public abstract void vertexAttribIPointer(int index, int size, int type, int stride, long pointer);
public abstract void enableVertexAttribArray(int index);
public abstract void disableVertexAttribArray(int index);
public abstract void vertexAttribDivisor(int index, int divisor);
public abstract void bindVertexBuffer(int bindingindex, int buffer, long offset, int stride);
public abstract void vertexAttribFormat(int attribindex, int size, int type, boolean normalized, int relativeoffset);
public abstract void vertexAttribIFormat(int attribindex, int size, int type, int relativeoffset);
public abstract void vertexAttribBinding(int attribindex, int bindingindex);
public abstract int createTextures(int target);
public abstract void bindTextureUnit(int unit, int texture);
public abstract void textureParameteri(int texture, int target, int pname, int param);
public abstract void textureParameterf(int texture, int target, int pname, float param);
public abstract void textureParameteriv(int texture, int target, int pname, IntBuffer params);
public abstract void texStorage1D(int target, int levels, int internalFormat, int width);
public abstract void texStorage2D(int target, int levels, int internalFormat, int width, int height);
public abstract void texStorage3D(int target, int levels, int internalFormat, int width, int height, int depth);
public abstract void textureStorage1D(int texture, int levels, int internalFormat, int width);
public abstract void textureStorage2D(int texture, int levels, int internalFormat, int width, int height);
public abstract void textureStorage3D(int texture, int levels, int internalFormat, int width, int height, int depth);
public abstract void generateTextureMipmap(int texture);
public abstract void textureImage2DEXT(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels);
public abstract void textureImage2DEXT(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels);
public abstract void textureSubImage2D(int texture, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
public abstract void textureSubImage2D(int texture, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
public abstract int createFramebuffers();
public abstract void namedFramebufferTexture(int framebuffer, int attachment, int texture, int level);
public abstract void namedFramebufferReadBuffer(int framebuffer, int mode);
public abstract void namedFramebufferDrawBuffers(int framebuffer, IntBuffer bufs);
public abstract void blitNamedFramebuffer(int readFramebuffer, int drawFramebuffer, int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public abstract int createBuffers();
public abstract void namedBufferData(int buffer, ByteBuffer data, int usage);
public abstract void namedBufferData(int buffer, FloatBuffer data, int usage);
public abstract void namedBufferSubData(int buffer, long offset, ByteBuffer data);
public abstract void copyTextureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int x, int y, int width, int height);
public abstract int getTextureParameteri(int texture, int target, int pname);
public abstract int getTextureLevelParameteri(int texture, int level, int pname);
public abstract int getInteger(int pname);
public abstract void getInteger(int pname, IntBuffer params);
public abstract float getFloat(int pname);
public abstract void getFloat(int pname, FloatBuffer params);
public abstract boolean getBoolean(int pname);
public abstract void getBoolean(int pname, ByteBuffer params);
public abstract String getString(int pname);
public abstract String getStringi(int name, int index);
public abstract int getError();
public abstract long fenceSync(int condition, int flags);
public abstract int clientWaitSync(long sync, int flags, long timeout);
public abstract void deleteSync(long sync);
public abstract void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data);
public abstract void clearTexImage(int texture, int level, int format, int type);
public abstract void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format);
public abstract void memoryBarrier(int barriers);
public abstract void copyImageSubData(int srcName, int srcTarget, int srcLevel, int srcX, int srcY, int srcZ,
int dstName, int dstTarget, int dstLevel, int dstX, int dstY, int dstZ,
int srcWidth, int srcHeight, int srcDepth);
// Debug output
public boolean supportsDebugOutput() { return false; }
public int setupDebugOutput(DebugMessageHandler handler) { return 0; }
public int disableDebugOutput() { return 0; }
// Object labeling
public void objectLabel(int identifier, int name, CharSequence label) {}
public String getObjectLabel(int identifier, int name) { return ""; }
// Debug groups
public void pushDebugGroup(int source, int id, CharSequence message) {}
public void popDebugGroup() {}
// Debug message insertion
public void debugMessageInsert(int source, int type, int id, int severity, CharSequence message) {}
}
@@ -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;
}
@@ -0,0 +1,142 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Cylinder;
import static org.lwjgl.opengl.GL11.GL_LINES;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Cylinder}.
*/
@SuppressWarnings("unused")
public class AngelicaCylinder extends Cylinder {
private static final float PI = (float) Math.PI;
private void emitNormal(float x, float y, float z) {
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
if (mag > 0.00001F) {
x /= mag;
y /= mag;
z /= mag;
}
GLStateManager.glNormal3f(x, y, z);
}
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float baseRadius, float topRadius, float height, int slices, int stacks) {
final float da, dr, dz, nz, nsign;
float r, x, y, z;
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
da = 2.0f * PI / slices;
dr = (topRadius - baseRadius) / stacks;
dz = height / stacks;
nz = (baseRadius - topRadius) / height;
if (super.drawStyle == GLU_POINT) {
GLStateManager.glBegin(GL_POINTS);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
z = 0.0f;
r = baseRadius;
for (int j = 0; j <= stacks; j++) {
GLStateManager.glVertex3f(x * r, y * r, z);
z += dz;
r += dr;
}
}
GLStateManager.glEnd();
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
// Draw rings
if (super.drawStyle == GLU_LINE) {
z = 0.0f;
r = baseRadius;
for (int j = 0; j <= stacks; j++) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * r, y * r, z);
}
GLStateManager.glEnd();
z += dz;
r += dr;
}
} else {
// draw one ring at each end
if (baseRadius != 0.0) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
}
GLStateManager.glEnd();
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
}
GLStateManager.glEnd();
}
}
// draw length lines
GLStateManager.glBegin(GL_LINES);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
}
GLStateManager.glEnd();
} else if (super.drawStyle == GLU_FILL) {
final float ds = 1.0f / slices;
final float dt = 1.0f / stacks;
float t = 0.0f;
z = 0.0f;
r = baseRadius;
for (int j = 0; j < stacks; j++) {
float s = 0.0f;
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int i = 0; i <= slices; i++) {
final float angle = (i == slices) ? 0.0f : i * da;
x = sin(angle);
y = cos(angle);
emitNormal(x * nsign, y * nsign, nz * nsign);
emitTexCoord(s, t);
GLStateManager.glVertex3f(x * r, y * r, z);
emitNormal(x * nsign, y * nsign, nz * nsign);
emitTexCoord(s, t + dt);
GLStateManager.glVertex3f(x * (r + dr), y * (r + dr), z + dz);
s += ds;
}
GLStateManager.glEnd();
r += dr;
t += dt;
z += dz;
}
}
}
}
@@ -0,0 +1,134 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Disk;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_NONE;
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Disk}.
*/
@SuppressWarnings("unused")
public class AngelicaDisk extends Disk {
private static final float PI = (float) Math.PI;
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float innerRadius, float outerRadius, int slices, int loops) {
final float da, dr;
if (super.normals != GLU_NONE) {
GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
}
da = 2.0f * PI / slices;
dr = (outerRadius - innerRadius) / loops;
switch (super.drawStyle) {
case GLU_FILL -> {
final float dtc = 2.0f * outerRadius;
float sa, ca;
float r1 = innerRadius;
for (int l = 0; l < loops; l++) {
final float r2 = r1 + dr;
if (super.orientation == GLU_OUTSIDE) {
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int s = 0; s <= slices; s++) {
final float a = (s == slices) ? 0.0f : s * da;
sa = sin(a);
ca = cos(a);
emitTexCoord(0.5f + sa * r2 / dtc, 0.5f + ca * r2 / dtc);
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
emitTexCoord(0.5f + sa * r1 / dtc, 0.5f + ca * r1 / dtc);
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
}
GLStateManager.glEnd();
} else {
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int s = slices; s >= 0; s--) {
final float a = (s == slices) ? 0.0f : s * da;
sa = sin(a);
ca = cos(a);
emitTexCoord(0.5f - sa * r2 / dtc, 0.5f + ca * r2 / dtc);
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
emitTexCoord(0.5f - sa * r1 / dtc, 0.5f + ca * r1 / dtc);
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
}
GLStateManager.glEnd();
}
r1 = r2;
}
}
case GLU_LINE -> {
/* draw loops */
for (int l = 0; l <= loops; l++) {
final float r = innerRadius + l * dr;
GLStateManager.glBegin(GL_LINE_LOOP);
for (int s = 0; s < slices; s++) {
final float a = s * da;
GLStateManager.glVertex2f(r * sin(a), r * cos(a));
}
GLStateManager.glEnd();
}
/* draw spokes */
for (int s = 0; s < slices; s++) {
final float a = s * da;
final float x = sin(a);
final float y = cos(a);
GLStateManager.glBegin(GL_LINE_STRIP);
for (int l = 0; l <= loops; l++) {
final float r = innerRadius + l * dr;
GLStateManager.glVertex2f(r * x, r * y);
}
GLStateManager.glEnd();
}
}
case GLU_POINT -> {
GLStateManager.glBegin(GL_POINTS);
for (int s = 0; s < slices; s++) {
final float a = s * da;
final float x = sin(a);
final float y = cos(a);
for (int l = 0; l <= loops; l++) {
final float r = innerRadius * l * dr;
GLStateManager.glVertex2f(r * x, r * y);
}
}
GLStateManager.glEnd();
}
case GLU_SILHOUETTE -> {
if (innerRadius != 0.0) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (float a = 0.0f; a < 2.0 * PI; a += da) {
final float x = innerRadius * sin(a);
final float y = innerRadius * cos(a);
GLStateManager.glVertex2f(x, y);
}
GLStateManager.glEnd();
}
GLStateManager.glBegin(GL_LINE_LOOP);
for (float a = 0; a < 2.0f * PI; a += da) {
final float x = outerRadius * sin(a);
final float y = outerRadius * cos(a);
GLStateManager.glVertex2f(x, y);
}
GLStateManager.glEnd();
}
default -> {
}
}
}
}
@@ -0,0 +1,243 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.PartialDisk;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_FLAT;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
import static org.lwjgl.util.glu.GLU.GLU_SMOOTH;
/**
* Core-profile compatible replacement for {@link PartialDisk}.
*/
@SuppressWarnings("unused")
public class AngelicaPartialDisk extends PartialDisk {
private static final float PI = (float) Math.PI;
private static final int CACHE_SIZE = 240;
private final float[] sinCache = new float[CACHE_SIZE];
private final float[] cosCache = new float[CACHE_SIZE];
@Override
public void draw(float innerRadius, float outerRadius, int slices, int loops, float startAngle, float sweepAngle) {
final float deltaRadius, angleOffset;
float angle, sintemp, costemp, radiusLow, radiusHigh, texLow = 0, texHigh = 0;
final int slices2, finish;
if (slices >= CACHE_SIZE) slices = CACHE_SIZE - 1;
if (slices < 2 || loops < 1 || outerRadius <= 0.0f || innerRadius < 0.0f || innerRadius > outerRadius) {
GLStateManager.LOGGER.warn("PartialDisk: GLU_INVALID_VALUE (slices={}, loops={}, innerRadius={}, outerRadius={})", slices, loops, innerRadius, outerRadius);
return;
}
if (sweepAngle < -360.0f) sweepAngle = 360.0f;
if (sweepAngle > 360.0f) sweepAngle = 360.0f;
if (sweepAngle < 0) {
startAngle += sweepAngle;
sweepAngle = -sweepAngle;
}
slices2 = (sweepAngle == 360.0f) ? slices : slices + 1;
/* Compute length (needed for normal calculations) */
deltaRadius = outerRadius - innerRadius;
angleOffset = startAngle / 180.0f * PI;
for (int i = 0; i <= slices; i++) {
angle = angleOffset + ((PI * sweepAngle) / 180.0f) * i / slices;
sinCache[i] = sin(angle);
cosCache[i] = cos(angle);
}
if (sweepAngle == 360.0f) {
sinCache[slices] = sinCache[0];
cosCache[slices] = cosCache[0];
}
switch (super.normals) {
case GLU_FLAT, GLU_SMOOTH -> GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
default -> {
}
}
switch (super.drawStyle) {
case GLU_FILL -> {
if (innerRadius == .0f) {
finish = loops - 1;
/* Triangle strip for inner polygons */
GLStateManager.glBegin(GL_TRIANGLE_FAN);
if (super.textureFlag) {
GLStateManager.glTexCoord2f(0.5f, 0.5f);
}
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
radiusLow = outerRadius - deltaRadius * ((float) (loops - 1) / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
if (super.orientation == GLU_OUTSIDE) {
for (int i = slices; i >= 0; i--) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
} else {
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
}
GLStateManager.glEnd();
} else {
finish = loops;
}
for (int j = 0; j < finish; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
radiusHigh = outerRadius - deltaRadius * ((float) (j + 1) / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
texHigh = radiusHigh / outerRadius / 2;
}
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.orientation == GLU_OUTSIDE) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texHigh * sinCache[i] + 0.5f, texHigh * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusHigh * sinCache[i], radiusHigh * cosCache[i], 0.0f);
} else {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texHigh * sinCache[i] + 0.5f, texHigh * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusHigh * sinCache[i], radiusHigh * cosCache[i], 0.0f);
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
}
GLStateManager.glEnd();
}
}
case GLU_POINT -> {
GLStateManager.glBegin(GL_POINTS);
for (int i = 0; i < slices2; i++) {
sintemp = sinCache[i];
costemp = cosCache[i];
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
}
}
GLStateManager.glEnd();
}
case GLU_LINE -> {
if (innerRadius == outerRadius) {
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(sinCache[i] / 2 + 0.5f, cosCache[i] / 2 + 0.5f);
}
GLStateManager.glVertex3f(innerRadius * sinCache[i], innerRadius * cosCache[i], 0.0f);
}
GLStateManager.glEnd();
break;
}
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
GLStateManager.glEnd();
}
for (int i = 0; i < slices2; i++) {
sintemp = sinCache[i];
costemp = cosCache[i];
GLStateManager.glBegin(GL_LINE_STRIP);
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
}
GLStateManager.glEnd();
}
}
case GLU_SILHOUETTE -> {
if (sweepAngle < 360.0f) {
for (int i = 0; i <= slices; i += slices) {
sintemp = sinCache[i];
costemp = cosCache[i];
GLStateManager.glBegin(GL_LINE_STRIP);
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
}
GLStateManager.glEnd();
}
}
for (int j = 0; j <= loops; j += loops) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
GLStateManager.glEnd();
if (innerRadius == outerRadius) break;
}
}
default -> {
}
}
}
}
@@ -0,0 +1,183 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Sphere;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_NONE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Sphere}.
*/
@SuppressWarnings("unused")
public class AngelicaSphere extends Sphere {
private static final float PI = (float) Math.PI;
private void emitNormal(float x, float y, float z) {
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
if (mag > 0.00001F) {
x /= mag;
y /= mag;
z /= mag;
}
GLStateManager.glNormal3f(x, y, z);
}
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float radius, int slices, int stacks) {
final float drho, dtheta, ds, dt, nsign;
float rho, theta, x, y, z, s, t;
final int imin, imax;
final boolean normals;
normals = super.normals != GLU_NONE;
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
drho = PI / stacks;
dtheta = 2.0f * PI / slices;
if (super.drawStyle == GLU_FILL) {
if (!super.textureFlag) {
// draw +Z end as a triangle fan
GLStateManager.glBegin(GL_TRIANGLE_FAN);
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
GLStateManager.glVertex3f(0.0f, 0.0f, nsign * radius);
for (int j = 0; j <= slices; j++) {
theta = (j == slices) ? 0.0f : j * dtheta;
x = -sin(theta) * sin(drho);
y = cos(theta) * sin(drho);
z = nsign * cos(drho);
if (normals) {
emitNormal(x * nsign, y * nsign, z * nsign);
}
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
ds = 1.0f / slices;
dt = 1.0f / stacks;
t = 1.0f;
if (super.textureFlag) {
imin = 0;
imax = stacks;
} else {
imin = 1;
imax = stacks - 1;
}
// draw intermediate stacks as triangle strips (GL_QUAD_STRIP removed in core profile)
for (int i = imin; i < imax; i++) {
rho = i * drho;
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
s = 0.0f;
for (int j = 0; j <= slices; j++) {
theta = (j == slices) ? 0.0f : j * dtheta;
x = -sin(theta) * sin(rho);
y = cos(theta) * sin(rho);
z = nsign * cos(rho);
if (normals) {
emitNormal(x * nsign, y * nsign, z * nsign);
}
emitTexCoord(s, t);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
x = -sin(theta) * sin(rho + drho);
y = cos(theta) * sin(rho + drho);
z = nsign * cos(rho + drho);
if (normals) {
emitNormal(x * nsign, y * nsign, z * nsign);
}
emitTexCoord(s, t - dt);
s += ds;
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
t -= dt;
}
if (!super.textureFlag) {
// draw -Z end as a triangle fan
GLStateManager.glBegin(GL_TRIANGLE_FAN);
GLStateManager.glNormal3f(0.0f, 0.0f, -1.0f);
GLStateManager.glVertex3f(0.0f, 0.0f, -radius * nsign);
rho = PI - drho;
s = 1.0f;
for (int j = slices; j >= 0; j--) {
theta = (j == slices) ? 0.0f : j * dtheta;
x = -sin(theta) * sin(rho);
y = cos(theta) * sin(rho);
z = nsign * cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
s -= ds;
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
// draw stack lines
for (int i = 1; i < stacks; i++) {
rho = i * drho;
GLStateManager.glBegin(GL_LINE_LOOP);
for (int j = 0; j < slices; j++) {
theta = j * dtheta;
x = cos(theta) * sin(rho);
y = sin(theta) * sin(rho);
z = cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
// draw slice lines
for (int j = 0; j < slices; j++) {
theta = j * dtheta;
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= stacks; i++) {
rho = i * drho;
x = cos(theta) * sin(rho);
y = sin(theta) * sin(rho);
z = cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
} else if (super.drawStyle == GLU_POINT) {
// top and bottom-most points
GLStateManager.glBegin(GL_POINTS);
if (normals) GLStateManager.glNormal3f(0.0f, 0.0f, nsign);
GLStateManager.glVertex3f(0.0f, 0.0f, radius);
if (normals) GLStateManager.glNormal3f(0.0f, 0.0f, -nsign);
GLStateManager.glVertex3f(0.0f, 0.0f, -radius);
// loop over stacks
for (int i = 1; i < stacks - 1; i++) {
rho = i * drho;
for (int j = 0; j < slices; j++) {
theta = j * dtheta;
x = cos(theta) * sin(rho);
y = sin(theta) * sin(rho);
z = cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
}
GLStateManager.glEnd();
}
}
}
@@ -0,0 +1,147 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public class DSAARB extends DSAUnsupported {
@Override
public void generateMipmaps(int texture, int target) {
RENDER_BACKEND.generateTextureMipmap(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) {
RENDER_BACKEND.textureSubImage2D(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) {
RENDER_BACKEND.textureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
}
@Override
public void texParameteri(int texture, int target, int pname, int param) {
param = GLStateManager.remapTexClamp(pname, param);
if(!GLStateManager.updateTexParameteriCache(target, texture, pname, param)) return;
RENDER_BACKEND.textureParameteri(texture, target, pname, param);
}
@Override
public void texParameterf(int texture, int target, int pname, float param) {
param = GLStateManager.remapTexClamp(pname, param);
if(!GLStateManager.updateTexParameterfCache(target, texture, pname, param)) return;
RENDER_BACKEND.textureParameterf(texture, target, pname, param);
}
@Override
public void texParameteriv(int texture, int target, int pname, IntBuffer params) {
GLStateManager.remapTexClampBuffer(pname, params);
RENDER_BACKEND.textureParameteriv(texture, target, pname, params);
}
@Override
public void readBuffer(int framebuffer, int buffer) {
RENDER_BACKEND.namedFramebufferReadBuffer(framebuffer, buffer);
}
@Override
public void drawBuffers(int framebuffer, IntBuffer buffers) {
RENDER_BACKEND.namedFramebufferDrawBuffers(framebuffer, buffers);
}
@Override
public int getTexParameteri(int texture, int target, int pname) {
return GLStateManager.getTexParameterOrDefault(texture, pname, () -> RENDER_BACKEND.getTextureParameteri(texture, target, pname));
}
@Override
public int getTexLevelParameteri(int texture, int level, int pname) {
return RENDER_BACKEND.getTextureLevelParameteri(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) {
RENDER_BACKEND.copyTextureSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
}
@Override
public void bindTextureToUnit(int unit, int texture) {
if(GLStateManager.getBoundTextureForServerState(unit) == texture) return;
if (texture == 0) {
super.bindTextureToUnit(unit, texture);
} else {
RENDER_BACKEND.bindTextureUnit(unit, texture);
GLStateManager.getTextures().getTextureUnitBindings(unit).setBinding(texture);
}
}
@Override
public void bindTextureToUnit(int target, int unit, int texture) {
bindTextureToUnit(unit, texture);
}
@Override
public int bufferStorage(int target, FloatBuffer data, int usage) {
final int buffer = RENDER_BACKEND.createBuffers();
RENDER_BACKEND.namedBufferData(buffer, data, usage);
return buffer;
}
@Override
public 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) {
RENDER_BACKEND.blitNamedFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
}
@Override
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
RENDER_BACKEND.namedFramebufferTexture(fb, attachment, texture, levels);
}
@Override
public int createFramebuffer() {
return RENDER_BACKEND.createFramebuffers();
}
@Override
public int createTexture(int target) {
return RENDER_BACKEND.createTextures(target);
}
@Override
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
RENDER_BACKEND.textureStorage1D(texture, levels, internalFormat, width);
}
@Override
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
RENDER_BACKEND.textureStorage2D(texture, levels, internalFormat, width, height);
}
@Override
public void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
RENDER_BACKEND.textureStorage3D(texture, levels, internalFormat, width, height, depth);
}
}
@@ -0,0 +1,46 @@
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);
void readBuffer(int framebuffer, int buffer);
void drawBuffers(int framebuffer, IntBuffer buffers);
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);
void bindTextureToUnit(int target, int unit, int texture);
int bufferStorage(int target, FloatBuffer data, int usage);
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);
void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels);
int createFramebuffer();
int createTexture(int target);
// Texture storage methods for custom images
void textureStorage1D(int texture, int target, int levels, int internalFormat, int width);
void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height);
void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth);
}
@@ -0,0 +1,18 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public class DSAEXT extends DSACore {
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.textureImage2DEXT(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) {
RENDER_BACKEND.textureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
}
@@ -0,0 +1,183 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
public class DSAUnsupported implements DSAAccess {
@Override
public void generateMipmaps(int texture, int target) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
RENDER_BACKEND.generateMipmap(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();
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
RENDER_BACKEND.bindTexture(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();
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
RENDER_BACKEND.bindTexture(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();
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
RENDER_BACKEND.bindTexture(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();
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
RENDER_BACKEND.bindTexture(target, cachedBinding);
}
@Override
public void texParameteri(int texture, int target, int pname, int param) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glTexParameteri(target, pname, param);
}
@Override
public void texParameterf(int texture, int target, int pname, float param) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glTexParameterf(target, pname, param);
}
@Override
public void texParameteriv(int texture, int target, int pname, IntBuffer params) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glTexParameter(target, pname, params);
}
@Override
public void readBuffer(int framebuffer, int buffer) {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
RENDER_BACKEND.readBuffer(buffer);
}
@Override
public void drawBuffers(int framebuffer, IntBuffer buffers) {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
RENDER_BACKEND.drawBuffers(buffers);
}
@Override
public int getTexParameteri(int texture, int target, int pname) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
return GLStateManager.glGetTexParameteri(target, pname);
}
@Override
public int getTexLevelParameteri(int texture, int level, int pname) {
final int previous = GLStateManager.getBoundTextureForServerState();
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, texture);
final int result = RENDER_BACKEND.getTexLevelParameteri(GL11.GL_TEXTURE_2D, level, pname);
RENDER_BACKEND.bindTexture(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.getBoundTextureForServerState();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, destTexture);
RENDER_BACKEND.copyTexSubImage2D(target, i, i1, i2, i3, i4, width, height);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, previous);
}
@Override
public void bindTextureToUnit(int unit, int texture) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
}
@Override
public void bindTextureToUnit(int target, int unit, int texture) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glBindTexture(target, texture);
}
@Override
public int bufferStorage(int target, FloatBuffer data, int usage) {
final int buffer = RENDER_BACKEND.genBuffers();
GLStateManager.glBindBuffer(target, buffer);
RENDER_BACKEND.bufferData(target, data, usage);
GLStateManager.glBindBuffer(target, 0);
return buffer;
}
@Override
public 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) {
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, source);
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, dest);
RENDER_BACKEND.blitFramebuffer(offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
}
@Override
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
GLStateManager.glBindFramebuffer(fbtarget, fb);
RENDER_BACKEND.framebufferTexture2D(fbtarget, attachment, target, texture, levels);
}
@Override
public int createFramebuffer() {
final int framebuffer = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
return framebuffer;
}
@Override
public int createTexture(int target) {
final int texture = RENDER_BACKEND.genTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
return texture;
}
@Override
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
final int previous = RENDER_BACKEND.getInteger(GL11.GL_TEXTURE_BINDING_1D);
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texStorage1D(target, levels, internalFormat, width);
RENDER_BACKEND.bindTexture(target, previous);
}
@Override
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
final int previous = (target == GL11.GL_TEXTURE_2D) ? GLStateManager.getBoundTextureForServerState() : RENDER_BACKEND.getInteger(GL31.GL_TEXTURE_BINDING_RECTANGLE);
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texStorage2D(target, levels, internalFormat, width, height);
RENDER_BACKEND.bindTexture(target, previous);
}
@Override
public void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
final int previous = RENDER_BACKEND.getInteger(GL12.GL_TEXTURE_BINDING_3D);
RENDER_BACKEND.bindTexture(target, texture);
RENDER_BACKEND.texStorage3D(target, levels, internalFormat, width, height, depth);
RENDER_BACKEND.bindTexture(target, previous);
}
}
@@ -0,0 +1,364 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import java.util.Arrays;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
/**
* Packed-long fragment shader permutation key for FFP emulation.
*
* Layout:
* long[0]: global bits (10) + unit 0 (47 bits at offset 10)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
* Per-unit packing (47 bits):
* 0: enabled
* 1-3: mode (MODULATE=0..COMBINE=5)
* 4: needsEnvColor
* 5-8: combineRgb (encoded 0-7)
* 9-12: combineAlpha (encoded 0-7)
* 13-14: scaleShiftRgb
* 15-16: scaleShiftAlpha
* 17-21: arg0 RGB (source:3 + operand:2)
* 22-26: arg1 RGB
* 27-31: arg2 RGB
* 32-36: arg0 Alpha
* 37-41: arg1 Alpha
* 42-46: arg2 Alpha
*/
public final class FragmentKey {
public static final int TEX_ENV_MODULATE = 0;
public static final int TEX_ENV_REPLACE = 1;
public static final int TEX_ENV_ADD = 2;
public static final int TEX_ENV_DECAL = 3;
public static final int TEX_ENV_BLEND = 4;
public static final int TEX_ENV_COMBINE = 5;
public static final int COMBINE_REPLACE = 0;
public static final int COMBINE_MODULATE = 1;
public static final int COMBINE_ADD = 2;
public static final int COMBINE_ADD_SIGNED = 3;
public static final int COMBINE_INTERPOLATE = 4;
public static final int COMBINE_SUBTRACT = 5;
public static final int COMBINE_DOT3_RGB = 6;
public static final int COMBINE_DOT3_RGBA = 7;
public static final int SRC_TEXTURE = 0;
public static final int SRC_CONSTANT = 1;
public static final int SRC_PRIMARY_COLOR = 2;
public static final int SRC_PREVIOUS = 3;
public static final int OP_SRC_COLOR = 0;
public static final int OP_ONE_MINUS_SRC_COLOR = 1;
public static final int OP_SRC_ALPHA = 2;
public static final int OP_ONE_MINUS_SRC_ALPHA = 3;
public static final int FOG_NONE = 0;
public static final int FOG_LINEAR = 1;
public static final int FOG_EXP = 2;
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 10;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
private static final int BIT_SEPARATE_SPECULAR = 6; // 1 bit
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
private static final int U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
private static final int U_NEEDS_ENV_COLOR = 4; // 1 bit
private static final int U_COMBINE_RGB = 5; // 4 bits
private static final int U_COMBINE_ALPHA = 9; // 4 bits
private static final int U_SCALE_RGB = 13; // 2 bits
private static final int U_SCALE_ALPHA = 15; // 2 bits
private static final int U_ARG0_RGB = 17; // 5 bits (source:3 + operand:2)
private static final int U_ARG1_RGB = 22; // 5 bits
private static final int U_ARG2_RGB = 27; // 5 bits
private static final int U_ARG0_ALPHA = 32; // 5 bits
private static final int U_ARG1_ALPHA = 37; // 5 bits
private static final int U_ARG2_ALPHA = 42; // 5 bits
static final int MAX_UNITS = 4;
private final long[] packed;
private final int hash;
private FragmentKey(long[] packed, int hash) {
this.packed = packed;
this.hash = hash;
}
/**
* Pack current GLSM fragment state into pre-allocated output array.
* @param out must be at least MAX_UNITS (4) longs
* @return number of significant longs (1..4)
*/
public static int packFromState(long[] out) {
long global = 0;
// Fog
if (GLStateManager.getFogMode().isEnabled()) {
final int fogMode = switch (GLStateManager.getFogState().getFogMode()) {
case GL11.GL_LINEAR -> FOG_LINEAR;
case GL11.GL_EXP -> FOG_EXP;
case GL11.GL_EXP2 -> FOG_EXP2;
default -> FOG_NONE;
};
global |= (long) fogMode;
}
// Alpha test
if (GLStateManager.getAlphaTest().isEnabled()) {
global |= (1L << BIT_ALPHA_TEST);
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
}
// Separate specular
if (GLStateManager.getLightingState().isEnabled()
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
global |= (1L << BIT_SEPARATE_SPECULAR);
}
// Per-unit state
int highestEnabled = -1;
long unit0Bits = 0;
for (int i = 0; i < MAX_UNITS; i++) {
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled();
if (texEnabled) highestEnabled = i;
final long unitBits = packUnit(i, texEnabled);
if (i == 0) {
unit0Bits = unitBits;
} else {
out[i] = unitBits;
}
}
final int nrEnabled = highestEnabled + 1;
global |= ((long) (nrEnabled & 0x7)) << BIT_NR_ENABLED_UNITS;
global |= (unit0Bits << GLOBAL_BITS);
out[0] = global;
return Math.max(1, nrEnabled);
}
private static long packUnit(int unitIndex, boolean texEnabled) {
if (!texEnabled) return 0;
final TexEnvState envState = GLStateManager.getTextures().getTexEnvState(unitIndex);
final int mode = encodeTexEnvMode(envState.mode);
long bits = 1L; // enabled
bits |= ((long) (mode & 0x7)) << U_MODE;
boolean needsEnvColor = (mode == TEX_ENV_BLEND);
if (mode == TEX_ENV_COMBINE) {
final int combRgb = encodeCombineFunc(envState.combineRgb);
final int combAlpha = encodeCombineFunc(envState.combineAlpha);
bits |= ((long) (combRgb & 0xF)) << U_COMBINE_RGB;
bits |= ((long) (combAlpha & 0xF)) << U_COMBINE_ALPHA;
bits |= ((long) (encodeScale(envState.scaleRgb) & 0x3)) << U_SCALE_RGB;
bits |= ((long) (encodeScale(envState.scaleAlpha) & 0x3)) << U_SCALE_ALPHA;
// Only pack args actually used by each channel's combine function.
// Unused arg slots stay 0, preventing TexEnvState defaults from inflating the cache.
final int numArgsRgb = combineNumArgs(combRgb);
final int numArgsAlpha = combineNumArgs(combAlpha);
final int maxArgs = Math.max(numArgsRgb, numArgsAlpha);
for (int a = 0; a < maxArgs; a++) {
if (a < numArgsRgb) {
final int srcR = encodeSource(envState.sourceRgb[a]);
final int opR = encodeOperand(envState.operandRgb[a]);
bits |= ((long) ((srcR & 0x7) | ((opR & 0x3) << 3))) << (U_ARG0_RGB + a * 5);
if (srcR == SRC_CONSTANT) needsEnvColor = true;
}
if (a < numArgsAlpha) {
final int srcA = encodeSource(envState.sourceAlpha[a]);
final int opA = encodeOperand(envState.operandAlpha[a]);
bits |= ((long) ((srcA & 0x7) | ((opA & 0x3) << 3))) << (U_ARG0_ALPHA + a * 5);
if (srcA == SRC_CONSTANT) needsEnvColor = true;
}
}
}
if (needsEnvColor) {
bits |= (1L << U_NEEDS_ENV_COLOR);
}
return bits;
}
/** Create FragmentKey from packed scratch data. Only called on cache miss. */
public static FragmentKey fromPacked(long[] scratch, int len) {
final long[] copy = Arrays.copyOf(scratch, len);
return new FragmentKey(copy, Arrays.hashCode(copy));
}
/** Convenience: pack + create. Allocates. For infrequent paths. */
public static FragmentKey fromState() {
final long[] scratch = new long[MAX_UNITS];
final int len = packFromState(scratch);
return fromPacked(scratch, len);
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof FragmentKey other)) return false;
if (hash != other.hash) return false;
return Arrays.equals(packed, other.packed);
}
public int fogMode() { return (int) (packed[0] & 0x3); }
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
private long unitBits(int i) {
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
}
public boolean unitEnabled(int i) { return (unitBits(i) & 1) != 0; }
public int unitMode(int i) { return (int) ((unitBits(i) >> U_MODE) & 0x7); }
public boolean unitNeedsEnvColor(int i) { return ((unitBits(i) >> U_NEEDS_ENV_COLOR) & 1) != 0; }
public int unitCombineRgb(int i) { return (int) ((unitBits(i) >> U_COMBINE_RGB) & 0xF); }
public int unitCombineAlpha(int i) { return (int) ((unitBits(i) >> U_COMBINE_ALPHA) & 0xF); }
public int unitScaleShiftRgb(int i) { return (int) ((unitBits(i) >> U_SCALE_RGB) & 0x3); }
public int unitScaleShiftAlpha(int i) { return (int) ((unitBits(i) >> U_SCALE_ALPHA) & 0x3); }
public int unitSourceRgb(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_RGB + arg * 5)) & 0x7); }
public int unitOperandRgb(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_RGB + arg * 5 + 3)) & 0x3); }
public int unitSourceAlpha(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_ALPHA + arg * 5)) & 0x7); }
public int unitOperandAlpha(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_ALPHA + arg * 5 + 3)) & 0x3); }
public boolean textureEnabled() {
for (int i = 0; i < nrEnabledUnits(); i++) {
if (unitEnabled(i)) return true;
}
return false;
}
public boolean lightmapEnabled() {
return nrEnabledUnits() > 1 && unitEnabled(1);
}
private static int encodeAlphaFunc(int glFunc) {
return glFunc & 0x7;
}
static int encodeTexEnvMode(int glMode) {
return switch (glMode) {
case GL11.GL_MODULATE -> TEX_ENV_MODULATE;
case GL11.GL_REPLACE -> TEX_ENV_REPLACE;
case GL11.GL_ADD -> TEX_ENV_ADD;
case GL11.GL_DECAL -> TEX_ENV_DECAL;
case GL11.GL_BLEND -> TEX_ENV_BLEND;
case GL13.GL_COMBINE -> TEX_ENV_COMBINE;
default -> TEX_ENV_MODULATE;
};
}
static int encodeCombineFunc(int glFunc) {
return switch (glFunc) {
case GL11.GL_REPLACE -> COMBINE_REPLACE;
case GL11.GL_MODULATE -> COMBINE_MODULATE;
case GL11.GL_ADD -> COMBINE_ADD;
case GL13.GL_ADD_SIGNED -> COMBINE_ADD_SIGNED;
case GL13.GL_INTERPOLATE -> COMBINE_INTERPOLATE;
case GL13.GL_SUBTRACT -> COMBINE_SUBTRACT;
case GL13.GL_DOT3_RGB -> COMBINE_DOT3_RGB;
case GL13.GL_DOT3_RGBA -> COMBINE_DOT3_RGBA;
default -> COMBINE_REPLACE;
};
}
static int encodeSource(int glSource) {
return switch (glSource) {
case GL11.GL_TEXTURE -> SRC_TEXTURE;
case GL13.GL_CONSTANT -> SRC_CONSTANT;
case GL13.GL_PRIMARY_COLOR -> SRC_PRIMARY_COLOR;
case GL13.GL_PREVIOUS -> SRC_PREVIOUS;
default -> {
GLStateManager.LOGGER.debug("Unknown TexEnv source 0x{}, falling back to SRC_PREVIOUS", Integer.toHexString(glSource));
yield SRC_PREVIOUS;
}
};
}
static int encodeOperand(int glOperand) {
return switch (glOperand) {
case GL11.GL_SRC_COLOR -> OP_SRC_COLOR;
case GL11.GL_ONE_MINUS_SRC_COLOR -> OP_ONE_MINUS_SRC_COLOR;
case GL11.GL_SRC_ALPHA -> OP_SRC_ALPHA;
case GL11.GL_ONE_MINUS_SRC_ALPHA -> OP_ONE_MINUS_SRC_ALPHA;
default -> OP_SRC_COLOR;
};
}
/** Number of source/operand args used by the given (encoded) combine function. */
public static int combineNumArgs(int func) {
return switch (func) {
case COMBINE_REPLACE -> 1;
case COMBINE_INTERPOLATE -> 3;
default -> 2; // MODULATE, ADD, ADD_SIGNED, SUBTRACT, DOT3_*
};
}
private static int encodeScale(float scale) {
if (scale >= 4.0f) return 2;
if (scale >= 2.0f) return 1;
return 0;
}
public static int decodeAlphaFunc(int encoded) {
return 0x0200 | encoded;
}
@Override
public String toString() {
final String fogName = switch (fogMode()) {
case FOG_NONE -> "NONE";
case FOG_LINEAR -> "LINEAR";
case FOG_EXP -> "EXP";
case FOG_EXP2 -> "EXP2";
default -> "?";
};
final StringBuilder sb = new StringBuilder();
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b units=%d",
fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
separateSpecular(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
} else {
final String modeName = switch (unitMode(i)) {
case TEX_ENV_MODULATE -> "MOD";
case TEX_ENV_REPLACE -> "REPL";
case TEX_ENV_ADD -> "ADD";
case TEX_ENV_DECAL -> "DEC";
case TEX_ENV_BLEND -> "BL";
case TEX_ENV_COMBINE -> "COMB";
default -> "?";
};
sb.append(String.format(" u%d=%s", i, modeName));
}
}
sb.append(']');
return sb.toString();
}
}
@@ -0,0 +1,322 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import org.lwjgl.opengl.GL11;
/**
* Generates GLSL 330 core fragment shaders for FFP emulation.
* Supports per-unit texenv chain including GL_COMBINE mode.
* Follows Mesa's ff_fragment_shader.c and st_nir_lower_fog.c.
*/
public final class FragmentShaderGenerator {
private FragmentShaderGenerator() {}
public static String generate(FragmentKey key) {
final StringBuilder sb = new StringBuilder(2048);
sb.append("#version 330 core\n\n");
emitInputs(sb, key);
emitUniforms(sb, key);
sb.append("out vec4 fragColor;\n\n");
sb.append("void main() {\n");
emitTextureSampling(sb, key);
emitTexEnvChain(sb, key);
emitSpecularAdd(sb, key);
emitAlphaTest(sb, key);
emitFog(sb, key);
sb.append(" fragColor = color;\n");
sb.append("}\n");
return sb.toString();
}
private static void emitInputs(StringBuilder sb, FragmentKey key) {
sb.append("// Inputs from vertex shader\n");
sb.append("in vec4 v_Color;\n");
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor;\n");
}
if (key.textureEnabled()) {
sb.append("in vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("in vec4 v_TexCoord1;\n");
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("in float v_FogCoord;\n");
}
sb.append('\n');
}
private static void emitUniforms(StringBuilder sb, FragmentKey key) {
// Samplers for each enabled unit
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i)) {
sb.append("uniform sampler2D u_Sampler").append(i).append(";\n");
}
}
if (key.alphaTestEnabled()) {
sb.append("uniform float u_AlphaRef;\n");
}
// Per-unit texEnvColor uniforms
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i) && key.unitNeedsEnvColor(i)) {
sb.append("uniform vec4 u_TexEnvColor").append(i).append(";\n");
}
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("uniform vec4 u_FogParams;\n");
sb.append("uniform vec4 u_FogColor;\n");
}
sb.append('\n');
}
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
// Unit 1 is the lightmap — uses v_TexCoord1.
// Units 2-3 intentionally share unit 0's texture coordinates since
// the vertex shader only provides 2 tex coord varyings (unit 0 and unit 1/lightmap).
final String texCoord = (i == 1) ? "v_TexCoord1.st" : "v_TexCoord0.st";
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", ").append(texCoord).append(");\n");
}
}
private static void emitTexEnvChain(StringBuilder sb, FragmentKey key) {
if (!key.textureEnabled()) {
sb.append(" // No texture -- vertex color only\n");
sb.append(" vec4 color = v_Color;\n");
return;
}
// Process units in order; "previous" starts as vertex color
boolean firstUnit = true;
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
final String texVar = "tex" + i + "Color";
final String envColorVar = "u_TexEnvColor" + i;
final String prevVar = firstUnit ? "v_Color" : "color";
final String assign = firstUnit ? " vec4 color = " : " color = ";
if (key.unitMode(i) == FragmentKey.TEX_ENV_COMBINE) {
emitCombineUnit(sb, key, i, texVar, envColorVar, prevVar, assign);
} else {
emitSimpleUnit(sb, key, i, texVar, envColorVar, prevVar, assign);
}
firstUnit = false;
}
}
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
switch (key.unitMode(unit)) {
case FragmentKey.TEX_ENV_REPLACE -> {
sb.append(" // Unit ").append(unit).append(" (REPLACE)\n");
sb.append(assign).append(texVar).append(";\n");
}
case FragmentKey.TEX_ENV_ADD -> {
sb.append(" // Unit ").append(unit).append(" (ADD)\n");
sb.append(assign).append("vec4(clamp(").append(prevVar).append(".rgb + ").append(texVar).append(".rgb, 0.0, 1.0), ")
.append(prevVar).append(".a * ").append(texVar).append(".a);\n");
}
case FragmentKey.TEX_ENV_DECAL -> {
sb.append(" // Unit ").append(unit).append(" (DECAL)\n");
sb.append(assign).append("vec4(mix(").append(prevVar).append(".rgb, ").append(texVar).append(".rgb, ").append(texVar).append(".a), ")
.append(prevVar).append(".a);\n");
}
case FragmentKey.TEX_ENV_BLEND -> {
sb.append(" // Unit ").append(unit).append(" (BLEND)\n");
sb.append(assign).append("vec4(").append(prevVar).append(".rgb * (vec3(1.0) - ").append(texVar).append(".rgb) + ")
.append(envColorVar).append(".rgb * ").append(texVar).append(".rgb, ")
.append(prevVar).append(".a * ").append(texVar).append(".a);\n");
}
default -> { // MODULATE
sb.append(" // Unit ").append(unit).append(" (MODULATE)\n");
sb.append(assign).append(prevVar).append(" * ").append(texVar).append(";\n");
}
}
}
/** Emit a GL_COMBINE texenv unit */
private static void emitCombineUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
sb.append(" // Unit ").append(unit).append(" (COMBINE)\n");
final String rgbResult = "combRgb" + unit;
final String alphaResult = "combAlpha" + unit;
if (key.unitCombineRgb(unit) == FragmentKey.COMBINE_DOT3_RGBA) {
sb.append(" vec3 ").append(rgbResult).append(";\n");
sb.append(" {\n");
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, true);
sb.append(" }\n");
sb.append(assign).append("vec4(").append(rgbResult).append(", ").append(rgbResult).append(".x);\n");
} else {
sb.append(" vec3 ").append(rgbResult).append(";\n");
sb.append(" float ").append(alphaResult).append(";\n");
sb.append(" {\n");
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, true);
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, false);
sb.append(" }\n");
sb.append(assign).append("vec4(").append(rgbResult).append(", ").append(alphaResult).append(");\n");
}
}
/**
* Emit one channel (RGB or Alpha) of a GL_COMBINE unit.
*/
private static void emitCombineChannel(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, boolean isRgb) {
final String suffix = isRgb ? "Rgb" : "Alpha";
final String type = isRgb ? "vec3" : "float";
final int combineFunc = isRgb ? key.unitCombineRgb(unit) : key.unitCombineAlpha(unit);
final int scaleShift = isRgb ? key.unitScaleShiftRgb(unit) : key.unitScaleShiftAlpha(unit);
// Guard: DOT3 is invalid for alpha channel — fallback to REPLACE
final int effectiveFunc;
if (!isRgb && (combineFunc == FragmentKey.COMBINE_DOT3_RGB || combineFunc == FragmentKey.COMBINE_DOT3_RGBA)) {
effectiveFunc = FragmentKey.COMBINE_REPLACE;
} else {
effectiveFunc = combineFunc;
}
// Determine how many args we need
final int numArgs = combineNumArgs(effectiveFunc);
// Emit argument resolution
for (int a = 0; a < numArgs; a++) {
final int source = isRgb ? key.unitSourceRgb(unit, a) : key.unitSourceAlpha(unit, a);
final int operand = isRgb ? key.unitOperandRgb(unit, a) : key.unitOperandAlpha(unit, a);
final String argName = "arg" + suffix + a + "_" + unit;
final String sourceExpr = resolveSource(source, texVar, envColorVar, prevVar);
final String operandExpr = applyOperand(sourceExpr, operand, isRgb);
sb.append(" ").append(type).append(" ").append(argName).append(" = ").append(operandExpr).append(";\n");
}
// Emit combine operation
final String resultName = "comb" + suffix + unit;
final String a0 = "arg" + suffix + "0_" + unit;
final String a1 = "arg" + suffix + "1_" + unit;
final String a2 = "arg" + suffix + "2_" + unit;
sb.append(" ").append(resultName).append(" = ");
switch (effectiveFunc) {
case FragmentKey.COMBINE_REPLACE -> sb.append(a0);
case FragmentKey.COMBINE_MODULATE -> sb.append(a0).append(" * ").append(a1);
case FragmentKey.COMBINE_ADD -> sb.append(a0).append(" + ").append(a1);
case FragmentKey.COMBINE_ADD_SIGNED ->
sb.append(a0).append(" + ").append(a1).append(" - ").append(isRgb ? "vec3(0.5)" : "0.5");
case FragmentKey.COMBINE_SUBTRACT -> sb.append(a0).append(" - ").append(a1);
case FragmentKey.COMBINE_INTERPOLATE ->
sb.append("mix(").append(a1).append(", ").append(a0).append(", ").append(a2).append(")");
case FragmentKey.COMBINE_DOT3_RGB, FragmentKey.COMBINE_DOT3_RGBA ->
sb.append("vec3(4.0 * dot(").append(a0).append(" - vec3(0.5), ").append(a1).append(" - vec3(0.5)))");
default -> sb.append(a0); // fallback to REPLACE
}
sb.append(";\n");
// Apply scale
if (scaleShift > 0) {
final String scaleFactor = scaleShift == 1 ? "2.0" : "4.0";
sb.append(" ").append(resultName).append(" *= ").append(scaleFactor).append(";\n");
}
// Clamp
sb.append(" ").append(resultName).append(" = clamp(").append(resultName).append(", ")
.append(isRgb ? "vec3(0.0), vec3(1.0)" : "0.0, 1.0").append(");\n");
}
private static String resolveSource(int source, String texVar, String envColorVar, String prevVar) {
return switch (source) {
case FragmentKey.SRC_TEXTURE -> texVar;
case FragmentKey.SRC_CONSTANT -> envColorVar;
case FragmentKey.SRC_PRIMARY_COLOR -> "v_Color";
case FragmentKey.SRC_PREVIOUS -> prevVar;
default -> prevVar;
};
}
private static String applyOperand(String sourceExpr, int operand, boolean isRgb) {
if (isRgb) {
return switch (operand) {
case FragmentKey.OP_SRC_COLOR -> sourceExpr + ".rgb";
case FragmentKey.OP_ONE_MINUS_SRC_COLOR -> "(vec3(1.0) - " + sourceExpr + ".rgb)";
case FragmentKey.OP_SRC_ALPHA -> "vec3(" + sourceExpr + ".a)";
case FragmentKey.OP_ONE_MINUS_SRC_ALPHA -> "vec3(1.0 - " + sourceExpr + ".a)";
default -> sourceExpr + ".rgb";
};
} else {
return switch (operand) {
case FragmentKey.OP_SRC_ALPHA -> sourceExpr + ".a";
case FragmentKey.OP_ONE_MINUS_SRC_ALPHA -> "(1.0 - " + sourceExpr + ".a)";
// Per GL spec, alpha channel operands are SRC_ALPHA or ONE_MINUS_SRC_ALPHA only, but handle color operands gracefully
case FragmentKey.OP_SRC_COLOR -> sourceExpr + ".a";
case FragmentKey.OP_ONE_MINUS_SRC_COLOR -> "(1.0 - " + sourceExpr + ".a)";
default -> sourceExpr + ".a";
};
}
}
private static int combineNumArgs(int func) {
return FragmentKey.combineNumArgs(func);
}
private static void emitSpecularAdd(StringBuilder sb, FragmentKey key) {
if (key.separateSpecular()) {
sb.append(" // Add separate specular\n");
sb.append(" color.rgb += v_SpecularColor;\n");
}
}
private static void emitAlphaTest(StringBuilder sb, FragmentKey key) {
if (!key.alphaTestEnabled()) return;
sb.append(" // Alpha test\n");
final int func = FragmentKey.decodeAlphaFunc(key.alphaTestFunc());
sb.append(" float qa = round(color.a * 255.0);\n");
switch (func) {
case GL11.GL_NEVER ->
sb.append(" discard;\n");
case GL11.GL_LESS ->
sb.append(" if (qa >= u_AlphaRef * 255.0) discard;\n");
case GL11.GL_EQUAL -> {
sb.append(" float qr = round(u_AlphaRef * 255.0);\n");
sb.append(" if (qa != qr) discard;\n");
}
case GL11.GL_LEQUAL ->
sb.append(" if (qa > u_AlphaRef * 255.0) discard;\n");
case GL11.GL_GREATER ->
sb.append(" if (qa <= u_AlphaRef * 255.0) discard;\n");
case GL11.GL_NOTEQUAL -> {
sb.append(" float qr = round(u_AlphaRef * 255.0);\n");
sb.append(" if (qa == qr) discard;\n");
}
case GL11.GL_GEQUAL ->
sb.append(" if (qa < u_AlphaRef * 255.0) discard;\n");
case GL11.GL_ALWAYS -> {}
}
}
private static void emitFog(StringBuilder sb, FragmentKey key) {
final int fogMode = key.fogMode();
if (fogMode == FragmentKey.FOG_NONE) return;
sb.append(" // Fog\n");
sb.append(" float f;\n");
switch (fogMode) {
case FragmentKey.FOG_LINEAR ->
sb.append(" f = v_FogCoord * u_FogParams.x + u_FogParams.y;\n");
case FragmentKey.FOG_EXP ->
sb.append(" f = exp2(-(v_FogCoord * u_FogParams.z));\n");
case FragmentKey.FOG_EXP2 -> {
sb.append(" float fogTmp = v_FogCoord * u_FogParams.w;\n");
sb.append(" f = exp2(-(fogTmp * fogTmp));\n");
}
}
sb.append(" f = clamp(f, 0.0, 1.0);\n");
sb.append(" color.rgb = mix(u_FogColor.rgb, color.rgb, f);\n");
}
}
@@ -0,0 +1,104 @@
package com.gtnewhorizons.angelica.glsm.ffp;
/**
* Generates GLSL 330 core geometry shaders for wide line emulation.
*/
public final class GeometryShaderGenerator {
private GeometryShaderGenerator() {}
public static String generate(VertexKey key) {
final StringBuilder sb = new StringBuilder(1024);
sb.append("#version 330 core\n\n");
sb.append("layout(lines) in;\n");
sb.append("layout(triangle_strip, max_vertices = 4) out;\n\n");
sb.append("uniform vec2 u_ViewportSize;\n");
sb.append("uniform float u_LineWidth;\n\n");
sb.append("// Pass-through varyings\n");
sb.append("in vec4 v_Color_gs[];\n");
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor_gs[];\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append("in vec4 v_TexCoord0_gs[];\n");
}
if (key.lightmapEnabled()) {
sb.append("in vec4 v_TexCoord1_gs[];\n");
}
if (key.fogEnabled()) {
sb.append("in float v_FogCoord_gs[];\n");
}
sb.append('\n');
sb.append("out vec4 v_Color;\n");
if (key.separateSpecular()) {
sb.append("out vec3 v_SpecularColor;\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append("out vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("out vec4 v_TexCoord1;\n");
}
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
sb.append('\n');
sb.append("void main() {\n");
sb.append(" vec4 c0 = gl_in[0].gl_Position;\n");
sb.append(" vec4 c1 = gl_in[1].gl_Position;\n\n");
sb.append(" // NDC positions\n");
sb.append(" vec2 n0 = c0.xy / c0.w;\n");
sb.append(" vec2 n1 = c1.xy / c1.w;\n\n");
sb.append(" // Screen-space direction and perpendicular\n");
sb.append(" vec2 dir = normalize((n1 - n0) * u_ViewportSize);\n");
sb.append(" vec2 offset = vec2(-dir.y, dir.x) * u_LineWidth / u_ViewportSize;\n\n");
emitVertex(sb, key, 0, "+offset");
emitVertex(sb, key, 0, "-offset");
emitVertex(sb, key, 1, "+offset");
emitVertex(sb, key, 1, "-offset");
sb.append(" EndPrimitive();\n");
sb.append("}\n");
return sb.toString();
}
private static void emitVertex(StringBuilder sb, VertexKey key, int endpointIdx, String offsetExpr) {
final String ci = "c" + endpointIdx;
final String ni = "n" + endpointIdx;
final String idx = "[" + endpointIdx + "]";
sb.append(" // Endpoint ").append(endpointIdx).append(' ').append(offsetExpr).append('\n');
sb.append(" v_Color = v_Color_gs").append(idx).append(";\n");
if (key.separateSpecular()) {
sb.append(" v_SpecularColor = v_SpecularColor_gs").append(idx).append(";\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
}
if (key.lightmapEnabled()) {
sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
}
if (key.fogEnabled()) {
sb.append(" v_FogCoord = v_FogCoord_gs").append(idx).append(";\n");
}
if (key.clipPlanesEnabled()) {
for (int i = 0; i < 8; i++) {
sb.append(" gl_ClipDistance[").append(i).append("] = gl_in").append(idx).append(".gl_ClipDistance[").append(i).append("];\n");
}
}
sb.append(" gl_Position = vec4((").append(ni).append(' ').append(offsetExpr).append(") * ").append(ci).append(".w, ").append(ci).append(".z, ").append(ci).append(".w);\n");
sb.append(" EmitVertex();\n\n");
}
}
@@ -0,0 +1,274 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import lombok.Getter;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.KHRDebug;
import java.util.concurrent.atomic.AtomicInteger;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* A compiled and linked FFP emulation program (vertex + fragment + optional geometry shaders). Owns the GL program handle and caches uniform locations.
*/
public class Program {
private static final AtomicInteger PROGRAM_COUNTER = new AtomicInteger();
@Getter private final int programId;
@Getter private final VertexKey vertexKey;
@Getter private final FragmentKey fragmentKey;
// Uniform locations (-1 = not present in this variant)
// Matrices
public int locModelViewMatrix = -1;
public int locProjectionMatrix = -1;
public int locMVPMatrix = -1;
public int locNormalMatrix = -1;
public int locTextureMatrix0 = -1;
// Current state defaults
public int locCurrentNormal = -1;
public int locCurrentColor = -1;
public int locCurrentTexCoord = -1;
public int locCurrentLightmapCoord = -1;
public int locLightmapTextureMatrix = -1;
// Lighting - color material path (raw values)
public int locLightModelAmbient = -1;
public int locMaterialEmission = -1;
public int locMaterialAmbient = -1;
public int locMaterialDiffuse = -1;
public int locMaterialSpecular = -1;
public int locMaterialShininess = -1;
public int locLight0Ambient = -1;
public int locLight0Diffuse = -1;
public int locLight0Specular = -1;
public int locLight1Ambient = -1;
public int locLight1Diffuse = -1;
public int locLight1Specular = -1;
// Lighting - pre-computed path
public int locSceneColor = -1;
public int locLightProd0Ambient = -1;
public int locLightProd0Diffuse = -1;
public int locLightProd0Specular = -1;
public int locLightProd1Ambient = -1;
public int locLightProd1Diffuse = -1;
public int locLightProd1Specular = -1;
// Light positions
public int locLight0Position = -1;
public int locLight1Position = -1;
// Normal scale (for rescale normals)
public int locNormalScale = -1;
// TexGen plane uniforms
public int locTexGenObjPlaneS = -1;
public int locTexGenObjPlaneT = -1;
public int locTexGenObjPlaneR = -1;
public int locTexGenObjPlaneQ = -1;
public int locTexGenEyePlaneS = -1;
public int locTexGenEyePlaneT = -1;
public int locTexGenEyePlaneR = -1;
public int locTexGenEyePlaneQ = -1;
// Clip planes
public int locClipPlanes = -1;
// Wide line emulation (geometry shader)
public int locViewportSize = -1;
public int locLineWidth = -1;
// Fragment uniforms
public final int[] locSampler = { -1, -1, -1, -1 };
public int locAlphaRef = -1;
public final int[] locTexEnvColor = { -1, -1, -1, -1 };
public int locFogParams = -1;
public int locFogColor = -1;
Program(int programId, VertexKey vertexKey, FragmentKey fragmentKey) {
this.programId = programId;
this.vertexKey = vertexKey;
this.fragmentKey = fragmentKey;
resolveLocations();
}
private void resolveLocations() {
locModelViewMatrix = loc("u_ModelViewMatrix");
locProjectionMatrix = loc("u_ProjectionMatrix");
locMVPMatrix = loc("u_MVPMatrix");
locNormalMatrix = loc("u_NormalMatrix");
locTextureMatrix0 = loc("u_TextureMatrix0");
locCurrentNormal = loc("u_CurrentNormal");
locCurrentColor = loc("u_CurrentColor");
locCurrentTexCoord = loc("u_CurrentTexCoord");
locCurrentLightmapCoord = loc("u_CurrentLightmapCoord");
locLightmapTextureMatrix = loc("u_LightmapTextureMatrix");
// Color material path
locLightModelAmbient = loc("u_LightModelAmbient");
locMaterialEmission = loc("u_MaterialEmission");
locMaterialAmbient = loc("u_MaterialAmbient");
locMaterialDiffuse = loc("u_MaterialDiffuse");
locMaterialSpecular = loc("u_MaterialSpecular");
locMaterialShininess = loc("u_MaterialShininess");
locLight0Ambient = loc("u_Light0Ambient");
locLight0Diffuse = loc("u_Light0Diffuse");
locLight0Specular = loc("u_Light0Specular");
locLight1Ambient = loc("u_Light1Ambient");
locLight1Diffuse = loc("u_Light1Diffuse");
locLight1Specular = loc("u_Light1Specular");
// Pre-computed path
locSceneColor = loc("u_SceneColor");
locLightProd0Ambient = loc("u_LightProd0Ambient");
locLightProd0Diffuse = loc("u_LightProd0Diffuse");
locLightProd0Specular = loc("u_LightProd0Specular");
locLightProd1Ambient = loc("u_LightProd1Ambient");
locLightProd1Diffuse = loc("u_LightProd1Diffuse");
locLightProd1Specular = loc("u_LightProd1Specular");
// Light positions
locLight0Position = loc("u_Light0Position");
locLight1Position = loc("u_Light1Position");
locNormalScale = loc("u_NormalScale");
// TexGen
locTexGenObjPlaneS = loc("u_TexGenObjPlaneS");
locTexGenObjPlaneT = loc("u_TexGenObjPlaneT");
locTexGenObjPlaneR = loc("u_TexGenObjPlaneR");
locTexGenObjPlaneQ = loc("u_TexGenObjPlaneQ");
locTexGenEyePlaneS = loc("u_TexGenEyePlaneS");
locTexGenEyePlaneT = loc("u_TexGenEyePlaneT");
locTexGenEyePlaneR = loc("u_TexGenEyePlaneR");
locTexGenEyePlaneQ = loc("u_TexGenEyePlaneQ");
// Clip planes
locClipPlanes = loc("u_ClipPlane[0]");
// Wide line emulation
locViewportSize = loc("u_ViewportSize");
locLineWidth = loc("u_LineWidth");
// Fragment
for (int i = 0; i < 4; i++) {
locSampler[i] = loc("u_Sampler" + i);
}
locAlphaRef = loc("u_AlphaRef");
for (int i = 0; i < 4; i++) {
locTexEnvColor[i] = loc("u_TexEnvColor" + i);
}
locFogParams = loc("u_FogParams");
locFogColor = loc("u_FogColor");
}
private int loc(String name) {
return RENDER_BACKEND.getUniformLocation(programId, name);
}
public void destroy() {
RENDER_BACKEND.deleteProgram(programId);
}
/**
* Compile vertex + fragment + optional geometry shaders, link into a program, and return the FFPProgram.
*/
static Program create(VertexKey vk, FragmentKey fk, String vertSrc, String fragSrc, String geomSrc) {
final RenderBackend backend = RENDER_BACKEND;
final int id = PROGRAM_COUNTER.getAndIncrement();
final String vkHex = Long.toHexString(vk.pack());
final boolean hasGeom = geomSrc != null;
int shaderCount = 0;
final int[] shaders = new int[5]; // vertex, fragment, geometry, tess control, tess evaluation
int program = 0;
try {
shaders[shaderCount++] = compileShader(GL20.GL_VERTEX_SHADER, vertSrc, "ffp_v_" + vkHex);
shaders[shaderCount++] = compileShader(GL20.GL_FRAGMENT_SHADER, fragSrc, "ffp_f_" + id);
if (geomSrc != null) {
shaders[shaderCount++] = compileShader(GL32.GL_GEOMETRY_SHADER, geomSrc, "ffp_g_" + id);
}
program = backend.createProgram();
for (int i = 0; i < shaderCount; i++) backend.attachShader(program, shaders[i]);
backend.linkProgram(program);
final String log = backend.getProgramInfoLog(program, backend.getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
if (!log.isEmpty()) {
GLStateManager.LOGGER.warn("FFP program link log (vk=0x{}, fk={}): {}", vkHex, fk, log);
}
if (backend.getProgrami(program, GL20.GL_LINK_STATUS) != GL11.GL_TRUE) {
throw new RuntimeException("FFP shader link failed (vk=0x" + vkHex + ", fk=" + fk + "): " + log);
}
final String debugName = "FFP(v=0x" + vkHex + ",f=" + id + (hasGeom ? ",g" : "") + ")";
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName);
// Detach and delete individual shaders — they're linked into the program
for (int i = 0; i < shaderCount; i++) {
backend.detachShader(program, shaders[i]);
backend.deleteShader(shaders[i]);
}
shaderCount = 0;
final Program ffpProgram = new Program(program, vk, fk);
final int previousProgram = GLStateManager.getActiveProgram();
backend.useProgram(program);
for (int i = 0; i < 4; i++) {
if (ffpProgram.locSampler[i] != -1) backend.uniform1i(ffpProgram.locSampler[i], i);
}
backend.useProgram(previousProgram);
return ffpProgram;
} catch (RuntimeException e) {
if (program != 0) backend.deleteProgram(program);
for (int i = 0; i < shaderCount; i++) backend.deleteShader(shaders[i]);
throw e;
}
}
private static int compileShader(int type, String src, String name) {
final RenderBackend backend = RENDER_BACKEND;
final int shader = backend.createShader(type);
backend.shaderSource(shader, src);
backend.compileShader(shader);
final String typeName = switch (type) {
case GL20.GL_VERTEX_SHADER -> "vertex";
case GL20.GL_FRAGMENT_SHADER -> "fragment";
case GL32.GL_GEOMETRY_SHADER -> "geometry";
case GL40.GL_TESS_CONTROL_SHADER -> "tess_control";
case GL40.GL_TESS_EVALUATION_SHADER -> "tess_evaluation";
case GL43.GL_COMPUTE_SHADER -> "compute";
default -> "unknown(0x" + Integer.toHexString(type) + ")";
};
GLDebug.nameObject(KHRDebug.GL_SHADER, shader, name + "(" + typeName + ")");
final String log = backend.getShaderInfoLog(shader, backend.getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
if (!log.isEmpty()) {
GLStateManager.LOGGER.warn("FFP {} shader compilation log for {}: {}", typeName, name, log);
}
final int result = backend.getShaderi(shader, GL20.GL_COMPILE_STATUS);
if (result != GL11.GL_TRUE) {
GLStateManager.LOGGER.error("FFP {} shader source:\n{}", typeName, src);
backend.deleteShader(shader);
throw new RuntimeException("FFP " + typeName + " shader compilation failed for " + name + ": " + log);
}
return shader;
}
}
@@ -0,0 +1,294 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import lombok.Setter;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.IdentityHashMap;
/**
* Open-addressed cache for FFP shader program variants.
* Keyed on (vertexKey, fragmentKey) pairs stored inline as raw longs — zero allocation on cache hits.
* Converges on Mesa's prog_cache.c: hash+memcmp over packed bytes, open addressing.
*
* <p>Layout per slot in {@code keys[]}: {@link #SLOT_STRIDE}=6 longs
* <pre>
* [0]: vertexKeyPacked
* [1]: (fkLen &lt;&lt; 48) | (hash &amp; 0x0000_FFFF_FFFF_FFFF) — never 0 for valid entries (fkLen &gt;= 1)
* [2..5]: fk0..fk3
* </pre>
*/
public class ShaderCache {
private static final int SLOT_STRIDE = 6;
private static final int INITIAL_CAPACITY = 64; // power of 2
private static final long EMPTY_MARKER = 0L; // slot [1] == 0 means empty (fkLen >= 1 guarantees nonzero)
private final Long2ObjectOpenHashMap<String> vertexSourceCache = new Long2ObjectOpenHashMap<>();
private final Long2ObjectOpenHashMap<String> geometrySourceCache = new Long2ObjectOpenHashMap<>();
// Open-addressed table — parallel arrays
private long[] keys;
private Program[] programs;
private String[] fragSources; // fragment source per slot; reused across VK variants via findFragSource
private int capacity;
private int count;
private int uniqueFragCount;
// Fast-path: last-used key (raw longs, no objects)
private long lastVK = Long.MIN_VALUE;
private int lastFKLen;
private long lastFK0, lastFK1, lastFK2, lastFK3;
private Program lastProgram;
private int fragmentDumpCounter;
@Setter private Path dumpDir = null;
public ShaderCache() {
capacity = INITIAL_CAPACITY;
keys = new long[capacity * SLOT_STRIDE];
programs = new Program[capacity];
fragSources = new String[capacity];
}
/**
* Get or create an FFP program for the given state keys.
*
* @param vkPacked packed vertex key
* @param fkPacked pre-allocated scratch array with packed fragment key longs
* @param fkLen number of significant longs in fkPacked (1..4)
*/
public Program getOrCreate(long vkPacked, long[] fkPacked, int fkLen) {
// Fast-path: compare raw longs inline
if (vkPacked == lastVK && fkLen == lastFKLen && lastProgram != null
&& fkPacked[0] == lastFK0
&& (fkLen < 2 || fkPacked[1] == lastFK1)
&& (fkLen < 3 || fkPacked[2] == lastFK2)
&& (fkLen < 4 || fkPacked[3] == lastFK3)) {
return lastProgram;
}
final long hash = hashKey(vkPacked, fkPacked, fkLen);
final long storedWord = ((long) fkLen << 48) | (hash & 0x0000_FFFF_FFFF_FFFFL);
final int mask = capacity - 1;
int idx = (int) (hash & mask);
while (true) {
final int base = idx * SLOT_STRIDE;
final long slot1 = keys[base + 1];
if (slot1 == EMPTY_MARKER) {
break;
}
if (slot1 == storedWord && keys[base] == vkPacked && keysMatch(base + 2, fkPacked, fkLen)) {
final Program p = programs[idx];
setLastUsed(vkPacked, fkPacked, fkLen, p);
return p;
}
idx = (idx + 1) & mask;
}
if (count >= (capacity * 3) / 4) {
grow();
return getOrCreate(vkPacked, fkPacked, fkLen);
}
final VertexKey vk = VertexKey.fromPacked(vkPacked);
final String vertSrc = getOrGenerateVertex(vk);
final FragmentKey fk = FragmentKey.fromPacked(fkPacked, fkLen);
String fragSrc = findFragSource(fkPacked, fkLen);
if (fragSrc == null) {
fragSrc = FragmentShaderGenerator.generate(fk);
uniqueFragCount++;
}
final String geomSrc = vk.wideLineEmulation() ? getOrGenerateGeometry(vk) : null;
final Program program = Program.create(vk, fk, vertSrc, fragSrc, geomSrc);
final int insertIdx = findEmptySlot(hash);
final int base = insertIdx * SLOT_STRIDE;
keys[base] = vkPacked;
keys[base + 1] = storedWord;
for (int i = 0; i < fkLen; i++) keys[base + 2 + i] = fkPacked[i];
programs[insertIdx] = program;
fragSources[insertIdx] = fragSrc;
count++;
setLastUsed(vkPacked, fkPacked, fkLen, program);
GLStateManager.LOGGER.debug("FFP variant compiled: {} + {} (total: {} programs)", vk, fk, count);
if (dumpDir != null) {
dumpShader(Long.toHexString(vkPacked), ".vert.glsl", vertSrc);
dumpShader("frag_" + fragmentDumpCounter++, ".frag.glsl", fragSrc);
if (geomSrc != null) {
dumpShader(Long.toHexString(vkPacked), ".geom.glsl", geomSrc);
}
}
return program;
}
private String findFragSource(long[] fkPacked, int fkLen) {
for (int i = 0; i < capacity; i++) {
final int base = i * SLOT_STRIDE;
if (keys[base + 1] == EMPTY_MARKER) continue;
final int storedLen = (int) (keys[base + 1] >>> 48);
if (storedLen != fkLen) continue;
if (keysMatch(base + 2, fkPacked, fkLen)) {
return fragSources[i];
}
}
return null;
}
private boolean keysMatch(int keysOffset, long[] fkPacked, int fkLen) {
for (int i = 0; i < fkLen; i++) {
if (keys[keysOffset + i] != fkPacked[i]) return false;
}
return true;
}
private void setLastUsed(long vkPacked, long[] fkPacked, int fkLen, Program program) {
lastVK = vkPacked;
lastFKLen = fkLen;
lastFK0 = fkPacked[0];
lastFK1 = fkLen >= 2 ? fkPacked[1] : 0;
lastFK2 = fkLen >= 3 ? fkPacked[2] : 0;
lastFK3 = fkLen >= 4 ? fkPacked[3] : 0;
lastProgram = program;
}
private int findEmptySlot(long hash) {
final int mask = capacity - 1;
int idx = (int) (hash & mask);
while (keys[idx * SLOT_STRIDE + 1] != EMPTY_MARKER) {
idx = (idx + 1) & mask;
}
return idx;
}
/**
* Hash over (vk + fk longs). Adapted from Mesa's hash_key — mix-rotate-multiply.
*/
private static long hashKey(long vkPacked, long[] fkPacked, int fkLen) {
long h = vkPacked * 0x9E3779B97F4A7C15L;
for (int i = 0; i < fkLen; i++) {
h ^= fkPacked[i] * 0x517CC1B727220A95L;
h = Long.rotateLeft(h, 31) * 0x9E3779B97F4A7C15L;
}
return h | 1L; // ensure nonzero lower bits
}
private void grow() {
final int oldCap = capacity;
final long[] oldKeys = keys;
final Program[] oldPrograms = programs;
final String[] oldFragSources = fragSources;
capacity = oldCap * 2;
keys = new long[capacity * SLOT_STRIDE];
programs = new Program[capacity];
fragSources = new String[capacity];
count = 0;
final int mask = capacity - 1;
for (int i = 0; i < oldCap; i++) {
final int oldBase = i * SLOT_STRIDE;
if (oldKeys[oldBase + 1] == EMPTY_MARKER) continue;
final long storedHash = oldKeys[oldBase + 1] & 0x0000_FFFF_FFFF_FFFFL;
int idx = (int) (storedHash & mask);
while (keys[idx * SLOT_STRIDE + 1] != EMPTY_MARKER) {
idx = (idx + 1) & mask;
}
final int newBase = idx * SLOT_STRIDE;
System.arraycopy(oldKeys, oldBase, keys, newBase, SLOT_STRIDE);
programs[idx] = oldPrograms[i];
fragSources[idx] = oldFragSources[i];
count++;
}
}
private String getOrGenerateVertex(VertexKey vk) {
return vertexSourceCache.computeIfAbsent(vk.pack(), k -> VertexShaderGenerator.generate(vk));
}
private String getOrGenerateGeometry(VertexKey vk) {
return geometrySourceCache.computeIfAbsent(vk.pack(), k -> GeometryShaderGenerator.generate(vk));
}
private void dumpShader(String name, String suffix, String source) {
if (dumpDir == null) return;
try {
Files.createDirectories(dumpDir);
final Path file = dumpDir.resolve("ffp_" + name + suffix);
if (!Files.exists(file)) {
Files.writeString(file, source, StandardCharsets.UTF_8);
}
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump FFP shader to {}: {}", dumpDir, e.getMessage());
}
}
/** Dump all cached shaders to disk. */
public void dumpAll(Path outputDir) {
try {
Files.createDirectories(outputDir);
for (var entry : vertexSourceCache.entrySet()) {
dumpShader(Long.toHexString(entry.getKey()), ".vert.glsl", entry.getValue());
}
final IdentityHashMap<String, Integer> seen = new IdentityHashMap<>();
for (int i = 0; i < capacity; i++) {
if (fragSources[i] != null && !seen.containsKey(fragSources[i])) {
final int idx = seen.size();
seen.put(fragSources[i], idx);
dumpShader("frag_" + idx, ".frag.glsl", fragSources[i]);
}
}
for (var entry : geometrySourceCache.entrySet()) {
dumpShader(Long.toHexString(entry.getKey()), ".geom.glsl", entry.getValue());
}
GLStateManager.LOGGER.info("Dumped {} vertex, {} geometry, and {} fragment FFP shader sources to {}",
vertexSourceCache.size(), geometrySourceCache.size(), seen.size(), outputDir);
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump FFP shaders: {}", e.getMessage());
}
}
/** Destroy all cached programs and clear caches. */
public void destroy() {
for (int i = 0; i < capacity; i++) {
if (programs[i] != null) {
programs[i].destroy();
}
}
Arrays.fill(keys, 0);
Arrays.fill(programs, null);
Arrays.fill(fragSources, null);
count = 0;
uniqueFragCount = 0;
vertexSourceCache.clear();
geometrySourceCache.clear();
lastProgram = null;
lastVK = Long.MIN_VALUE;
GLStateManager.LOGGER.debug("FFP shader cache cleared");
}
public int getProgramCount() {
return count;
}
public int getVertexVariantCount() {
return vertexSourceCache.size();
}
public int getFragmentVariantCount() {
return uniqueFragCount;
}
}
@@ -0,0 +1,198 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.stacks.Vec3fStack;
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import lombok.Getter;
import org.joml.Vector3f;
import org.joml.Vector4f;
import java.nio.file.Paths;
import java.util.Arrays;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* FFP shader manager
*/
public class ShaderManager {
private static final class Holder {
static final ShaderManager INSTANCE = new ShaderManager();
}
private final ShaderCache cache = new ShaderCache();
private final Uniforms uniforms = new Uniforms();
private final Int2IntOpenHashMap vaoVertexFlags = new Int2IntOpenHashMap();
@Getter
private boolean active = false;
private Program currentProgram = null;
private long currentVertexKeyPacked = Long.MIN_VALUE;
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
private int currentFKLen = 0;
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
@Getter private static final Vector4f currentTexCoord = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal);
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoord);
@Getter private static int normalGeneration;
@Getter private static int texCoordGeneration;
@Getter private boolean enabled = false;
private int currentVertexFlags = VertexFlags.TEXTURE_BIT | VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT;
private ShaderManager() {
vaoVertexFlags.defaultReturnValue(-1);
if (Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false"))) {
cache.setDumpDir(Paths.get("ffp_shaders"));
}
}
public static ShaderManager getInstance() {
return Holder.INSTANCE;
}
public void enable() {
enabled = true;
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
currentVertexFlags = format.getVertexFlags();
vaoVertexFlags.put(GLStateManager.getBoundVAO(), currentVertexFlags);
return false;
});
GLStateManager.LOGGER.info("FFP shader emulation enabled");
}
public void disable() {
enabled = false;
}
public void activate() {
active = true;
updateVariant(true, true, true, true);
uploadUniforms();
}
public void deactivate() {
active = false;
currentProgram = null;
currentVertexKeyPacked = Long.MIN_VALUE;
currentFKLen = 0;
}
public void preDraw(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
GLStateManager.flushDeferredVertexAttribs();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
if (!active) {
if (enabled) {
final int currentProgramId = GLStateManager.getActiveProgram();
if (currentProgramId != 0) {
CompatUniformManager.onUseProgram(currentProgramId);
return;
}
active = true;
} else {
return;
}
}
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
final int fkLen = FragmentKey.packFromState(currentFKScratch);
if (vkPacked != currentVertexKeyPacked || !Arrays.equals(currentFKScratch, 0, fkLen, currentFKPacked, 0, currentFKLen)) {
commitVariant(vkPacked, fkLen);
}
uploadUniforms();
}
public void preDraw(int vertexFlags) {
currentVertexFlags = vertexFlags;
preDraw(
(vertexFlags & VertexFlags.COLOR_BIT) != 0,
(vertexFlags & VertexFlags.NORMAL_BIT) != 0,
(vertexFlags & VertexFlags.TEXTURE_BIT) != 0,
(vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0
);
}
public void preDraw() {
preDraw(currentVertexFlags);
}
private void updateVariant(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
final int fkLen = FragmentKey.packFromState(currentFKScratch);
commitVariant(vkPacked, fkLen);
}
private void commitVariant(long vkPacked, int fkLen) {
currentVertexKeyPacked = vkPacked;
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
currentFKLen = fkLen;
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
RENDER_BACKEND.useProgram(currentProgram.getProgramId());
}
private void uploadUniforms() {
if (currentProgram != null) {
uniforms.upload(currentProgram);
}
}
public static void setCurrentNormal(float x, float y, float z) {
currentNormal.set(x, y, z);
normalGeneration++;
}
public static void setCurrentTexCoord(float s, float t, float r, float q) {
currentTexCoord.set(s, t, r, q);
texCoordGeneration++;
}
public static void bumpNormalGeneration() { normalGeneration++; }
public static void bumpTexCoordGeneration() { texCoordGeneration++; }
public void enableClientVertexFlag(int flag) {currentVertexFlags |= flag;}
public void disableClientVertexFlag(int flag) {currentVertexFlags &= ~flag;}
public int getCurrentVertexFlags() {return currentVertexFlags;}
public void setCurrentVertexFlags(int flags) {currentVertexFlags = flags;}
public void onBindVertexArray(int vaoId) {
final int flags = vaoVertexFlags.get(vaoId);
if (flags != -1) {
currentVertexFlags = flags;
}
}
public void onDeleteVertexArray(int vaoId) {
vaoVertexFlags.remove(vaoId);
}
public void destroy() {
cache.destroy();
uniforms.destroy();
final GLSMInitConfig config = GLStateManager.getInitConfig();
if (config != null && config.getStreamingDrawerDestroy() != null) config.getStreamingDrawerDestroy().run();
com.gtnewhorizons.angelica.glsm.QuadConverter.destroy();
active = false;
currentProgram = null;
}
public String getDebugInfo() {
return String.format("FFP: %d programs (%d vert, %d frag variants)",
cache.getProgramCount(), cache.getVertexVariantCount(), cache.getFragmentVariantCount());
}
}
@@ -0,0 +1,494 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.FogState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import java.nio.FloatBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Uploads GLSM cached state to the active FFP program's uniforms.
*
* Follows Mesa's prog_statevars.c patterns:
* - Pre-computed derived state (light products, scene color) when material is static
* - Raw light/material values when color material is active
* - Optimized fog params (pre-divided for single MAD in shader)
*/
public class Uniforms {
// Reusable temp buffers for uniform upload
private final FloatBuffer mat4Buf = memAllocFloat(16);
private final FloatBuffer mat3Buf = memAllocFloat(9);
private final FloatBuffer vec4Buf = memAllocFloat(4);
private final FloatBuffer vec3Buf = memAllocFloat(3);
private final FloatBuffer clipPlaneBuf = memAllocFloat(32); // 8 planes * vec4
// Derived matrices (computed on CPU)
private final Matrix4f mvpMatrix = new Matrix4f();
private final Matrix3f normalMatrix = new Matrix3f();
// Temp vectors for light product and normal scale computation
private final Vector4f tempVec4 = new Vector4f();
private final org.joml.Vector3f tempVec3 = new org.joml.Vector3f();
// Pre-computed constants for fog params (Mesa STATE_FOG_PARAMS_OPTIMIZED)
private static final double LN2 = Math.log(2.0);
private static final double SQRT_LN2 = Math.sqrt(LN2);
// Dirty tracking: last-uploaded generation per category + program ID.
// Program change forces full re-upload since uniform locations differ.
private int lastProgramId = -1;
private int lastMvGen = -1;
private int lastProjGen = -1;
private int lastTexMatGen = -1;
private int lastLightingGen = -1;
private int lastFragmentGen = -1;
private int lastColorGen = -1;
private int lastNormalGen = -1;
private int lastTexCoordGen = -1;
private float lastLightmapX = Float.NaN;
private float lastLightmapY = Float.NaN;
private int lastTexGenGen = -1;
private int lastClipPlaneGen = -1;
private float lastLineWidth = Float.NaN;
private int lastViewportWidth = -1;
private int lastViewportHeight = -1;
/**
* Upload all relevant uniforms to the given FFP program based on current GLSM state.
* Uses generation counters to skip categories that haven't changed since last upload.
*/
public void upload(Program program) {
final boolean programChanged = program.getProgramId() != lastProgramId;
lastProgramId = program.getProgramId();
final int mvGen = GLStateManager.mvGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
final boolean mvChanged = programChanged || mvGen != lastMvGen;
final boolean projChanged = programChanged || projGen != lastProjGen;
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(program, mvChanged, projChanged, texMatChanged);
lastMvGen = mvGen;
lastProjGen = projGen;
lastTexMatGen = texMatGen;
}
if (program.getVertexKey().lightingEnabled()) {
final int litGen = GLStateManager.lightingGeneration;
if (programChanged || litGen != lastLightingGen) {
uploadLighting(program);
lastLightingGen = litGen;
}
}
// Current color/normal/texcoord — skip if generation unchanged
if (!program.getVertexKey().hasVertexColor()) {
final int colorGen = GLStateManager.colorGeneration;
if (programChanged || colorGen != lastColorGen) {
uploadCurrentColor(program);
lastColorGen = colorGen;
}
}
if (!program.getVertexKey().hasVertexNormal() && program.getVertexKey().lightingEnabled()) {
final int normalGen = ShaderManager.getNormalGeneration();
if (programChanged || normalGen != lastNormalGen) {
uploadCurrentNormal(program);
lastNormalGen = normalGen;
}
}
if (!program.getVertexKey().hasVertexTexCoord() && program.getVertexKey().textureEnabled()) {
final int texGen = ShaderManager.getTexCoordGeneration();
if (programChanged || texGen != lastTexCoordGen) {
uploadCurrentTexCoord(program);
lastTexCoordGen = texGen;
}
}
if (program.getVertexKey().lightmapEnabled() && !program.getVertexKey().hasVertexLightmap()) {
if (programChanged || GLSMConfig.lastBrightnessX != lastLightmapX || GLSMConfig.lastBrightnessY != lastLightmapY) {
uploadCurrentLightmapCoord(program);
lastLightmapX = GLSMConfig.lastBrightnessX;
lastLightmapY = GLSMConfig.lastBrightnessY;
}
}
if (program.getVertexKey().texGenEnabled()) {
final int tgGen = GLStateManager.texGenGeneration;
if (programChanged || tgGen != lastTexGenGen) {
uploadTexGen(program);
lastTexGenGen = tgGen;
}
}
if (program.getVertexKey().clipPlanesEnabled()) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
uploadClipPlanes(program);
lastClipPlaneGen = cpGen;
}
}
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
uploadFragmentUniforms(program);
lastFragmentGen = fragGen;
}
// Wide line emulation uniforms
if (program.locLineWidth != -1 && program.locViewportSize != -1) {
uploadWideLineUniforms(program, programChanged);
}
}
private void uploadMatrices(Program program, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
// ModelView + derived (normal matrix, normal scale)
if (mvChanged) {
if (program.locModelViewMatrix != -1) {
mv.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
}
// Normal matrix = inverse transpose of upper-left 3x3 of ModelView
if (program.locNormalMatrix != -1 || program.locNormalScale != -1) {
mv.normal(normalMatrix);
}
if (program.locNormalMatrix != -1) {
normalMatrix.get(mat3Buf);
RENDER_BACKEND.uniformMatrix3(program.locNormalMatrix, false, mat3Buf);
}
// Normal scale (for GL_RESCALE_NORMAL without GL_NORMALIZE)
if (program.locNormalScale != -1) {
// Scale factor = 1/length of first column of normal matrix
final float scale = 1.0f / normalMatrix.getColumn(0, tempVec3).length();
RENDER_BACKEND.uniform1f(program.locNormalScale, scale);
}
}
// Projection
if (projChanged) {
if (program.locProjectionMatrix != -1) {
proj.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locProjectionMatrix, false, mat4Buf);
}
}
// MVP = Projection * ModelView — needs recompute if either input changed
if ((mvChanged || projChanged) && program.locMVPMatrix != -1) {
proj.mul(mv, mvpMatrix);
mvpMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locMVPMatrix, false, mat4Buf);
}
// Texture matrices
if (texMatChanged) {
// Texture matrix unit 0
if (program.locTextureMatrix0 != -1) {
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
texMat.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locTextureMatrix0, false, mat4Buf);
}
// Texture matrix unit 1 (lightmap)
if (program.locLightmapTextureMatrix != -1) {
final Matrix4f lmTexMat = GLStateManager.getTextures().getTextureUnitMatrix(1);
lmTexMat.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locLightmapTextureMatrix, false, mat4Buf);
}
}
}
private void uploadLighting(Program program) {
final VertexKey vk = program.getVertexKey();
if (vk.colorMaterialEnabled()) {
uploadLightingColorMaterial(program, vk);
} else {
uploadLightingPreComputed(program, vk);
}
}
/**
* Color material active: upload raw light and material values.
* The shader multiplies them with vertex color at runtime.
*/
private void uploadLightingColorMaterial(Program program, VertexKey vk) {
final MaterialState mat = GLStateManager.getFrontMaterial();
uploadVec4(program.locLightModelAmbient, GLStateManager.getLightModel().ambient);
uploadVec4(program.locMaterialEmission, mat.emission);
uploadVec4(program.locMaterialAmbient, mat.ambient);
uploadVec4(program.locMaterialDiffuse, mat.diffuse);
uploadVec4(program.locMaterialSpecular, mat.specular);
if (program.locMaterialShininess != -1) {
RENDER_BACKEND.uniform1f(program.locMaterialShininess, mat.shininess);
}
if (vk.light0Enabled()) {
final LightState light0 = GLStateManager.getLightDataStates()[0];
uploadVec4(program.locLight0Ambient, light0.ambient);
uploadVec4(program.locLight0Diffuse, light0.diffuse);
uploadVec4(program.locLight0Specular, light0.specular);
uploadVec4(program.locLight0Position, light0.position);
}
if (vk.light1Enabled()) {
final LightState light1 = GLStateManager.getLightDataStates()[1];
uploadVec4(program.locLight1Ambient, light1.ambient);
uploadVec4(program.locLight1Diffuse, light1.diffuse);
uploadVec4(program.locLight1Specular, light1.specular);
uploadVec4(program.locLight1Position, light1.position);
}
}
/**
* No color material: upload pre-computed light products and scene color.
* Fewer shader multiplies since material is static.
*/
private void uploadLightingPreComputed(Program program, VertexKey vk) {
final MaterialState mat = GLStateManager.getFrontMaterial();
// Scene color = emission + ambient * lightModel.ambient
// Alpha = material.diffuse.a
if (program.locSceneColor != -1) {
tempVec4.set(
mat.emission.x + mat.ambient.x * GLStateManager.getLightModel().ambient.x,
mat.emission.y + mat.ambient.y * GLStateManager.getLightModel().ambient.y,
mat.emission.z + mat.ambient.z * GLStateManager.getLightModel().ambient.z,
mat.diffuse.w // alpha from diffuse
);
uploadVec4(program.locSceneColor, tempVec4);
}
if (program.locMaterialShininess != -1) {
RENDER_BACKEND.uniform1f(program.locMaterialShininess, mat.shininess);
}
// Per-light products
if (vk.light0Enabled()) {
final LightState light0 = GLStateManager.getLightDataStates()[0];
uploadVec4(program.locLight0Position, light0.position);
uploadLightProduct(program.locLightProd0Ambient, light0.ambient, mat.ambient);
uploadLightProduct(program.locLightProd0Diffuse, light0.diffuse, mat.diffuse);
uploadLightProduct(program.locLightProd0Specular, light0.specular, mat.specular);
}
if (vk.light1Enabled()) {
final LightState light1 = GLStateManager.getLightDataStates()[1];
uploadVec4(program.locLight1Position, light1.position);
uploadLightProduct(program.locLightProd1Ambient, light1.ambient, mat.ambient);
uploadLightProduct(program.locLightProd1Diffuse, light1.diffuse, mat.diffuse);
uploadLightProduct(program.locLightProd1Specular, light1.specular, mat.specular);
}
}
/**
* Upload pre-multiplied light product: light.X * material.X (RGB only).
*/
private void uploadLightProduct(int loc, Vector4f lightVal, Vector4f materialVal) {
if (loc == -1) return;
vec3Buf.clear();
vec3Buf.put(lightVal.x * materialVal.x);
vec3Buf.put(lightVal.y * materialVal.y);
vec3Buf.put(lightVal.z * materialVal.z);
vec3Buf.flip();
RENDER_BACKEND.uniform3(loc, vec3Buf);
}
private void uploadCurrentColor(Program program) {
if (program.locCurrentColor == -1) return;
// Upload the current color from GLSM
final var color = GLStateManager.getColor();
vec4Buf.clear();
vec4Buf.put(color.getRed());
vec4Buf.put(color.getGreen());
vec4Buf.put(color.getBlue());
vec4Buf.put(color.getAlpha());
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locCurrentColor, vec4Buf);
}
private void uploadCurrentTexCoord(Program program) {
if (program.locCurrentTexCoord == -1) return;
final var tc = ShaderManager.getCurrentTexCoord();
uploadVec4(program.locCurrentTexCoord, tc);
}
private void uploadCurrentLightmapCoord(Program program) {
if (program.locCurrentLightmapCoord == -1) return;
RENDER_BACKEND.uniform2f(program.locCurrentLightmapCoord,
GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY);
}
private void uploadCurrentNormal(Program program) {
if (program.locCurrentNormal == -1) return;
// Upload current normal from FFPShaderManager's tracked normal
final var normal = ShaderManager.getCurrentNormal();
vec3Buf.clear();
vec3Buf.put(normal.x);
vec3Buf.put(normal.y);
vec3Buf.put(normal.z);
vec3Buf.flip();
RENDER_BACKEND.uniform3(program.locCurrentNormal, vec3Buf);
}
private void uploadTexGen(Program program) {
final VertexKey vk = program.getVertexKey();
final TexGenState tg = GLStateManager.getTextures().getTexGenState(0);
if (vk.texGenModeS() == VertexKey.TG_OBJ_LINEAR) {
uploadPlane(program.locTexGenObjPlaneS, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_S));
} else if (vk.texGenModeS() == VertexKey.TG_EYE_LINEAR) {
uploadPlane(program.locTexGenEyePlaneS, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_S));
}
if (vk.texGenModeT() == VertexKey.TG_OBJ_LINEAR) {
uploadPlane(program.locTexGenObjPlaneT, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_T));
} else if (vk.texGenModeT() == VertexKey.TG_EYE_LINEAR) {
uploadPlane(program.locTexGenEyePlaneT, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_T));
}
if (vk.texGenModeR() == VertexKey.TG_OBJ_LINEAR) {
uploadPlane(program.locTexGenObjPlaneR, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_R));
} else if (vk.texGenModeR() == VertexKey.TG_EYE_LINEAR) {
uploadPlane(program.locTexGenEyePlaneR, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_R));
}
if (vk.texGenModeQ() == VertexKey.TG_OBJ_LINEAR) {
uploadPlane(program.locTexGenObjPlaneQ, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_Q));
} else if (vk.texGenModeQ() == VertexKey.TG_EYE_LINEAR) {
uploadPlane(program.locTexGenEyePlaneQ, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_Q));
}
}
private void uploadPlane(int loc, float[] plane) {
if (loc == -1) return;
vec4Buf.clear();
vec4Buf.put(plane[0]);
vec4Buf.put(plane[1]);
vec4Buf.put(plane[2]);
vec4Buf.put(plane[3]);
vec4Buf.flip();
RENDER_BACKEND.uniform4(loc, vec4Buf);
}
private void uploadClipPlanes(Program program) {
if (program.locClipPlanes == -1) return;
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
clipPlaneBuf.flip();
RENDER_BACKEND.uniform4(program.locClipPlanes, clipPlaneBuf);
}
private void uploadFragmentUniforms(Program program) {
final FragmentKey fk = program.getFragmentKey();
// Alpha test reference
if (fk.alphaTestEnabled() && program.locAlphaRef != -1) {
RENDER_BACKEND.uniform1f(program.locAlphaRef, GLStateManager.getAlphaState().getReference());
}
// Per-unit tex env color
for (int i = 0; i < 4; i++) {
if (program.locTexEnvColor[i] != -1) {
final var envState = GLStateManager.getTextures().getTexEnvState(i);
vec4Buf.clear();
vec4Buf.put(envState.envColorR);
vec4Buf.put(envState.envColorG);
vec4Buf.put(envState.envColorB);
vec4Buf.put(envState.envColorA);
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locTexEnvColor[i], vec4Buf);
}
}
// Fog
if (fk.fogMode() != FragmentKey.FOG_NONE) {
uploadFog(program);
}
}
/**
* Upload optimized fog params (following Mesa's STATE_FOG_PARAMS_OPTIMIZED):
*/
private void uploadFog(Program program) {
final FogState fog = GLStateManager.getFogState();
if (program.locFogParams != -1) {
final float start = fog.getStart();
final float end = fog.getEnd();
final float density = fog.getDensity();
final float range = end - start;
vec4Buf.clear();
vec4Buf.put(range != 0.0f ? -1.0f / range : 0.0f); // [0]: -1/(end-start)
vec4Buf.put(range != 0.0f ? end / range : 1.0f); // [1]: end/(end-start)
vec4Buf.put((float)(density / LN2)); // [2]: density/ln(2)
vec4Buf.put((float)(density / SQRT_LN2)); // [3]: density/sqrt(ln(2))
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locFogParams, vec4Buf);
}
if (program.locFogColor != -1) {
vec4Buf.clear();
vec4Buf.put((float) fog.getFogColor().x);
vec4Buf.put((float) fog.getFogColor().y);
vec4Buf.put((float) fog.getFogColor().z);
vec4Buf.put(fog.getFogAlpha());
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locFogColor, vec4Buf);
}
}
private void uploadWideLineUniforms(Program program, boolean programChanged) {
final float lineWidth = GLStateManager.getLineState().getWidth();
if (programChanged || lineWidth != lastLineWidth) {
RENDER_BACKEND.uniform1f(program.locLineWidth, lineWidth);
lastLineWidth = lineWidth;
}
final int vw = GLStateManager.getViewportState().width;
final int vh = GLStateManager.getViewportState().height;
if (programChanged || vw != lastViewportWidth || vh != lastViewportHeight) {
RENDER_BACKEND.uniform2f(program.locViewportSize, vw, vh);
lastViewportWidth = vw;
lastViewportHeight = vh;
}
}
private void uploadVec4(int loc, Vector4f v) {
if (loc == -1) return;
vec4Buf.clear();
vec4Buf.put(v.x);
vec4Buf.put(v.y);
vec4Buf.put(v.z);
vec4Buf.put(v.w);
vec4Buf.flip();
RENDER_BACKEND.uniform4(loc, vec4Buf);
}
public void destroy() {
memFree(mat4Buf);
memFree(mat3Buf);
memFree(vec4Buf);
memFree(vec3Buf);
memFree(clipPlaneBuf);
}
}
@@ -0,0 +1,265 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
/**
* Compact key capturing the vertex shader permutation axes for FFP emulation.
* Packed into a single long for fast hash/compare.
*/
public final class VertexKey {
private final long packed;
private static final int BIT_LIGHTING = 0;
private static final int BIT_LIGHT0 = 1;
private static final int BIT_LIGHT1 = 2;
private static final int BIT_LIGHT0_DIR = 3;
private static final int BIT_LIGHT1_DIR = 4;
private static final int BIT_COLOR_MATERIAL = 5;
private static final int BIT_SEPARATE_SPECULAR = 6;
private static final int BIT_FOG = 7;
private static final int BIT_FOG_DIST_MODE = 8;
private static final int BIT_NORMALIZE = 10;
private static final int BIT_RESCALE_NORMAL = 11;
private static final int BIT_TEXTURE = 12;
private static final int BIT_LIGHTMAP = 13;
private static final int BIT_TEX_MATRIX = 14;
private static final int BIT_HAS_VERTEX_COLOR = 15;
private static final int BIT_HAS_VERTEX_NORMAL = 16;
private static final int BIT_HAS_VERTEX_TEX = 17;
private static final int BIT_HAS_VERTEX_LIGHTMAP = 18;
private static final int BIT_COLOR_MAT_MODE = 19;
// bits 19-21: colorMaterialMode (3 bits)
// TexGen modes: 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR)
private static final int BIT_TEXGEN_S = 22;
private static final int BIT_TEXGEN_T = 25;
private static final int BIT_TEXGEN_R = 28;
private static final int BIT_TEXGEN_Q = 31;
// bits 31-33: texGenModeQ (3 bits)
private static final int BIT_CLIP_PLANES = 34;
private static final int BIT_WIDE_LINE = 35;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
public static final int TG_EYE_LINEAR = 2;
public static final int CM_AMBIENT = 0;
public static final int CM_DIFFUSE = 1;
public static final int CM_SPECULAR = 2;
public static final int CM_EMISSION = 3;
public static final int CM_AMBIENT_AND_DIFFUSE = 4;
private VertexKey(long packed) {
this.packed = packed;
}
public long pack() { return packed; }
public boolean lightingEnabled() { return bit(BIT_LIGHTING); }
public boolean light0Enabled() { return bit(BIT_LIGHT0); }
public boolean light1Enabled() { return bit(BIT_LIGHT1); }
public boolean light0Directional() { return bit(BIT_LIGHT0_DIR); }
public boolean light1Directional() { return bit(BIT_LIGHT1_DIR); }
public boolean colorMaterialEnabled() { return bit(BIT_COLOR_MATERIAL); }
public boolean separateSpecular() { return bit(BIT_SEPARATE_SPECULAR); }
public boolean fogEnabled() { return bit(BIT_FOG); }
public int fogDistanceMode() { return (int)((packed >> BIT_FOG_DIST_MODE) & 0x3); }
public boolean normalizeEnabled() { return bit(BIT_NORMALIZE); }
public boolean rescaleNormalsEnabled() { return bit(BIT_RESCALE_NORMAL); }
public boolean textureEnabled() { return bit(BIT_TEXTURE); }
public boolean lightmapEnabled() { return bit(BIT_LIGHTMAP); }
public boolean textureMatrixEnabled() { return bit(BIT_TEX_MATRIX); }
public boolean hasVertexColor() { return bit(BIT_HAS_VERTEX_COLOR); }
public boolean hasVertexNormal() { return bit(BIT_HAS_VERTEX_NORMAL); }
public boolean hasVertexTexCoord() { return bit(BIT_HAS_VERTEX_TEX); }
public boolean hasVertexLightmap() { return bit(BIT_HAS_VERTEX_LIGHTMAP); }
/** Color material mode (CM_AMBIENT, CM_DIFFUSE, CM_SPECULAR, CM_EMISSION, CM_AMBIENT_AND_DIFFUSE). Only meaningful when colorMaterialEnabled(). */
public int colorMaterialMode() { return (int)((packed >> BIT_COLOR_MAT_MODE) & 0x7); }
/** TexGen mode for S coordinate (TG_NONE, TG_OBJ_LINEAR, TG_EYE_LINEAR). */
public int texGenModeS() { return (int)((packed >> BIT_TEXGEN_S) & 0x7); }
/** TexGen mode for T coordinate. */
public int texGenModeT() { return (int)((packed >> BIT_TEXGEN_T) & 0x7); }
/** TexGen mode for R coordinate. */
public int texGenModeR() { return (int)((packed >> BIT_TEXGEN_R) & 0x7); }
/** TexGen mode for Q coordinate. */
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
/** Whether any texgen coordinate is enabled. */
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
/** Whether any clip plane is enabled. */
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
/** Whether wide line GS emulation is active for this variant. */
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
/** Whether vertex color replaces material ambient in this variant. */
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
/** Whether vertex color replaces material diffuse in this variant. */
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
/** Whether vertex color replaces material specular in this variant. */
public boolean cmReplacesSpecular() { return colorMaterialMode() == CM_SPECULAR; }
/** Whether vertex color replaces material emission in this variant. */
public boolean cmReplacesEmission() { return colorMaterialMode() == CM_EMISSION; }
private boolean bit(int pos) { return ((packed >> pos) & 1) != 0; }
static int encodeTexGenMode(int glMode) {
return switch (glMode) {
case GL11.GL_OBJECT_LINEAR -> TG_OBJ_LINEAR;
case GL11.GL_EYE_LINEAR -> TG_EYE_LINEAR;
default -> TG_NONE;
};
}
static int encodeColorMaterialMode(int glMode) {
return switch (glMode) {
case GL11.GL_AMBIENT -> CM_AMBIENT;
case GL11.GL_DIFFUSE -> CM_DIFFUSE;
case GL11.GL_SPECULAR -> CM_SPECULAR;
case GL11.GL_EMISSION -> CM_EMISSION;
case GL11.GL_AMBIENT_AND_DIFFUSE -> CM_AMBIENT_AND_DIFFUSE;
default -> CM_AMBIENT_AND_DIFFUSE;
};
}
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
long bits = 0;
final boolean lighting = GLStateManager.getLightingState().isEnabled();
if (lighting) {
bits |= (1L << BIT_LIGHTING);
if (GLStateManager.getLightStates()[0].isEnabled()) {
bits |= (1L << BIT_LIGHT0);
// position.w == 0 means directional
if (GLStateManager.getLightDataStates()[0].position.w == 0.0f) {
bits |= (1L << BIT_LIGHT0_DIR);
}
}
if (GLStateManager.getLightStates()[1].isEnabled()) {
bits |= (1L << BIT_LIGHT1);
if (GLStateManager.getLightDataStates()[1].position.w == 0.0f) {
bits |= (1L << BIT_LIGHT1_DIR);
}
}
if (GLStateManager.getColorMaterial().isEnabled()) {
bits |= (1L << BIT_COLOR_MATERIAL);
final int cmMode = encodeColorMaterialMode(GLStateManager.getColorMaterialParameter().getValue());
bits |= ((long) cmMode & 0x7) << BIT_COLOR_MAT_MODE;
}
if (GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
bits |= (1L << BIT_SEPARATE_SPECULAR);
}
}
if (GLStateManager.getFogMode().isEnabled()) {
bits |= (1L << BIT_FOG);
final int fogDistMode = GLStateManager.getFogState().getFogDistanceMode();
bits |= ((long) fogDistMode & 0x3) << BIT_FOG_DIST_MODE;
}
if (GLStateManager.getNormalizeState().isEnabled()) {
bits |= (1L << BIT_NORMALIZE);
}
if (GLStateManager.getRescaleNormalState().isEnabled()) {
bits |= (1L << BIT_RESCALE_NORMAL);
}
// Texture unit 0 (base texture)
if (GLStateManager.getTextures().getTextureUnitStates(0).isEnabled()) {
bits |= (1L << BIT_TEXTURE);
}
// Texture unit 1 (lightmap)
if (GLStateManager.getTextures().getTextureUnitStates(1).isEnabled()) {
bits |= (1L << BIT_LIGHTMAP);
}
// Check if texture matrix unit 0 is non-identity
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
if (!DisplayListManager.isIdentity(texMat)) {
bits |= (1L << BIT_TEX_MATRIX);
}
// TexGen (unit 0 only) — encode per-coordinate mode if enabled
final var texUnit = GLStateManager.getTextures();
final TexGenState tg = texUnit.getTexGenState(0);
boolean anyTexGen = false;
if (texUnit.getTexGenSStates(0).isEnabled() && tg.getMode(GL11.GL_S) != 0) {
final int m = encodeTexGenMode(tg.getMode(GL11.GL_S));
bits |= ((long) m & 0x7) << BIT_TEXGEN_S;
if (m != TG_NONE) anyTexGen = true;
}
if (texUnit.getTexGenTStates(0).isEnabled() && tg.getMode(GL11.GL_T) != 0) {
final int m = encodeTexGenMode(tg.getMode(GL11.GL_T));
bits |= ((long) m & 0x7) << BIT_TEXGEN_T;
if (m != TG_NONE) anyTexGen = true;
}
if (texUnit.getTexGenRStates(0).isEnabled() && tg.getMode(GL11.GL_R) != 0) {
final int m = encodeTexGenMode(tg.getMode(GL11.GL_R));
bits |= ((long) m & 0x7) << BIT_TEXGEN_R;
if (m != TG_NONE) anyTexGen = true;
}
if (texUnit.getTexGenQStates(0).isEnabled() && tg.getMode(GL11.GL_Q) != 0) {
final int m = encodeTexGenMode(tg.getMode(GL11.GL_Q));
bits |= ((long) m & 0x7) << BIT_TEXGEN_Q;
if (m != TG_NONE) anyTexGen = true;
}
// Force texture matrix on when texgen is active (end portal pattern always uses it)
if (anyTexGen) {
bits |= (1L << BIT_TEX_MATRIX);
}
// Vertex format flags
if (hasColor) bits |= (1L << BIT_HAS_VERTEX_COLOR);
if (hasNormal) bits |= (1L << BIT_HAS_VERTEX_NORMAL);
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
// Clip planes
if (GLStateManager.anyClipPlaneEnabled()) {
bits |= (1L << BIT_CLIP_PLANES);
}
// Wide line GS emulation
if (GLStateManager.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
return bits;
}
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap));
}
static VertexKey fromPacked(long packed) {
return new VertexKey(packed);
}
@Override
public int hashCode() {
return Long.hashCode(packed);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof VertexKey other)) return false;
return packed == other.packed;
}
@Override
public String toString() {
return String.format("FFPVertexKey[0x%09X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%b lm=%b col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b]",
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
colorMaterialEnabled(), fogEnabled(), textureEnabled(), lightmapEnabled(),
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation());
}
}
@@ -0,0 +1,416 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
/**
* Generates GLSL 330 core vertex shaders for FFP emulation.
* Follows Mesa's ffvertex_prog.c build order.
*
* Attribute locations are sourced from {@link VertexFormatElement.Usage} to stay in sync with GTNHLib's vertex format setup.
*/
public final class VertexShaderGenerator {
private VertexShaderGenerator() {}
public static String generate(VertexKey key) {
final StringBuilder sb = new StringBuilder(2048);
sb.append("#version 330 core\n\n");
emitAttributes(sb, key);
emitUniforms(sb, key);
emitOutputs(sb, key);
sb.append("void main() {\n");
emitPositionTransform(sb, key);
if (key.lightingEnabled()) {
emitNormalTransform(sb, key);
emitLighting(sb, key);
} else {
emitColorPassthrough(sb, key);
}
emitTexCoordPassthrough(sb, key);
if (key.fogEnabled()) {
emitFogDistance(sb, key);
}
if (key.clipPlanesEnabled()) {
emitClipDistances(sb);
}
sb.append("}\n");
String source = sb.toString();
if (key.wideLineEmulation()) {
source = source.replace("v_SpecularColor", "v_SpecularColor_gs")
.replace("v_TexCoord0", "v_TexCoord0_gs")
.replace("v_TexCoord1", "v_TexCoord1_gs")
.replace("v_FogCoord", "v_FogCoord_gs")
.replace("v_Color", "v_Color_gs");
}
return source;
}
private static void emitAttributes(StringBuilder sb, VertexKey key) {
sb.append("// Vertex attributes\n");
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
if (key.hasVertexColor()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
}
if (key.hasVertexTexCoord()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
}
if (key.hasVertexLightmap()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
}
if (key.hasVertexNormal()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
}
sb.append('\n');
}
private static void emitUniforms(StringBuilder sb, VertexKey key) {
sb.append("// Matrices\n");
sb.append("uniform mat4 u_ModelViewMatrix;\n");
sb.append("uniform mat4 u_ProjectionMatrix;\n");
sb.append("uniform mat4 u_MVPMatrix;\n");
if (key.lightingEnabled()) {
sb.append("uniform mat3 u_NormalMatrix;\n");
}
if (key.textureMatrixEnabled()) {
sb.append("uniform mat4 u_TextureMatrix0;\n");
}
// TexGen plane uniforms
if (key.texGenModeS() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneS;\n");
if (key.texGenModeS() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneS;\n");
if (key.texGenModeT() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneT;\n");
if (key.texGenModeT() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneT;\n");
if (key.texGenModeR() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneR;\n");
if (key.texGenModeR() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneR;\n");
if (key.texGenModeQ() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneQ;\n");
if (key.texGenModeQ() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneQ;\n");
if (!key.hasVertexNormal() && key.lightingEnabled()) {
sb.append("uniform vec3 u_CurrentNormal;\n");
}
if (!key.hasVertexColor()) {
sb.append("uniform vec4 u_CurrentColor;\n");
}
if (!key.hasVertexTexCoord() && key.textureEnabled()) {
sb.append("uniform vec4 u_CurrentTexCoord;\n");
}
if (key.lightmapEnabled() && !key.hasVertexLightmap()) {
sb.append("uniform vec2 u_CurrentLightmapCoord;\n");
}
if (key.lightmapEnabled()) {
sb.append("uniform mat4 u_LightmapTextureMatrix;\n");
}
if (key.lightingEnabled()) {
emitLightingUniforms(sb, key);
}
if (key.lightingEnabled() && (key.normalizeEnabled() || key.rescaleNormalsEnabled())) {
// normalize is done in shader; rescale uses normalScale
if (key.rescaleNormalsEnabled() && !key.normalizeEnabled()) {
sb.append("uniform float u_NormalScale;\n");
}
}
if (key.clipPlanesEnabled()) {
sb.append("uniform vec4 u_ClipPlane[8];\n");
}
sb.append('\n');
}
private static void emitLightingUniforms(StringBuilder sb, VertexKey key) {
sb.append("\n// Lighting\n");
if (key.colorMaterialEnabled()) {
// When color material is active, we can't pre-compute light products so we upload raw light and material values
sb.append("uniform vec4 u_LightModelAmbient;\n");
sb.append("uniform vec4 u_MaterialEmission;\n");
sb.append("uniform vec4 u_MaterialAmbient;\n");
sb.append("uniform vec4 u_MaterialDiffuse;\n");
sb.append("uniform vec4 u_MaterialSpecular;\n");
sb.append("uniform float u_MaterialShininess;\n");
if (key.light0Enabled()) {
sb.append("uniform vec4 u_Light0Ambient;\n");
sb.append("uniform vec4 u_Light0Diffuse;\n");
sb.append("uniform vec4 u_Light0Specular;\n");
sb.append("uniform vec4 u_Light0Position;\n");
}
if (key.light1Enabled()) {
sb.append("uniform vec4 u_Light1Ambient;\n");
sb.append("uniform vec4 u_Light1Diffuse;\n");
sb.append("uniform vec4 u_Light1Specular;\n");
sb.append("uniform vec4 u_Light1Position;\n");
}
} else {
// Pre-computed: sceneColor + lightProducts
sb.append("uniform vec4 u_SceneColor;\n");
if (key.light0Enabled()) {
sb.append("uniform vec4 u_Light0Position;\n");
sb.append("uniform vec3 u_LightProd0Ambient;\n");
sb.append("uniform vec3 u_LightProd0Diffuse;\n");
sb.append("uniform vec3 u_LightProd0Specular;\n");
}
if (key.light1Enabled()) {
sb.append("uniform vec4 u_Light1Position;\n");
sb.append("uniform vec3 u_LightProd1Ambient;\n");
sb.append("uniform vec3 u_LightProd1Diffuse;\n");
sb.append("uniform vec3 u_LightProd1Specular;\n");
}
sb.append("uniform float u_MaterialShininess;\n");
}
}
private static void emitOutputs(StringBuilder sb, VertexKey key) {
sb.append("// Outputs\n");
sb.append("out vec4 v_Color;\n");
if (key.separateSpecular()) {
sb.append("out vec3 v_SpecularColor;\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append("out vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("out vec4 v_TexCoord1;\n");
}
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
sb.append('\n');
}
private static void emitPositionTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Position transform\n");
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
// Eye position needed for lighting, fog, EYE_LINEAR texgen, and clip planes
if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key) || key.clipPlanesEnabled()) {
sb.append(" vec4 eyePos = u_ModelViewMatrix * pos4;\n");
}
sb.append('\n');
}
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Normal transform\n");
if (key.hasVertexNormal()) {
sb.append(" vec3 normal = u_NormalMatrix * a_Normal;\n");
} else {
sb.append(" vec3 normal = u_NormalMatrix * u_CurrentNormal;\n");
}
if (key.normalizeEnabled()) {
sb.append(" normal = normalize(normal);\n");
} else if (key.rescaleNormalsEnabled()) {
sb.append(" normal *= u_NormalScale;\n");
}
sb.append('\n');
}
private static void emitLighting(StringBuilder sb, VertexKey key) {
sb.append(" // Gouraud lighting\n");
if (key.colorMaterialEnabled()) {
// Color material: vertex color replaces specific material properties per glColorMaterial mode (Mesa ffvertex_prog.c get_material() bitmask approach)
sb.append(" vec4 matColor = ");
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
sb.append(";\n");
// Emission source: vertex color for CM_EMISSION, else material uniform
final String emSrc = key.cmReplacesEmission() ? "matColor.rgb" : "u_MaterialEmission.rgb";
// Ambient source for scene color: vertex color for CM_AMBIENT / CM_AMBIENT_AND_DIFFUSE
final String amSrc = key.cmReplacesAmbient() ? "matColor.rgb" : "u_MaterialAmbient.rgb";
sb.append(" vec3 sceneColor = ").append(emSrc).append(" + ").append(amSrc).append(" * u_LightModelAmbient.rgb;\n");
// Alpha from diffuse source (per GL spec)
sb.append(" float outAlpha = ").append(key.cmReplacesDiffuse() ? "matColor.a" : "u_MaterialDiffuse.a").append(";\n");
} else {
// Pre-computed scene color
sb.append(" vec3 sceneColor = u_SceneColor.rgb;\n");
sb.append(" float outAlpha = u_SceneColor.a;\n");
}
sb.append(" vec3 color0 = sceneColor;\n");
if (key.separateSpecular()) {
sb.append(" vec3 specAccum = vec3(0.0);\n");
}
// Per-light accumulation
if (key.light0Enabled()) {
emitLightContribution(sb, key, 0);
}
if (key.light1Enabled()) {
emitLightContribution(sb, key, 1);
}
sb.append(" v_Color = vec4(clamp(color0, 0.0, 1.0), outAlpha);\n");
if (key.separateSpecular()) {
sb.append(" v_SpecularColor = clamp(specAccum, 0.0, 1.0);\n");
}
sb.append('\n');
}
private static void emitLightContribution(StringBuilder sb, VertexKey key, int lightIndex) {
final String li = String.valueOf(lightIndex);
final boolean directional = (lightIndex == 0) ? key.light0Directional() : key.light1Directional();
final String posUniform = "u_Light" + li + "Position";
sb.append(" { // Light ").append(li).append('\n');
// Light vector
if (directional) {
// Directional: position is already normalized direction
sb.append(" vec3 VPpli = normalize(").append(posUniform).append(".xyz);\n");
} else {
// Positional light
sb.append(" vec3 VPpli = ").append(posUniform).append(".xyz - eyePos.xyz;\n");
sb.append(" VPpli = normalize(VPpli);\n");
}
// Dot products
sb.append(" float NdotVP = max(dot(normal, VPpli), 0.0);\n");
// Half vector for specular (infinite viewer: H = normalize(L + (0,0,1)))
sb.append(" vec3 halfVec = normalize(VPpli + vec3(0.0, 0.0, 1.0));\n");
sb.append(" float NdotHV = max(dot(normal, halfVec), 0.0);\n");
// Specular power
sb.append(" float spec = (NdotVP > 0.0 && u_MaterialShininess > 0.0) ? pow(NdotHV, u_MaterialShininess) : 0.0;\n");
// Accumulate — per-property source depends on color material mode
if (key.colorMaterialEnabled()) {
final String ambU = "u_Light" + li + "Ambient";
final String difU = "u_Light" + li + "Diffuse";
final String speU = "u_Light" + li + "Specular";
// Ambient product: light.ambient * (vertex color or material)
final String ambMat = key.cmReplacesAmbient() ? "matColor.rgb" : "u_MaterialAmbient.rgb";
sb.append(" vec3 ambient = ").append(ambU).append(".rgb * ").append(ambMat).append(";\n");
// Diffuse product
final String difMat = key.cmReplacesDiffuse() ? "matColor.rgb" : "u_MaterialDiffuse.rgb";
sb.append(" vec3 diffuse = ").append(difU).append(".rgb * ").append(difMat).append(";\n");
// Specular product
final String speMat = key.cmReplacesSpecular() ? "matColor.rgb" : "u_MaterialSpecular.rgb";
sb.append(" vec3 specular = ").append(speU).append(".rgb * ").append(speMat).append(";\n");
} else {
sb.append(" vec3 ambient = u_LightProd").append(li).append("Ambient;\n");
sb.append(" vec3 diffuse = u_LightProd").append(li).append("Diffuse;\n");
sb.append(" vec3 specular = u_LightProd").append(li).append("Specular;\n");
}
sb.append(" color0 += ambient + NdotVP * diffuse;\n");
if (key.separateSpecular()) {
sb.append(" specAccum += spec * specular;\n");
} else {
sb.append(" color0 += spec * specular;\n");
}
sb.append(" }\n");
}
private static void emitColorPassthrough(StringBuilder sb, VertexKey key) {
sb.append(" // Color passthrough (no lighting)\n");
if (key.hasVertexColor()) {
sb.append(" v_Color = a_Color;\n");
} else {
sb.append(" v_Color = u_CurrentColor;\n");
}
sb.append('\n');
}
private static void emitTexCoordPassthrough(StringBuilder sb, VertexKey key) {
if (key.texGenEnabled()) {
emitTexGenCoordGeneration(sb, key);
} else if (key.textureEnabled() || key.hasVertexTexCoord()) {
sb.append(" // Texture coordinates\n");
if (key.hasVertexTexCoord()) {
if (key.textureMatrixEnabled()) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * vec4(a_TexCoord0, 0.0, 1.0);\n");
} else {
sb.append(" v_TexCoord0 = vec4(a_TexCoord0, 0.0, 1.0);\n");
}
} else {
if (key.textureMatrixEnabled()) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * u_CurrentTexCoord;\n");
} else {
sb.append(" v_TexCoord0 = u_CurrentTexCoord;\n");
}
}
}
if (key.lightmapEnabled()) {
if (key.hasVertexLightmap()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
} else {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
}
}
}
/**
* Emit per-coordinate texgen code following Mesa's ffvertex_prog.c build order.
* OBJ_LINEAR: dot(pos4, objPlane), EYE_LINEAR: dot(eyePos, eyePlane).
* Coordinates without texgen use vertex attrib or default (0,0,0,1).
*/
private static void emitTexGenCoordGeneration(StringBuilder sb, VertexKey key) {
sb.append(" // TexGen coordinate generation\n");
sb.append(" vec4 texGenCoord = vec4(0.0, 0.0, 0.0, 1.0);\n");
emitTexGenComponent(sb, key.texGenModeS(), "s", "S", key);
emitTexGenComponent(sb, key.texGenModeT(), "t", "T", key);
emitTexGenComponent(sb, key.texGenModeR(), "r", "R", key);
emitTexGenComponent(sb, key.texGenModeQ(), "q", "Q", key);
// Always apply texture matrix when texgen is active (forced on in VertexKey)
sb.append(" v_TexCoord0 = u_TextureMatrix0 * texGenCoord;\n");
}
private static void emitTexGenComponent(StringBuilder sb, int mode, String swizzle, String coordName, VertexKey key) {
switch (mode) {
case VertexKey.TG_OBJ_LINEAR ->
sb.append(" texGenCoord.").append(swizzle).append(" = dot(pos4, u_TexGenObjPlane").append(coordName).append(");\n");
case VertexKey.TG_EYE_LINEAR ->
sb.append(" texGenCoord.").append(swizzle).append(" = dot(eyePos, u_TexGenEyePlane").append(coordName).append(");\n");
// TG_NONE: keep default (0 for s/t/r, 1 for q) — already set in texGenCoord init
}
}
private static boolean texGenNeedsEyePos(VertexKey key) {
return key.texGenModeS() == VertexKey.TG_EYE_LINEAR
|| key.texGenModeT() == VertexKey.TG_EYE_LINEAR
|| key.texGenModeR() == VertexKey.TG_EYE_LINEAR
|| key.texGenModeQ() == VertexKey.TG_EYE_LINEAR;
}
private static void emitFogDistance(StringBuilder sb, VertexKey key) {
sb.append(" // Fog distance\n");
// Mesa ffvertex_prog.c build_fog(): FDM_EYE_RADIAL/EYE_PLANE/EYE_PLANE_ABS
final int fogDistMode = key.fogDistanceMode();
switch (fogDistMode) {
case 0 -> // FDM_EYE_RADIAL: Euclidean distance
sb.append(" v_FogCoord = length(eyePos.xyz);\n");
case 1 -> // FDM_EYE_PLANE: raw Z (can be negative, clamped by fog factor)
sb.append(" v_FogCoord = eyePos.z;\n");
case 2 -> // FDM_EYE_PLANE_ABS: absolute Z
sb.append(" v_FogCoord = abs(eyePos.z);\n");
default ->
sb.append(" v_FogCoord = abs(eyePos.z);\n");
}
}
private static void emitClipDistances(StringBuilder sb) {
sb.append(" // Clip distances\n");
for (int i = 0; i < 8; i++) {
sb.append(" gl_ClipDistance[").append(i).append("] = dot(u_ClipPlane[").append(i).append("], eyePos);\n");
}
}
}
@@ -0,0 +1,7 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredAlphaHandler {
boolean isAlphaTestLocked();
void deferAlphaTestToggle(boolean enabled);
void deferAlphaFunc(int function, float reference);
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredBlendHandler {
boolean isBlendLocked();
void deferBlendModeToggle(boolean enabled);
void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha);
void flushDeferredBlend();
}
@@ -0,0 +1,7 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredDepthColorHandler {
boolean isDepthColorLocked();
void deferDepthEnable(boolean enabled);
void deferColorMask(boolean r, boolean g, boolean b, boolean a);
}
@@ -0,0 +1,12 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public final class GLSMConfig {
// Lightmap (replaces OpenGlHelper.lastBrightnessX/Y)
public static float lastBrightnessX;
public static float lastBrightnessY;
public static boolean hudCacheOverride;
private GLSMConfig() {}
}
@@ -0,0 +1,37 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.hooks.events.BlendFuncChangeEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.FogStateChangeEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.LightmapCoordsEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.ProgramChangeEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureBindEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureDeleteEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureUnitStateEvent;
import net.minecraftforge.eventbus.api.bus.EventBus;
public final class GLSMHooks {
public static DeferredBlendHandler blendHandler;
public static DeferredAlphaHandler alphaHandler;
public static DeferredDepthColorHandler depthColorHandler;
// EventBus instances
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class);
public static final EventBus<TextureDeleteEvent> TEXTURE_DELETE = EventBus.create(TextureDeleteEvent.class);
public static final EventBus<TextureUnitStateEvent> TEXTURE_UNIT_STATE = EventBus.create(TextureUnitStateEvent.class);
public static final EventBus<ProgramChangeEvent> PROGRAM_CHANGE = EventBus.create(ProgramChangeEvent.class);
public static final EventBus<BlendFuncChangeEvent> BLEND_FUNC_CHANGE = EventBus.create(BlendFuncChangeEvent.class);
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class);
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
// Reusable event instances
public static final TextureBindEvent textureBindEvent = new TextureBindEvent();
public static final TextureDeleteEvent textureDeleteEvent = new TextureDeleteEvent();
public static final TextureUnitStateEvent textureUnitStateEvent = new TextureUnitStateEvent();
public static final ProgramChangeEvent programChangeEvent = new ProgramChangeEvent();
public static final BlendFuncChangeEvent blendFuncChangeEvent = new BlendFuncChangeEvent();
public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent();
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
private GLSMHooks() {}
}
@@ -0,0 +1,103 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import java.util.function.Consumer;
public final class GLSMInitConfig {
private final boolean lwjglDebug;
private final StreamingUploader.UploadStrategy streamingUploadStrategy;
private final boolean framebufferSupported;
private final boolean fboEnabled;
private final Consumer<DirectTessellator> directDrawer;
private final Runnable streamingDrawerDestroy;
private final int displayWidth;
private final int displayHeight;
private final Runnable postInitCallback;
private GLSMInitConfig(Builder builder) {
this.lwjglDebug = builder.lwjglDebug;
this.streamingUploadStrategy = builder.streamingUploadStrategy;
this.framebufferSupported = builder.framebufferSupported;
this.fboEnabled = builder.fboEnabled;
this.directDrawer = builder.directDrawer;
this.streamingDrawerDestroy = builder.streamingDrawerDestroy;
this.displayWidth = builder.displayWidth;
this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback;
}
public static Builder builder() {
return new Builder();
}
public boolean isLwjglDebug() { return lwjglDebug; }
public StreamingUploader.UploadStrategy getStreamingUploadStrategy() { return streamingUploadStrategy; }
public boolean isFramebufferSupported() { return framebufferSupported; }
public boolean isFboEnabled() { return fboEnabled; }
public Consumer<DirectTessellator> getDirectDrawer() { return directDrawer; }
public Runnable getStreamingDrawerDestroy() { return streamingDrawerDestroy; }
public int getDisplayWidth() { return displayWidth; }
public int getDisplayHeight() { return displayHeight; }
public Runnable getPostInitCallback() { return postInitCallback; }
public static final class Builder {
private boolean lwjglDebug = false;
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
private boolean framebufferSupported = false;
private boolean fboEnabled = false;
private Consumer<DirectTessellator> directDrawer = null;
private Runnable streamingDrawerDestroy = null;
private int displayWidth = 0;
private int displayHeight = 0;
private Runnable postInitCallback = null;
private Builder() {}
public Builder lwjglDebug(boolean lwjglDebug) {
this.lwjglDebug = lwjglDebug;
return this;
}
public Builder streamingUploadStrategy(StreamingUploader.UploadStrategy strategy) {
this.streamingUploadStrategy = strategy;
return this;
}
public Builder framebufferSupported(boolean framebufferSupported) {
this.framebufferSupported = framebufferSupported;
return this;
}
public Builder fboEnabled(boolean fboEnabled) {
this.fboEnabled = fboEnabled;
return this;
}
public Builder directDrawer(Consumer<DirectTessellator> directDrawer) {
this.directDrawer = directDrawer;
return this;
}
public Builder streamingDrawerDestroy(Runnable streamingDrawerDestroy) {
this.streamingDrawerDestroy = streamingDrawerDestroy;
return this;
}
public Builder displaySize(int width, int height) {
this.displayWidth = width;
this.displayHeight = height;
return this;
}
public Builder postInitCallback(Runnable callback) {
this.postInitCallback = callback;
return this;
}
public GLSMInitConfig build() {
return new GLSMInitConfig(this);
}
}
}
@@ -0,0 +1,10 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class BlendFuncChangeEvent extends MutableEvent {
public int srcRgb;
public int dstRgb;
public int srcAlpha;
public int dstAlpha;
}
@@ -0,0 +1,6 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
/** FogState changed - Listeners should query GLSM fog state directly if they care what changed. */
public final class FogStateChangeEvent extends MutableEvent {}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class LightmapCoordsEvent extends MutableEvent {
public float x;
public float y;
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ProgramChangeEvent extends MutableEvent {
public int previousProgram;
public int newProgram;
}
@@ -0,0 +1,7 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class TextureBindEvent extends MutableEvent {
public int textureId;
}
@@ -0,0 +1,7 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class TextureDeleteEvent extends MutableEvent {
public int textureId;
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class TextureUnitStateEvent extends MutableEvent {
public int unit;
public boolean enabled;
}
@@ -0,0 +1,64 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import java.nio.ByteBuffer;
import java.util.ArrayList;
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 final class AccumulatedDraw {
public final VertexFormat format;
public final List<ByteBuffer> drawBuffers;
public final int drawMode;
public final int commandIndex; // Position in command list for state tracking
public final int stateGeneration; // Position in command list for state tracking
public RestoreData restoreData;
public AccumulatedDraw(DirectTessellator tessellator, int commandIndex, int stateGeneration, boolean copyLast) {
this.format = tessellator.getVertexFormat();
this.drawMode = tessellator.drawMode;
this.drawBuffers = new ArrayList<>();
this.drawBuffers.add(tessellator.allocateBufferCopy());
this.commandIndex = commandIndex;
this.stateGeneration = stateGeneration;
if (copyLast) {
this.restoreData = new RestoreData(tessellator);
}
}
public void mergeDraw(DirectTessellator tessellator, boolean copyLast) {
this.drawBuffers.add(tessellator.allocateBufferCopy());
if (copyLast) {
this.restoreData = new RestoreData(tessellator);
}
}
/**
* Holds last vertex attribute values for GL state restoration after VBO draw.
* Used to sync GLSM cache with actual GL state after VBO rendering.
*/
public static final class RestoreData {
public final int lastColor;
public final int lastNormal;
public final float lastTexCoordU;
public final float lastTexCoordV;
public final int flags;
public RestoreData(DirectTessellator tessellator) {
this.lastColor = tessellator.getPackedColor();
this.lastNormal = tessellator.getPackedNormal();
this.lastTexCoordU = (float) tessellator.getLastTextureU();
this.lastTexCoordV = (float) tessellator.getLastTextureV();
this.flags = tessellator.getVertexFormat().getVertexFlags();
}
}
}
@@ -0,0 +1,784 @@
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
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 writeLong(long value) {
memPutLong(writePointer, value);
writePointer += 8;
}
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 writeScissor(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.SCISSOR);
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));
}
}
public void writeClipPlane(int plane, double a, double b, double c, double d) {
ensureCapacity(40);
writeInt(GLCommand.CLIP_PLANE);
writeInt(plane);
writeDouble(a);
writeDouble(b);
writeDouble(c);
writeDouble(d);
}
// === Draw commands ===
public void writeDrawRange(int vboIndex) {
ensureCapacity(8);
writeInt(GLCommand.DRAW_RANGE);
writeInt(vboIndex);
}
/**
* 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
*/
public void writeDrawRangeRestore(
int vboIndex, AccumulatedDraw.RestoreData restoreData
) {
ensureCapacity(28);
writeInt(GLCommand.DRAW_RANGE_RESTORE);
writeInt(vboIndex);
writeInt(restoreData.flags);
// Last color (4 bytes)
writeInt(restoreData.lastColor);
// Last normal (4 bytes)
writeInt(restoreData.lastNormal);
// Last texcoord (2 floats = 8 bytes)
writeFloat(restoreData.lastTexCoordU);
writeFloat(restoreData.lastTexCoordV);
}
public void writeCallList(int listId) {
ensureCapacity(8);
writeInt(GLCommand.CALL_LIST);
writeInt(listId);
}
public void writeDrawArrays(int mode, int start, int count) {
ensureCapacity(16);
writeInt(GLCommand.DRAW_ARRAYS);
writeInt(mode);
writeInt(start);
writeInt(count);
}
public void writeDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
ensureCapacity(24);
writeInt(GLCommand.DRAW_ELEMENTS);
writeInt(mode);
writeInt(indices_count);
writeInt(type);
writeLong(indices_buffer_offset);
}
public void writeBindVBO(int vbo) {
ensureCapacity(8);
writeInt(GLCommand.BIND_VBO);
writeInt(vbo);
}
public void writeBindVAO(int vao) {
ensureCapacity(8);
writeInt(GLCommand.BIND_VAO);
writeInt(vao);
}
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;
}
public long readLong() {
final long value = memGetLong(readPointer);
readPointer += 8;
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,561 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
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 org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
/**
* 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 clip plane equation
private static final DoubleBuffer PARAMS_DOUBLE_BUFFER = BufferUtils.createDoubleBuffer(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, DisplayListVBO 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.SCISSOR -> {
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.glScissor(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);
}
case GLCommand.CLIP_PLANE -> {
final int plane = memGetInt(ptr);
final double a = memGetDouble(ptr + 4);
final double b = memGetDouble(ptr + 12);
final double c = memGetDouble(ptr + 20);
final double d = memGetDouble(ptr + 28);
ptr += 36;
PARAMS_DOUBLE_BUFFER.clear();
PARAMS_DOUBLE_BUFFER.put(a).put(b).put(c).put(d);
PARAMS_DOUBLE_BUFFER.flip();
GLStateManager.glClipPlane(plane, PARAMS_DOUBLE_BUFFER);
}
// === Draw commands ===
case GLCommand.DRAW_RANGE -> {
final int vboIndex = memGetInt(ptr);
ptr += 4;
ownedVbos.render(vboIndex);
}
case GLCommand.DRAW_RANGE_RESTORE -> {
// Draw VBO range then restore GL current state from last vertex attributes
final int vboIndex = memGetInt(ptr);
final int flags = memGetInt(ptr + 4);
// Draw the VBO
ownedVbos.render(vboIndex);
// Restore attributes based on flags
if ((flags & VertexFlags.COLOR_BIT) != 0) {
final int color = memGetInt(ptr + 8);
final byte a = (byte) ((color >> 24) & 0xFF);
final byte b = (byte) ((color >> 16) & 0xFF);
final byte g = (byte) ((color >> 8) & 0xFF);
final byte r = (byte) ((color & 0xFF));
GLStateManager.glColor4ub(r, g, b, a);
}
if ((flags & VertexFlags.NORMAL_BIT) != 0) {
final int normal = memGetInt(ptr + 12);
float nx = (byte)(normal) / 127.0f;
float ny = (byte)(normal >> 8) / 127.0f;
float nz = (byte)(normal >> 16) / 127.0f;
GLStateManager.glNormal3f(nx, ny, nz);
}
if ((flags & VertexFlags.TEXTURE_BIT) != 0) {
final float s = memGetFloat(ptr + 16);
final float t = memGetFloat(ptr + 20);
GLStateManager.glTexCoord2f(s, t);
}
ptr += 24; // Skip full command size (28 - 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);
}
case GLCommand.DRAW_ARRAYS -> {
GLStateManager.glDrawArrays(memGetInt(ptr), memGetInt(ptr + 4), memGetInt(ptr + 8));
ptr += 12;
}
case GLCommand.DRAW_ELEMENTS -> {
GLStateManager.glDrawElements(memGetInt(ptr), memGetInt(ptr + 4), memGetInt(ptr + 8), memGetLong(ptr + 12));
ptr += 20;
}
case GLCommand.BIND_VBO -> {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, memGetInt(ptr));
ptr += 4;
}
case GLCommand.BIND_VAO -> {
GLStateManager.glBindVertexArray(memGetInt(ptr));
ptr += 4;
}
// === 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,168 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.joml.Matrix4f;
import java.nio.FloatBuffer;
public final class CommandBufferProcessor {
private static final Matrix4f tempMatrix = new Matrix4f();
private CommandBufferProcessor() {}
/**
* 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.SCISSOR -> out.writeScissor(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);
}
// Clip plane command (4 doubles)
case GLCommand.CLIP_PLANE -> {
final int plane = raw.readInt();
final double a = raw.readDouble();
final double b = raw.readDouble();
final double c = raw.readDouble();
final double d = raw.readDouble();
out.writeClipPlane(plane, a, b, c, d);
}
// Draw/call commands
case GLCommand.DRAW_RANGE -> out.writeDrawRange(raw.readInt());
case GLCommand.CALL_LIST -> out.writeCallList(raw.readInt());
case GLCommand.DRAW_ARRAYS -> out.writeDrawArrays(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.DRAW_ELEMENTS -> out.writeDrawElements(raw.readInt(), raw.readInt(), raw.readInt(), raw.readLong());
case GLCommand.BIND_VBO -> out.writeBindVBO(raw.readInt());
case GLCommand.BIND_VAO -> out.writeBindVAO(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 -> GLStateManager.LOGGER.warn("[CommandBufferProcessor] Unknown opcode in buffer copy: {}", opcode);
}
}
}
@@ -0,0 +1,374 @@
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 final 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 recordScissor(int x, int y, int width, int height) {
buffer.writeScissor(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 recordClipPlane(int plane, double a, double b, double c, double d) {
buffer.writeClipPlane(plane, a, b, c, d);
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 recordDrawArrays(int mode, int start, int count) {
buffer.writeDrawArrays(mode, start, count);
commandCount++;
}
public void recordDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
buffer.writeDrawElements(mode, indices_count, type, indices_buffer_offset);
commandCount++;
}
public void recordBindVBO(int vbo) {
buffer.writeBindVBO(vbo);
commandCount++;
}
public void recordBindVAO(int vao) {
buffer.writeBindVAO(vao);
commandCount++;
}
public void recordComplexCommand(DisplayListCommand cmd) {
buffer.writeComplexRef(cmd);
commandCount++;
}
}
@@ -0,0 +1,136 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
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 DisplayListVBO ownedVbos; // GPU resources referenced by index
public CompiledDisplayList(ByteBuffer commandBuffer, Object[] complexObjects, DisplayListVBO 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) {
ownedVbos.delete();
}
// 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;
}
public DisplayListVBO 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 += GLCommand.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 += GLCommand.getCommandSize(cmd, ptr);
}
return opcodes;
}
@Override
public String toString() {
return DisplayListManager.getCompiledDisplayListString(0, this, null);
}
}
@@ -0,0 +1,58 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
/**
* A class that stores multiple vertex formats & their corresponding buffers.
*/
public final class DisplayListVBO {
private final SubVBO[] vbos;
DisplayListVBO(SubVBO[] vbos) {
this.vbos = vbos;
}
public void delete() {
for (SubVBO vbo : vbos) {
vbo.delete();
}
}
public SubVBO[] getVBOs() {
return vbos;
}
public void render(int index) {
vbos[index].render();
}
public static final class SubVBO {
private final IVertexArrayObject vao;
private final int drawMode;
private final int start;
private final int count;
public SubVBO(IVertexArrayObject vao, int drawMode, int start, int count) {
this.vao = vao;
this.drawMode = drawMode;
this.start = start;
this.count = count;
}
public void delete() {
vao.delete();
}
public void render() {
vao.bind();
vao.draw(drawMode, start, count);
vao.unbind();
}
public IVertexArrayObject getVAO() {
return vao;
}
}
}
@@ -0,0 +1,134 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexedVAO;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.IVertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.nmemFree;
/**
* Compiles multiple ByteBuffers of the same Vertex format into a VBO. Any buffer added to the builder will be freed afterward.
*/
public final class DisplayListVBOBuilder {
// Removes the need for a HashMap. Since it's only an array of 16 values, this should be faster
private final List<FormatData>[] formats = new List[VertexFlags.BITSET_SIZE];
private int count;
public DisplayListVBOBuilder addDraw(VertexFormat format, int drawMode, List<ByteBuffer> buffers) {
List<FormatData> data = formats[format.getVertexFlags()];
if (data == null) {
data = new ArrayList<>();
formats[format.getVertexFlags()] = data;
}
data.add(new FormatData(buffers, drawMode, count));
count++;
return this;
}
public DisplayListVBOBuilder addDraws(List<AccumulatedDraw> draws) {
for (AccumulatedDraw draw : draws) {
addDraw(draw.format, draw.drawMode, draw.drawBuffers);
}
return this;
}
public DisplayListVBO build() {
final DisplayListVBO.SubVBO[] vbos = new DisplayListVBO.SubVBO[count];
final ArrayList<ByteBuffer> allBuffers = new ArrayList<>();
for (int i = 0; i < VertexFlags.BITSET_SIZE; i++) {
List<FormatData> formatData = formats[i];
if (formatData == null) continue;
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[i];
int start = 0;
final IVertexArrayObject vao = VAOManager.createStorageVAO(format, -1, 0); // drawMode will be ignored
final IVertexBuffer vbo = vao.getVBO();
for (FormatData data : formatData) {
int vertexCount;
final List<ByteBuffer> drawBuffers = data.buffers;
if (drawBuffers.size() == 1) {
final ByteBuffer buffer = drawBuffers.get(0);
allBuffers.add(buffer);
vertexCount = format.getVertexCount(buffer);
} else {
allBuffers.addAll(drawBuffers);
int size = 0;
for (ByteBuffer buffer : drawBuffers) {
size += buffer.remaining();
}
vertexCount = format.getVertexCount(size);
}
if (data.drawMode == GL11.GL_QUADS) {
final IVertexArrayObject indexedVAO = new IndexedVAO(vbo, IndexBuffer.convertQuadsToTrigs(start, start + vertexCount));
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(indexedVAO, GL11.GL_TRIANGLES, 0, vertexCount / 4 * 6);
} else if (data.drawMode == GL11.GL_POLYGON) {
// GL_POLYGON is removed in core profile; convert to GL_TRIANGLE_FAN which is equivalent for convex polygons
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_FAN, start, vertexCount);
} else {
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, data.drawMode, start, vertexCount);
}
start += vertexCount;
}
ByteBuffer bigBuffer = mergeAndDelete(allBuffers);
vbo.allocate(bigBuffer, start);
memFree(bigBuffer);
allBuffers.clear();
}
return new DisplayListVBO(vbos);
}
private static ByteBuffer mergeAndDelete(List<ByteBuffer> buffers) {
if (buffers.size() == 1) {
return buffers.get(0);
}
int needed = 0;
for (ByteBuffer buffer : buffers) {
needed += buffer.remaining();
}
ByteBuffer out = memAlloc(needed);
long dst = memAddress0(out);
for (ByteBuffer buffer : buffers) {
final int len = buffer.limit();
final long address = memAddress0(buffer);
memCopy(address, dst, len);
dst += len;
nmemFree(address);
}
out.limit(needed);
return out;
}
private static final class FormatData {
private final List<ByteBuffer> buffers;
private final int drawMode;
private final int drawIndex;
public FormatData(List<ByteBuffer> buffers, int drawMode, int drawIndex) {
this.buffers = buffers;
this.drawMode = drawMode;
this.drawIndex = drawIndex;
}
}
}
@@ -0,0 +1,264 @@
package com.gtnewhorizons.angelica.glsm.recording;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
/**
* 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]
public static final int SCISSOR = 45; // [cmd:4][x:4][y:4][width:4][height: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
public static final int CLIP_PLANE = 94; // [cmd:4][plane:4][a:8d][b:8d][c:8d][d:8d] = 40 bytes
// === Draw commands ===
public static final int DRAW_RANGE = 100; // [cmd:4][vboIndex:4] = 8 bytes
public static final int DRAW_RANGE_RESTORE = 101; // [cmd:4][vboIndex:4][flags:4][color:4][normal:4][lastTexCoord:8f] = 28 bytes
public static final int CALL_LIST = 102; // [cmd:4][listId:4] = 8 bytes
public static final int DRAW_ARRAYS = 110; // [cmd:4][drawMode:4][start:4][count:4] = 20 bytes
public static final int DRAW_ELEMENTS = 111; // [cmd:4][mode:4][indices_count:4][type:4][indices_buffer_offset:8] = 28 bytes
public static final int DRAW_BUFFER = 112; // [cmd:4][mode:4] = 8 bytes
public static final int DRAW_BUFFERS = 113; // [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).
*/
// === Bind commands ===
public static final int BIND_VBO = 120;
public static final int BIND_VAO = 121;
// === 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 SCISSOR -> "SCISSOR";
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 CLIP_PLANE -> "CLIP_PLANE";
case DRAW_RANGE -> "DRAW_RANGE";
case CALL_LIST -> "CALL_LIST";
case DRAW_BUFFER -> "DRAW_BUFFER";
case DRAW_BUFFERS -> "DRAW_BUFFERS";
case COMPLEX_REF -> "COMPLEX_REF";
case DRAW_RANGE_RESTORE -> "DRAW_RANGE_RESTORE";
case BIND_VAO -> "BIND_VAO";
case BIND_VBO -> "BIND_VBO";
default -> "UNKNOWN(" + opcode + ")";
};
}
/**
* Get size of a command in bytes (including the opcode).
*/
public 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, GLCommand.DRAW_RANGE, GLCommand.BIND_VBO,
GLCommand.BIND_VAO -> 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, GLCommand.DRAW_ARRAYS -> 16;
// Four int commands (20 bytes)
case GLCommand.VIEWPORT, GLCommand.BLEND_FUNC, GLCommand.COLOR_MASK,
GLCommand.STENCIL_FUNC_SEPARATE, GLCommand.STENCIL_OP_SEPARATE,
GLCommand.SCISSOR,
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR -> 20;
case GLCommand.DRAW_ELEMENTS -> 24;
// DRAW_RANGE_RESTORE: 28 bytes
case GLCommand.DRAW_RANGE_RESTORE -> 28;
// 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;
}
case GLCommand.CLIP_PLANE -> 40; // cmd + plane + 4 doubles
default -> throw new IllegalStateException("Unknown command: " + cmd);
};
}
}
@@ -0,0 +1,449 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Records immediate mode GL calls (glBegin/glEnd/glVertex) via {@link DirectTessellator}.
* Used both during display list compilation and for live immediate mode emulation in core profile.
*/
public final class ImmediateModeRecorder {
private static final DirectTessellator tessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
/** Separate tessellator for the splash thread. Nulled after splash completes. */
private static DirectTessellator splashTessellator;
private ImmediateModeRecorder() {
}
public static void initSplashTessellator() {
splashTessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
}
public static void destroySplashTessellator() {
splashTessellator = null;
}
private static DirectTessellator tessellator() {
final DirectTessellator splash = splashTessellator;
if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) {
return splash;
}
return tessellator;
}
public static DirectTessellator getInternalTessellator() {
return tessellator();
}
public static boolean isDrawing() {
return tessellator().isDrawing;
}
/**
* Set the current texture coordinate.
* Called by GLStateManager.glTexCoord*.
*/
public static void setTexCoord(float s, float t) {
tessellator().setTextureUV(s, t);
}
/**
* Set the current lightmap coordinate as packed brightness.
* Called by GLStateManager.glMultiTexCoord2f for unit 1 during recording/drawing.
*/
public static void setLightmapCoord(float s, float t) {
tessellator().setBrightness(((int) t << 16) | ((int) s & 0xFFFF));
}
/**
* Set the current normal vector.
* Called by GLStateManager.glNormal*.
*/
public static void setNormal(float x, float y, float z) {
tessellator().setNormal(x, y, z);
}
/**
* Set the current color on the internal tessellator.
* Called by GLStateManager.glColor* when inside a glBegin/glEnd block.
*/
public static void setColor(float r, float g, float b, float a) {
tessellator().setColorRGBA((int) (r * 255.0f), (int) (g * 255.0f), (int) (b * 255.0f), (int) (a * 255.0f));
}
/**
* Start recording a new primitive.
* Resets the tessellator to clear any data from the previous draw.
*
* @param primitiveType GL primitive type (GL_QUADS, GL_TRIANGLES, etc.)
*/
public static void begin(int primitiveType) {
final DirectTessellator t = tessellator();
t.reset();
t.startDrawing(primitiveType);
}
/**
* Start recording a new primitive with initial color sync from GLSM cache.
* Used for live immediate mode emulation only (not display list recording).
*/
public static void beginLive(int primitiveType) {
final DirectTessellator t = tessellator();
t.reset();
t.startDrawing(primitiveType);
final var c = GLStateManager.getColor();
t.setColorRGBA((int) (c.getRed() * 255.0f), (int) (c.getGreen() * 255.0f), (int) (c.getBlue() * 255.0f), (int) (c.getAlpha() * 255.0f));
}
/**
* 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 static DirectTessellator end() {
final DirectTessellator t = tessellator();
if (!t.isDrawing) {
throw new IllegalStateException("glEnd called without glBegin");
}
t.isDrawing = false;
if (t.isEmpty()) return null;
return t;
}
/**
* Record a vertex with current attributes.
* Color is read from GLStateManager at call time.
*/
public static void vertex(float x, float y, float z) {
final DirectTessellator t = tessellator();
if (!t.isDrawing) {
throw new IllegalStateException("glVertex called outside glBegin/glEnd");
}
t.addVertex(x, y, z);
}
/**
* 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 static boolean hasGeometry() {
return tessellator().vertexCount != 0;
}
/**
* Reset all state including current texcoord/normal.
* Called when starting a new display list.
*/
public static void reset() {
tessellator().reset();
}
private static final int LOC_POSITION = Usage.POSITION.getAttributeLocation();
private static final int LOC_COLOR = Usage.COLOR.getAttributeLocation();
private static final int LOC_UV0 = Usage.PRIMARY_UV.getAttributeLocation();
private static final int LOC_UV1 = Usage.SECONDARY_UV.getAttributeLocation();
private static final int LOC_NORMAL = Usage.NORMAL.getAttributeLocation();
private static final int NUM_ATTRIBS = 1 + Arrays.stream(Usage.values())
.mapToInt(Usage::getAttributeLocation)
.max().orElse(0);
private static final ByteBuffer[] scratchAttribBuffers = new ByteBuffer[NUM_ATTRIBS];
private static final long[] scratchAttribBaseOffsets = new long[NUM_ATTRIBS];
private static final int[] scratchVBOIds = new int[NUM_ATTRIBS];
private static final ByteBuffer[] scratchVBOBuffers = new ByteBuffer[NUM_ATTRIBS];
private static final long[] scratchVBOReadOffsets = new long[NUM_ATTRIBS];
private static final class AttribReader {
ByteBuffer data;
int stride;
int baseOffset;
int size;
int loc;
VertexAttribState.Attrib attrib;
}
private static final AttribReader[] readers = new AttribReader[NUM_ATTRIBS];
private static int nonPositionCount;
private static final int[] nonPositionIndices = new int[NUM_ATTRIBS - 1];
private static int positionReaderIndex = -1;
static {
for (int i = 0; i < NUM_ATTRIBS; i++) {
readers[i] = new AttribReader();
}
}
/**
* Process a glDrawArrays call during display list recording.
* Reads vertex data from VBOs or client memory referenced by VertexAttribState and converts to immediate mode geometry.
*/
public static DirectTessellator processDrawArraysFromAttribs(int mode, int first, int count) {
final int readVBOCount = readbackAttribVBOs(first, count);
try {
prepareReaders();
if (positionReaderIndex < 0) return null;
return readVerticesFromBuffer(mode, first, count);
} finally {
freeReadbackBuffers(readVBOCount);
}
}
/**
* Process a glDrawElements call during display list recording when a VBO is bound.
*/
public static DirectTessellator processDrawElementsFromVBO(int mode, int indicesCount, int indexType, long indicesOffset, int eboId) {
if (eboId == 0) return null;
final int readVBOCount = readbackAttribVBOs(-1, 0);
try {
prepareReaders();
if (positionReaderIndex < 0) return null;
final int prevEBO = GLStateManager.getBoundEBO();
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
final int eboSize = RENDER_BACKEND.getBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (eboSize <= 0) {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
return null;
}
final int indexSize = VertexAttribState.Attrib.glTypeSizeBytes(indexType);
final int eboReadOffset = (int) indicesOffset;
final int eboReadSize = indicesCount * indexSize;
if (eboReadOffset + eboReadSize > eboSize) {
GLStateManager.LOGGER.warn("[EBO] Read range {}+{} exceeds buffer size {}", eboReadOffset, eboReadSize, eboSize);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
return null;
}
final ByteBuffer eboData = MemoryUtilities.memAlloc(eboReadSize);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboReadOffset, eboData);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
try {
return readVerticesFromBufferIndexed(mode, eboData, indexType, 0, indicesCount);
} finally {
MemoryUtilities.memFree(eboData);
}
} finally {
freeReadbackBuffers(readVBOCount);
}
}
/**
* Read data for all enabled attribs into scratchAttribBuffers.
* For VBO-backed attribs, reads VBO data. For client-memory attribs, uses the client pointer directly.
*/
private static int readbackAttribVBOs(int firstVertex, int vertexCount) {
int readVBOCount = 0;
final int prevVBO = GLStateManager.getBoundVBO();
final boolean partialRead = firstVertex >= 0;
for (int i = 0; i < NUM_ATTRIBS; i++) {
scratchAttribBuffers[i] = null;
scratchAttribBaseOffsets[i] = 0;
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled) continue;
if (a.vboId != 0) {
// VBO-backed attrib — read from GPU
ByteBuffer existing = null;
int existingIdx = -1;
for (int j = 0; j < readVBOCount; j++) {
if (scratchVBOIds[j] == a.vboId) {
existing = scratchVBOBuffers[j];
existingIdx = j;
break;
}
}
if (existing != null) {
scratchAttribBuffers[i] = existing;
scratchAttribBaseOffsets[i] = scratchVBOReadOffsets[existingIdx];
} else {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
final int bufferSize = RENDER_BACKEND.getBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (bufferSize <= 0) {
GLStateManager.LOGGER.warn("[VBO Readback] GL_BUFFER_SIZE={} for VBO {}", bufferSize, a.vboId);
continue;
}
final long readOffset;
final int readSize;
if (partialRead) {
final int stride = a.effectiveStride();
long rangeStart = a.offset + (long) firstVertex * stride;
long rangeEnd = a.offset + (long) (firstVertex + vertexCount - 1) * stride + (long) a.size * a.typeSizeBytes();
for (int k = 0; k < NUM_ATTRIBS; k++) {
if (k == i) continue;
final VertexAttribState.Attrib ak = VertexAttribState.get(k);
if (!ak.enabled || ak.vboId != a.vboId) continue;
final int ks = ak.effectiveStride();
rangeStart = Math.min(rangeStart, ak.offset + (long) firstVertex * ks);
rangeEnd = Math.max(rangeEnd, ak.offset + (long) (firstVertex + vertexCount - 1) * ks
+ (long) ak.size * ak.typeSizeBytes());
}
rangeStart = Math.max(0, rangeStart);
rangeEnd = Math.min(bufferSize, rangeEnd);
readOffset = rangeStart;
readSize = (int) (rangeEnd - rangeStart);
} else {
readOffset = 0;
readSize = bufferSize;
}
if (readSize <= 0) continue;
final ByteBuffer buf = MemoryUtilities.memAlloc(readSize);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, readOffset, buf);
scratchAttribBuffers[i] = buf;
scratchAttribBaseOffsets[i] = readOffset;
scratchVBOIds[readVBOCount] = a.vboId;
scratchVBOReadOffsets[readVBOCount] = readOffset;
scratchVBOBuffers[readVBOCount] = buf;
readVBOCount++;
}
} else if (a.clientPointer != null) {
// Client-memory attrib — use pointer directly (no allocation needed)
scratchAttribBuffers[i] = a.clientPointer;
}
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
return readVBOCount;
}
private static void freeReadbackBuffers(int readVBOCount) {
for (int i = 0; i < readVBOCount; i++) {
MemoryUtilities.memFree(scratchVBOBuffers[i]);
scratchVBOBuffers[i] = null;
}
}
private static void prepareReaders() {
nonPositionCount = 0;
positionReaderIndex = -1;
for (int i = 0; i < NUM_ATTRIBS; i++) {
final ByteBuffer buf = scratchAttribBuffers[i];
if (buf == null) continue;
final VertexAttribState.Attrib a = VertexAttribState.get(i);
final AttribReader r = readers[i];
r.data = buf;
r.stride = a.effectiveStride();
r.baseOffset = (int) (a.offset - scratchAttribBaseOffsets[i]);
r.size = a.size;
r.loc = i;
r.attrib = a;
if (i == LOC_POSITION) {
positionReaderIndex = i;
} else {
nonPositionIndices[nonPositionCount++] = i;
}
}
}
private static DirectTessellator readVerticesFromBuffer(int mode, int first, int count) {
begin(mode);
final DirectTessellator t = tessellator();
for (int i = first; i < first + count; i++) {
readVertexFromBuffer(t, i);
}
return end();
}
private static DirectTessellator readVerticesFromBufferIndexed(int mode, ByteBuffer eboData, int indexType, int eboOffset, int indicesCount) {
begin(mode);
final DirectTessellator t = tessellator();
for (int i = 0; i < indicesCount; i++) {
final int index = readIndex(eboData, indexType, eboOffset, i);
readVertexFromBuffer(t, index);
}
return end();
}
public static int readIndex(ByteBuffer ebo, int indexType, int eboOffset, int i) {
return switch (indexType) {
case GL11.GL_UNSIGNED_BYTE -> ebo.get(eboOffset + i) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> ebo.getShort(eboOffset + i * 2) & 0xFFFF;
case GL11.GL_UNSIGNED_INT -> ebo.getInt(eboOffset + i * 4);
default -> i;
};
}
private static void readVertexFromBuffer(DirectTessellator t, int vertexIndex) {
for (int i = 0; i < nonPositionCount; i++) {
final AttribReader r = readers[nonPositionIndices[i]];
final int base = r.baseOffset + vertexIndex * r.stride;
emitAttrib(t, r, base);
}
if (positionReaderIndex >= 0) {
final AttribReader r = readers[positionReaderIndex];
final int base = r.baseOffset + vertexIndex * r.stride;
t.addVertex(
r.attrib.readComponent(r.data, base, 0),
r.attrib.readComponent(r.data, base, 1),
(r.size >= 3) ? r.attrib.readComponent(r.data, base, 2) : 0.0f);
}
}
private static void emitAttrib(DirectTessellator t, AttribReader r, int base) {
if (r.loc == LOC_NORMAL) {
t.setNormal(
r.attrib.readComponent(r.data, base, 0),
r.attrib.readComponent(r.data, base, 1),
r.attrib.readComponent(r.data, base, 2));
} else if (r.loc == LOC_UV0) {
t.setTextureUV(
r.attrib.readComponent(r.data, base, 0),
(r.size >= 2) ? r.attrib.readComponent(r.data, base, 1) : 0.0f);
} else if (r.loc == LOC_UV1) {
if (r.attrib.type == GL11.GL_SHORT) {
final int s = r.data.getShort(base) & 0xFFFF;
final int u = (r.size >= 2) ? (r.data.getShort(base + 2) & 0xFFFF) : 0;
t.setBrightness((u << 16) | s);
} else if (r.attrib.type == GL11.GL_FLOAT) {
final int s = (int) r.data.getFloat(base);
final int u = (r.size >= 2) ? (int) r.data.getFloat(base + 4) : 0;
t.setBrightness((u << 16) | (s & 0xFFFF));
}
} else if (r.loc == LOC_COLOR) {
final float cr = r.attrib.readComponent(r.data, base, 0);
final float cg = r.attrib.readComponent(r.data, base, 1);
final float cb = r.attrib.readComponent(r.data, base, 2);
final float ca = (r.size >= 4) ? r.attrib.readComponent(r.data, base, 3) : 1.0f;
t.setColorRGBA((int) (cr * 255.0f), (int) (cg * 255.0f), (int) (cb * 255.0f), (int) (ca * 255.0f));
}
}
}
@@ -0,0 +1,20 @@
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
}
}
@@ -0,0 +1,36 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.jetbrains.annotations.NotNull;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import java.nio.FloatBuffer;
/**
* Command: glMultMatrix(matrix)
* Multiplies the current matrix by the specified matrix.
*/
public record MultMatrixCmd(Matrix4f matrix, FloatBuffer buffer) implements DisplayListCommand {
/**
* Create a MultMatrixCmd with pre-allocated buffer.
*/
public static MultMatrixCmd create(Matrix4f matrix) {
final FloatBuffer buffer = BufferUtils.createFloatBuffer(16);
matrix.get(buffer);
buffer.rewind();
return new MultMatrixCmd(new Matrix4f(matrix), buffer);
}
@Override
public void execute() {
buffer.rewind();
GLStateManager.glMultMatrix(buffer);
}
@Override
public @NotNull String toString() {
return String.format("MultMatrix(%s)", matrix);
}
}
@@ -0,0 +1,115 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
/**
* Command: glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels)
* Specifies a two-dimensional texture image during display list playback.
*/
public record TexImage2DCmd(
int target,
int level,
int internalformat,
int width,
int height,
int border,
int format,
int type,
@Nullable byte[] pixelData
) implements DisplayListCommand {
/**
* Create command from IntBuffer
*/
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per int
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asIntBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
}
/**
* Create command from FloatBuffer
*/
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per float
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asFloatBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
}
/**
* Create command from DoubleBuffer
*/
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 8; // 8 bytes per double
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asDoubleBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
}
/**
* Create command from ByteBuffer
*/
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = limit - pos;
data = new byte[size];
pixels.get(data);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
}
@Override
public void execute() {
ByteBuffer buffer = null;
if (pixelData != null) {
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
buffer.put(pixelData);
buffer.flip();
}
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, buffer);
}
@Override
public @NotNull String toString() {
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s)",
GLDebug.getTextureTargetName(target), level, GLDebug.getTextureFormatName(internalformat),
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
}
}
@@ -0,0 +1,115 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
/**
* Command: glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels)
* Updates a portion of an existing two-dimensional texture image during display list playback.
*/
public record TexSubImage2DCmd(
int target,
int level,
int xoffset,
int yoffset,
int width,
int height,
int format,
int type,
@Nullable byte[] pixelData
) implements DisplayListCommand {
/**
* Create command from IntBuffer
*/
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per int
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asIntBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
}
/**
* Create command from FloatBuffer
*/
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per float
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asFloatBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
}
/**
* Create command from DoubleBuffer
*/
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 8; // 8 bytes per double
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asDoubleBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
}
/**
* Create command from ByteBuffer
*/
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = limit - pos;
data = new byte[size];
pixels.get(data);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
}
@Override
public void execute() {
ByteBuffer buffer = null;
if (pixelData != null) {
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
buffer.put(pixelData);
buffer.flip();
}
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, buffer);
}
@Override
public @NotNull String toString() {
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s)",
GLDebug.getTextureTargetName(target), level, xoffset, yoffset, width, height,
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
}
}
@@ -0,0 +1,657 @@
package com.gtnewhorizons.angelica.glsm.redirect;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Core GL call redirector for GLSM. Rewrites bytecode so that GL calls go through {@code GLStateManager} for state tracking and backend abstraction.
* <p>
* THIS CLASS MIGHT BE LOADED ON A DIFFERENT CLASS LOADER.
* IT SHOULD NOT CALL ANY CODE FROM THE MAIN MOD.
*/
public class GLSMRedirector {
private static final boolean ASSERT_MAIN_THREAD = Boolean.getBoolean("angelica.assertMainThread");
private static final boolean LOG_SPAM = Boolean.getBoolean("angelica.redirectorLogspam");
private static final Logger LOGGER = LogManager.getLogger("GLSMRedirector");
private static final String Drawable = "org/lwjgl/opengl/Drawable";
private static final String GLStateManager = "com/gtnewhorizons/angelica/glsm/GLStateManager";
private static final String GL_PREFIX = "org/lwjgl/opengl/GL";
private static final String ARBVertexArrayObject = "org/lwjgl/opengl/ARBVertexArrayObject";
private static final String APPLEVertexArrayObject = "org/lwjgl/opengl/APPLEVertexArrayObject";
private static final String Project = "org/lwjgl/util/glu/Project";
private static final String GLU = "org/lwjgl/util/glu/GLU";
private static final String OpenGlHelper = "net/minecraft/client/renderer/OpenGlHelper";
private static final String EXTBlendFunc = "org/lwjgl/opengl/EXTBlendFuncSeparate";
private static final String ARBMultiTexture = "org/lwjgl/opengl/ARBMultitexture";
private static final String ARBShaderObjects = "org/lwjgl/opengl/ARBShaderObjects";
private static final String ARBInstancedArrays = "org/lwjgl/opengl/ARBInstancedArrays";
// Redirect VAO related calls from NHLib
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
private static final String MinecraftClient = "net.minecraft.client";
private static final String SplashProgress = "cpw.mods.fml.client.SplashProgress";
private static final Set<String> ExcludedMinecraftMainThreadChecks = ImmutableSet.of(
"startGame", "func_71384_a",
"initializeTextures", "func_77474_a"
);
private static final Map<String, Map<String, String>> methodRedirects = new HashMap<>(32);
private static final Map<String, String> glMethodRedirects = new HashMap<>(256);
private static final Map<Integer, String> glCapRedirects = new HashMap<>();
private static final Map<String, String> typeRedirects = new HashMap<>();
private static final ClassConstantPoolParser cstPoolParser;
private static final String[] CORE_EXCLUSIONS = {
"org.lwjgl",
"com.gtnewhorizon.gtnhlib.asm",
"com.gtnewhorizons.angelica.glsm.",
"me.eigenraven.lwjgl3ify"
};
static {
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_ALPHA_TEST, "AlphaTest");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_BLEND, "Blend");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_DEPTH_TEST, "DepthTest");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_CULL_FACE, "Cull");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_LIGHTING, "Lighting");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_TEXTURE_2D, "Texture");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_FOG, "Fog");
glCapRedirects.put(org.lwjgl.opengl.GL12.GL_RESCALE_NORMAL, "RescaleNormal");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_SCISSOR_TEST, "ScissorTest");
final var gl11 = RedirectMap.newMap()
// glEnable/Disable - Special cased in GLSMRedirector, but included here for the external API
.add("glEnable")
.add("glDisable")
.add("glAlphaFunc")
.add("glBegin")
.add("glBindTexture")
.add("glBlendFunc")
.add("glCallList")
.add("glCallLists")
.add("glClear")
.add("glClearColor")
.add("glClearDepth")
.add("glClearStencil")
.add("glClipPlane")
.add("glColor3b")
.add("glColor3d")
.add("glColor3f")
.add("glColor3ub")
.add("glColor4b")
.add("glColor4d")
.add("glColor4f")
.add("glColor4ub")
.add("glColorMask")
.add("glColorMaterial")
.add("glDeleteLists")
.add("glGenLists")
.add("glGenTextures")
.add("glIsList")
.add("glDeleteTextures")
.add("glDepthFunc")
.add("glDepthMask")
.add("glDepthRange")
.add("glDrawArrays")
.add("glDrawBuffer")
.add("glDrawElements")
.add("glEdgeFlag")
.add("glEnd")
.add("glEndList")
.add("glFog")
.add("glFogf")
.add("glFogi")
.add("glFrustum")
.add("glGetBoolean")
.add("glGetFloat")
.add("glGetInteger")
.add("glGetLight")
.add("glGetMaterial")
.add("glGetTexLevelParameteri")
.add("glGetTexParameterf")
.add("glGetTexParameteri")
.add("glIsEnabled")
.add("glMaterial")
.add("glMaterialf")
.add("glMateriali")
.add("glLight")
.add("glLightf")
.add("glLighti")
.add("glLightModel")
.add("glLightModelf")
.add("glLightModeli")
.add("glLineStipple")
.add("glLineWidth")
.add("glListBase")
.add("glLoadIdentity")
.add("glLoadMatrix")
.add("glLogicOp")
.add("glMatrixMode")
.add("glMultMatrix")
.add("glNewList")
.add("glNormal3b")
.add("glNormal3d")
.add("glFlush")
.add("glFinish")
.add("glGetError")
.add("glGetString")
.add("glGetTexImage")
.add("glNormal3f")
.add("glNormal3i")
.add("glOrtho")
.add("glPopAttrib")
.add("glPopMatrix")
.add("glPushAttrib")
.add("glPushMatrix")
.add("glRasterPos2d")
.add("glRasterPos2f")
.add("glRasterPos2i")
.add("glRasterPos3d")
.add("glRasterPos3f")
.add("glRasterPos3i")
.add("glRasterPos4d")
.add("glRasterPos4f")
.add("glRasterPos4i")
.add("glRotated")
.add("glRotatef")
.add("glScaled")
.add("glScalef")
.add("glShadeModel")
.add("glTexCoord1d")
.add("glTexCoord1f")
.add("glTexCoord2d")
.add("glTexCoord2f")
.add("glTexCoord3d")
.add("glTexCoord3f")
.add("glTexCoord4d")
.add("glTexCoord4f")
.add("glTexEnvi")
.add("glTexEnvf")
.add("glTexEnv")
.add("glGetTexEnv")
.add("glGetTexEnvi")
.add("glGetTexEnvf")
.add("glTexGeni")
.add("glTexGenf")
.add("glTexGend")
.add("glTexGen")
.add("glGetTexGen")
.add("glGetTexGeni")
.add("glGetTexGenf")
.add("glGetTexGend")
.add("glTexImage1D")
.add("glTexImage2D")
.add("glTexImage3D")
.add("glTexSubImage1D")
.add("glTexSubImage2D")
.add("glCopyTexImage1D")
.add("glCopyTexImage2D")
.add("glCopyTexSubImage1D")
.add("glCopyTexSubImage2D")
.add("glPixelStoref")
.add("glPixelStorei")
.add("glPixelTransferf")
.add("glPixelTransferi")
.add("glTexParameter")
.add("glTexParameterf")
.add("glTexParameteri")
.add("glCullFace")
.add("glFrontFace")
.add("glHint")
.add("glLineStipple")
.add("glLineWidth")
.add("glPointSize")
.add("glPolygonMode")
.add("glPolygonOffset")
.add("glPolygonStipple")
.add("glAccum")
.add("glReadBuffer")
.add("glSampleCoverage")
.add("glScissor")
.add("glStencilFunc")
.add("glStencilFuncSeparate")
.add("glStencilMask")
.add("glStencilMaskSeparate")
.add("glStencilOp")
.add("glStencilOpSeparate")
.add("glTranslated")
.add("glTranslatef")
.add("glVertex2d")
.add("glVertex2f")
.add("glVertex2i")
.add("glVertex3d")
.add("glVertex3f")
.add("glVertex3i")
.add("glVertexPointer")
.add("glColorPointer")
.add("glNormalPointer")
.add("glTexCoordPointer")
.add("glEnableClientState")
.add("glDisableClientState")
.add("glInterleavedArrays")
.add("glPushClientAttrib")
.add("glPopClientAttrib")
.add("glViewport")
.add("glFeedbackBuffer")
.add("glRenderMode")
.add("glPassThrough")
.add("glSelectBuffer")
.add("glInitNames")
.add("glPushName")
.add("glPopName")
.add("glLoadName");
final var gl12 = RedirectMap.newMap()
.add("glTexImage3D")
.add("glTexSubImage3D")
.add("glCopyTexSubImage3D");
final var gl13 = RedirectMap.newMap()
.add("glActiveTexture")
.add("glSampleCoverage")
.add("glClientActiveTexture")
.add("glMultiTexCoord2f")
.add("glMultiTexCoord2d")
.add("glMultiTexCoord2s");
final var gl14 = RedirectMap.newMap()
.add("glBlendFuncSeparate", "tryBlendFuncSeparate")
.add("glBlendColor")
.add("glBlendEquation");
final var gl15 = RedirectMap.newMap()
.add("glGenBuffers")
.add("glBindBuffer")
.add("glDeleteBuffers")
.add("glBufferData")
.add("glBufferSubData")
.add("glMapBuffer")
.add("glUnmapBuffer")
.add("glGetBufferSubData")
.add("glGetBufferParameteri")
.add("glIsBuffer");
final var gl20 = RedirectMap.newMap()
.add("glBlendEquationSeparate")
.add("glDrawBuffers")
.add("glStencilFuncSeparate")
.add("glStencilMaskSeparate")
.add("glStencilOpSeparate")
.add("glUseProgram")
.add("glShaderSource")
.add("glLinkProgram")
.add("glDeleteProgram")
.add("glCreateShader")
.add("glCompileShader")
.add("glCreateProgram")
.add("glAttachShader")
.add("glDetachShader")
.add("glValidateProgram")
.add("glGetUniformLocation")
.add("glGetAttribLocation")
.add("glUniform1f")
.add("glUniform2f")
.add("glUniform3f")
.add("glUniform4f")
.add("glUniform1i")
.add("glUniform2i")
.add("glUniform3i")
.add("glUniform4i")
.add("glUniform1")
.add("glUniform2")
.add("glUniform3")
.add("glUniform4")
.add("glUniformMatrix2")
.add("glUniformMatrix3")
.add("glUniformMatrix4")
.add("glDeleteShader")
.add("glGetShaderi")
.add("glGetShaderInfoLog")
.add("glGetProgrami")
.add("glGetProgramInfoLog")
.add("glBindAttribLocation")
.add("glVertexAttrib2f")
.add("glVertexAttrib2s")
.add("glVertexAttrib3f")
.add("glVertexAttrib4f")
.add("glGetActiveUniform")
.add("glGetAttachedShaders")
.add("glGetShaderSource")
.add("glGetUniform")
.add("glVertexAttribPointer")
.add("glEnableVertexAttribArray")
.add("glDisableVertexAttribArray");
final var gl30 = RedirectMap.newMap()
.add("glGenVertexArrays")
.add("glBindVertexArray")
.add("glDeleteVertexArrays")
.add("glBindFramebuffer")
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
.add("glCheckFramebufferStatus")
.add("glFramebufferTexture2D")
.add("glVertexAttribIPointer")
.add("glGenerateMipmap")
.add("glGetFramebufferAttachmentParameteri")
.add("glBlitFramebuffer");
final var gl31 = RedirectMap.newMap()
.add("glDrawElementsInstanced");
final var gl32 = RedirectMap.newMap()
.add("glFramebufferTexture");
final var gl33 = RedirectMap.newMap()
.add("glGenSamplers")
.add("glDeleteSamplers")
.add("glBindSampler")
.add("glSamplerParameteri")
.add("glSamplerParameterf")
.add("glVertexAttribDivisor");
final var gl42 = RedirectMap.newMap()
.add("glBindImageTexture")
.add("glMemoryBarrier")
.add("glTexStorage2D");
final var gl43 = RedirectMap.newMap()
.add("glDispatchCompute")
.add("glClearBufferSubData")
.add("glBindVertexBuffer")
.add("glVertexAttribFormat")
.add("glVertexAttribIFormat")
.add("glVertexAttribBinding");
final var gl44 = RedirectMap.newMap()
.add("glBufferStorage")
.add("glClearTexImage");
// Merge all GL version maps into the flat lookup
glMethodRedirects.putAll(gl11);
glMethodRedirects.putAll(gl12);
glMethodRedirects.putAll(gl13);
glMethodRedirects.putAll(gl14);
glMethodRedirects.putAll(gl15);
glMethodRedirects.putAll(gl20);
glMethodRedirects.putAll(gl30);
glMethodRedirects.putAll(gl31);
glMethodRedirects.putAll(gl32);
glMethodRedirects.putAll(gl33);
glMethodRedirects.putAll(gl42);
glMethodRedirects.putAll(gl43);
glMethodRedirects.putAll(gl44);
// MINECRAFT
methodRedirects.put(OpenGlHelper, RedirectMap.newMap()
.add("glBlendFunc", "tryBlendFuncSeparate")
.add("func_148821_a", "tryBlendFuncSeparate")
.add("func_153171_g", "glBindFramebuffer")
.add("func_153174_h", "glDeleteFramebuffers")
.add("func_153165_e", "glGenFramebuffers")
.add("func_153167_i", "glCheckFramebufferStatus")
.add("func_153188_a", "glFramebufferTexture2D")
.add("setActiveTexture")
.add("setLightmapTextureCoords")
.add("isFramebufferEnabled")
);
methodRedirects.put(EXTBlendFunc, RedirectMap.newMap().add("glBlendFuncSeparateEXT", "tryBlendFuncSeparate"));
// ARB
methodRedirects.put(ARBMultiTexture, RedirectMap.newMap()
.add("glActiveTextureARB")
);
methodRedirects.put(ARBShaderObjects, RedirectMap.newMap()
.add("glUseProgramObjectARB", "glUseProgram")
.add("glShaderSourceARB", "glShaderSource")
.add("glLinkProgramARB", "glLinkProgram")
.add("glCreateShaderObjectARB", "glCreateShader")
.add("glCompileShaderARB", "glCompileShader")
.add("glCreateProgramObjectARB", "glCreateProgram")
.add("glAttachObjectARB", "glAttachShader")
.add("glDetachObjectARB", "glDetachShader")
.add("glValidateProgramARB", "glValidateProgram")
.add("glGetUniformLocationARB", "glGetUniformLocation")
.add("glGetAttribLocationARB", "glGetAttribLocation")
.add("glDeleteObjectARB")
.add("glGetObjectParameterARB")
.add("glGetObjectParameteriARB")
.add("glGetInfoLogARB")
.add("glGetHandleARB")
.add("glUniform1fARB", "glUniform1f")
.add("glUniform2fARB", "glUniform2f")
.add("glUniform3fARB", "glUniform3f")
.add("glUniform4fARB", "glUniform4f")
.add("glUniform1iARB", "glUniform1i")
.add("glUniform2iARB", "glUniform2i")
.add("glUniform3iARB", "glUniform3i")
.add("glUniform4iARB", "glUniform4i")
.add("glUniform1ARB", "glUniform1")
.add("glUniform2ARB", "glUniform2")
.add("glUniform3ARB", "glUniform3")
.add("glUniform4ARB", "glUniform4")
.add("glUniformMatrix2ARB", "glUniformMatrix2")
.add("glUniformMatrix3ARB", "glUniformMatrix3")
.add("glUniformMatrix4ARB", "glUniformMatrix4")
.add("glGetActiveUniformARB", "glGetActiveUniform")
.add("glGetAttachedObjectsARB", "glGetAttachedShaders")
.add("glGetShaderSourceARB", "glGetShaderSource")
.add("glGetUniformARB", "glGetUniform")
);
methodRedirects.put(ARBVertexArrayObject, RedirectMap.newMap()
.add("glBindVertexArray")
.add("glDeleteVertexArrays")
);
methodRedirects.put(ARBInstancedArrays, RedirectMap.newMap()
.add("glVertexAttribDivisorARB")
);
// APPLE
methodRedirects.put(APPLEVertexArrayObject, RedirectMap.newMap()
.add("glBindVertexArrayAPPLE", "glBindVertexArray")
);
// GTNHLib VAO
methodRedirects.put(UniversalVAO, RedirectMap.newMap()
.add("bindVertexArray", "glBindVertexArray")
.add("deleteVertexArrays", "glDeleteVertexArrays")
);
// GLU
methodRedirects.put(Project, RedirectMap.newMap()
.add("gluPerspective")
.add("gluLookAt")
.add("gluPickMatrix")
);
methodRedirects.put(GLU, RedirectMap.newMap()
.add("gluPerspective")
.add("gluLookAt")
.add("gluOrtho2D")
.add("gluPickMatrix")
);
// Quadric type replacements
typeRedirects.put("org/lwjgl/util/glu/Sphere", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaSphere");
typeRedirects.put("org/lwjgl/util/glu/Cylinder", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaCylinder");
typeRedirects.put("org/lwjgl/util/glu/Disk", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaDisk");
typeRedirects.put("org/lwjgl/util/glu/PartialDisk", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaPartialDisk");
// Build constant pool scanner
final List<String> stringsToSearch = new ArrayList<>(32);
stringsToSearch.add(GL_PREFIX);
stringsToSearch.addAll(typeRedirects.keySet());
for (String key : methodRedirects.keySet()) {
stringsToSearch.add(key);
}
cstPoolParser = new ClassConstantPoolParser(stringsToSearch.toArray(new String[0]));
}
/** Core exclusions that GLSM always requires. */
public String[] getCoreExclusions() {
return CORE_EXCLUSIONS.clone();
}
/** The internal-name prefix used to match GL version classes (e.g., {@code "org/lwjgl/opengl/GL"}). */
public static String getGLPrefix() { return GL_PREFIX; }
/** The internal name of the redirect target class. */
public static String getTargetClassName() { return GLStateManager; }
/** Method redirects for GL-prefix classes. Key: original method name, Value: GLStateManager method name. */
public static Map<String, String> getGLPrefixMethodRedirects() {
return Collections.unmodifiableMap(glMethodRedirects);
}
/** Method redirects for named (non-GL-prefix) classes. Key: internal class name, Value: per-method redirect map. */
public static Map<String, Map<String, String>> getNamedClassMethodRedirects() {
return Collections.unmodifiableMap(methodRedirects);
}
public boolean shouldTransform(byte[] basicClass) {
return cstPoolParser.find(basicClass, true);
}
/** @return Was the class changed? */
public boolean transformClassNode(String transformedName, ClassNode cn) {
boolean changed = false;
final boolean isOpenGlHelper = transformedName.equals("net.minecraft.client.renderer.OpenGlHelper");
for (MethodNode mn : (List<MethodNode>) cn.methods) {
if (isOpenGlHelper && (mn.name.equals("glBlendFunc") || mn.name.equals("func_148821_a"))) {
continue;
}
boolean redirectInMethod = false;
for (AbstractInsnNode node : mn.instructions.toArray()) {
if (node instanceof TypeInsnNode tNode) {
// Redirect NEW and CHECKCAST for quadric classes
if (tNode.getOpcode() == Opcodes.NEW || tNode.getOpcode() == Opcodes.CHECKCAST) {
final String redirect = typeRedirects.get(tNode.desc);
if (redirect != null) {
if (LOG_SPAM) {
LOGGER.info("Redirecting {} in {} from {} to {}", tNode.getOpcode() == Opcodes.NEW ? "NEW" : "CHECKCAST", transformedName, tNode.desc, redirect);
}
tNode.desc = redirect;
changed = true;
}
}
} else if (node instanceof MethodInsnNode mNode) {
if (mNode.desc.equals("(I)V") && mNode.owner.startsWith(GL_PREFIX) && (mNode.name.equals("glEnable") || mNode.name.equals("glDisable"))) {
final AbstractInsnNode prevNode = node.getPrevious();
String name = null;
if (prevNode instanceof LdcInsnNode ldcNode) {
name = glCapRedirects.get(((Integer) ldcNode.cst));
} else if (prevNode instanceof IntInsnNode intNode) {
name = glCapRedirects.get(intNode.operand);
}
if (name != null) {
if (mNode.name.equals("glEnable")) {
name = "enable" + name;
} else {
name = "disable" + name;
}
}
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
if (name == null) {
LOGGER.info("Redirecting call in {} from {}.{}(I)V to GLStateManager.{}(I)V", transformedName, shortOwner, mNode.name, mNode.name);
} else {
LOGGER.info("Redirecting call in {} from {}.{}(I)V to GLStateManager.{}()V", transformedName, shortOwner, mNode.name, name);
}
}
mNode.owner = GLStateManager;
if (name != null) {
mNode.name = name;
mNode.desc = "()V";
mn.instructions.remove(prevNode);
}
changed = true;
redirectInMethod = true;
} else if (mNode.name.equals("makeCurrent") && mNode.owner.startsWith(Drawable)) {
mNode.setOpcode(Opcodes.INVOKESTATIC);
mNode.owner = GLStateManager;
mNode.desc = "(L" + Drawable + ";)V";
mNode.itf = false;
changed = true;
if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.makeCurrent()", transformedName);
}
} else {
final Map<String, String> redirects = mNode.owner.startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(mNode.owner);
if (redirects != null) {
final String glsmName = redirects.get(mNode.name);
if (glsmName != null) {
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
LOGGER.info("Redirecting call in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, mNode.name, mNode.desc, glsmName, mNode.desc);
}
mNode.owner = GLStateManager;
mNode.name = glsmName;
changed = true;
redirectInMethod = true;
}
}
}
// Redirect <init> calls for quadric classes
if (mNode.getOpcode() == Opcodes.INVOKESPECIAL && mNode.name.equals("<init>")) {
final String redirect = typeRedirects.get(mNode.owner);
if (redirect != null) {
if (LOG_SPAM) {
LOGGER.info("Redirecting <init> in {} from {} to {}", transformedName, mNode.owner, redirect);
}
mNode.owner = redirect;
changed = true;
}
}
} else if (node instanceof InvokeDynamicInsnNode dynNode) {
// Redirect method handles in invokedynamic bootstrap arguments
for (int i = 0; i < dynNode.bsmArgs.length; i++) {
if (!(dynNode.bsmArgs[i] instanceof Handle handle)) continue;
final Map<String, String> redirects = handle.getOwner().startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(handle.getOwner());
if (redirects == null) continue;
final String glsmName = redirects.get(handle.getName());
if (glsmName == null) continue;
if (LOG_SPAM) {
final String shortOwner = handle.getOwner().substring(handle.getOwner().lastIndexOf("/") + 1);
LOGGER.info("Redirecting invokedynamic handle in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, handle.getName(), handle.getDesc(), glsmName, handle.getDesc());
}
dynNode.bsmArgs[i] = new Handle(handle.getTag(), GLStateManager, glsmName, handle.getDesc());
changed = true;
redirectInMethod = true;
}
}
}
if (ASSERT_MAIN_THREAD && redirectInMethod && !transformedName.startsWith(SplashProgress) && !(transformedName.startsWith(MinecraftClient) && ExcludedMinecraftMainThreadChecks.contains(mn.name))) {
mn.instructions.insert(new MethodInsnNode(Opcodes.INVOKESTATIC, GLStateManager, "assertMainThread", "()V", false));
}
}
return changed;
}
private static final class RedirectMap<K> extends HashMap<K, K> {
public static RedirectMap<String> newMap() {
return new RedirectMap<>();
}
public RedirectMap<K> add(K name) {
this.put(name, name);
return this;
}
public RedirectMap<K> add(K name, K newName) {
this.put(name, newName);
return this;
}
}
}
@@ -0,0 +1,128 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.BooleanState;
/**
* A stack for boolean GL state with lazy copy-on-write optimization.
* <p>
* When using the global attrib depth tracking (from glPushAttrib/glPopAttrib), state is only
* saved when actually modified, dramatically reducing overhead when most states
* don't change (e.g., GL_ENABLE_BIT saves ~270 states but typically only 3-4 change).
*/
public class BooleanStateStack extends BooleanState implements IStateStack<BooleanStateStack> {
protected final boolean[] stack;
/**
* The depth at which state has been saved. Compared against GLStateManager.getAttribDepth()
* to determine if we need to save before modification or restore on pop.
*/
protected int savedDepth;
public BooleanStateStack(int glCap) {
this(glCap, false, false);
}
/**
* Create a BooleanStateStack with a custom initial state.
* Useful for GL states that default to true (e.g., GL_DITHER, GL_MULTISAMPLE).
*
* @param glCap GL capability constant
* @param initialState initial enabled state
*/
public BooleanStateStack(int glCap, boolean initialState) {
this(glCap, initialState, false);
}
/**
* Create a BooleanStateStack with custom initial state and FFP flag.
*
* @param glCap GL capability constant
* @param initialState initial enabled state
* @param ffpStateOnly when true, GL calls are skipped when FFP emulation is active
*/
public BooleanStateStack(int glCap, boolean initialState, boolean ffpStateOnly) {
super(glCap, ffpStateOnly);
this.enabled = initialState;
stack = new boolean[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
}
// ==================== Traditional Stack Operations ====================
@Override
public BooleanStateStack push() {
if (savedDepth >= stack.length) {
throw new IllegalStateException("Stack overflow size " + (savedDepth + 1) + " reached");
}
stack[savedDepth++] = enabled;
return this;
}
@Override
public BooleanStateStack pop() {
if (savedDepth == 0) {
throw new IllegalStateException("Stack underflow");
}
final boolean oldValue = stack[--savedDepth];
// Call setEnabledDirect to avoid triggering beforeModify during restore
setEnabledDirect(oldValue);
return this;
}
@Override
public boolean isEmpty() {
return savedDepth == 0;
}
// ==================== Lazy Copy-on-Write Operations ====================
@Override
public int pushDepth() {
// No-op: global depth is managed by GLStateManager
return GLStateManager.getAttribDepth();
}
/**
* Restore state if it was modified at the current depth.
* Only called by GLStateManager.popState() for states that registered as modified.
*/
@Override
public BooleanStateStack popDepth() {
// We're only called if we were modified, so savedDepth should match
if (savedDepth > 0) {
final boolean oldValue = stack[--savedDepth];
setEnabledDirect(oldValue);
}
return this;
}
/**
* Called before modifying state. If we haven't saved at the current depth yet,
* save the current value and register with GLStateManager for restoration.
*/
@Override
public void beforeModify() {
final int globalDepth = GLStateManager.getAttribDepth();
if (savedDepth < globalDepth) {
stack[savedDepth++] = enabled;
GLStateManager.registerModifiedState(this);
}
}
@Override
public int getDepth() {
return GLStateManager.getAttribDepth();
}
@Override
public void setEnabled(boolean enabled) {
beforeModify();
super.setEnabled(enabled);
}
private void setEnabledDirect(boolean enabled) {
super.setEnabled(enabled);
}
}
@@ -0,0 +1,42 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.LineState;
/**
* Stack for GL line state (width, stipple factor, stipple pattern).
* Used by GL_LINE_BIT push/pop attrib.
*/
public class LineStateStack extends LineState implements IStateStack<LineStateStack> {
protected final LineState[] stack;
protected int pointer;
public LineStateStack() {
stack = new LineState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new LineState();
}
}
public LineStateStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
}
public LineStateStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
}
public boolean isEmpty() {
return pointer == 0;
}
}
@@ -0,0 +1,42 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PointState;
/**
* Stack for GL point state (size).
* Used by GL_POINT_BIT push/pop attrib.
*/
public class PointStateStack extends PointState implements IStateStack<PointStateStack> {
protected final PointState[] stack;
protected int pointer;
public PointStateStack() {
stack = new PointState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new PointState();
}
}
public PointStateStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
}
public PointStateStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
}
public boolean isEmpty() {
return pointer == 0;
}
}
@@ -2,41 +2,37 @@ package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.BooleanState;
import org.lwjgl.opengl.GL11;
import com.gtnewhorizons.angelica.glsm.states.PolygonState;
public class BooleanStateStack extends BooleanState implements IStateStack<BooleanStateStack> {
protected final BooleanState[] stack;
/**
* Stack for GL polygon state (mode, offset, cull face mode, front face).
* Used by GL_POLYGON_BIT push/pop attrib.
*/
public class PolygonStateStack extends PolygonState implements IStateStack<PolygonStateStack> {
protected final PolygonState[] stack;
protected int pointer;
public BooleanStateStack(int glCap) {
super(glCap);
stack = new BooleanState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public PolygonStateStack() {
stack = new PolygonState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new BooleanState(glCap);
stack[i] = new PolygonState();
}
}
public BooleanStateStack push() {
if(pointer == stack.length) {
public PolygonStateStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
}
public BooleanStateStack pop() {
if(pointer == 0) {
public PolygonStateStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
if (GLStateManager.isAMD() && glCap == GL11.GL_BLEND) {
setEnabled(enabled);
}
return this;
}
@@ -0,0 +1,42 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.StencilState;
/**
* Stack for GL stencil buffer state.
* Used by GL_STENCIL_BUFFER_BIT push/pop attrib.
*/
public class StencilStateStack extends StencilState implements IStateStack<StencilStateStack> {
protected final StencilState[] stack;
protected int pointer;
public StencilStateStack() {
stack = new StencilState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new StencilState();
}
}
public StencilStateStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
}
public StencilStateStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
}
public boolean isEmpty() {
return pointer == 0;
}
}
@@ -0,0 +1,58 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import org.lwjgl.opengl.GL13;
/**
* BooleanStateStack for per-texture-unit state.
* Switches to correct unit (raw GL) before enable/disable calls.
*/
public class TextureUnitBooleanStateStack extends BooleanStateStack {
private final int unitIndex;
public TextureUnitBooleanStateStack(int glCap, int unitIndex) {
super(glCap, false, true); // All texture enable/disable states are FFP-only
this.unitIndex = unitIndex;
}
public TextureUnitBooleanStateStack(int glCap, int unitIndex, boolean initialState) {
super(glCap, initialState, true);
this.unitIndex = unitIndex;
}
@Override
public void setEnabled(boolean enabled) {
beforeModify();
setEnabledWithUnitSwitch(enabled);
}
@Override
public TextureUnitBooleanStateStack popDepth() {
if (savedDepth > 0) {
final boolean oldValue = stack[--savedDepth];
setEnabledWithUnitSwitch(oldValue);
}
return this;
}
private void setEnabledWithUnitSwitch(boolean enabled) {
final boolean bypass = GLStateManager.shouldBypassCache();
if (bypass || enabled != this.enabled) {
if (!bypass) this.enabled = enabled;
if (!ffpStateOnly) {
final int currentUnit = GLStateManager.getActiveTextureUnit();
final boolean needsSwitch = currentUnit != unitIndex;
if (needsSwitch) BackendManager.RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + unitIndex);
if (enabled) BackendManager.RENDER_BACKEND.enable(glCap); else BackendManager.RENDER_BACKEND.disable(glCap);
if (needsSwitch) BackendManager.RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + currentUnit);
}
}
}
public int getUnitIndex() {
return unitIndex;
}
}
@@ -0,0 +1,47 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Vector3f;
/**
* Stack for a Vector3f value (e.g. current normal).
*/
public class Vec3fStack implements IStateStack<Vec3fStack> {
private final Vector3f value;
private final float[][] stack;
private int pointer;
public Vec3fStack(Vector3f value) {
this.value = value;
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][3];
}
@Override
public Vec3fStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer][0] = value.x;
stack[pointer][1] = value.y;
stack[pointer][2] = value.z;
pointer++;
return this;
}
@Override
public Vec3fStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
pointer--;
value.set(stack[pointer][0], stack[pointer][1], stack[pointer][2]);
return this;
}
@Override
public boolean isEmpty() {
return pointer == 0;
}
}
@@ -0,0 +1,48 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Vector4f;
/**
* Stack for a Vector4f value (e.g. current texture coordinates).
*/
public class Vec4fStack implements IStateStack<Vec4fStack> {
private final Vector4f value;
private final float[][] stack;
private int pointer;
public Vec4fStack(Vector4f value) {
this.value = value;
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][4];
}
@Override
public Vec4fStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer][0] = value.x;
stack[pointer][1] = value.y;
stack[pointer][2] = value.z;
stack[pointer][3] = value.w;
pointer++;
return this;
}
@Override
public Vec4fStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
pointer--;
value.set(stack[pointer][0], stack[pointer][1], stack[pointer][2], stack[pointer][3]);
return this;
}
@Override
public boolean isEmpty() {
return pointer == 0;
}
}
@@ -7,7 +7,7 @@ import org.lwjgl.opengl.GL11;
@Getter
public class AlphaState implements ISettableState<AlphaState> {
@Setter protected int function = GL11.GL_ALWAYS;
@Setter protected float reference = -1.0F;
@Setter protected float reference = 0.0F;
@Override
public AlphaState set(AlphaState state) {
@@ -0,0 +1,75 @@
package com.gtnewhorizons.angelica.glsm.states;
import lombok.Getter;
import lombok.Setter;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
@Getter @Setter
public class BlendState implements ISettableState<BlendState> {
protected int srcRgb = GL11.GL_ONE;
protected int dstRgb = GL11.GL_ZERO;
protected int srcAlpha = GL11.GL_ONE;
protected int dstAlpha = GL11.GL_ZERO;
protected int equationRgb = GL14.GL_FUNC_ADD;
protected int equationAlpha = GL14.GL_FUNC_ADD;
protected float blendColorR = 0.0f;
protected float blendColorG = 0.0f;
protected float blendColorB = 0.0f;
protected float blendColorA = 0.0f;
public BlendState() {}
public BlendState(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
this.srcRgb = srcRgb;
this.dstRgb = dstRgb;
this.srcAlpha = srcAlpha;
this.dstAlpha = dstAlpha;
}
public void setAll(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
this.srcRgb = srcRgb;
this.dstRgb = dstRgb;
this.srcAlpha = srcAlpha;
this.dstAlpha = dstAlpha;
}
public void setSrcDstRgb(int srcRgb, int dstRgb) {
this.srcRgb = srcRgb;
this.dstRgb = dstRgb;
}
@Override
public BlendState set(BlendState state) {
this.srcRgb = state.srcRgb;
this.dstRgb = state.dstRgb;
this.srcAlpha = state.srcAlpha;
this.dstAlpha = state.dstAlpha;
this.equationRgb = state.equationRgb;
this.equationAlpha = state.equationAlpha;
this.blendColorR = state.blendColorR;
this.blendColorG = state.blendColorG;
this.blendColorB = state.blendColorB;
this.blendColorA = state.blendColorA;
return this;
}
@Override
public boolean sameAs(Object state) {
if (this == state) return true;
if (!(state instanceof BlendState blendState)) return false;
return srcRgb == blendState.srcRgb && dstRgb == blendState.dstRgb
&& srcAlpha == blendState.srcAlpha && dstAlpha == blendState.dstAlpha
&& equationRgb == blendState.equationRgb && equationAlpha == blendState.equationAlpha
&& Float.compare(blendColorR, blendState.blendColorR) == 0
&& Float.compare(blendColorG, blendState.blendColorG) == 0
&& Float.compare(blendColorB, blendState.blendColorB) == 0
&& Float.compare(blendColorA, blendState.blendColorA) == 0;
}
@Override
public BlendState copy() {
return new BlendState().set(this);
}
}
@@ -4,13 +4,26 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import lombok.Getter;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public class BooleanState implements ISettableState<BooleanState> {
protected final int glCap;
protected final boolean ffpStateOnly;
@Getter protected boolean enabled;
protected boolean stateUnknown;
public BooleanState(int glCap) {
this(glCap, false);
}
public BooleanState(int glCap, boolean ffpStateOnly) {
this.glCap = glCap;
this.ffpStateOnly = ffpStateOnly;
}
public void setUnknownState() {
stateUnknown = true;
}
public void disable() {
@@ -22,12 +35,18 @@ public class BooleanState implements ISettableState<BooleanState> {
}
public void setEnabled(boolean enabled) {
if (GLStateManager.shouldBypassCache() || enabled != this.enabled || (this.glCap == GL11.GL_BLEND && GLStateManager.isAMD() && GLStateManager.isPoppingAttributes())) {
this.enabled = enabled;
if (enabled) {
GL11.glEnable(this.glCap);
} else {
GL11.glDisable(this.glCap);
final boolean bypass = GLStateManager.shouldBypassCache();
if (stateUnknown || bypass || enabled != this.enabled || (this.glCap == GL11.GL_BLEND && GLStateManager.vendorIsAMD() && GLStateManager.isPoppingAttributes())) {
stateUnknown = false;
if (!bypass) {
this.enabled = enabled;
}
if (!ffpStateOnly) {
if (enabled) {
RENDER_BACKEND.enable(this.glCap);
} else {
RENDER_BACKEND.disable(this.glCap);
}
}
}
}
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.glsm.states;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import java.nio.FloatBuffer;
/**
* Stores eye-space clip plane equations for core profile emulation.
*/
public class ClipPlaneState {
private final float[][] eyePlanes = new float[GLStateManager.MAX_CLIP_PLANES][4];
private static final Vector4f tempPlane = new Vector4f();
private static final Matrix4f tempInverse = new Matrix4f();
public void setPlane(int index, double a, double b, double c, double d, Matrix4f modelView) {
final float[] dest = eyePlanes[index];
modelView.invert(tempInverse);
tempPlane.set((float) a, (float) b, (float) c, (float) d);
tempInverse.transformTranspose(tempPlane);
dest[0] = tempPlane.x;
dest[1] = tempPlane.y;
dest[2] = tempPlane.z;
dest[3] = tempPlane.w;
}
/** Writes the 4-component eye-space plane equation into {@code buf} at its current position. */
public void putEyePlane(int index, FloatBuffer buf) {
final float[] p = eyePlanes[index];
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
}
}
@@ -6,6 +6,13 @@ public class ColorMask implements ISettableState<ColorMask> {
public boolean blue = true;
public boolean alpha = true;
public void setAll(boolean red, boolean green, boolean blue, boolean alpha) {
this.red = red;
this.green = green;
this.blue = blue;
this.alpha = alpha;
}
@Override
public ColorMask set(ColorMask state) {
this.red = state.red;
Loaded 100 of 1172 files, more files were not shown because too many files have changed in this diff. Show more